sellmate-design-system-react 8.1.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 (41) hide show
  1. package/AGENTS.md +156 -109
  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/SGhostButton/README.md +4 -0
  11. package/dist/components/SGuide/README.md +1 -1
  12. package/dist/components/SGuide/SGuide.d.ts +1 -1
  13. package/dist/components/SIcon/README.md +2 -0
  14. package/dist/components/SIcon/icons.gen.d.ts +1 -0
  15. package/dist/components/SList/README.md +1 -0
  16. package/dist/components/SList/SList.d.ts +2 -0
  17. package/dist/components/SListItem/README.md +2 -2
  18. package/dist/components/SListItem/SListItem.d.ts +2 -2
  19. package/dist/components/SPage/README.md +21 -0
  20. package/dist/components/SPage/SPage.d.ts +10 -0
  21. package/dist/components/SPage/SPageHeader.d.ts +32 -0
  22. package/dist/components/SPage/index.d.ts +1 -0
  23. package/dist/components/SPortal/README.md +1 -1
  24. package/dist/components/SPortal/SPortal.d.ts +7 -1
  25. package/dist/components/SRadio/README.md +2 -0
  26. package/dist/components/SRadioButton/README.md +13 -0
  27. package/dist/components/SSelect/README.md +2 -2
  28. package/dist/components/SSelect/SSelect.d.ts +2 -2
  29. package/dist/components/STag/README.md +2 -0
  30. package/dist/components/STextLink/README.md +2 -0
  31. package/dist/components/STooltip/README.md +2 -0
  32. package/dist/index.cjs +1632 -223
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.js +1644 -237
  36. package/dist/index.js.map +1 -1
  37. package/dist/lib/floating-width.d.ts +22 -0
  38. package/dist/llms-full.txt +277 -115
  39. package/dist/llms.txt +158 -111
  40. package/dist/styles.css +197 -5
  41. package/package.json +7 -4
package/dist/index.cjs CHANGED
@@ -7,7 +7,7 @@ var react = require('react');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
8
  var reactDom = require('react-dom');
9
9
  var RCheckbox = require('@radix-ui/react-checkbox');
10
- var RPopover2 = require('@radix-ui/react-popover');
10
+ var RPopover9 = require('@radix-ui/react-popover');
11
11
  var client = require('react-dom/client');
12
12
  var RTooltip = require('@radix-ui/react-tooltip');
13
13
  var RDropdown = require('@radix-ui/react-dropdown-menu');
@@ -34,7 +34,7 @@ function _interopNamespace(e) {
34
34
  }
35
35
 
36
36
  var RCheckbox__namespace = /*#__PURE__*/_interopNamespace(RCheckbox);
37
- var RPopover2__namespace = /*#__PURE__*/_interopNamespace(RPopover2);
37
+ var RPopover9__namespace = /*#__PURE__*/_interopNamespace(RPopover9);
38
38
  var RTooltip__namespace = /*#__PURE__*/_interopNamespace(RTooltip);
39
39
  var RDropdown__namespace = /*#__PURE__*/_interopNamespace(RDropdown);
40
40
  var RSwitch__namespace = /*#__PURE__*/_interopNamespace(RSwitch);
@@ -2593,6 +2593,27 @@ var Icon_sidebar = (p) => {
2593
2593
  }
2594
2594
  );
2595
2595
  };
2596
+ var Icon_star = (p) => {
2597
+ const { size = 24, ...rest } = p;
2598
+ return /* @__PURE__ */ jsxRuntime.jsx(
2599
+ "svg",
2600
+ {
2601
+ width: size,
2602
+ height: size,
2603
+ viewBox: "0 0 24 24",
2604
+ fill: "none",
2605
+ xmlns: "http://www.w3.org/2000/svg",
2606
+ ...rest,
2607
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2608
+ "path",
2609
+ {
2610
+ d: "M12.3358 3.45335C12.0755 2.84888 11.2185 2.84888 10.9582 3.45335L8.78136 8.50811C8.67278 8.76024 8.43512 8.93291 8.16178 8.95826L2.68175 9.46652C2.02642 9.5273 1.76157 10.3424 2.25602 10.7768L6.39071 14.409C6.59695 14.5902 6.68773 14.8696 6.62737 15.1374L5.41733 20.5063C5.27263 21.1483 5.966 21.6521 6.5319 21.3161L11.2641 18.5062C11.5001 18.366 11.7939 18.366 12.0299 18.5062L16.7621 21.3161C17.328 21.6521 18.0214 21.1483 17.8767 20.5063L16.6666 15.1374C16.6063 14.8696 16.6971 14.5902 16.9033 14.409L21.038 10.7768C21.5324 10.3424 21.2676 9.5273 20.6123 9.46652L15.1322 8.95826C14.8589 8.93291 14.6212 8.76024 14.5126 8.50811L12.3358 3.45335Z",
2611
+ fill: "currentColor"
2612
+ }
2613
+ )
2614
+ }
2615
+ );
2616
+ };
2596
2617
  var Icon_store = (p) => {
2597
2618
  const { size = 24, ...rest } = p;
2598
2619
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -3087,6 +3108,7 @@ var ICONS = {
3087
3108
  settingOutline: Icon_settingOutline,
3088
3109
  shield: Icon_shield,
3089
3110
  sidebar: Icon_sidebar,
3111
+ star: Icon_star,
3090
3112
  store: Icon_store,
3091
3113
  synchronize: Icon_synchronize,
3092
3114
  thumbsDown: Icon_thumbsDown,
@@ -3220,12 +3242,13 @@ var SDraggableItemView = /* @__PURE__ */ react.forwardRef(
3220
3242
  !disabled && !selected && !dragging && "hover:bg-(--cmp-listDraggableItem-bg-hover)",
3221
3243
  hasSupportingText && supportingTextPosition === "bottom" ? dense ? "min-h-[52px]" : "min-h-[60px]" : dense ? "h-[32px]" : "h-[40px]",
3222
3244
  paddingYClass,
3223
- bordered ? cn(
3245
+ // 항목 사이 구분선은 리스트가 그린다 — SDraggableList 의 separator (SList.tsx)
3246
+ bordered && cn(
3224
3247
  "rounded-(--cmp-listDraggableItem-radius-bordered) border border-(--cmp-listDraggableItem-border-default)",
3225
3248
  !disabled && !selected && !dragging && "hover:border-(--cmp-listDraggableItem-bordered-border-hover)",
3226
3249
  !disabled && selected && !dragging && "border-(--cmp-listDraggableItem-bordered-border-selected) bg-(--cmp-listDraggableItem-bordered-bg-selected)",
3227
3250
  !disabled && selected && dragging && "border-[length:var(--cmp-listDraggableItem-bordered-dragging-border-width)] border-(--cmp-listDraggableItem-bordered-dragging-border-color) bg-(--cmp-listDraggableItem-bordered-dragging-bg)"
3228
- ) : "border-b border-(--cmp-listDraggableItem-border-default)",
3251
+ ),
3229
3252
  disabled && "cursor-not-allowed",
3230
3253
  className
3231
3254
  ),
@@ -3427,7 +3450,7 @@ var SDraggableItemImpl = /* @__PURE__ */ react.forwardRef(function SDraggableIte
3427
3450
  ] });
3428
3451
  });
3429
3452
  var SDraggableItem2 = /* @__PURE__ */ withDisplayName(SDraggableItemImpl, "SDraggableItem");
3430
- var SListImpl = /* @__PURE__ */ react.forwardRef(function SList({ className, children, useGap = false, usePadding = false, ...rest }, ref) {
3453
+ var SListImpl = /* @__PURE__ */ react.forwardRef(function SList({ className, children, useGap = false, usePadding = false, separator = false, ...rest }, ref) {
3431
3454
  return /* @__PURE__ */ jsxRuntime.jsx(
3432
3455
  "div",
3433
3456
  {
@@ -3435,6 +3458,7 @@ var SListImpl = /* @__PURE__ */ react.forwardRef(function SList({ className, chi
3435
3458
  className: cn(
3436
3459
  useGap && "flex flex-col gap-(--cmp-list-group-gap)",
3437
3460
  usePadding && "p-(--cmp-list-group-padding)",
3461
+ separator && "[&>*]:border-b [&>*]:border-(--cmp-listItem-border-default)",
3438
3462
  className
3439
3463
  ),
3440
3464
  ...rest,
@@ -3806,7 +3830,7 @@ var SExpansionItem = /* @__PURE__ */ react.forwardRef(
3806
3830
  ref,
3807
3831
  className: cn(
3808
3832
  "group/expansion-item relative overflow-hidden bg-(--cmp-listExpansionItem-title-bg-default)",
3809
- !bordered && "border-b border-(--cmp-listExpansionItem-border-default)",
3833
+ // 항목 사이 구분선은 리스트가 그린다 — SList/SExpansionList 의 separator (SList.tsx)
3810
3834
  // 보더 색은 시맨틱 토큰(border-default → hover strong → 펼침 accentLight)을 쓴다.
3811
3835
  // TODO: expansionItem/bordered/radius 토큰 생성 후 rounded 고정값도 토큰으로 교체한다.
3812
3836
  bordered && "rounded-[8px] border border-border-default hover:border-border-strong [&:has(>button:hover)]:border-border-strong",
@@ -3916,7 +3940,7 @@ var SListItemImpl = /* @__PURE__ */ react.forwardRef(function SListItem({
3916
3940
  trailing,
3917
3941
  bordered = false,
3918
3942
  selected = false,
3919
- clickable = true,
3943
+ clickable = false,
3920
3944
  interaction,
3921
3945
  accentStripe = false,
3922
3946
  dense = false,
@@ -3969,10 +3993,10 @@ var SListItemImpl = /* @__PURE__ */ react.forwardRef(function SListItem({
3969
3993
  "gap-(--cmp-listItem-gap)",
3970
3994
  "pr-(--cmp-listItem-paddingX)",
3971
3995
  depth === 2 ? "pl-[36px]" : depth === 3 ? "pl-[56px]" : "pl-[16px]",
3972
- bordered ? cn(
3996
+ bordered && cn(
3973
3997
  "overflow-hidden rounded-(--cmp-listItem-radius-bordered) border border-(--cmp-listItem-border-default)",
3974
3998
  isSelected ? "border-(--cmp-listItem-bordered-border-selected)" : isInteractive && "hover:border-(--cmp-listItem-bordered-border-hover)"
3975
- ) : "border-b border-(--cmp-listItem-border-default)",
3999
+ ),
3976
4000
  className
3977
4001
  ),
3978
4002
  ...rest,
@@ -4051,6 +4075,8 @@ var SExpansionListImpl = /* @__PURE__ */ react.forwardRef(function SExpansionLis
4051
4075
  const depth2 = child.props.depth ?? 1;
4052
4076
  const itemKey2 = String(child.key ?? index);
4053
4077
  return react.cloneElement(child, {
4078
+ // 선택을 리스트가 관리하는 이상 아이템은 클릭 가능해야 한다 — SListItem 의 기본값(false)을 뒤집는다
4079
+ clickable: child.props.clickable ?? true,
4054
4080
  selected: selectedKeys[depth2] === itemKey2,
4055
4081
  onClick: (event) => {
4056
4082
  setSelectedKeys((current) => ({ ...current, [depth2]: itemKey2 }));
@@ -4356,8 +4382,8 @@ function SGhostButton({
4356
4382
  }
4357
4383
  );
4358
4384
  if (!hasTooltip) return btn;
4359
- return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpen, children: [
4360
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4385
+ return /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open, onOpenChange: setOpen, children: [
4386
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4361
4387
  "span",
4362
4388
  {
4363
4389
  className: cn("inline-flex", anchorClassName),
@@ -4369,8 +4395,8 @@ function SGhostButton({
4369
4395
  children: btn
4370
4396
  }
4371
4397
  ) }),
4372
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
4373
- RPopover2__namespace.Content,
4398
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
4399
+ RPopover9__namespace.Content,
4374
4400
  {
4375
4401
  side: "top",
4376
4402
  sideOffset: 4,
@@ -4841,7 +4867,7 @@ var STooltipImpl = /* @__PURE__ */ react.forwardRef(function STooltip({
4841
4867
  ...restContentProps
4842
4868
  } = contentProps ?? {};
4843
4869
  const contentClass = cn(
4844
- "z-50 w-fit whitespace-nowrap rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px] shadow-normal-sm",
4870
+ "z-50 w-fit max-w-[var(--radix-popover-content-available-width)] rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px] shadow-normal-sm",
4845
4871
  FLOATING_SLIDE_ANIM,
4846
4872
  className,
4847
4873
  contentPropsClassName
@@ -4853,14 +4879,14 @@ var STooltipImpl = /* @__PURE__ */ react.forwardRef(function STooltip({
4853
4879
  ...style,
4854
4880
  ...contentPropsStyle
4855
4881
  };
4856
- return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpenState, children: [
4882
+ return /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open, onOpenChange: setOpenState, children: [
4857
4883
  isNone ? (
4858
4884
  // 트리거 없음: children 을 래퍼 없이 그대로 앵커로 쓴다 (소비자 레이아웃 보존).
4859
4885
  // mouse/focus 핸들러가 없으므로 useClose 없이도 자동 닫힘이 발생하지 않는다.
4860
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { asChild: true, children: triggerNode })
4886
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Anchor, { asChild: true, children: triggerNode })
4861
4887
  ) : isHover ? (
4862
4888
  // hover: Anchor(위치 기준) + 직접 mouse 핸들러 — 클릭으로 토글되지 않도록 Trigger 미사용
4863
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4889
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4864
4890
  "span",
4865
4891
  {
4866
4892
  className: "inline-flex",
@@ -4871,9 +4897,9 @@ var STooltipImpl = /* @__PURE__ */ react.forwardRef(function STooltip({
4871
4897
  children: triggerNode
4872
4898
  }
4873
4899
  ) })
4874
- ) : /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: triggerNode }),
4875
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
4876
- RPopover2__namespace.Content,
4900
+ ) : /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: triggerNode }),
4901
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
4902
+ RPopover9__namespace.Content,
4877
4903
  {
4878
4904
  side: placement,
4879
4905
  sideOffset,
@@ -4907,7 +4933,7 @@ var STooltipImpl = /* @__PURE__ */ react.forwardRef(function STooltip({
4907
4933
  }
4908
4934
  )
4909
4935
  ] }),
4910
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Arrow, { width: 16, height: 12, style: { fill: bg } })
4936
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Arrow, { width: 16, height: 12, style: { fill: bg } })
4911
4937
  ]
4912
4938
  }
4913
4939
  ) })
@@ -5915,6 +5941,15 @@ var STextLink = /* @__PURE__ */ react.forwardRef(function STextLink2({
5915
5941
  /* @__PURE__ */ jsxRuntime.jsx(RTooltip__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(RTooltip__namespace.Content, { side: "top", sideOffset: 4, className: cn("z-50", FLOATING_SLIDE_ANIM), children: /* @__PURE__ */ jsxRuntime.jsx(STag, { shape: "square", size: "xs", color: "grey", label: text }) }) })
5916
5942
  ] }) });
5917
5943
  });
5944
+
5945
+ // src/lib/floating-width.ts
5946
+ var POPOVER_AVAILABLE_WIDTH = "var(--radix-popover-content-available-width)";
5947
+ var DROPDOWN_AVAILABLE_WIDTH = "var(--radix-dropdown-menu-content-available-width)";
5948
+ function clampToAvailableWidth(value, availableWidth) {
5949
+ if (value == null || value === "") return availableWidth;
5950
+ const css = typeof value === "number" ? `${value}px` : value;
5951
+ return `min(${css}, ${availableWidth})`;
5952
+ }
5918
5953
  var SIZE2 = {
5919
5954
  xs: { height: 24, paddingX: 8, gap: 6, arrow: 12, fontSize: 12, minWidth: 64, splitMinWidth: 72 },
5920
5955
  sm: {
@@ -5951,6 +5986,7 @@ function splitDivider(color, outline) {
5951
5986
  };
5952
5987
  return (outline ? outlined : solid)[color];
5953
5988
  }
5989
+ var VIEWPORT_PADDING = 12;
5954
5990
  var itemClass = cn(
5955
5991
  "flex min-h-[28px] cursor-pointer select-none items-center gap-[8px] rounded-[2px] px-[12px] py-[4px] text-[12px] leading-[20px] outline-none",
5956
5992
  "text-[color:var(--sys-color-fg-secondary)]",
@@ -6032,12 +6068,16 @@ var SDropdownButtonImpl = /* @__PURE__ */ react.forwardRef(
6032
6068
  sideOffset: 4,
6033
6069
  align: "start",
6034
6070
  alignOffset: split ? splitAlignOffset : 0,
6071
+ collisionPadding: VIEWPORT_PADDING,
6035
6072
  className: cn(
6036
6073
  "z-50 overflow-hidden rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] shadow-normal-sm",
6037
- !split && "min-w-[var(--radix-dropdown-menu-trigger-width)]",
6074
+ // 창보다 넓은 메뉴는 창 폭에 맞춰 줄인다. 트리거 너비를 따르는 하한도 같은 상한으로
6075
+ // 감싸야 한다 — min-width 가 max-width 를 이기기 때문이다.
6076
+ "max-w-[var(--radix-dropdown-menu-content-available-width)]",
6077
+ !split && "min-w-[min(var(--radix-dropdown-menu-trigger-width),var(--radix-dropdown-menu-content-available-width))]",
6038
6078
  FLOATING_SLIDE_ANIM
6039
6079
  ),
6040
- style: split ? { minWidth: splitWidth } : void 0,
6080
+ style: split ? { minWidth: clampToAvailableWidth(splitWidth, DROPDOWN_AVAILABLE_WIDTH) } : void 0,
6041
6081
  children: items.map((it) => /* @__PURE__ */ jsxRuntime.jsxs(
6042
6082
  RDropdown__namespace.Item,
6043
6083
  {
@@ -6049,7 +6089,7 @@ var SDropdownButtonImpl = /* @__PURE__ */ react.forwardRef(
6049
6089
  /* @__PURE__ */ jsxRuntime.jsx(
6050
6090
  "span",
6051
6091
  {
6052
- className: "min-w-0 whitespace-nowrap",
6092
+ className: "min-w-0 break-words",
6053
6093
  dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(it.label) }
6054
6094
  }
6055
6095
  )
@@ -6261,16 +6301,20 @@ function SPopover({
6261
6301
  const descColor = type === "default" ? "var(--cmp-popover-default-description)" : "var(--cmp-popover-light-description)";
6262
6302
  const leftLinkLabelClass = type === "default" ? "text-[color:var(--cmp-popover-default-subAction)]" : "text-[color:var(--cmp-popover-light-subAction)]";
6263
6303
  const triggerNode = trigger ?? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex cursor-pointer", "aria-label": ariaLabel, children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: iconSize, color }) });
6264
- return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open: actualOpen, onOpenChange: setOpen, children: [
6265
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: triggerNode }),
6266
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
6267
- RPopover2__namespace.Content,
6304
+ return /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open: actualOpen, onOpenChange: setOpen, children: [
6305
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: triggerNode }),
6306
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
6307
+ RPopover9__namespace.Content,
6268
6308
  {
6269
6309
  side: placement,
6270
6310
  sideOffset: 8,
6271
6311
  className: cn(
6272
6312
  // 닫기 버튼이 항상 있으므로 우측은 close clearance(36px) 로 고정
6273
- "relative z-50 flex min-w-[160px] flex-col gap-[var(--cmp-popover-gap)] rounded-[var(--cmp-popover-radius)] py-[var(--cmp-popover-paddingY)] px-[var(--cmp-popover-paddingX)] text-[12px] leading-[20px] shadow-normal-sm",
6313
+ "relative z-50 flex flex-col gap-[var(--cmp-popover-gap)] rounded-[var(--cmp-popover-radius)] py-[var(--cmp-popover-paddingY)] px-[var(--cmp-popover-paddingX)] text-[12px] leading-[20px] shadow-normal-sm",
6314
+ // 창보다 넓어지면 창 폭에 맞춘다. 최소 너비도 같은 상한으로 감싼다 —
6315
+ // min-width 가 max-width 를 이기므로 감싸지 않으면 상한이 무력화된다.
6316
+ "max-w-[var(--radix-popover-content-available-width)]",
6317
+ "min-w-[min(160px,var(--radix-popover-content-available-width))]",
6274
6318
  FLOATING_SLIDE_ANIM,
6275
6319
  menuClassName
6276
6320
  ),
@@ -6326,7 +6370,7 @@ function SPopover({
6326
6370
  ]
6327
6371
  }
6328
6372
  ),
6329
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Arrow, { width: 16, height: 12, style: { fill: bg } })
6373
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Arrow, { width: 16, height: 12, style: { fill: bg } })
6330
6374
  ]
6331
6375
  }
6332
6376
  ) })
@@ -6355,8 +6399,9 @@ function SPortal({
6355
6399
  ariaLabel
6356
6400
  }) {
6357
6401
  const portalContainer = usePortalContainer();
6402
+ const { minWidth: styleMinWidth, maxWidth: styleMaxWidth, ...restStyle } = style ?? {};
6358
6403
  return /* @__PURE__ */ jsxRuntime.jsxs(
6359
- RPopover2__namespace.Root,
6404
+ RPopover9__namespace.Root,
6360
6405
  {
6361
6406
  open,
6362
6407
  onOpenChange: (next) => {
@@ -6364,9 +6409,9 @@ function SPortal({
6364
6409
  if (!next) onClose?.();
6365
6410
  },
6366
6411
  children: [
6367
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { virtualRef: anchorRef }),
6368
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
6369
- RPopover2__namespace.Content,
6412
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Anchor, { virtualRef: anchorRef }),
6413
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
6414
+ RPopover9__namespace.Content,
6370
6415
  {
6371
6416
  side: placement,
6372
6417
  align,
@@ -6393,11 +6438,18 @@ function SPortal({
6393
6438
  className: cn(
6394
6439
  "z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) shadow-normal-sm",
6395
6440
  maxHeight != null && "overflow-y-auto",
6396
- fit && "min-w-(--radix-popover-trigger-width)",
6441
+ // 앵커가 창보다 넓을 수 있는 자리(가로 스크롤 안의 트리거 등)에서도 하한이 상한을
6442
+ // 이기지 않도록, 앵커 너비 역시 가용 폭으로 가둔다.
6443
+ fit && "min-w-[min(var(--radix-popover-trigger-width),var(--radix-popover-content-available-width))]",
6397
6444
  FLOATING_SLIDE_ANIM,
6398
6445
  className
6399
6446
  ),
6400
- style: { maxHeight, ...style },
6447
+ style: {
6448
+ maxHeight,
6449
+ ...restStyle,
6450
+ ...styleMinWidth != null ? { minWidth: clampToAvailableWidth(styleMinWidth, POPOVER_AVAILABLE_WIDTH) } : {},
6451
+ maxWidth: clampToAvailableWidth(styleMaxWidth, POPOVER_AVAILABLE_WIDTH)
6452
+ },
6401
6453
  children
6402
6454
  }
6403
6455
  ) })
@@ -6557,16 +6609,18 @@ function SGuide({
6557
6609
  );
6558
6610
  if (isNotion)
6559
6611
  return /* @__PURE__ */ jsxRuntime.jsx("span", { className, style, children: trigger });
6560
- return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpen, children: [
6561
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: trigger }),
6562
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
6563
- RPopover2__namespace.Content,
6612
+ return /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open, onOpenChange: setOpen, children: [
6613
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: trigger }),
6614
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
6615
+ RPopover9__namespace.Content,
6564
6616
  {
6565
6617
  side: "bottom",
6566
6618
  align: "start",
6567
6619
  sideOffset: 4,
6568
6620
  className: cn(
6569
6621
  "relative z-50 rounded-[var(--cmp-guide-contents-radius)] bg-[var(--color-white)]",
6622
+ // popupWidth(기본 426px)가 창보다 넓으면 창 폭에 맞춘다 — max-width 가 width 를 이긴다.
6623
+ "max-w-[var(--radix-popover-content-available-width)]",
6570
6624
  FLOATING_SLIDE_ANIM
6571
6625
  ),
6572
6626
  style: {
@@ -8639,10 +8693,10 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
8639
8693
  }
8640
8694
  ),
8641
8695
  header === "fix" && menuResizeHandle,
8642
- /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open: labelTip !== null, onOpenChange: (open) => !open && setLabelTip(null), children: [
8643
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { virtualRef: labelTipAnchorRef }),
8644
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
8645
- RPopover2__namespace.Content,
8696
+ /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open: labelTip !== null, onOpenChange: (open) => !open && setLabelTip(null), children: [
8697
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Anchor, { virtualRef: labelTipAnchorRef }),
8698
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
8699
+ RPopover9__namespace.Content,
8646
8700
  {
8647
8701
  side: "right",
8648
8702
  sideOffset: 4,
@@ -8660,16 +8714,81 @@ var SGnb = /* @__PURE__ */ react.forwardRef(function SGnb2({
8660
8714
  }
8661
8715
  );
8662
8716
  });
8717
+ var SPageHeader = /* @__PURE__ */ withDisplayName(
8718
+ react.forwardRef(function SPageHeader2({ title, subText, fix = false, onBack, slot, scrolled = false, className, style, ...rest }, ref) {
8719
+ const hasBack = onBack != null;
8720
+ return /* @__PURE__ */ jsxRuntime.jsxs(
8721
+ "header",
8722
+ {
8723
+ ref,
8724
+ className: cn(
8725
+ "relative z-10 box-border flex shrink-0 items-center gap-(--cmp-pageHeader-gap) px-(--cmp-pageHeader-paddingX)",
8726
+ fix ? "h-(--cmp-pageHeader-bar-height) border-b border-(--cmp-pageHeader-bar-border) bg-(--cmp-pageHeader-bar-bg) py-(--cmp-pageHeader-bar-paddingY)" : cn(
8727
+ // 배경은 항상 투명 — 페이지 배경을 따라갈지는 fix 가 정하지 scrolled 가 정하지 않는다.
8728
+ // 스크롤되면 그림자만 더해 콘텐츠 위에 뜬 것처럼 보이게 한다.
8729
+ "bg-transparent pt-(--cmp-pageHeader-plain-paddingTop) pb-(--cmp-pageHeader-plain-paddingBottom)",
8730
+ scrolled && "shadow-floating"
8731
+ ),
8732
+ className
8733
+ ),
8734
+ style,
8735
+ ...rest,
8736
+ children: [
8737
+ hasBack && /* @__PURE__ */ jsxRuntime.jsx(
8738
+ SGhostButton,
8739
+ {
8740
+ icon: "chevronLeft",
8741
+ size: "xs",
8742
+ ariaLabel: "\uB4A4\uB85C\uAC00\uAE30",
8743
+ onClick: onBack,
8744
+ className: "shrink-0"
8745
+ }
8746
+ ),
8747
+ /* @__PURE__ */ jsxRuntime.jsxs(
8748
+ "div",
8749
+ {
8750
+ className: cn(
8751
+ "flex min-w-0 flex-1 items-center",
8752
+ fix ? "flex-row gap-(--cmp-pageHeader-gap)" : "flex-col items-start gap-(--cmp-pageHeader-plain-title-gap)",
8753
+ !hasBack && "pl-(--cmp-pageHeader-title-paddingLeft)"
8754
+ ),
8755
+ children: [
8756
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-heading-lg min-w-0 truncate text-(--cmp-pageHeader-title-text)", children: title }),
8757
+ subText && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-md-default min-w-0 truncate text-(--cmp-pageHeader-subtitle-text)", children: subText })
8758
+ ]
8759
+ }
8760
+ ),
8761
+ slot && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex shrink-0 items-center", children: slot })
8762
+ ]
8763
+ }
8764
+ );
8765
+ }),
8766
+ "SPageHeader"
8767
+ );
8663
8768
  var MIN_CONTENT_WIDTH = `calc(var(${SLAYOUT_MIN_WIDTH_VAR}, 0px) - 100cqw + 100%)`;
8664
8769
  var BACKGROUND_CLASS = {
8665
8770
  frame: `bg-[var(--sys-color-bg-frame)] ${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_CLASS}`,
8666
8771
  neutral: `bg-[var(--sys-color-bg-neutralLight)] ${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_WHITE_CLASS}`,
8667
8772
  screen: `bg-[var(--sys-color-bg-screen)] ${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_CLASS}`
8668
8773
  };
8669
- var SPage = /* @__PURE__ */ react.forwardRef(function SPage2({ background = "frame", scrollEndSpacing = true, className, style, children, ...rest }, ref) {
8774
+ var SPage = /* @__PURE__ */ react.forwardRef(function SPage2({
8775
+ background = "frame",
8776
+ scrollEndSpacing = true,
8777
+ header,
8778
+ className,
8779
+ style,
8780
+ children,
8781
+ onScroll,
8782
+ ...rest
8783
+ }, ref) {
8670
8784
  const layout = react.useContext(SLayoutContext);
8671
8785
  const gridStyle = layout?.header === "full" ? { gridArea: "page" } : void 0;
8672
- return /* @__PURE__ */ jsxRuntime.jsx(
8786
+ const [scrolled, setScrolled] = react.useState(false);
8787
+ const handleScroll = (e) => {
8788
+ onScroll?.(e);
8789
+ if (header != null && !header.fix) setScrolled(e.currentTarget.scrollTop > 0);
8790
+ };
8791
+ const main = /* @__PURE__ */ jsxRuntime.jsx(
8673
8792
  "main",
8674
8793
  {
8675
8794
  ref,
@@ -8680,11 +8799,29 @@ var SPage = /* @__PURE__ */ react.forwardRef(function SPage2({ background = "fra
8680
8799
  BACKGROUND_CLASS[background],
8681
8800
  className
8682
8801
  ),
8683
- style: { ...gridStyle, ...style },
8802
+ style: header == null ? { ...gridStyle, ...style } : style,
8803
+ onScroll: handleScroll,
8684
8804
  ...rest,
8685
8805
  children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { minWidth: MIN_CONTENT_WIDTH }, children })
8686
8806
  }
8687
8807
  );
8808
+ if (header == null) return main;
8809
+ return (
8810
+ // fix 가 아닌 헤더는 자기 배경이 투명이라(SPageHeader.tsx) 이 배경이 그대로 비친다 —
8811
+ // 그래서 SPage 의 background 를 main 뿐 아니라 여기에도 칠한다(스크롤바 관련 클래스는
8812
+ // 스크롤 요소가 아닌 여기서는 그냥 무시된다). fix 헤더는 자체 흰 배경이 덮으므로 무관하다.
8813
+ /* @__PURE__ */ jsxRuntime.jsxs(
8814
+ "div",
8815
+ {
8816
+ className: cn("flex min-h-0 min-w-0 flex-1 flex-col", BACKGROUND_CLASS[background]),
8817
+ style: gridStyle,
8818
+ children: [
8819
+ /* @__PURE__ */ jsxRuntime.jsx(SPageHeader, { ...header, scrolled }),
8820
+ main
8821
+ ]
8822
+ }
8823
+ )
8824
+ );
8688
8825
  });
8689
8826
 
8690
8827
  // src/components/SPage/page.config.ts
@@ -11058,7 +11195,7 @@ var SInput = /* @__PURE__ */ react.forwardRef(function SInput2({
11058
11195
  lineHeight: `${f.lineHeight}px`
11059
11196
  },
11060
11197
  children: [
11061
- prefix != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: prefix }),
11198
+ prefix != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex flex-shrink-0 items-center", children: prefix }),
11062
11199
  /* @__PURE__ */ jsxRuntime.jsx(
11063
11200
  "input",
11064
11201
  {
@@ -11090,7 +11227,7 @@ var SInput = /* @__PURE__ */ react.forwardRef(function SInput2({
11090
11227
  ...rest
11091
11228
  }
11092
11229
  ),
11093
- suffix != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0", children: suffix }),
11230
+ suffix != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex flex-shrink-0 items-center", children: suffix }),
11094
11231
  clearable && hasValue && !disabled && !readOnly && /* @__PURE__ */ jsxRuntime.jsx(
11095
11232
  SGhostButton,
11096
11233
  {
@@ -11988,8 +12125,8 @@ function SDatePicker({
11988
12125
  disabled,
11989
12126
  className,
11990
12127
  style,
11991
- children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
11992
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
12128
+ children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
12129
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
11993
12130
  "button",
11994
12131
  {
11995
12132
  type: "button",
@@ -12025,13 +12162,16 @@ function SDatePicker({
12025
12162
  ]
12026
12163
  }
12027
12164
  ) }),
12028
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
12029
- RPopover2__namespace.Content,
12165
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
12166
+ RPopover9__namespace.Content,
12030
12167
  {
12031
12168
  side: "bottom",
12032
12169
  align: "start",
12033
12170
  sideOffset: 4,
12034
- className: cn("z-50", FLOATING_SLIDE_ANIM),
12171
+ className: cn(
12172
+ "z-50 max-w-[var(--radix-popover-content-available-width)]",
12173
+ FLOATING_SLIDE_ANIM
12174
+ ),
12035
12175
  children: /* @__PURE__ */ jsxRuntime.jsx(
12036
12176
  SCalendar,
12037
12177
  {
@@ -12179,78 +12319,81 @@ function RangeCalendar({
12179
12319
  }
12180
12320
  };
12181
12321
  const GRID_COLS = "grid grid-cols-[repeat(7,var(--cmp-datepicker-calendar-day-width))]";
12182
- const renderPanel = (gy, gm, isLeft) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-[var(--cmp-datepicker-calendar-gap)]", children: [
12183
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-center gap-[var(--cmp-datepicker-calendar-gap)]", children: [
12184
- /* @__PURE__ */ jsxRuntime.jsx(
12185
- SGhostButton,
12186
- {
12187
- icon: "chevronLeft",
12188
- size: "xxs",
12189
- ariaLabel: "prevMonth",
12190
- onClick: goPrevMonth,
12191
- className: cn(!isLeft && "invisible pointer-events-none")
12192
- }
12193
- ),
12194
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-[40px] flex-1 items-center justify-center text-center text-[14px] font-medium leading-[24px]", children: [
12195
- gy,
12196
- ".",
12197
- String(gm).padStart(2, "0")
12322
+ const renderPanel = (gy, gm, isLeft) => (
12323
+ // 좁은 창에서 눌려 격자가 깨지지 않도록, 줄이는 대신 부모가 가로 스크롤하게 둔다.
12324
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-col gap-[var(--cmp-datepicker-calendar-gap)]", children: [
12325
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-center gap-[var(--cmp-datepicker-calendar-gap)]", children: [
12326
+ /* @__PURE__ */ jsxRuntime.jsx(
12327
+ SGhostButton,
12328
+ {
12329
+ icon: "chevronLeft",
12330
+ size: "xxs",
12331
+ ariaLabel: "prevMonth",
12332
+ onClick: goPrevMonth,
12333
+ className: cn(!isLeft && "invisible pointer-events-none")
12334
+ }
12335
+ ),
12336
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-[40px] flex-1 items-center justify-center text-center text-[14px] font-medium leading-[24px]", children: [
12337
+ gy,
12338
+ ".",
12339
+ String(gm).padStart(2, "0")
12340
+ ] }),
12341
+ /* @__PURE__ */ jsxRuntime.jsx(
12342
+ SGhostButton,
12343
+ {
12344
+ icon: "chevronRight",
12345
+ size: "xxs",
12346
+ ariaLabel: "nextMonth",
12347
+ onClick: goNextMonth,
12348
+ className: cn(isLeft && "invisible pointer-events-none")
12349
+ }
12350
+ )
12198
12351
  ] }),
12199
- /* @__PURE__ */ jsxRuntime.jsx(
12200
- SGhostButton,
12201
- {
12202
- icon: "chevronRight",
12203
- size: "xxs",
12204
- ariaLabel: "nextMonth",
12205
- onClick: goNextMonth,
12206
- className: cn(isLeft && "invisible pointer-events-none")
12207
- }
12208
- )
12209
- ] }),
12210
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: GRID_COLS, children: WEEK_LABELS.map((l) => /* @__PURE__ */ jsxRuntime.jsx(
12211
- "span",
12212
- {
12213
- className: "flex h-[20px] items-center justify-center text-[12px] font-normal text-[color:var(--cmp-datepicker-calendar-week-color)]",
12214
- children: l
12215
- },
12216
- l
12217
- )) }),
12218
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(GRID_COLS, "gap-y-[var(--cmp-datepicker-calendar-grid-rowGap)]"), children: monthCells(gy, gm).map((cell) => {
12219
- const isEdge = cell.inCurrentMonth && (cell.date === rStart || complete && cell.date === rEnd);
12220
- const between = cell.inCurrentMonth && inRange(cell.date);
12221
- const disabled = cell.inCurrentMonth && isDisabled(cell.date);
12222
- const isToday = cell.inCurrentMonth && today === cell.date;
12223
- let cellBg;
12224
- if (between) cellBg = RANGE_BG;
12225
- else if (cell.inCurrentMonth && hasRange && cell.date === rStart)
12226
- cellBg = `linear-gradient(to right, transparent 50%, ${RANGE_BG} 50%)`;
12227
- else if (cell.inCurrentMonth && hasRange && cell.date === rEnd) cellBg = RANGE_BG;
12228
- return /* @__PURE__ */ jsxRuntime.jsx(
12229
- "div",
12352
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: GRID_COLS, children: WEEK_LABELS.map((l) => /* @__PURE__ */ jsxRuntime.jsx(
12353
+ "span",
12230
12354
  {
12231
- className: "flex h-[var(--cmp-datepicker-calendar-range-height)] w-[var(--cmp-datepicker-calendar-day-width)] items-center justify-center",
12232
- style: { background: cellBg },
12233
- children: /* @__PURE__ */ jsxRuntime.jsx(
12234
- "button",
12235
- {
12236
- type: "button",
12237
- disabled: !cell.inCurrentMonth || disabled,
12238
- onMouseEnter: () => setHover(cell.date),
12239
- onClick: () => cell.inCurrentMonth && click(cell.date),
12240
- className: cn(
12241
- "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]",
12242
- !cell.inCurrentMonth ? "invisible pointer-events-none" : disabled ? "cursor-not-allowed" : "cursor-pointer",
12243
- 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)]",
12244
- isToday && !disabled && "font-bold"
12245
- ),
12246
- children: cell.inCurrentMonth ? cell.day : ""
12247
- }
12248
- )
12355
+ className: "flex h-[20px] items-center justify-center text-[12px] font-normal text-[color:var(--cmp-datepicker-calendar-week-color)]",
12356
+ children: l
12249
12357
  },
12250
- cell.date
12251
- );
12252
- }) })
12253
- ] });
12358
+ l
12359
+ )) }),
12360
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(GRID_COLS, "gap-y-[var(--cmp-datepicker-calendar-grid-rowGap)]"), children: monthCells(gy, gm).map((cell) => {
12361
+ const isEdge = cell.inCurrentMonth && (cell.date === rStart || complete && cell.date === rEnd);
12362
+ const between = cell.inCurrentMonth && inRange(cell.date);
12363
+ const disabled = cell.inCurrentMonth && isDisabled(cell.date);
12364
+ const isToday = cell.inCurrentMonth && today === cell.date;
12365
+ let cellBg;
12366
+ if (between) cellBg = RANGE_BG;
12367
+ else if (cell.inCurrentMonth && hasRange && cell.date === rStart)
12368
+ cellBg = `linear-gradient(to right, transparent 50%, ${RANGE_BG} 50%)`;
12369
+ else if (cell.inCurrentMonth && hasRange && cell.date === rEnd) cellBg = RANGE_BG;
12370
+ return /* @__PURE__ */ jsxRuntime.jsx(
12371
+ "div",
12372
+ {
12373
+ className: "flex h-[var(--cmp-datepicker-calendar-range-height)] w-[var(--cmp-datepicker-calendar-day-width)] items-center justify-center",
12374
+ style: { background: cellBg },
12375
+ children: /* @__PURE__ */ jsxRuntime.jsx(
12376
+ "button",
12377
+ {
12378
+ type: "button",
12379
+ disabled: !cell.inCurrentMonth || disabled,
12380
+ onMouseEnter: () => setHover(cell.date),
12381
+ onClick: () => cell.inCurrentMonth && click(cell.date),
12382
+ className: cn(
12383
+ "relative z-[1] flex h-[var(--cmp-datepicker-calendar-day-circle-size)] w-[var(--cmp-datepicker-calendar-day-circle-size)] items-center justify-center rounded-[var(--cmp-datepicker-calendar-day-circle-radius)] border border-solid border-transparent bg-transparent text-[14px]",
12384
+ !cell.inCurrentMonth ? "invisible pointer-events-none" : disabled ? "cursor-not-allowed" : "cursor-pointer",
12385
+ isEdge ? "bg-[var(--cmp-datepicker-calendar-day-select-bg)] text-[color:var(--cmp-datepicker-calendar-day-select-text)]" : disabled ? "text-[color:var(--cmp-datepicker-calendar-day-disabled-text)]" : "text-[color:var(--cmp-datepicker-calendar-day-default-text)] hover:border-[color:var(--cmp-datepicker-calendar-day-hover-border)] hover:text-[color:var(--cmp-datepicker-calendar-day-hover-text)]",
12386
+ isToday && !disabled && "font-bold"
12387
+ ),
12388
+ children: cell.inCurrentMonth ? cell.day : ""
12389
+ }
12390
+ )
12391
+ },
12392
+ cell.date
12393
+ );
12394
+ }) })
12395
+ ] })
12396
+ );
12254
12397
  const renderTimeSection = (which, date, suffix) => {
12255
12398
  const active = !!date;
12256
12399
  const [yy = "", mm = "", dd = ""] = date ? date.split("-") : [];
@@ -12318,8 +12461,11 @@ function RangeCalendar({
12318
12461
  "div",
12319
12462
  {
12320
12463
  className: cn(
12321
- "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",
12322
- useTimePicker ? "h-auto" : "h-[364px]"
12464
+ "inline-flex max-w-full flex-col gap-[var(--cmp-datepicker-calendar-gap)] rounded-[var(--cmp-datepicker-calendar-radius)] bg-[var(--cmp-datepicker-calendar-bg)] p-[var(--cmp-datepicker-calendar-paddingXY)] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.2)] select-none",
12465
+ // 높이는 고정이 아니라 하한이다 — 5주/6주 달을 오갈 때의 높이 흔들림만 막으면 되고,
12466
+ // 창이 좁아 패널이 가로 스크롤될 때는 스크롤바 높이만큼 늘어날 수 있어야 한다.
12467
+ // (단일 달 SCalendar 도 min-h 를 쓴다)
12468
+ useTimePicker ? "h-auto" : "min-h-[364px]"
12323
12469
  ),
12324
12470
  children: [
12325
12471
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center", children: [
@@ -12338,11 +12484,20 @@ function RangeCalendar({
12338
12484
  }
12339
12485
  )
12340
12486
  ] }),
12341
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-[var(--cmp-datepicker-calendar-range-panelGap)]", children: [
12342
- renderPanel(y, m, true),
12343
- /* @__PURE__ */ jsxRuntime.jsx(SDivider, { vertical: true }),
12344
- renderPanel(nextMonthYM.year, nextMonthYM.month, false)
12345
- ] }),
12487
+ /* @__PURE__ */ jsxRuntime.jsxs(
12488
+ "div",
12489
+ {
12490
+ className: cn(
12491
+ "flex max-w-full gap-[var(--cmp-datepicker-calendar-range-panelGap)] overflow-x-auto",
12492
+ SCROLLBAR_CLASS
12493
+ ),
12494
+ children: [
12495
+ renderPanel(y, m, true),
12496
+ /* @__PURE__ */ jsxRuntime.jsx(SDivider, { vertical: true }),
12497
+ renderPanel(nextMonthYM.year, nextMonthYM.month, false)
12498
+ ]
12499
+ }
12500
+ ),
12346
12501
  useTimePicker && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
12347
12502
  /* @__PURE__ */ jsxRuntime.jsx(SDivider, {}),
12348
12503
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-[var(--cmp-datepicker-calendar-time-gap)]", children: [
@@ -12401,7 +12556,25 @@ function SDateRangePicker({
12401
12556
  });
12402
12557
  const resolvedError = error || ruleError !== "";
12403
12558
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
12404
- const dims = size === "md" ? { padX: 16, gap: 12, icon: 20, minW: 104, fontSize: 14, lineHeight: 24, radius: 6 } : { padX: 12, gap: 8, icon: 16, minW: 80, fontSize: 12, lineHeight: 20, radius: 4 };
12559
+ const dims = size === "md" ? {
12560
+ padX: 16,
12561
+ gap: 12,
12562
+ icon: 20,
12563
+ minW: 104,
12564
+ fieldMinW: 260,
12565
+ fontSize: 14,
12566
+ lineHeight: 24,
12567
+ radius: 6
12568
+ } : {
12569
+ padX: 12,
12570
+ gap: 8,
12571
+ icon: 16,
12572
+ minW: 80,
12573
+ fieldMinW: 216,
12574
+ fontSize: 12,
12575
+ lineHeight: 20,
12576
+ radius: 4
12577
+ };
12405
12578
  const select = (r) => {
12406
12579
  setPendingStart(null);
12407
12580
  onValueChange?.(r);
@@ -12427,11 +12600,12 @@ function SDateRangePicker({
12427
12600
  focused: open,
12428
12601
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
12429
12602
  width,
12603
+ minWidth: dims.fieldMinW,
12430
12604
  disabled,
12431
12605
  className,
12432
12606
  style,
12433
12607
  children: /* @__PURE__ */ jsxRuntime.jsxs(
12434
- RPopover2__namespace.Root,
12608
+ RPopover9__namespace.Root,
12435
12609
  {
12436
12610
  open,
12437
12611
  onOpenChange: (o) => {
@@ -12440,7 +12614,7 @@ function SDateRangePicker({
12440
12614
  if (!o) setPendingStart(null);
12441
12615
  },
12442
12616
  children: [
12443
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
12617
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
12444
12618
  "button",
12445
12619
  {
12446
12620
  type: "button",
@@ -12481,13 +12655,16 @@ function SDateRangePicker({
12481
12655
  ]
12482
12656
  }
12483
12657
  ) }),
12484
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
12485
- RPopover2__namespace.Content,
12658
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
12659
+ RPopover9__namespace.Content,
12486
12660
  {
12487
12661
  side: "bottom",
12488
12662
  align: "start",
12489
12663
  sideOffset: 4,
12490
- className: cn("z-50", FLOATING_SLIDE_ANIM),
12664
+ className: cn(
12665
+ "z-50 max-w-[var(--radix-popover-content-available-width)]",
12666
+ FLOATING_SLIDE_ANIM
12667
+ ),
12491
12668
  children: /* @__PURE__ */ jsxRuntime.jsx(
12492
12669
  RangeCalendar,
12493
12670
  {
@@ -13475,7 +13652,7 @@ function SRadioButton({
13475
13652
  disabled: od,
13476
13653
  onClick: () => !od && !selected && onValueChange?.(o.value),
13477
13654
  className: cn(
13478
- "relative box-border inline-flex items-center justify-center whitespace-nowrap border border-solid text-[12px] font-medium leading-[20px] transition-colors",
13655
+ "relative box-border grow inline-flex items-center justify-center whitespace-nowrap border border-solid text-[12px] font-medium leading-[20px] transition-colors",
13479
13656
  i === 0 ? "rounded-l-[var(--cmp-radioButton-radius)]" : "-ml-px",
13480
13657
  i === options.length - 1 ? "rounded-r-[var(--cmp-radioButton-radius)]" : "",
13481
13658
  selected ? "z-10" : "",
@@ -13497,85 +13674,1309 @@ function SRadioButton({
13497
13674
  }
13498
13675
  );
13499
13676
  }
13500
- var SChipInput = /* @__PURE__ */ react.forwardRef(function SChipInput2({
13501
- values = [],
13677
+ var CHIPFILTER_LISTBOX_WIDTH = 240;
13678
+ var CHIPFILTER_DATE_PRESET_LISTBOX_WIDTH = 246;
13679
+ var CHIPFILTER = {
13680
+ barBg: "var(--cmp-chipFilter-bar-bg)",
13681
+ barBorder: "var(--cmp-chipFilter-bar-border)",
13682
+ barPaddingX: "var(--cmp-chipFilter-bar-paddingX)",
13683
+ barPaddingY: "var(--cmp-chipFilter-bar-paddingY)",
13684
+ barGap: "var(--cmp-chipFilter-bar-gap)",
13685
+ dividerHeight: "var(--cmp-chipFilter-divider-height)",
13686
+ chipHeight: "var(--cmp-chipFilter-chip-height)",
13687
+ chipRadius: "var(--cmp-chipFilter-chip-radius)",
13688
+ chipGap: "var(--cmp-chipFilter-chip-gap)",
13689
+ chipDefaultBg: "var(--cmp-chipFilter-chip-default-bg)",
13690
+ chipDefaultBorder: "var(--cmp-chipFilter-chip-default-border)",
13691
+ chipDefaultText: "var(--cmp-chipFilter-chip-default-text)",
13692
+ chipActiveBorder: "var(--cmp-chipFilter-chip-active-border)",
13693
+ chipWarningBorder: "var(--cmp-chipFilter-chip-warning-border)",
13694
+ chipAppliedBg: "var(--cmp-chipFilter-chip-applied-bg)",
13695
+ chipAppliedBorder: "var(--cmp-chipFilter-chip-applied-border)",
13696
+ chipAppliedLabel: "var(--cmp-chipFilter-chip-applied-text-label)",
13697
+ chipAppliedValue: "var(--color-brilliantblue-80)",
13698
+ chipDisabledBg: "var(--cmp-chipFilter-chip-disabled-bg)",
13699
+ chipDisabledBorder: "var(--cmp-chipFilter-chip-disabled-border)",
13700
+ chipDisabledText: "var(--cmp-chipFilter-chip-disabled-text)",
13701
+ addBorder: "var(--cmp-chipFilter-addButton-border-default)",
13702
+ addBorderDisabled: "var(--cmp-chipFilter-addButton-border-disabled)",
13703
+ addText: "var(--cmp-chipFilter-addButton-text-default)",
13704
+ addTextDisabled: "var(--cmp-chipFilter-addButton-text-disabled)",
13705
+ listboxBg: "var(--cmp-chipFilter-listbox-bg)",
13706
+ listboxBorder: "var(--cmp-chipFilter-listbox-border-color)",
13707
+ listboxRadius: "var(--cmp-chipFilter-listbox-radius)",
13708
+ listboxLabel: "var(--cmp-chipFilter-listbox-top-label-color)",
13709
+ listboxBottomDividerHeight: "var(--cmp-chipFilter-listbox-bottom-divider-height)",
13710
+ itemPaddingLeft: "var(--cmp-chipFilter-item-paddingLeft)",
13711
+ itemPaddingRight: "var(--cmp-chipFilter-item-paddingRight)",
13712
+ itemPaddingY: "var(--cmp-chipFilter-item-paddingY)",
13713
+ itemMultiPaddingX: "var(--cmp-chipFilter-item-multi-paddingX)",
13714
+ itemTextDefault: "var(--cmp-chipFilter-item-text-default)",
13715
+ itemTextSelected: "var(--cmp-chipFilter-item-text-selected)"
13716
+ };
13717
+ var isKeywordValue = (value) => typeof value === "object" && value != null && !Array.isArray(value) && "keywords" in value;
13718
+ var keywordEntries = (value) => {
13719
+ if (isKeywordValue(value)) return value.keywords;
13720
+ if (Array.isArray(value)) {
13721
+ return value.filter((v) => typeof v === "string");
13722
+ }
13723
+ return [];
13724
+ };
13725
+ var isEmptyValue = (value) => {
13726
+ if (value == null) return true;
13727
+ if (Array.isArray(value)) return value.length === 0 || value.every((v) => v == null || v === "");
13728
+ if (isKeywordValue(value)) return value.keywords.length === 0;
13729
+ return value === "";
13730
+ };
13731
+ var normalizeValues = (value) => value ?? {};
13732
+ var toArrayValue = (value) => Array.isArray(value) ? value.filter(
13733
+ (v) => typeof v === "string" || typeof v === "number"
13734
+ ) : [];
13735
+ var todayIso = () => {
13736
+ const d = /* @__PURE__ */ new Date();
13737
+ const pad = (n) => String(n).padStart(2, "0");
13738
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
13739
+ };
13740
+ var isInlineDateField = (field) => field.type === "date" && field.radioButton === true && (field.presets?.length ?? 0) > 0;
13741
+ var resolveFieldDefault = (field) => {
13742
+ if (field.defaultValue !== void 0) return field.defaultValue;
13743
+ if (isInlineDateField(field)) return todayIso();
13744
+ if (!field.required) return void 0;
13745
+ if (field.type === "single") return field.options?.[0]?.value;
13746
+ if (field.type === "date") return todayIso();
13747
+ return void 0;
13748
+ };
13749
+ var valuesEqual = (a, b) => {
13750
+ if (isKeywordValue(a) || isKeywordValue(b)) {
13751
+ return isKeywordValue(a) && isKeywordValue(b) && a.mode === b.mode && valuesEqual(a.keywords, b.keywords);
13752
+ }
13753
+ if (Array.isArray(a) || Array.isArray(b)) {
13754
+ return Array.isArray(a) && Array.isArray(b) && a.length === b.length && a.every((v, i) => v === b[i]);
13755
+ }
13756
+ return a === b;
13757
+ };
13758
+ var valuesMapEqual = (a, b) => {
13759
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
13760
+ for (const key of keys) {
13761
+ if (!valuesEqual(a[key], b[key])) return false;
13762
+ }
13763
+ return true;
13764
+ };
13765
+ var isAtDefaultValue = (field, value) => {
13766
+ const def = resolveFieldDefault(field);
13767
+ return def !== void 0 && valuesEqual(value, def);
13768
+ };
13769
+ var isInlineField = (field) => isInlineDateField(field) || field.type === "custom";
13770
+ var isGroupedFields = (fields) => fields.length > 0 && "fields" in fields[0];
13771
+ var isGroupSatisfied = (group, values) => {
13772
+ if (!group.rule) return true;
13773
+ const keys = group.fields.map((f) => f.key);
13774
+ if (group.rule.type === "requireKey") return !isEmptyValue(values[group.rule.key]);
13775
+ if (group.rule.type === "requireAll") return keys.every((k) => !isEmptyValue(values[k]));
13776
+ if (group.rule.type === "requireAny") return keys.some((k) => !isEmptyValue(values[k]));
13777
+ return true;
13778
+ };
13779
+ var koreanParticle = (word, consonant, vowel) => {
13780
+ const code = word.charCodeAt(word.length - 1);
13781
+ if (code < 44032 || code > 55203) return consonant;
13782
+ return (code - 44032) % 28 === 0 ? vowel : consonant;
13783
+ };
13784
+ var defaultGroupTooltipMessage = (rule, fields) => {
13785
+ if (rule.type === "requireAll") return "\uD56D\uBAA9\uC744 \uBAA8\uB450 \uC120\uD0DD\uD574 \uC8FC\uC138\uC694.";
13786
+ if (rule.type === "requireAny") {
13787
+ const name = rule.dataGroupName;
13788
+ if (!name) return "\uCD5C\uC18C 1\uAC1C\uC758 \uD544\uD130\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694.";
13789
+ return `\uCD5C\uC18C 1\uAC1C\uC758 ${name}${koreanParticle(name, "\uC744", "\uB97C")} \uC120\uD0DD\uD574\uC8FC\uC138\uC694.`;
13790
+ }
13791
+ const label = fields.find((f) => f.key === rule.key)?.label ?? "\uD56D\uBAA9";
13792
+ return `${label}${koreanParticle(label, "\uC744", "\uB97C")} \uC120\uD0DD\uD574 \uC8FC\uC138\uC694.`;
13793
+ };
13794
+ var matchPresetId = (presets, value) => {
13795
+ if (isEmptyValue(value)) return null;
13796
+ for (const preset of presets) {
13797
+ if (preset.custom || !preset.resolve) continue;
13798
+ const resolved = preset.resolve();
13799
+ if (Array.isArray(resolved) && Array.isArray(value)) {
13800
+ if (resolved[0] === value[0] && resolved[1] === value[1]) return preset.value;
13801
+ } else if (!Array.isArray(resolved) && !Array.isArray(value) && resolved === value) {
13802
+ return preset.value;
13803
+ }
13804
+ }
13805
+ return presets.find((preset) => preset.custom)?.value ?? null;
13806
+ };
13807
+ var optionLabel = (field, value) => field.options?.find((option) => option.value === value)?.label ?? String(value);
13808
+ var formatValueList = (labels) => {
13809
+ if (labels.length <= 1) return labels[0] ?? "";
13810
+ return `${labels[0]} \uC678 ${labels.length - 1}\uAC1C`;
13811
+ };
13812
+ var displayValue = (field, value) => {
13813
+ if (isEmptyValue(value)) return "";
13814
+ if (field.type === "multi")
13815
+ return formatValueList(toArrayValue(value).map((v) => optionLabel(field, v)));
13816
+ if (field.type === "single") return optionLabel(field, value);
13817
+ if (field.type === "date")
13818
+ return Array.isArray(value) ? `${value[0]} ~ ${value[1]}` : String(value);
13819
+ if (field.type === "keyword") return formatValueList(keywordEntries(value));
13820
+ return String(value);
13821
+ };
13822
+ var chipDisplayValue = (field, value) => {
13823
+ const text = displayValue(field, value);
13824
+ if (field.type !== "date" || isEmptyValue(value)) return text;
13825
+ const presets = field.presets ?? [];
13826
+ if (presets.length === 0) return text;
13827
+ const preset = presets.find((p) => p.value === matchPresetId(presets, value));
13828
+ return preset && !preset.custom ? `${preset.label}(${text})` : text;
13829
+ };
13830
+ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
13831
+ fields: fieldsProp = [],
13832
+ value,
13502
13833
  onValueChange,
13503
- onInput,
13504
- onFocus,
13505
- onBlur,
13506
- errors = [],
13507
- disabledChips = [],
13508
- size = "sm",
13834
+ onFilterChange,
13835
+ onSearch,
13836
+ activeKeys,
13837
+ onActiveKeysChange,
13838
+ onReset,
13839
+ onAddFilter,
13840
+ label = "\uAC80\uC0C9 \uD544\uD130",
13841
+ showLabel = false,
13842
+ showAddButton = true,
13843
+ showReset = true,
13509
13844
  disabled = false,
13510
- placeholder = "\uD0DC\uADF8 \uC785\uB825 (Enter\uB85C \uB4F1\uB85D / \uCF64\uB9C8\uB85C \uAD6C\uBD84 / \uB744\uC5B4\uC4F0\uAE30 \uBD88\uAC00)",
13511
- name,
13512
- rules,
13513
- error,
13514
- useReset = false,
13515
- maxCount,
13516
- metaPlacement = "end",
13517
- duplicateLabel,
13518
- suggestions = [],
13519
- loadingSuggestions = false,
13520
- recommendedItems = [],
13521
- loadingRecommendedItems = false,
13522
- dropdownMinWidth = 200,
13523
- label,
13524
- labelWidth,
13525
- hint,
13526
- errorMessage,
13527
- width,
13528
- status,
13529
- icon,
13530
- iconColor,
13531
- labelTooltip,
13532
- labelTooltipProps,
13533
13845
  className,
13534
13846
  style
13535
13847
  }, ref) {
13536
- const autoId = react.useId();
13537
- const resolvedName = name ?? autoId;
13538
- const [inputValue, setInputValue] = react.useState("");
13539
- const [focused, setFocused] = react.useState(false);
13540
- const [hovered, setHovered] = react.useState(false);
13541
- const [activeIndex, setActiveIndex] = react.useState(-1);
13542
- const [suppressSuggestions, setSuppressSuggestions] = react.useState(false);
13543
- const [suppressRecommendations, setSuppressRecommendations] = react.useState(false);
13544
- const inputRef = react.useRef(null);
13545
- const containerRef = react.useRef(null);
13546
- const inputShellRef = react.useRef(null);
13547
- const isComposingRef = react.useRef(false);
13548
- react.useImperativeHandle(ref, () => ({ focus: () => inputRef.current?.focus() }), []);
13549
- const chipError = (i) => {
13550
- if (values.indexOf(values[i]) !== i) return true;
13551
- if (typeof errors === "function") return errors(values[i] ?? "");
13552
- return errors[i] ?? false;
13553
- };
13554
- const chipDisabled = (i) => {
13555
- if (disabled) return true;
13556
- if (typeof disabledChips === "function") return disabledChips(values[i] ?? "");
13557
- return disabledChips[i] ?? false;
13558
- };
13559
- const duplicateMessage = react.useMemo(() => {
13560
- const seen = /* @__PURE__ */ new Set();
13561
- for (const v of values) {
13562
- if (seen.has(v))
13563
- return duplicateLabel ? `\uC774\uBBF8 \uC785\uB825\uB41C ${duplicateLabel}\uC785\uB2C8\uB2E4.` : "\uC911\uBCF5\uB41C \uAC12\uC774 \uC874\uC7AC\uD569\uB2C8\uB2E4.";
13564
- seen.add(v);
13565
- }
13566
- return "";
13567
- }, [values, duplicateLabel]);
13568
- const ruleMessage = react.useMemo(() => runRules(values, rules), [values, rules]);
13569
- const resolvedErrorMessage = duplicateMessage || ruleMessage || errorMessage;
13570
- const resolvedError = error || duplicateMessage !== "" || ruleMessage !== "";
13571
- const resolvedStatus = status ?? (resolvedError ? "error" : void 0);
13572
- useFormField({
13573
- name,
13574
- validate: () => runRules(values, rules) === "" && duplicateMessage === "",
13575
- resetValidation: () => {
13848
+ const usingGroupedFields = isGroupedFields(fieldsProp);
13849
+ const groups = react.useMemo(
13850
+ () => usingGroupedFields ? fieldsProp : [{ fields: fieldsProp }],
13851
+ [usingGroupedFields, fieldsProp]
13852
+ );
13853
+ const fields = react.useMemo(() => groups.flatMap((group) => group.fields), [groups]);
13854
+ const values = normalizeValues(value);
13855
+ const portalContainer = usePortalContainer();
13856
+ const [internalActiveKeys, setInternalActiveKeys] = react.useState(
13857
+ () => fields.filter((field) => field.defaultActive !== false).map((field) => field.key)
13858
+ );
13859
+ const [openKey, setOpenKey] = react.useState(null);
13860
+ const [addOpen, setAddOpen] = react.useState(false);
13861
+ const triggerRefs = react.useRef(/* @__PURE__ */ new Map());
13862
+ const initializedDefaultsRef = react.useRef(/* @__PURE__ */ new Set());
13863
+ const prevOpenKeyRef = react.useRef(null);
13864
+ const valuesRef = react.useRef(values);
13865
+ valuesRef.current = values;
13866
+ const lastSearchedValuesRef = react.useRef(values);
13867
+ const invalidGroups = react.useMemo(() => {
13868
+ const invalid = /* @__PURE__ */ new Set();
13869
+ groups.forEach((group, groupIndex) => {
13870
+ if (!isGroupSatisfied(group, values)) invalid.add(groupIndex);
13871
+ });
13872
+ return invalid;
13873
+ }, [groups, values]);
13874
+ const isGroupsValid = react.useCallback(
13875
+ (current) => groups.every((group) => isGroupSatisfied(group, current)),
13876
+ [groups]
13877
+ );
13878
+ const runSearch = react.useCallback(
13879
+ (next) => {
13880
+ if (valuesMapEqual(lastSearchedValuesRef.current, next)) return;
13881
+ if (!isGroupsValid(next)) return;
13882
+ lastSearchedValuesRef.current = next;
13883
+ onSearch?.(next);
13884
+ },
13885
+ [isGroupsValid, onSearch]
13886
+ );
13887
+ react.useEffect(() => {
13888
+ const prevKey = prevOpenKeyRef.current;
13889
+ if (prevKey != null && prevKey !== openKey) {
13890
+ runSearch(valuesRef.current);
13576
13891
  }
13892
+ prevOpenKeyRef.current = openKey;
13893
+ }, [openKey, runSearch]);
13894
+ react.useEffect(() => {
13895
+ if (activeKeys) return;
13896
+ setInternalActiveKeys((current) => {
13897
+ const known = new Set(fields.map((field) => field.key));
13898
+ const kept = current.filter((key) => known.has(key));
13899
+ const additions = fields.filter((field) => field.defaultActive !== false && !kept.includes(field.key)).map((field) => field.key);
13900
+ return [...kept, ...additions];
13901
+ });
13902
+ }, [activeKeys, fields]);
13903
+ react.useEffect(() => {
13904
+ const patch = {};
13905
+ let changed = false;
13906
+ fields.forEach((field) => {
13907
+ if (initializedDefaultsRef.current.has(field.key)) return;
13908
+ if (!isEmptyValue(values[field.key])) {
13909
+ initializedDefaultsRef.current.add(field.key);
13910
+ return;
13911
+ }
13912
+ const def = resolveFieldDefault(field);
13913
+ if (def === void 0) return;
13914
+ patch[field.key] = def;
13915
+ initializedDefaultsRef.current.add(field.key);
13916
+ changed = true;
13917
+ });
13918
+ if (changed) onValueChange?.({ ...values, ...patch });
13919
+ }, [fields, values, onValueChange]);
13920
+ const resolvedActiveKeys = activeKeys ?? internalActiveKeys;
13921
+ const activeFields = fields.filter((field) => resolvedActiveKeys.includes(field.key));
13922
+ const inactiveFields = fields.filter((field) => !resolvedActiveKeys.includes(field.key));
13923
+ const groupIndexByKey = /* @__PURE__ */ new Map();
13924
+ groups.forEach((group, groupIndex) => {
13925
+ group.fields.forEach((field) => groupIndexByKey.set(field.key, groupIndex));
13577
13926
  });
13578
- const isMax = maxCount != null && values.length >= maxCount;
13927
+ const hasValue = fields.some((field) => !isEmptyValue(values[field.key]));
13928
+ const hasRightActions = showAddButton || showReset;
13929
+ const currentOpenField = fields.find((field) => field.key === openKey);
13930
+ const editorPopoverStyle = react.useMemo(() => {
13931
+ if (currentOpenField?.type === "keyword") {
13932
+ return { width: CHIPFILTER_LISTBOX_WIDTH };
13933
+ }
13934
+ if (currentOpenField?.type === "date" && (currentOpenField.presets?.length ?? 0) > 0) {
13935
+ return { width: CHIPFILTER_DATE_PRESET_LISTBOX_WIDTH };
13936
+ }
13937
+ return { width: CHIPFILTER_LISTBOX_WIDTH };
13938
+ }, [currentOpenField?.type, currentOpenField?.presets?.length]);
13939
+ const setActive = (next) => {
13940
+ if (!activeKeys) setInternalActiveKeys(next);
13941
+ onActiveKeysChange?.(next);
13942
+ };
13943
+ const emitValue = (key, nextValue) => {
13944
+ const next = { ...values, [key]: nextValue };
13945
+ onValueChange?.(next);
13946
+ onFilterChange?.({ key, value: nextValue, values: next });
13947
+ return next;
13948
+ };
13949
+ const emitValueAndSearch = (key, nextValue) => {
13950
+ runSearch(emitValue(key, nextValue));
13951
+ };
13952
+ const clearValue = (key) => {
13953
+ const field = fields.find((f) => f.key === key);
13954
+ const def = field ? resolveFieldDefault(field) : void 0;
13955
+ runSearch(emitValue(key, def !== void 0 ? def : null));
13956
+ };
13957
+ const reset = react.useCallback(() => {
13958
+ const next = fields.reduce((acc, field) => {
13959
+ const def = resolveFieldDefault(field);
13960
+ acc[field.key] = def !== void 0 ? def : null;
13961
+ return acc;
13962
+ }, {});
13963
+ onValueChange?.(next);
13964
+ runSearch(next);
13965
+ onReset?.();
13966
+ }, [fields, onReset, onValueChange, runSearch]);
13967
+ const open = react.useCallback(
13968
+ (key) => {
13969
+ const field = fields.find((f) => f.key === key);
13970
+ if (!field || isInlineField(field)) return;
13971
+ setOpenKey(key);
13972
+ },
13973
+ [fields]
13974
+ );
13975
+ const validate = react.useCallback(() => invalidGroups.size === 0, [invalidGroups]);
13976
+ react.useImperativeHandle(ref, () => ({ open, reset, validate }), [open, reset, validate]);
13977
+ const addField = (key) => {
13978
+ const next = [...resolvedActiveKeys, key];
13979
+ setActive(next);
13980
+ onAddFilter?.(key);
13981
+ setAddOpen(false);
13982
+ const field = fields.find((f) => f.key === key);
13983
+ if (field && !isInlineField(field)) requestAnimationFrame(() => setOpenKey(key));
13984
+ };
13985
+ const rootStyle = react.useMemo(
13986
+ () => ({
13987
+ "--chipfilter-bar-bg": CHIPFILTER.barBg,
13988
+ "--chipfilter-bar-border": CHIPFILTER.barBorder,
13989
+ "--chipfilter-bar-padding-x": CHIPFILTER.barPaddingX,
13990
+ "--chipfilter-bar-padding-y": CHIPFILTER.barPaddingY,
13991
+ "--chipfilter-bar-gap": CHIPFILTER.barGap,
13992
+ "--chipfilter-divider-height": CHIPFILTER.dividerHeight,
13993
+ ...style
13994
+ }),
13995
+ [style]
13996
+ );
13997
+ return /* @__PURE__ */ jsxRuntime.jsxs(
13998
+ "div",
13999
+ {
14000
+ className: cn(
14001
+ "flex w-full items-start gap-[var(--chipfilter-bar-gap)] border-b border-solid bg-[var(--chipfilter-bar-bg)] px-[var(--chipfilter-bar-padding-x)] py-[var(--chipfilter-bar-padding-y)]",
14002
+ className
14003
+ ),
14004
+ style: { ...rootStyle, borderColor: "var(--chipfilter-bar-border)" },
14005
+ children: [
14006
+ showLabel && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14007
+ /* @__PURE__ */ jsxRuntime.jsx(STag, { className: "shrink-0", shape: "square", size: "sm", color: "darkblue", label }),
14008
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, { top: true })
14009
+ ] }),
14010
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-[var(--cmp-chipFilter-chip-group)]", children: [
14011
+ (() => {
14012
+ const nodes = [];
14013
+ let runBuffer = [];
14014
+ let runGroupIndex = null;
14015
+ const flushRun = () => {
14016
+ if (runBuffer.length === 0) return;
14017
+ const group = runGroupIndex != null ? groups[runGroupIndex] : void 0;
14018
+ const message = usingGroupedFields && group?.rule ? group.tooltipMessage ?? defaultGroupTooltipMessage(group.rule, group.fields) : void 0;
14019
+ if (message) {
14020
+ nodes.push(
14021
+ /* @__PURE__ */ jsxRuntime.jsx(
14022
+ GroupTooltip,
14023
+ {
14024
+ message,
14025
+ open: invalidGroups.has(runGroupIndex),
14026
+ children: runBuffer
14027
+ },
14028
+ `group-${runGroupIndex}`
14029
+ )
14030
+ );
14031
+ } else {
14032
+ nodes.push(...runBuffer);
14033
+ }
14034
+ runBuffer = [];
14035
+ };
14036
+ activeFields.forEach((field, index) => {
14037
+ const inline = isInlineField(field);
14038
+ const groupIndex = groupIndexByKey.get(field.key) ?? 0;
14039
+ const warning = field.warning || invalidGroups.has(groupIndex);
14040
+ const prevField = index > 0 ? activeFields[index - 1] : null;
14041
+ const prevGroupIndex = prevField ? groupIndexByKey.get(prevField.key) ?? 0 : null;
14042
+ const needsLeadingDivider = index === 0 ? false : inline || prevField != null && isInlineField(prevField) || groupIndex !== prevGroupIndex;
14043
+ if (groupIndex !== runGroupIndex) {
14044
+ flushRun();
14045
+ runGroupIndex = groupIndex;
14046
+ }
14047
+ const showClear = !isAtDefaultValue(field, values[field.key]);
14048
+ const node = isInlineDateField(field) ? /* @__PURE__ */ jsxRuntime.jsx(
14049
+ InlineDatePresetFilter,
14050
+ {
14051
+ field,
14052
+ value: values[field.key],
14053
+ disabled: disabled || field.disabled,
14054
+ warning,
14055
+ showClear,
14056
+ onValueChange: (next) => emitValueAndSearch(field.key, next),
14057
+ onClear: () => clearValue(field.key)
14058
+ },
14059
+ field.key
14060
+ ) : field.type === "custom" ? /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: field.render?.({
14061
+ value: values[field.key],
14062
+ disabled: disabled || field.disabled,
14063
+ warning,
14064
+ onValueChange: (next) => emitValueAndSearch(field.key, next)
14065
+ }) }, field.key) : /* @__PURE__ */ jsxRuntime.jsx(
14066
+ FilterChip,
14067
+ {
14068
+ field,
14069
+ valueText: chipDisplayValue(field, values[field.key]),
14070
+ active: openKey === field.key,
14071
+ applied: !isEmptyValue(values[field.key]),
14072
+ disabled: disabled || field.disabled,
14073
+ warning,
14074
+ showClear,
14075
+ refCallback: (node2) => triggerRefs.current.set(field.key, node2),
14076
+ onOpen: () => setOpenKey(openKey === field.key ? null : field.key),
14077
+ onClear: () => clearValue(field.key)
14078
+ },
14079
+ field.key
14080
+ );
14081
+ runBuffer.push(
14082
+ needsLeadingDivider ? /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
14083
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, { inset: true }),
14084
+ node
14085
+ ] }, `${field.key}-wrap`) : node
14086
+ );
14087
+ });
14088
+ flushRun();
14089
+ return nodes;
14090
+ })(),
14091
+ (() => {
14092
+ const lastField = activeFields[activeFields.length - 1];
14093
+ const needsTrailingDivider = lastField != null && isInlineField(lastField) && !hasRightActions;
14094
+ return needsTrailingDivider ? /* @__PURE__ */ jsxRuntime.jsx(Divider, { inset: true }) : null;
14095
+ })(),
14096
+ hasRightActions && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14097
+ /* @__PURE__ */ jsxRuntime.jsx(Divider, { inset: true }),
14098
+ showAddButton && /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open: addOpen, onOpenChange: setAddOpen, children: [
14099
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
14100
+ "button",
14101
+ {
14102
+ type: "button",
14103
+ disabled: disabled || inactiveFields.length === 0,
14104
+ className: "inline-flex shrink-0 cursor-pointer items-center justify-center gap-[var(--cmp-chipFilter-addButton-gap)] whitespace-nowrap border border-solid bg-[var(--cmp-chipFilter-addButton-bg)] px-[var(--cmp-chipFilter-addButton-paddingX)] text-[12px] font-medium leading-[20px] outline-none disabled:cursor-not-allowed",
14105
+ style: {
14106
+ minHeight: "var(--cmp-chipFilter-addButton-height)" ,
14107
+ borderRadius: "var(--cmp-chipFilter-addButton-radius)" ,
14108
+ borderColor: disabled || inactiveFields.length === 0 ? CHIPFILTER.addBorderDisabled : CHIPFILTER.addBorder,
14109
+ color: disabled || inactiveFields.length === 0 ? CHIPFILTER.addTextDisabled : CHIPFILTER.addText
14110
+ },
14111
+ children: [
14112
+ /* @__PURE__ */ jsxRuntime.jsx(
14113
+ SIcon,
14114
+ {
14115
+ name: "add",
14116
+ size: 12,
14117
+ color: disabled || inactiveFields.length === 0 ? CHIPFILTER.addTextDisabled : CHIPFILTER.addText
14118
+ }
14119
+ ),
14120
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\uD544\uD130 \uCD94\uAC00" })
14121
+ ]
14122
+ }
14123
+ ) }),
14124
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
14125
+ RPopover9__namespace.Content,
14126
+ {
14127
+ side: "bottom",
14128
+ align: "start",
14129
+ sideOffset: 4,
14130
+ className: cn("z-50 min-w-[180px]", FLOATING_SLIDE_ANIM),
14131
+ children: /* @__PURE__ */ jsxRuntime.jsx(ListBox, { children: inactiveFields.length > 0 ? inactiveFields.map((field) => /* @__PURE__ */ jsxRuntime.jsx(MenuItem, { onClick: () => addField(field.key), children: field.label }, field.key)) : /* @__PURE__ */ jsxRuntime.jsx(EmptyItem, { children: "\uCD94\uAC00\uD560 \uD544\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) })
14132
+ }
14133
+ ) })
14134
+ ] }),
14135
+ showReset && /* @__PURE__ */ jsxRuntime.jsxs(
14136
+ "button",
14137
+ {
14138
+ type: "button",
14139
+ disabled: disabled || !hasValue,
14140
+ onClick: reset,
14141
+ className: cn(
14142
+ "inline-flex shrink-0 cursor-pointer items-center gap-[4px] border-none bg-transparent p-0 text-[12px] font-medium leading-[20px] disabled:cursor-not-allowed",
14143
+ // 부모 flex gap(6px)은 칩 사이 간격 기준이라, 필터 추가 버튼과의 12px 간격을 맞추려면
14144
+ // 부족한 만큼(12px - gap)을 추가로 띄운다. 필터 추가가 없을 땐 divider와 이미 12px이라 불필요.
14145
+ showAddButton && "ml-[calc(12px-var(--cmp-chipFilter-chip-group))]"
14146
+ ),
14147
+ style: {
14148
+ color: disabled || !hasValue ? "var(--cmp-textLink-content-disabled)" : "var(--cmp-textLink-content-default)"
14149
+ },
14150
+ children: [
14151
+ /* @__PURE__ */ jsxRuntime.jsx(
14152
+ SIcon,
14153
+ {
14154
+ name: "reset",
14155
+ size: 16,
14156
+ color: disabled || !hasValue ? "var(--cmp-textLink-icon-disabled)" : "var(--cmp-textLink-icon-default)"
14157
+ }
14158
+ ),
14159
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\uAC80\uC0C9 \uCD08\uAE30\uD654" })
14160
+ ]
14161
+ }
14162
+ )
14163
+ ] })
14164
+ ] }),
14165
+ /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open: openKey != null, onOpenChange: (next) => !next && setOpenKey(null), children: [
14166
+ /* @__PURE__ */ jsxRuntime.jsx(
14167
+ RPopover9__namespace.Anchor,
14168
+ {
14169
+ virtualRef: {
14170
+ current: openKey ? triggerRefs.current.get(openKey) : null
14171
+ }
14172
+ }
14173
+ ),
14174
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
14175
+ RPopover9__namespace.Content,
14176
+ {
14177
+ side: "bottom",
14178
+ align: "start",
14179
+ sideOffset: 4,
14180
+ onOpenAutoFocus: (event) => event.preventDefault(),
14181
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
14182
+ style: editorPopoverStyle,
14183
+ children: currentOpenField && /* @__PURE__ */ jsxRuntime.jsx(
14184
+ EditorPanel,
14185
+ {
14186
+ field: currentOpenField,
14187
+ value: values[currentOpenField.key],
14188
+ disabled: disabled || currentOpenField.disabled,
14189
+ onValueChange: (next) => emitValue(currentOpenField.key, next),
14190
+ onClose: () => setOpenKey(null)
14191
+ }
14192
+ )
14193
+ }
14194
+ ) })
14195
+ ] })
14196
+ ]
14197
+ }
14198
+ );
14199
+ });
14200
+ function Divider({ inset, top } = {}) {
14201
+ return /* @__PURE__ */ jsxRuntime.jsx(
14202
+ "span",
14203
+ {
14204
+ "aria-hidden": true,
14205
+ className: cn(
14206
+ "inline-flex w-px shrink-0 bg-[var(--chipfilter-bar-border)]",
14207
+ top ? "mt-[2px] self-start" : "my-auto",
14208
+ inset && "mx-[calc(12px-var(--cmp-chipFilter-chip-group))]"
14209
+ ),
14210
+ style: { height: "var(--chipfilter-divider-height)" }
14211
+ }
14212
+ );
14213
+ }
14214
+ function GroupTooltip({
14215
+ message,
14216
+ open,
14217
+ children
14218
+ }) {
14219
+ return /* @__PURE__ */ jsxRuntime.jsx(STooltip2, { trigger: "none", open, placement: "top", tooltipType: "warning", message: [message], children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-[var(--cmp-chipFilter-chip-group)]", children }) });
14220
+ }
14221
+ function FilterChip({
14222
+ field,
14223
+ valueText,
14224
+ active,
14225
+ applied,
14226
+ disabled,
14227
+ warning,
14228
+ showClear,
14229
+ refCallback,
14230
+ onOpen,
14231
+ onClear
14232
+ }) {
14233
+ const border = disabled ? CHIPFILTER.chipDisabledBorder : warning ? CHIPFILTER.chipWarningBorder : applied ? active ? "var(--color-blue-strong)" : CHIPFILTER.chipAppliedBorder : active ? CHIPFILTER.chipActiveBorder : CHIPFILTER.chipDefaultBorder;
14234
+ const background = disabled ? CHIPFILTER.chipDisabledBg : applied ? CHIPFILTER.chipAppliedBg : CHIPFILTER.chipDefaultBg;
14235
+ const textColor = disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipDefaultText;
14236
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14237
+ "button",
14238
+ {
14239
+ ref: refCallback,
14240
+ type: "button",
14241
+ disabled,
14242
+ "aria-expanded": active,
14243
+ onPointerDown: (event) => event.stopPropagation(),
14244
+ onClick: onOpen,
14245
+ className: "group/chip-filter inline-flex max-w-full shrink-0 cursor-pointer items-center justify-center whitespace-nowrap border border-solid px-[var(--cmp-chipFilter-chip-paddingX)] text-[12px] leading-[20px] outline-none disabled:cursor-not-allowed",
14246
+ style: {
14247
+ minHeight: CHIPFILTER.chipHeight,
14248
+ gap: CHIPFILTER.chipGap,
14249
+ borderRadius: CHIPFILTER.chipRadius,
14250
+ borderColor: border,
14251
+ background,
14252
+ color: textColor
14253
+ },
14254
+ children: [
14255
+ field.required && /* @__PURE__ */ jsxRuntime.jsx(
14256
+ SIcon,
14257
+ {
14258
+ name: "asterisk",
14259
+ size: 12,
14260
+ color: disabled ? CHIPFILTER.chipDisabledText : "var(--cmp-tag-darkblue-content)"
14261
+ }
14262
+ ),
14263
+ applied ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-0 items-center gap-[4px] truncate", children: [
14264
+ /* @__PURE__ */ jsxRuntime.jsx(
14265
+ "span",
14266
+ {
14267
+ className: "font-bold",
14268
+ style: { color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipAppliedLabel },
14269
+ children: field.label
14270
+ }
14271
+ ),
14272
+ /* @__PURE__ */ jsxRuntime.jsx(
14273
+ "span",
14274
+ {
14275
+ style: { color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipAppliedLabel },
14276
+ children: ":"
14277
+ }
14278
+ ),
14279
+ /* @__PURE__ */ jsxRuntime.jsx(
14280
+ "span",
14281
+ {
14282
+ className: "min-w-0 truncate font-bold",
14283
+ style: { color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipAppliedValue },
14284
+ children: valueText
14285
+ }
14286
+ )
14287
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14288
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("min-w-0 truncate", active && "font-bold"), children: field.label }),
14289
+ /* @__PURE__ */ jsxRuntime.jsx(
14290
+ SIcon,
14291
+ {
14292
+ name: "caretDown",
14293
+ size: 12,
14294
+ color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.chipDefaultText
14295
+ }
14296
+ )
14297
+ ] }),
14298
+ applied && !disabled && showClear && /* @__PURE__ */ jsxRuntime.jsx(
14299
+ "span",
14300
+ {
14301
+ role: "button",
14302
+ tabIndex: 0,
14303
+ "aria-label": `${field.label} \uD544\uD130 \uC9C0\uC6B0\uAE30`,
14304
+ className: "inline-flex size-[16px] items-center justify-center rounded-[var(--cmp-ghostButton-radius-sm)]",
14305
+ onClick: (event) => {
14306
+ event.preventDefault();
14307
+ event.stopPropagation();
14308
+ onClear();
14309
+ },
14310
+ onKeyDown: (event) => {
14311
+ if (event.key !== "Enter" && event.key !== " ") return;
14312
+ event.preventDefault();
14313
+ event.stopPropagation();
14314
+ onClear();
14315
+ },
14316
+ children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
14317
+ }
14318
+ )
14319
+ ]
14320
+ }
14321
+ );
14322
+ }
14323
+ function EditorPanel({
14324
+ field,
14325
+ value,
14326
+ disabled,
14327
+ onValueChange,
14328
+ onClose
14329
+ }) {
14330
+ const wrapsInPaddedTop = field.type === "date";
14331
+ const isMulti = field.type === "multi";
14332
+ const selectableValues = isMulti ? (field.options ?? []).filter((option) => !option.disabled).map((option) => option.value) : [];
14333
+ const selectedValues = isMulti ? toArrayValue(value) : [];
14334
+ const allSelected = isMulti && selectableValues.length > 0 && selectableValues.every((v) => selectedValues.includes(v));
14335
+ const toggleSelectAll = () => {
14336
+ onValueChange(
14337
+ allSelected ? selectedValues.filter((v) => !selectableValues.includes(v)) : [...selectedValues, ...selectableValues.filter((v) => !selectedValues.includes(v))]
14338
+ );
14339
+ };
14340
+ return /* @__PURE__ */ jsxRuntime.jsxs(ListBox, { children: [
14341
+ wrapsInPaddedTop ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-[var(--cmp-chipFilter-listbox-top-gap)] px-[var(--cmp-chipFilter-listbox-top-paddingX)] py-[var(--cmp-chipFilter-listbox-top-paddingY)]", children: /* @__PURE__ */ jsxRuntime.jsx(
14342
+ FilterEditor,
14343
+ {
14344
+ field,
14345
+ value,
14346
+ disabled,
14347
+ onValueChange
14348
+ }
14349
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(
14350
+ FilterEditor,
14351
+ {
14352
+ field,
14353
+ value,
14354
+ disabled,
14355
+ onValueChange
14356
+ }
14357
+ ),
14358
+ /* @__PURE__ */ jsxRuntime.jsxs(
14359
+ "div",
14360
+ {
14361
+ className: "flex items-center justify-end gap-[var(--cmp-chipFilter-listbox-bottom-gap)] border-t border-solid px-[var(--cmp-chipFilter-listbox-bottom-paddingX)] py-[var(--cmp-chipFilter-listbox-bottom-paddingY)]",
14362
+ style: { borderColor: CHIPFILTER.listboxBorder },
14363
+ children: [
14364
+ isMulti && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14365
+ /* @__PURE__ */ jsxRuntime.jsx(
14366
+ STextLink,
14367
+ {
14368
+ size: "sm",
14369
+ label: allSelected ? "\uC120\uD0DD \uD574\uC81C" : "\uC804\uCCB4 \uC120\uD0DD",
14370
+ labelClass: "font-bold",
14371
+ disabled,
14372
+ onClick: toggleSelectAll,
14373
+ style: { color: "var(--sys-color-fg-secondary)" }
14374
+ }
14375
+ ),
14376
+ /* @__PURE__ */ jsxRuntime.jsx(
14377
+ "span",
14378
+ {
14379
+ "aria-hidden": true,
14380
+ className: "inline-flex w-px shrink-0",
14381
+ style: {
14382
+ height: CHIPFILTER.listboxBottomDividerHeight,
14383
+ background: CHIPFILTER.listboxBorder
14384
+ }
14385
+ }
14386
+ )
14387
+ ] }),
14388
+ /* @__PURE__ */ jsxRuntime.jsx(
14389
+ STextLink,
14390
+ {
14391
+ size: "sm",
14392
+ label: "\uAC80\uC0C9",
14393
+ labelClass: "font-bold",
14394
+ onClick: onClose,
14395
+ style: { color: CHIPFILTER.chipAppliedValue }
14396
+ }
14397
+ )
14398
+ ]
14399
+ }
14400
+ )
14401
+ ] });
14402
+ }
14403
+ function FilterEditor({
14404
+ field,
14405
+ value,
14406
+ disabled,
14407
+ onValueChange
14408
+ }) {
14409
+ if (field.type === "date") {
14410
+ return /* @__PURE__ */ jsxRuntime.jsx(
14411
+ DatePresetEditor,
14412
+ {
14413
+ field,
14414
+ value,
14415
+ disabled,
14416
+ onValueChange
14417
+ }
14418
+ );
14419
+ }
14420
+ if (field.type === "keyword") {
14421
+ return /* @__PURE__ */ jsxRuntime.jsx(
14422
+ KeywordEditor,
14423
+ {
14424
+ field,
14425
+ value: isKeywordValue(value) ? value : keywordEntries(value),
14426
+ disabled,
14427
+ onValueChange
14428
+ }
14429
+ );
14430
+ }
14431
+ return /* @__PURE__ */ jsxRuntime.jsx(
14432
+ OptionList,
14433
+ {
14434
+ field,
14435
+ value: field.type === "multi" ? toArrayValue(value) : value,
14436
+ multiple: field.type === "multi",
14437
+ disabled,
14438
+ onValueChange
14439
+ }
14440
+ );
14441
+ }
14442
+ function DatePresetEditor({
14443
+ field,
14444
+ value,
14445
+ disabled,
14446
+ onValueChange
14447
+ }) {
14448
+ const presets = field.presets ?? [];
14449
+ const [selectedId, setSelectedId] = react.useState(() => matchPresetId(presets, value));
14450
+ const syncedInitialValueRef = react.useRef(!isEmptyValue(value));
14451
+ react.useEffect(() => {
14452
+ if (syncedInitialValueRef.current || isEmptyValue(value)) return;
14453
+ syncedInitialValueRef.current = true;
14454
+ setSelectedId(matchPresetId(presets, value));
14455
+ }, [value, presets]);
14456
+ const selectedPreset = presets.find((preset) => preset.value === selectedId);
14457
+ const selectPreset = (preset) => {
14458
+ setSelectedId(preset.value);
14459
+ if (!preset.custom) onValueChange(preset.resolve ? preset.resolve() : null);
14460
+ };
14461
+ if (presets.length === 0) {
14462
+ return /* @__PURE__ */ jsxRuntime.jsx(
14463
+ SDatePicker,
14464
+ {
14465
+ value: typeof value === "string" ? value : null,
14466
+ onValueChange,
14467
+ selectable: field.selectable,
14468
+ disabled,
14469
+ placeholder: field.placeholder
14470
+ }
14471
+ );
14472
+ }
14473
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-[var(--cmp-chipFilter-listbox-top-gap)]", children: [
14474
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-[8px]", children: presets.map((preset) => /* @__PURE__ */ jsxRuntime.jsx(
14475
+ SRadio,
14476
+ {
14477
+ val: preset.value,
14478
+ value: selectedId,
14479
+ label: preset.label,
14480
+ disabled,
14481
+ onValueChange: () => selectPreset(preset)
14482
+ },
14483
+ preset.value
14484
+ )) }),
14485
+ selectedPreset?.custom && /* @__PURE__ */ jsxRuntime.jsx(
14486
+ SDateRangePicker,
14487
+ {
14488
+ value: Array.isArray(value) && value.length === 2 ? value : null,
14489
+ onValueChange,
14490
+ selectable: field.selectable,
14491
+ maxRange: field.maxRange,
14492
+ disabled
14493
+ }
14494
+ )
14495
+ ] });
14496
+ }
14497
+ function InlineDatePresetFilter({
14498
+ field,
14499
+ value,
14500
+ disabled,
14501
+ warning,
14502
+ showClear,
14503
+ onValueChange,
14504
+ onClear
14505
+ }) {
14506
+ const presets = (field.presets ?? []).filter((preset) => !preset.custom);
14507
+ const [selectedId, setSelectedId] = react.useState(() => matchPresetId(presets, value));
14508
+ const syncedInitialValueRef = react.useRef(!isEmptyValue(value));
14509
+ react.useEffect(() => {
14510
+ if (syncedInitialValueRef.current || isEmptyValue(value)) return;
14511
+ syncedInitialValueRef.current = true;
14512
+ setSelectedId(matchPresetId(presets, value));
14513
+ }, [value, presets]);
14514
+ const applied = !isEmptyValue(value);
14515
+ const rangeValue = Array.isArray(value) ? value : typeof value === "string" && value !== "" ? [value, value] : null;
14516
+ const [pickerOpen, setPickerOpen] = react.useState(false);
14517
+ const portalContainer = usePortalContainer();
14518
+ const selectPreset = (id) => {
14519
+ const preset = presets.find((p) => p.value === id);
14520
+ if (!preset) return;
14521
+ setSelectedId(preset.value);
14522
+ onValueChange(preset.resolve ? preset.resolve() : null);
14523
+ };
14524
+ const badge = applied && /* @__PURE__ */ jsxRuntime.jsxs(
14525
+ "span",
14526
+ {
14527
+ className: "inline-flex shrink-0 items-center gap-[4px] whitespace-nowrap border border-solid px-[var(--cmp-chipFilter-chip-paddingX)] text-[12px] leading-[20px]",
14528
+ style: {
14529
+ minHeight: CHIPFILTER.chipHeight,
14530
+ borderRadius: CHIPFILTER.chipRadius,
14531
+ borderColor: warning ? CHIPFILTER.chipWarningBorder : CHIPFILTER.chipAppliedBorder,
14532
+ background: CHIPFILTER.chipAppliedBg,
14533
+ color: CHIPFILTER.chipAppliedLabel,
14534
+ cursor: rangeValue ? "pointer" : void 0
14535
+ },
14536
+ children: [
14537
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-bold", children: [
14538
+ field.label,
14539
+ ":"
14540
+ ] }),
14541
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold", style: { color: CHIPFILTER.chipAppliedValue }, children: displayValue(field, value) }),
14542
+ !disabled && showClear && /* @__PURE__ */ jsxRuntime.jsx(
14543
+ "span",
14544
+ {
14545
+ role: "button",
14546
+ tabIndex: 0,
14547
+ "aria-label": `${field.label} \uD544\uD130 \uC9C0\uC6B0\uAE30`,
14548
+ className: "inline-flex size-[16px] items-center justify-center rounded-[var(--cmp-ghostButton-radius-sm)]",
14549
+ onClick: (event) => {
14550
+ event.stopPropagation();
14551
+ onClear();
14552
+ },
14553
+ onKeyDown: (event) => {
14554
+ if (event.key !== "Enter" && event.key !== " ") return;
14555
+ event.preventDefault();
14556
+ event.stopPropagation();
14557
+ onClear();
14558
+ },
14559
+ children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
14560
+ }
14561
+ )
14562
+ ]
14563
+ }
14564
+ );
14565
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-[8px]", children: [
14566
+ /* @__PURE__ */ jsxRuntime.jsx(
14567
+ SRadioButton,
14568
+ {
14569
+ size: "xs",
14570
+ options: presets.map((preset) => ({ value: preset.value, label: preset.label })),
14571
+ value: selectedId,
14572
+ disabled,
14573
+ onValueChange: (next) => selectPreset(String(next))
14574
+ }
14575
+ ),
14576
+ applied && (rangeValue ? (
14577
+ // 뱃지 모양은 그대로 두고, 눌렀을 때 캘린더가 열려 기간을 직접 다시 고를 수 있게 한다.
14578
+ // 직접 고르면 프리셋 선택은 해제된다.
14579
+ /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open: pickerOpen, onOpenChange: setPickerOpen, children: [
14580
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
14581
+ "button",
14582
+ {
14583
+ type: "button",
14584
+ disabled,
14585
+ className: "border-none bg-transparent p-0 outline-none disabled:cursor-not-allowed",
14586
+ children: badge
14587
+ }
14588
+ ) }),
14589
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
14590
+ RPopover9__namespace.Content,
14591
+ {
14592
+ side: "bottom",
14593
+ align: "start",
14594
+ sideOffset: 4,
14595
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
14596
+ children: /* @__PURE__ */ jsxRuntime.jsx(
14597
+ RangeCalendar,
14598
+ {
14599
+ value: rangeValue,
14600
+ selectable: field.selectable,
14601
+ maxRange: field.maxRange,
14602
+ onSelect: (range) => {
14603
+ setSelectedId(null);
14604
+ onValueChange(range);
14605
+ setPickerOpen(false);
14606
+ }
14607
+ }
14608
+ )
14609
+ }
14610
+ ) })
14611
+ ] })
14612
+ ) : badge)
14613
+ ] });
14614
+ }
14615
+ var MATCH_MODE_OPTIONS = [
14616
+ { value: "contains", label: "\uD3EC\uD568" },
14617
+ { value: "exact", label: "\uC77C\uCE58" }
14618
+ ];
14619
+ var EXCLUDE_MATCH_MODE_OPTION = {
14620
+ value: "excludes",
14621
+ label: "\uBBF8\uD3EC\uD568"
14622
+ };
14623
+ function KeywordEditor({
14624
+ field,
14625
+ value,
14626
+ disabled,
14627
+ onValueChange
14628
+ }) {
14629
+ const entries3 = keywordEntries(value);
14630
+ const [draft, setDraft] = react.useState("");
14631
+ const [mode, setMode] = react.useState(
14632
+ isKeywordValue(value) ? value.mode : "contains"
14633
+ );
14634
+ const emit3 = (nextEntries) => {
14635
+ onValueChange(field.matchModes ? { keywords: nextEntries, mode } : nextEntries);
14636
+ };
14637
+ const addEntry = (raw) => {
14638
+ const next = raw.trim();
14639
+ if (next === "" || entries3.includes(next)) return;
14640
+ emit3([...entries3, next]);
14641
+ setDraft("");
14642
+ };
14643
+ const removeEntry = (entry) => {
14644
+ emit3(entries3.filter((e) => e !== entry));
14645
+ };
14646
+ const onKeyDown = (event) => {
14647
+ if (event.key === "Enter") {
14648
+ if (event.nativeEvent.isComposing) return;
14649
+ event.preventDefault();
14650
+ addEntry(draft);
14651
+ }
14652
+ };
14653
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14654
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-[var(--cmp-chipFilter-listbox-top-gap)] px-[var(--cmp-chipFilter-listbox-top-paddingX)] py-[var(--cmp-chipFilter-listbox-top-paddingY)]", children: [
14655
+ field.matchModes && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
14656
+ /* @__PURE__ */ jsxRuntime.jsx(
14657
+ "span",
14658
+ {
14659
+ className: "shrink-0 whitespace-nowrap text-[12px] font-medium leading-[20px]",
14660
+ style: { color: CHIPFILTER.listboxLabel },
14661
+ children: "\uAC80\uC0C9 \uC870\uAC74 :"
14662
+ }
14663
+ ),
14664
+ /* @__PURE__ */ jsxRuntime.jsx(
14665
+ SRadioButton,
14666
+ {
14667
+ size: "xs",
14668
+ style: { width: 150 },
14669
+ options: field.excludeMode ? [...MATCH_MODE_OPTIONS, EXCLUDE_MATCH_MODE_OPTION] : MATCH_MODE_OPTIONS,
14670
+ value: mode,
14671
+ disabled,
14672
+ onValueChange: (next) => setMode(next)
14673
+ }
14674
+ )
14675
+ ] }),
14676
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-[6px] rounded-[4px] border border-solid border-[color:var(--sys-color-field-border-default)] bg-[var(--sys-color-field-bg-default)] px-[8px] py-[4px] focus-within:border-[color:var(--sys-color-field-border-focus)]", children: [
14677
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--sys-color-field-icon-default)" }),
14678
+ /* @__PURE__ */ jsxRuntime.jsx(
14679
+ "input",
14680
+ {
14681
+ value: draft,
14682
+ disabled,
14683
+ placeholder: field.placeholder ?? "\uC785\uB825 \uD6C4 Enter\uB85C \uCD94\uAC00",
14684
+ onChange: (event) => setDraft(event.target.value),
14685
+ onKeyDown,
14686
+ className: "min-w-0 flex-1 border-none bg-transparent text-[12px] leading-[20px] text-[color:var(--sys-color-field-text-default)] outline-none placeholder:text-[color:var(--sys-color-field-text-placeholder)] disabled:cursor-not-allowed"
14687
+ }
14688
+ )
14689
+ ] })
14690
+ ] }),
14691
+ entries3.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
14692
+ "div",
14693
+ {
14694
+ className: "max-h-[220px] overflow-auto border-t border-solid py-[var(--cmp-chipFilter-listbox-list-paddingY)]",
14695
+ style: { borderColor: CHIPFILTER.listboxBorder },
14696
+ children: entries3.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
14697
+ KeywordEntryRow,
14698
+ {
14699
+ label: optionLabel(field, entry),
14700
+ disabled,
14701
+ onRemove: () => removeEntry(entry)
14702
+ },
14703
+ entry
14704
+ ))
14705
+ }
14706
+ )
14707
+ ] });
14708
+ }
14709
+ function KeywordEntryRow({
14710
+ label,
14711
+ disabled,
14712
+ onRemove
14713
+ }) {
14714
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14715
+ "div",
14716
+ {
14717
+ className: "group flex w-full items-center justify-between gap-[8px] text-[12px] leading-[20px]",
14718
+ style: {
14719
+ paddingTop: CHIPFILTER.itemPaddingY,
14720
+ paddingBottom: CHIPFILTER.itemPaddingY,
14721
+ paddingLeft: CHIPFILTER.itemPaddingLeft,
14722
+ paddingRight: CHIPFILTER.itemPaddingRight,
14723
+ color: disabled ? CHIPFILTER.chipDisabledText : CHIPFILTER.itemTextDefault
14724
+ },
14725
+ children: [
14726
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("min-w-0 truncate", !disabled && "group-hover:font-bold"), children: label }),
14727
+ !disabled && /* @__PURE__ */ jsxRuntime.jsx(
14728
+ "button",
14729
+ {
14730
+ type: "button",
14731
+ "aria-label": `${label} \uC0AD\uC81C`,
14732
+ className: "invisible inline-flex size-[16px] shrink-0 cursor-pointer items-center justify-center group-hover:visible",
14733
+ onClick: onRemove,
14734
+ children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
14735
+ }
14736
+ )
14737
+ ]
14738
+ }
14739
+ );
14740
+ }
14741
+ var OPTION_SEARCH_THRESHOLD = 11;
14742
+ function OptionList({
14743
+ field,
14744
+ value,
14745
+ multiple,
14746
+ disabled,
14747
+ onValueChange
14748
+ }) {
14749
+ const selected = multiple ? toArrayValue(value) : [];
14750
+ const singleValue = !multiple && (typeof value === "string" || typeof value === "number") ? value : null;
14751
+ const [search, setSearch] = react.useState("");
14752
+ const options = field.options ?? [];
14753
+ const showSearch = options.length >= OPTION_SEARCH_THRESHOLD;
14754
+ const filteredOptions = react.useMemo(() => {
14755
+ if (!showSearch || search.trim() === "") return options;
14756
+ const query = search.trim().toLowerCase();
14757
+ return options.filter((option) => option.label.toLowerCase().includes(query));
14758
+ }, [options, search, showSearch]);
14759
+ if (options.length === 0) return /* @__PURE__ */ jsxRuntime.jsx(EmptyItem, { children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." });
14760
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14761
+ "div",
14762
+ {
14763
+ className: cn(
14764
+ "max-h-[240px] overflow-auto",
14765
+ !showSearch && "pt-[var(--cmp-chipFilter-listbox-list-paddingY)]",
14766
+ "pb-[var(--cmp-chipFilter-listbox-list-paddingY)]"
14767
+ ),
14768
+ children: [
14769
+ showSearch && /* @__PURE__ */ jsxRuntime.jsx(
14770
+ "div",
14771
+ {
14772
+ className: "sticky top-0 z-[1] border-b border-solid px-[8px] pb-[4px] pt-[var(--cmp-chipFilter-listbox-list-paddingY)]",
14773
+ style: { background: CHIPFILTER.listboxBg, borderColor: CHIPFILTER.listboxBorder },
14774
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-[6px] rounded-[4px] border border-solid border-[color:var(--sys-color-field-border-default)] bg-[var(--sys-color-field-bg-default)] px-[8px] py-[4px] focus-within:border-[color:var(--sys-color-field-border-focus)]", children: [
14775
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "search", size: 16, color: "var(--sys-color-field-icon-default)" }),
14776
+ /* @__PURE__ */ jsxRuntime.jsx(
14777
+ "input",
14778
+ {
14779
+ value: search,
14780
+ disabled,
14781
+ placeholder: "\uAC80\uC0C9",
14782
+ onChange: (event) => setSearch(event.target.value),
14783
+ className: "min-w-0 flex-1 border-none bg-transparent text-[12px] leading-[20px] text-[color:var(--sys-color-field-text-default)] outline-none placeholder:text-[color:var(--sys-color-field-text-placeholder)] disabled:cursor-not-allowed"
14784
+ }
14785
+ ),
14786
+ search !== "" && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
14787
+ SGhostButton,
14788
+ {
14789
+ icon: "close",
14790
+ size: "xxs",
14791
+ ariaLabel: "\uAC80\uC0C9\uC5B4 \uC9C0\uC6B0\uAE30",
14792
+ className: "flex-shrink-0",
14793
+ onClick: () => setSearch("")
14794
+ }
14795
+ )
14796
+ ] })
14797
+ }
14798
+ ),
14799
+ showSearch && filteredOptions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(EmptyItem, { children: "\uAC80\uC0C9 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }),
14800
+ filteredOptions.map((option) => {
14801
+ const checked = multiple ? selected.includes(option.value) : singleValue === option.value;
14802
+ return /* @__PURE__ */ jsxRuntime.jsxs(
14803
+ MenuItem,
14804
+ {
14805
+ selected: checked,
14806
+ disabled: disabled || option.disabled,
14807
+ multi: multiple,
14808
+ onClick: () => {
14809
+ if (multiple) {
14810
+ const next = checked ? selected.filter((v) => v !== option.value) : [...selected, option.value];
14811
+ onValueChange(next);
14812
+ } else {
14813
+ onValueChange(option.value);
14814
+ }
14815
+ },
14816
+ children: [
14817
+ multiple && /* @__PURE__ */ jsxRuntime.jsx(OptionCheckbox, { checked }),
14818
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: option.label })
14819
+ ]
14820
+ },
14821
+ String(option.value)
14822
+ );
14823
+ })
14824
+ ]
14825
+ }
14826
+ );
14827
+ }
14828
+ function OptionCheckbox({ checked }) {
14829
+ return /* @__PURE__ */ jsxRuntime.jsx(
14830
+ "span",
14831
+ {
14832
+ className: cn(
14833
+ "inline-flex size-[16px] shrink-0 items-center justify-center rounded-[2px] border border-solid",
14834
+ checked ? "border-[color:var(--cmp-checkbox-checked-bg-default)] bg-[var(--cmp-checkbox-checked-bg-default)]" : "border-[color:var(--cmp-checkbox-unchecked-border-default)] bg-[var(--cmp-checkbox-unchecked-bg-default)]"
14835
+ ),
14836
+ children: checked && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "checkboxCheck", size: 12, color: "var(--cmp-checkbox-checked-icon-default)" })
14837
+ }
14838
+ );
14839
+ }
14840
+ function ListBox({ children }) {
14841
+ return /* @__PURE__ */ jsxRuntime.jsx(
14842
+ "div",
14843
+ {
14844
+ className: "overflow-hidden border border-solid shadow-[var(--shadow-overlay)]",
14845
+ style: {
14846
+ borderRadius: CHIPFILTER.listboxRadius,
14847
+ background: CHIPFILTER.listboxBg,
14848
+ borderColor: CHIPFILTER.listboxBorder
14849
+ },
14850
+ children
14851
+ }
14852
+ );
14853
+ }
14854
+ function MenuItem({
14855
+ children,
14856
+ selected,
14857
+ disabled,
14858
+ multi,
14859
+ onMouseEnter,
14860
+ onClick
14861
+ }) {
14862
+ return /* @__PURE__ */ jsxRuntime.jsx(
14863
+ "button",
14864
+ {
14865
+ type: "button",
14866
+ disabled,
14867
+ onMouseEnter,
14868
+ onClick,
14869
+ className: cn(
14870
+ "flex w-full min-w-0 cursor-pointer items-center gap-[8px] border-none bg-transparent text-left text-[12px] leading-[20px] outline-none disabled:cursor-not-allowed",
14871
+ selected ? "font-bold" : "font-normal",
14872
+ !disabled && "hover:font-bold"
14873
+ ),
14874
+ style: {
14875
+ paddingTop: CHIPFILTER.itemPaddingY,
14876
+ paddingBottom: CHIPFILTER.itemPaddingY,
14877
+ paddingLeft: multi ? CHIPFILTER.itemMultiPaddingX : CHIPFILTER.itemPaddingLeft,
14878
+ paddingRight: multi ? CHIPFILTER.itemMultiPaddingX : CHIPFILTER.itemPaddingRight,
14879
+ color: disabled ? CHIPFILTER.chipDisabledText : selected ? CHIPFILTER.itemTextSelected : CHIPFILTER.itemTextDefault
14880
+ },
14881
+ children
14882
+ }
14883
+ );
14884
+ }
14885
+ function EmptyItem({ children }) {
14886
+ return /* @__PURE__ */ jsxRuntime.jsx(
14887
+ "div",
14888
+ {
14889
+ className: "text-[12px] leading-[20px]",
14890
+ style: {
14891
+ paddingTop: CHIPFILTER.itemPaddingY,
14892
+ paddingBottom: CHIPFILTER.itemPaddingY,
14893
+ paddingLeft: CHIPFILTER.itemPaddingLeft,
14894
+ paddingRight: CHIPFILTER.itemPaddingRight,
14895
+ color: CHIPFILTER.chipDisabledText
14896
+ },
14897
+ children
14898
+ }
14899
+ );
14900
+ }
14901
+ var SChipInput = /* @__PURE__ */ react.forwardRef(function SChipInput2({
14902
+ values = [],
14903
+ onValueChange,
14904
+ onInput,
14905
+ onFocus,
14906
+ onBlur,
14907
+ errors = [],
14908
+ disabledChips = [],
14909
+ size = "sm",
14910
+ disabled = false,
14911
+ placeholder = "\uD0DC\uADF8 \uC785\uB825 (Enter\uB85C \uB4F1\uB85D / \uCF64\uB9C8\uB85C \uAD6C\uBD84 / \uB744\uC5B4\uC4F0\uAE30 \uBD88\uAC00)",
14912
+ name,
14913
+ rules,
14914
+ error,
14915
+ useReset = false,
14916
+ maxCount,
14917
+ metaPlacement = "end",
14918
+ duplicateLabel,
14919
+ suggestions = [],
14920
+ loadingSuggestions = false,
14921
+ recommendedItems = [],
14922
+ loadingRecommendedItems = false,
14923
+ dropdownMinWidth = 200,
14924
+ label,
14925
+ labelWidth,
14926
+ hint,
14927
+ errorMessage,
14928
+ width,
14929
+ status,
14930
+ icon,
14931
+ iconColor,
14932
+ labelTooltip,
14933
+ labelTooltipProps,
14934
+ className,
14935
+ style
14936
+ }, ref) {
14937
+ const autoId = react.useId();
14938
+ const resolvedName = name ?? autoId;
14939
+ const [inputValue, setInputValue] = react.useState("");
14940
+ const [focused, setFocused] = react.useState(false);
14941
+ const [hovered, setHovered] = react.useState(false);
14942
+ const [activeIndex, setActiveIndex] = react.useState(-1);
14943
+ const [suppressSuggestions, setSuppressSuggestions] = react.useState(false);
14944
+ const [suppressRecommendations, setSuppressRecommendations] = react.useState(false);
14945
+ const inputRef = react.useRef(null);
14946
+ const containerRef = react.useRef(null);
14947
+ const inputShellRef = react.useRef(null);
14948
+ const isComposingRef = react.useRef(false);
14949
+ react.useImperativeHandle(ref, () => ({ focus: () => inputRef.current?.focus() }), []);
14950
+ const chipError = (i) => {
14951
+ if (values.indexOf(values[i]) !== i) return true;
14952
+ if (typeof errors === "function") return errors(values[i] ?? "");
14953
+ return errors[i] ?? false;
14954
+ };
14955
+ const chipDisabled = (i) => {
14956
+ if (disabled) return true;
14957
+ if (typeof disabledChips === "function") return disabledChips(values[i] ?? "");
14958
+ return disabledChips[i] ?? false;
14959
+ };
14960
+ const duplicateMessage = react.useMemo(() => {
14961
+ const seen = /* @__PURE__ */ new Set();
14962
+ for (const v of values) {
14963
+ if (seen.has(v))
14964
+ return duplicateLabel ? `\uC774\uBBF8 \uC785\uB825\uB41C ${duplicateLabel}\uC785\uB2C8\uB2E4.` : "\uC911\uBCF5\uB41C \uAC12\uC774 \uC874\uC7AC\uD569\uB2C8\uB2E4.";
14965
+ seen.add(v);
14966
+ }
14967
+ return "";
14968
+ }, [values, duplicateLabel]);
14969
+ const ruleMessage = react.useMemo(() => runRules(values, rules), [values, rules]);
14970
+ const resolvedErrorMessage = duplicateMessage || ruleMessage || errorMessage;
14971
+ const resolvedError = error || duplicateMessage !== "" || ruleMessage !== "";
14972
+ const resolvedStatus = status ?? (resolvedError ? "error" : void 0);
14973
+ useFormField({
14974
+ name,
14975
+ validate: () => runRules(values, rules) === "" && duplicateMessage === "",
14976
+ resetValidation: () => {
14977
+ }
14978
+ });
14979
+ const isMax = maxCount != null && values.length >= maxCount;
13579
14980
  const resolvedDropdownMinWidth = dropdownMinWidth == null ? void 0 : typeof dropdownMinWidth === "number" ? `${dropdownMinWidth}px` : dropdownMinWidth;
13580
14981
  const commit = (next) => onValueChange?.(next);
13581
14982
  const addChip = (v) => {
@@ -14220,7 +15621,7 @@ function TimeSelector({
14220
15621
  const renderColumn = (type) => {
14221
15622
  const isHour = type === "hour";
14222
15623
  const val = isHour ? displayHour : minute;
14223
- const displayValue = editingPart === type ? inputDraft[type] ?? "" : String(val).padStart(2, "0");
15624
+ const displayValue2 = editingPart === type ? inputDraft[type] ?? "" : String(val).padStart(2, "0");
14224
15625
  return /* @__PURE__ */ jsxRuntime.jsxs(
14225
15626
  "div",
14226
15627
  {
@@ -14241,7 +15642,7 @@ function TimeSelector({
14241
15642
  {
14242
15643
  "aria-label": isHour ? "\uC2DC" : "\uBD84",
14243
15644
  inputMode: "numeric",
14244
- value: displayValue,
15645
+ value: displayValue2,
14245
15646
  placeholder: "-",
14246
15647
  onFocus: () => beginEdit(type, val),
14247
15648
  onBlur: () => endEdit(type),
@@ -14436,8 +15837,8 @@ function STimePicker({
14436
15837
  disabled,
14437
15838
  className,
14438
15839
  style,
14439
- children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: commitOpen, children: [
14440
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
15840
+ children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open, onOpenChange: commitOpen, children: [
15841
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
14441
15842
  "div",
14442
15843
  {
14443
15844
  ref: triggerRef,
@@ -14497,14 +15898,17 @@ function STimePicker({
14497
15898
  ]
14498
15899
  }
14499
15900
  ) }),
14500
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
14501
- RPopover2__namespace.Content,
15901
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
15902
+ RPopover9__namespace.Content,
14502
15903
  {
14503
15904
  side: "bottom",
14504
15905
  align: "start",
14505
15906
  sideOffset: 4,
14506
15907
  alignOffset: popoverAlignOffset,
14507
- className: cn("z-50", FLOATING_SLIDE_ANIM),
15908
+ className: cn(
15909
+ "z-50 max-w-[var(--radix-popover-content-available-width)]",
15910
+ FLOATING_SLIDE_ANIM
15911
+ ),
14508
15912
  children: /* @__PURE__ */ jsxRuntime.jsx(
14509
15913
  TimeSelector,
14510
15914
  {
@@ -14663,7 +16067,7 @@ function RangeTimeSelector({
14663
16067
  const { minute } = getParts(side);
14664
16068
  const val = isHour ? getDisplayHour(side) : minute;
14665
16069
  const currentEditingKey = getEditingKey(side, kind);
14666
- const displayValue = editingKey === currentEditingKey ? inputDraft[currentEditingKey] ?? "" : String(val).padStart(2, "0");
16070
+ const displayValue2 = editingKey === currentEditingKey ? inputDraft[currentEditingKey] ?? "" : String(val).padStart(2, "0");
14667
16071
  return /* @__PURE__ */ jsxRuntime.jsxs(
14668
16072
  "div",
14669
16073
  {
@@ -14684,7 +16088,7 @@ function RangeTimeSelector({
14684
16088
  {
14685
16089
  "aria-label": `${side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC"} ${isHour ? "\uC2DC" : "\uBD84"}`,
14686
16090
  inputMode: "numeric",
14687
- value: displayValue,
16091
+ value: displayValue2,
14688
16092
  placeholder: "-",
14689
16093
  onFocus: () => beginEdit(side, kind, val),
14690
16094
  onBlur: () => endEdit(side, kind),
@@ -14895,8 +16299,8 @@ function STimeRangePicker({
14895
16299
  disabled,
14896
16300
  className,
14897
16301
  style,
14898
- children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: commitOpen, children: [
14899
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
16302
+ children: /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open, onOpenChange: commitOpen, children: [
16303
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
14900
16304
  "div",
14901
16305
  {
14902
16306
  ref: triggerRef,
@@ -14956,14 +16360,17 @@ function STimeRangePicker({
14956
16360
  ]
14957
16361
  }
14958
16362
  ) }),
14959
- /* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
14960
- RPopover2__namespace.Content,
16363
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
16364
+ RPopover9__namespace.Content,
14961
16365
  {
14962
16366
  side: "bottom",
14963
16367
  align: "start",
14964
16368
  sideOffset: 4,
14965
16369
  alignOffset: popoverAlignOffset,
14966
- className: cn("z-50", FLOATING_SLIDE_ANIM),
16370
+ className: cn(
16371
+ "z-50 max-w-[var(--radix-popover-content-available-width)]",
16372
+ FLOATING_SLIDE_ANIM
16373
+ ),
14967
16374
  children: /* @__PURE__ */ jsxRuntime.jsx(
14968
16375
  RangeTimeSelector,
14969
16376
  {
@@ -14996,6 +16403,7 @@ exports.GNB_RAIL = GNB_RAIL;
14996
16403
  exports.GNB_RAIL_WIDTH = GNB_RAIL_WIDTH;
14997
16404
  exports.ICONS = ICONS;
14998
16405
  exports.PAGE_BACKGROUNDS = PAGE_BACKGROUNDS;
16406
+ exports.RangeCalendar = RangeCalendar;
14999
16407
  exports.SActionModal = SActionModal;
15000
16408
  exports.SBadge = SBadge;
15001
16409
  exports.SBarcodeInput = SBarcodeInput;
@@ -15006,6 +16414,7 @@ exports.SCallout = SCallout;
15006
16414
  exports.SCard = SCard;
15007
16415
  exports.SCheckbox = SCheckbox;
15008
16416
  exports.SChip = SChip2;
16417
+ exports.SChipFilter = SChipFilter;
15009
16418
  exports.SChipInput = SChipInput;
15010
16419
  exports.SCircleProgress = SCircleProgress;
15011
16420
  exports.SConfirmModal = SConfirmModal;