sellmate-design-system-react 9.0.0-beta.0 → 9.0.0-beta.10

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 (59) hide show
  1. package/AGENTS.md +153 -45
  2. package/README.md +63 -0
  3. package/dist/components/SChipFilter/README.md +2 -1
  4. package/dist/components/SChipFilter/SChipFilter.d.ts +13 -6
  5. package/dist/components/SCircleProgress/README.md +3 -0
  6. package/dist/components/SCircleProgress/SCircleProgress.d.ts +7 -1
  7. package/dist/components/SDatePicker/README.md +41 -3
  8. package/dist/components/SDatePicker/SDatePicker.d.ts +36 -3
  9. package/dist/components/SDatePicker/index.d.ts +1 -1
  10. package/dist/components/SField/README.md +3 -0
  11. package/dist/components/SField/SField.d.ts +12 -0
  12. package/dist/components/SGhostButton/README.md +2 -0
  13. package/dist/components/SGnb/gnb.config.d.ts +2 -0
  14. package/dist/components/SIcon/README.md +4 -0
  15. package/dist/components/SImage/README.md +44 -0
  16. package/dist/components/SImage/SImage.d.ts +41 -0
  17. package/dist/components/SImage/index.d.ts +1 -0
  18. package/dist/components/SKeyValueTable/README.md +1 -0
  19. package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +8 -1
  20. package/dist/components/SList/README.md +0 -1
  21. package/dist/components/SList/SList.d.ts +0 -2
  22. package/dist/components/SPage/README.md +2 -0
  23. package/dist/components/SPage/SPage.d.ts +17 -1
  24. package/dist/components/SPage/index.d.ts +1 -1
  25. package/dist/components/SPage/page.config.d.ts +8 -0
  26. package/dist/components/SSearchInput/README.md +48 -0
  27. package/dist/components/SSearchInput/SSearchInput.d.ts +46 -0
  28. package/dist/components/SSearchInput/index.d.ts +1 -0
  29. package/dist/components/SSectionHeaderCard/README.md +10 -23
  30. package/dist/components/SSectionHeaderCard/SSectionHeaderCard.d.ts +21 -14
  31. package/dist/components/SSectionHeaderCard/index.d.ts +1 -1
  32. package/dist/components/SSelect/README.md +2 -0
  33. package/dist/components/STable/README.md +1 -0
  34. package/dist/components/STable/STable.d.ts +20 -0
  35. package/dist/components/STabs/README.md +0 -1
  36. package/dist/components/STabs/STabs.d.ts +2 -4
  37. package/dist/components/STabs/index.d.ts +1 -1
  38. package/dist/components/STabs/tabs.config.d.ts +3 -4
  39. package/dist/components/STimePicker/timepicker.config.d.ts +5 -0
  40. package/dist/components/SToggle/README.md +1 -0
  41. package/dist/components/SToggle/SToggle.d.ts +4 -1
  42. package/dist/components/STree/README.md +3 -0
  43. package/dist/components/STree/STree.d.ts +8 -0
  44. package/dist/components/STree/index.d.ts +1 -1
  45. package/dist/index.cjs +1545 -359
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.js +1540 -360
  49. package/dist/index.js.map +1 -1
  50. package/dist/lib/truncated-value-tooltip.d.ts +18 -0
  51. package/dist/llms-full.txt +300 -69
  52. package/dist/llms.txt +157 -48
  53. package/dist/styles.css +201 -32
  54. package/dist/theme.css +34 -11
  55. package/eslint/index.mjs +5 -0
  56. package/eslint/rules/field-width-grade.d.mts +41 -0
  57. package/eslint/rules/field-width-grade.mjs +294 -0
  58. package/eslint/scale.gen.mjs +1 -1
  59. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { forwardRef, useState, useRef, useEffect, createContext, useContext, use
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import { createPortal } from 'react-dom';
7
7
  import * as RCheckbox from '@radix-ui/react-checkbox';
8
- import * as RPopover9 from '@radix-ui/react-popover';
8
+ import * as RPopover10 from '@radix-ui/react-popover';
9
9
  import { createRoot } from 'react-dom/client';
10
10
  import * as RTooltip from '@radix-ui/react-tooltip';
11
11
  import * as RDropdown from '@radix-ui/react-dropdown-menu';
@@ -3113,6 +3113,209 @@ var SIcon = /* @__PURE__ */ forwardRef(function SIcon2({ name, size = 24, color,
3113
3113
  }
3114
3114
  );
3115
3115
  });
3116
+ var VIEWBOX = 80;
3117
+ var RADIUS = 34;
3118
+ var STROKE = 12;
3119
+ var CIRCUMFERENCE = 2 * Math.PI * RADIUS;
3120
+ var COLOR_MAP = {
3121
+ primary: { arc: "var(--cmp-progress-color-active)", track: "var(--cmp-progress-color-track)" },
3122
+ // inverse: 어두운 배경용 — arc 흰색, track 은 흰색 40%(회색 링)
3123
+ inverse: { arc: "var(--color-white)", track: "rgba(255,255,255,0.4)" },
3124
+ error: { arc: "var(--color-red-75)", track: "var(--cmp-progress-color-track)" },
3125
+ complete: { arc: "#01BB4B", track: "var(--cmp-progress-color-track)" },
3126
+ // neutral: 콘텐츠가 아직 없는 자리를 채우는 용도 — arc 도 회색이라 진행률보다 "대기"로 읽힌다
3127
+ neutral: { arc: "var(--color-grey-35)", track: "var(--cmp-progress-color-track)" }
3128
+ };
3129
+ var DEFAULT_SIZE = 80;
3130
+ var clamp = (v) => Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 0;
3131
+ var SCircleProgress = /* @__PURE__ */ forwardRef(
3132
+ function SCircleProgress2({
3133
+ value = 0,
3134
+ type = "primary",
3135
+ indeterminate = false,
3136
+ size = DEFAULT_SIZE,
3137
+ label,
3138
+ innerValue = false,
3139
+ className,
3140
+ style
3141
+ }, ref) {
3142
+ const v = clamp(value);
3143
+ const dimension = typeof size === "number" ? `${size}px` : size;
3144
+ const colors = COLOR_MAP[type];
3145
+ const c = VIEWBOX / 2;
3146
+ const percentColor = v === 0 ? "var(--sys-color-fg-tertiary, #888888)" : colors.arc;
3147
+ const filled = v / 100 * CIRCUMFERENCE;
3148
+ const showPercent = !indeterminate;
3149
+ const percentNode = /* @__PURE__ */ jsxs(
3150
+ "span",
3151
+ {
3152
+ className: cn(
3153
+ "text-center text-[16px] font-bold",
3154
+ innerValue ? (
3155
+ // leading-[normal]: 26px leading 은 폰트 content(19px) 위아래로 홀수(7px)만큼 남아
3156
+ // 3px/4px 로 쪼개지고 글자가 링 중심보다 위로 뜬다. (leading-normal 은 1.5 라 동일 문제)
3157
+ "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 leading-[normal]"
3158
+ ) : "leading-[26px]"
3159
+ ),
3160
+ style: { color: percentColor },
3161
+ children: [
3162
+ Math.round(v),
3163
+ "%"
3164
+ ]
3165
+ }
3166
+ );
3167
+ return /* @__PURE__ */ jsxs(
3168
+ "div",
3169
+ {
3170
+ ref,
3171
+ className: cn(
3172
+ "inline-flex flex-col items-center gap-[var(--cmp-progress-circular-gap)]",
3173
+ className
3174
+ ),
3175
+ style,
3176
+ children: [
3177
+ /* @__PURE__ */ jsxs("div", { className: cn(innerValue && "relative"), children: [
3178
+ /* @__PURE__ */ jsxs(
3179
+ "svg",
3180
+ {
3181
+ viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}`,
3182
+ xmlns: "http://www.w3.org/2000/svg",
3183
+ className: cn(indeterminate && "origin-center animate-circle-spin"),
3184
+ style: { width: dimension, height: dimension },
3185
+ children: [
3186
+ /* @__PURE__ */ jsx(
3187
+ "circle",
3188
+ {
3189
+ cx: c,
3190
+ cy: c,
3191
+ r: RADIUS,
3192
+ fill: "none",
3193
+ strokeWidth: STROKE,
3194
+ style: { stroke: colors.track }
3195
+ }
3196
+ ),
3197
+ /* @__PURE__ */ jsx(
3198
+ "circle",
3199
+ {
3200
+ cx: c,
3201
+ cy: c,
3202
+ r: RADIUS,
3203
+ fill: "none",
3204
+ strokeWidth: STROKE,
3205
+ strokeLinecap: "round",
3206
+ className: cn(
3207
+ "origin-center",
3208
+ indeterminate ? "animate-circle-arc" : "-rotate-90 transition-[stroke-dasharray] duration-[400ms] ease-linear"
3209
+ ),
3210
+ style: {
3211
+ stroke: colors.arc,
3212
+ ...indeterminate ? {} : { strokeDasharray: `${filled} ${CIRCUMFERENCE}`, strokeDashoffset: 0 }
3213
+ }
3214
+ }
3215
+ )
3216
+ ]
3217
+ }
3218
+ ),
3219
+ showPercent && innerValue && percentNode
3220
+ ] }),
3221
+ showPercent && !innerValue && percentNode,
3222
+ label && /* @__PURE__ */ jsx("span", { className: "text-center text-[12px] font-normal leading-[20px] text-[color:var(--cmp-progress-color-label)]", children: label })
3223
+ ]
3224
+ }
3225
+ );
3226
+ }
3227
+ );
3228
+ var IMAGE_FITS = ["cover", "contain", "fill", "none", "scale-down"];
3229
+ var EMPTY_ICON_SIZE = "60cqh";
3230
+ var SPINNER_SIZE = "45cqh";
3231
+ var SImage = /* @__PURE__ */ forwardRef(function SImage2({
3232
+ src,
3233
+ alt = "",
3234
+ ratio = 1,
3235
+ fit = "cover",
3236
+ position = "50% 50%",
3237
+ loading: loading2 = "lazy",
3238
+ loadingShowDelay = 0,
3239
+ noSpinner = false,
3240
+ loadingSlot,
3241
+ errorSlot,
3242
+ imgProps,
3243
+ onLoad,
3244
+ onError,
3245
+ className,
3246
+ style,
3247
+ ...rest
3248
+ }, ref) {
3249
+ const [status, setStatus] = useState("loading");
3250
+ const [spinnerVisible, setSpinnerVisible] = useState(loadingShowDelay <= 0);
3251
+ const [trackedSrc, setTrackedSrc] = useState(src);
3252
+ const imgRef = useRef(null);
3253
+ if (src !== trackedSrc) {
3254
+ setTrackedSrc(src);
3255
+ setStatus("loading");
3256
+ }
3257
+ useEffect(() => {
3258
+ if (!src) return;
3259
+ const node = imgRef.current;
3260
+ if (node?.complete && node.naturalWidth > 0) {
3261
+ setStatus((current) => current === "loading" ? "loaded" : current);
3262
+ }
3263
+ }, [src]);
3264
+ useEffect(() => {
3265
+ if (status !== "loading" || loadingShowDelay <= 0) {
3266
+ setSpinnerVisible(status === "loading");
3267
+ return;
3268
+ }
3269
+ setSpinnerVisible(false);
3270
+ const timer = setTimeout(() => setSpinnerVisible(true), loadingShowDelay);
3271
+ return () => clearTimeout(timer);
3272
+ }, [status, loadingShowDelay, src]);
3273
+ const isEmpty = !src;
3274
+ const showFallback = isEmpty || status === "error";
3275
+ const showSpinner = !isEmpty && status === "loading" && !noSpinner && spinnerVisible;
3276
+ return /* @__PURE__ */ jsxs(
3277
+ "div",
3278
+ {
3279
+ ref,
3280
+ className: cn("relative overflow-hidden bg-(--cmp-image-bg)", className),
3281
+ style: { containerType: "size", aspectRatio: ratio, ...style },
3282
+ ...rest,
3283
+ children: [
3284
+ !isEmpty && /* @__PURE__ */ jsx(
3285
+ "img",
3286
+ {
3287
+ ...imgProps,
3288
+ ref: imgRef,
3289
+ src,
3290
+ alt,
3291
+ loading: loading2,
3292
+ onLoad: () => {
3293
+ setStatus("loaded");
3294
+ onLoad?.(src);
3295
+ },
3296
+ onError: (event) => {
3297
+ setStatus("error");
3298
+ onError?.(event);
3299
+ },
3300
+ className: cn(
3301
+ "h-full w-full",
3302
+ // 로딩 중에도 자리를 지켜야 브라우저가 계속 받아온다 — 숨기되 없애지 않는다
3303
+ status === "loaded" ? "visible" : "invisible",
3304
+ imgProps?.className
3305
+ ),
3306
+ style: {
3307
+ objectFit: fit,
3308
+ objectPosition: position,
3309
+ ...imgProps?.style
3310
+ }
3311
+ }
3312
+ ),
3313
+ showFallback && /* @__PURE__ */ jsx("span", { className: "absolute inset-0 flex items-center justify-center", children: errorSlot ?? /* @__PURE__ */ jsx(SIcon, { name: "image", size: EMPTY_ICON_SIZE, color: "var(--cmp-image-icon)" }) }),
3314
+ showSpinner && /* @__PURE__ */ jsx("span", { className: "absolute inset-0 flex items-center justify-center", children: loadingSlot ?? /* @__PURE__ */ jsx(SCircleProgress, { indeterminate: true, type: "neutral", size: SPINNER_SIZE }) })
3315
+ ]
3316
+ }
3317
+ );
3318
+ });
3116
3319
  var BADGE_COLORS = [
3117
3320
  "red",
3118
3321
  "orange",
@@ -3214,7 +3417,7 @@ var SDraggableItemView = /* @__PURE__ */ forwardRef(
3214
3417
  !disabled && !selected && !dragging && "hover:bg-(--cmp-listDraggableItem-bg-hover)",
3215
3418
  hasSupportingText && supportingTextPosition === "bottom" ? dense ? "min-h-[52px]" : "min-h-[60px]" : dense ? "h-[32px]" : "h-[40px]",
3216
3419
  paddingYClass,
3217
- // 항목 사이 구분선은 리스트가 그린다 — SDraggableList 의 separator (SList.tsx)
3420
+ // 항목 사이 구분선은 리스트가 그린다.
3218
3421
  bordered && cn(
3219
3422
  "rounded-(--cmp-listDraggableItem-radius-bordered) border border-(--cmp-listDraggableItem-border-default)",
3220
3423
  !disabled && !selected && !dragging && "hover:border-(--cmp-listDraggableItem-bordered-border-hover)",
@@ -3422,7 +3625,7 @@ var SDraggableItemImpl = /* @__PURE__ */ forwardRef(function SDraggableItem({
3422
3625
  ] });
3423
3626
  });
3424
3627
  var SDraggableItem2 = /* @__PURE__ */ withDisplayName(SDraggableItemImpl, "SDraggableItem");
3425
- var SListImpl = /* @__PURE__ */ forwardRef(function SList({ className, children, useGap = false, usePadding = false, separator = false, ...rest }, ref) {
3628
+ var SListImpl = /* @__PURE__ */ forwardRef(function SList({ className, children, useGap = false, usePadding = false, ...rest }, ref) {
3426
3629
  return /* @__PURE__ */ jsx(
3427
3630
  "div",
3428
3631
  {
@@ -3430,7 +3633,7 @@ var SListImpl = /* @__PURE__ */ forwardRef(function SList({ className, children,
3430
3633
  className: cn(
3431
3634
  useGap && "flex flex-col gap-(--cmp-list-group-gap)",
3432
3635
  usePadding && "p-(--cmp-list-group-padding)",
3433
- separator && "[&>*]:border-b [&>*]:border-(--cmp-listItem-border-default)",
3636
+ "[&>*]:border-b [&>*]:border-(--cmp-listItem-border-default)",
3434
3637
  className
3435
3638
  ),
3436
3639
  ...rest,
@@ -3802,7 +4005,7 @@ var SExpansionItem = /* @__PURE__ */ forwardRef(
3802
4005
  ref,
3803
4006
  className: cn(
3804
4007
  "group/expansion-item relative overflow-hidden bg-(--cmp-listExpansionItem-title-bg-default)",
3805
- // 항목 사이 구분선은 리스트가 그린다 — SList/SExpansionList 의 separator (SList.tsx)
4008
+ // 항목 사이 구분선은 리스트가 그린다.
3806
4009
  // 보더 색은 시맨틱 토큰(border-default → hover strong → 펼침 accentLight)을 쓴다.
3807
4010
  // TODO: expansionItem/bordered/radius 토큰 생성 후 rounded 고정값도 토큰으로 교체한다.
3808
4011
  bordered && "rounded-[8px] border border-border-default hover:border-border-strong [&:has(>button:hover)]:border-border-strong",
@@ -4354,8 +4557,8 @@ function SGhostButton({
4354
4557
  }
4355
4558
  );
4356
4559
  if (!hasTooltip) return btn;
4357
- return /* @__PURE__ */ jsxs(RPopover9.Root, { open, onOpenChange: setOpen, children: [
4358
- /* @__PURE__ */ jsx(RPopover9.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
4560
+ return /* @__PURE__ */ jsxs(RPopover10.Root, { open, onOpenChange: setOpen, children: [
4561
+ /* @__PURE__ */ jsx(RPopover10.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
4359
4562
  "span",
4360
4563
  {
4361
4564
  className: cn("inline-flex", anchorClassName),
@@ -4367,8 +4570,8 @@ function SGhostButton({
4367
4570
  children: btn
4368
4571
  }
4369
4572
  ) }),
4370
- /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
4371
- RPopover9.Content,
4573
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
4574
+ RPopover10.Content,
4372
4575
  {
4373
4576
  side: "top",
4374
4577
  sideOffset: 4,
@@ -4382,6 +4585,18 @@ function SGhostButton({
4382
4585
  ) })
4383
4586
  ] });
4384
4587
  }
4588
+ var TREE_LABEL_TYPOGRAPHY = {
4589
+ sm: {
4590
+ default: "typo-body-sm-medium",
4591
+ selected: "typo-body-sm-bold",
4592
+ rowHeight: "h-[28px]"
4593
+ },
4594
+ md: {
4595
+ default: "typo-body-md-medium",
4596
+ selected: "typo-body-md-bold",
4597
+ rowHeight: "h-[32px]"
4598
+ }
4599
+ };
4385
4600
  function collectIds(nodes) {
4386
4601
  return nodes.flatMap((node) => [node.id, ...collectIds(node.children ?? [])]);
4387
4602
  }
@@ -4392,18 +4607,19 @@ function collectSelectableIds(node) {
4392
4607
  function toSelectedSet(value) {
4393
4608
  return new Set(value ?? []);
4394
4609
  }
4395
- function getSelectionState(node, selectedSet) {
4396
- if (selectedSet.has(node.id)) {
4397
- return { selected: true, indeterminate: false };
4610
+ var TREE_ALL_NODE = { id: "__all__", label: "\uC804\uCCB4" };
4611
+ function getSelectionState(node, selectedSet, cascadeSelection) {
4612
+ if (!cascadeSelection) {
4613
+ return { selected: selectedSet.has(node.id), indeterminate: false };
4398
4614
  }
4399
4615
  const selectableChildren = (node.children ?? []).filter(
4400
4616
  (child) => collectSelectableIds(child).length > 0
4401
4617
  );
4402
4618
  if (selectableChildren.length === 0) {
4403
- return { selected: false, indeterminate: false };
4619
+ return { selected: selectedSet.has(node.id), indeterminate: false };
4404
4620
  }
4405
4621
  const childStates = selectableChildren.map(
4406
- (child) => getSelectionState(child, selectedSet)
4622
+ (child) => getSelectionState(child, selectedSet, cascadeSelection)
4407
4623
  );
4408
4624
  const selectedCount = childStates.filter((state2) => state2.selected).length;
4409
4625
  return {
@@ -4413,6 +4629,7 @@ function getSelectionState(node, selectedSet) {
4413
4629
  }
4414
4630
  function TreeConnector({
4415
4631
  guideline,
4632
+ size,
4416
4633
  depth,
4417
4634
  hasChildren,
4418
4635
  isLast,
@@ -4420,6 +4637,8 @@ function TreeConnector({
4420
4637
  }) {
4421
4638
  if (depth <= 1) return null;
4422
4639
  const ancestorConnectors = ancestorLast.slice(0, -1);
4640
+ const branchTop = size === "sm" ? "top-[13px]" : "top-[15px]";
4641
+ const nodeHeight = size === "sm" ? "h-[15px]" : "h-[17px]";
4423
4642
  return /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "flex self-stretch", children: [
4424
4643
  ancestorConnectors.map((last, index) => /* @__PURE__ */ jsx(
4425
4644
  "span",
@@ -4433,13 +4652,22 @@ function TreeConnector({
4433
4652
  /* @__PURE__ */ jsxs("span", { className: "relative block w-[20px] shrink-0", children: [
4434
4653
  guideline && !isLast && /* @__PURE__ */ jsx("span", { className: "absolute bottom-0 left-[6px] top-[-1px] w-px bg-(--cmp-tree-connector-line-color)" }),
4435
4654
  guideline && /* @__PURE__ */ jsxs(Fragment, { children: [
4436
- /* @__PURE__ */ jsx("span", { className: "absolute left-[6px] top-[-1px] h-[17px] w-px bg-(--cmp-tree-connector-line-color)" }),
4655
+ /* @__PURE__ */ jsx(
4656
+ "span",
4657
+ {
4658
+ className: cn(
4659
+ "absolute left-[6px] top-[-1px] w-px bg-(--cmp-tree-connector-line-color)",
4660
+ nodeHeight
4661
+ )
4662
+ }
4663
+ ),
4437
4664
  /* @__PURE__ */ jsx(
4438
4665
  "span",
4439
4666
  {
4440
4667
  "data-tree-connector-branch": hasChildren ? "parent" : "leaf",
4441
4668
  className: cn(
4442
- "absolute left-[6px] top-[15px] h-px bg-(--cmp-tree-connector-line-color)",
4669
+ "absolute left-[6px] h-px bg-(--cmp-tree-connector-line-color)",
4670
+ branchTop,
4443
4671
  hasChildren ? "w-[14px]" : "w-[calc(14px+var(--cmp-tree-item-arrow-icon))]"
4444
4672
  )
4445
4673
  }
@@ -4448,6 +4676,44 @@ function TreeConnector({
4448
4676
  ] })
4449
4677
  ] });
4450
4678
  }
4679
+ function STreeAllItem({
4680
+ selected,
4681
+ indeterminate,
4682
+ border,
4683
+ size,
4684
+ onSelect
4685
+ }) {
4686
+ const checkboxValue = indeterminate ? null : selected;
4687
+ const typography = TREE_LABEL_TYPOGRAPHY[size];
4688
+ const labelTypo = selected || indeterminate ? typography.selected : typography.default;
4689
+ return /* @__PURE__ */ jsx(
4690
+ "div",
4691
+ {
4692
+ role: "treeitem",
4693
+ "aria-level": 1,
4694
+ "aria-selected": selected,
4695
+ onClick: onSelect,
4696
+ className: cn(
4697
+ "group/tree-item box-border flex w-full cursor-pointer items-center overflow-hidden bg-(--cmp-tree-item-bg-default) px-(--cmp-tree-item-paddingX) text-left hover:bg-(--cmp-tree-item-bg-hover)",
4698
+ typography.rowHeight,
4699
+ border && "border-b border-(--cmp-tree-item-border-default)"
4700
+ ),
4701
+ children: /* @__PURE__ */ jsxs("div", { className: "box-border flex min-w-0 flex-1 items-center gap-(--cmp-tree-item-contentGap)", children: [
4702
+ /* @__PURE__ */ jsx("span", { onClick: (event) => event.stopPropagation(), className: "flex shrink-0", children: /* @__PURE__ */ jsx(SCheckbox, { value: checkboxValue, onValueChange: () => onSelect() }) }),
4703
+ /* @__PURE__ */ jsx(
4704
+ "span",
4705
+ {
4706
+ className: cn(
4707
+ "block min-w-0 flex-1 truncate py-(--cmp-tree-item-paddingY) text-(--cmp-tree-item-text-default)",
4708
+ labelTypo
4709
+ ),
4710
+ children: "\uC804\uCCB4"
4711
+ }
4712
+ )
4713
+ ] })
4714
+ }
4715
+ );
4716
+ }
4451
4717
  var STreeItemImpl = /* @__PURE__ */ forwardRef(function STreeItem({
4452
4718
  node,
4453
4719
  depth = 1,
@@ -4459,6 +4725,7 @@ var STreeItemImpl = /* @__PURE__ */ forwardRef(function STreeItem({
4459
4725
  border = true,
4460
4726
  indeterminate = false,
4461
4727
  selectable = true,
4728
+ size = "sm",
4462
4729
  leading,
4463
4730
  trailing,
4464
4731
  onToggle,
@@ -4470,6 +4737,7 @@ var STreeItemImpl = /* @__PURE__ */ forwardRef(function STreeItem({
4470
4737
  const renderState = {
4471
4738
  node,
4472
4739
  depth,
4740
+ size,
4473
4741
  expanded,
4474
4742
  selected,
4475
4743
  indeterminate,
@@ -4479,6 +4747,8 @@ var STreeItemImpl = /* @__PURE__ */ forwardRef(function STreeItem({
4479
4747
  const leadingNode = typeof leading === "function" ? leading(renderState) : leading;
4480
4748
  const trailingNode = typeof trailing === "function" ? trailing(renderState) : trailing;
4481
4749
  const checkboxValue = indeterminate ? null : selected;
4750
+ const typography = TREE_LABEL_TYPOGRAPHY[size];
4751
+ const labelTypo = selected || indeterminate ? typography.selected : typography.default;
4482
4752
  const handleSelect = (event) => {
4483
4753
  if (node.disabled) return;
4484
4754
  onTreeSelect?.(node, event);
@@ -4498,7 +4768,8 @@ var STreeItemImpl = /* @__PURE__ */ forwardRef(function STreeItem({
4498
4768
  "aria-disabled": node.disabled || void 0,
4499
4769
  onClick: handleRowClick,
4500
4770
  className: cn(
4501
- "group/tree-item flex min-h-[32px] w-full items-center gap-(--cmp-tree-item-rowGap) bg-(--cmp-tree-item-bg-default) px-(--cmp-tree-item-paddingX) text-left",
4771
+ "group/tree-item box-border flex w-full items-center gap-(--cmp-tree-item-rowGap) overflow-hidden bg-(--cmp-tree-item-bg-default) px-(--cmp-tree-item-paddingX) text-left",
4772
+ typography.rowHeight,
4502
4773
  border && "border-b border-(--cmp-tree-item-border-default)",
4503
4774
  !node.disabled && "hover:bg-(--cmp-tree-item-bg-hover)",
4504
4775
  node.disabled && "cursor-not-allowed opacity-60",
@@ -4511,13 +4782,14 @@ var STreeItemImpl = /* @__PURE__ */ forwardRef(function STreeItem({
4511
4782
  TreeConnector,
4512
4783
  {
4513
4784
  guideline,
4785
+ size,
4514
4786
  depth,
4515
4787
  hasChildren,
4516
4788
  isLast,
4517
4789
  ancestorLast
4518
4790
  }
4519
4791
  ),
4520
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-(--cmp-tree-item-contentGap) py-(--cmp-tree-item-paddingY)", children: [
4792
+ /* @__PURE__ */ jsxs("div", { className: "box-border flex min-w-0 flex-1 items-center gap-(--cmp-tree-item-contentGap)", children: [
4521
4793
  hasChildren ? /* @__PURE__ */ jsx(
4522
4794
  SGhostButton,
4523
4795
  {
@@ -4553,25 +4825,17 @@ var STreeItemImpl = /* @__PURE__ */ forwardRef(function STreeItem({
4553
4825
  /* @__PURE__ */ jsx(
4554
4826
  "span",
4555
4827
  {
4828
+ onClick: (event) => {
4829
+ if (!selectable) return;
4830
+ event.stopPropagation();
4831
+ if (!node.disabled && selectable) onTreeSelect?.(node, event);
4832
+ },
4556
4833
  className: cn(
4557
- "min-w-0 flex-1 text-[14px] leading-[24px]",
4558
- selected || indeterminate ? "font-bold text-(--cmp-tree-item-text-selected)" : "font-medium text-(--cmp-tree-item-text-default)"
4834
+ "block min-w-0 flex-1 truncate py-(--cmp-tree-item-paddingY) text-(--cmp-tree-item-text-default)",
4835
+ labelTypo,
4836
+ node.disabled ? "cursor-not-allowed" : selectable ? "cursor-pointer" : void 0
4559
4837
  ),
4560
- children: /* @__PURE__ */ jsx(
4561
- "span",
4562
- {
4563
- onClick: (event) => {
4564
- if (!selectable) return;
4565
- event.stopPropagation();
4566
- if (!node.disabled && selectable) onTreeSelect?.(node, event);
4567
- },
4568
- className: cn(
4569
- "inline-block max-w-full truncate",
4570
- node.disabled ? "cursor-not-allowed" : selectable ? "cursor-pointer" : void 0
4571
- ),
4572
- children: node.label
4573
- }
4574
- )
4838
+ children: node.label
4575
4839
  }
4576
4840
  ),
4577
4841
  trailingNode
@@ -4588,6 +4852,8 @@ var STreeImpl = /* @__PURE__ */ forwardRef(function STree({
4588
4852
  expandedIds,
4589
4853
  defaultExpandedIds,
4590
4854
  selectable = true,
4855
+ size = "sm",
4856
+ useAll = false,
4591
4857
  cascadeSelection = true,
4592
4858
  guideline = true,
4593
4859
  border = true,
@@ -4606,6 +4872,11 @@ var STreeImpl = /* @__PURE__ */ forwardRef(function STree({
4606
4872
  const currentExpandedIds = expandedIds ?? internalExpandedIds;
4607
4873
  const selectedSet = useMemo(() => toSelectedSet(currentValue), [currentValue]);
4608
4874
  const expandedSet = useMemo(() => new Set(currentExpandedIds), [currentExpandedIds]);
4875
+ const selectableIds = useMemo(() => items.flatMap(collectSelectableIds), [items]);
4876
+ const allSelected = selectableIds.length > 0 && selectableIds.every((id) => selectedSet.has(id));
4877
+ const someSelected = selectableIds.some((id) => selectedSet.has(id));
4878
+ const allIndeterminate = someSelected && !allSelected;
4879
+ const showAllItem = selectable && useAll;
4609
4880
  const updateExpanded = (node) => {
4610
4881
  const next = new Set(currentExpandedIds);
4611
4882
  if (next.has(node.id)) next.delete(node.id);
@@ -4617,7 +4888,7 @@ var STreeImpl = /* @__PURE__ */ forwardRef(function STree({
4617
4888
  const updateSelected = (node) => {
4618
4889
  if (node.disabled) return;
4619
4890
  const targetIds = cascadeSelection ? collectSelectableIds(node) : [node.id];
4620
- const everySelected = getSelectionState(node, selectedSet).selected;
4891
+ const everySelected = getSelectionState(node, selectedSet, cascadeSelection).selected;
4621
4892
  const next = new Set(selectedSet);
4622
4893
  targetIds.forEach((id) => {
4623
4894
  if (everySelected) next.delete(id);
@@ -4627,10 +4898,22 @@ var STreeImpl = /* @__PURE__ */ forwardRef(function STree({
4627
4898
  if (value === void 0) setInternalValue(orderedIds);
4628
4899
  onValueChange?.(orderedIds, node);
4629
4900
  };
4901
+ const updateAllSelected = (event) => {
4902
+ event?.stopPropagation();
4903
+ if (selectableIds.length === 0) return;
4904
+ const next = new Set(selectedSet);
4905
+ selectableIds.forEach((id) => {
4906
+ if (allSelected) next.delete(id);
4907
+ else next.add(id);
4908
+ });
4909
+ const orderedIds = collectIds(items).filter((id) => next.has(id));
4910
+ if (value === void 0) setInternalValue(orderedIds);
4911
+ onValueChange?.(orderedIds, TREE_ALL_NODE);
4912
+ };
4630
4913
  const renderNodes = (nodes, depth = 1, ancestorLast = []) => nodes.map((node, index) => {
4631
4914
  const isLast = index === nodes.length - 1;
4632
4915
  const isExpanded = expandedSet.has(node.id);
4633
- const { selected, indeterminate } = getSelectionState(node, selectedSet);
4916
+ const { selected, indeterminate } = getSelectionState(node, selectedSet, cascadeSelection);
4634
4917
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [
4635
4918
  /* @__PURE__ */ jsx(
4636
4919
  STreeItem2,
@@ -4643,6 +4926,7 @@ var STreeImpl = /* @__PURE__ */ forwardRef(function STree({
4643
4926
  selected,
4644
4927
  indeterminate,
4645
4928
  selectable,
4929
+ size,
4646
4930
  guideline,
4647
4931
  border,
4648
4932
  leading,
@@ -4664,14 +4948,26 @@ var STreeImpl = /* @__PURE__ */ forwardRef(function STree({
4664
4948
  ) : null
4665
4949
  ] }, node.id);
4666
4950
  });
4667
- return /* @__PURE__ */ jsx(
4951
+ return /* @__PURE__ */ jsxs(
4668
4952
  "div",
4669
4953
  {
4670
4954
  ref,
4671
4955
  role: "tree",
4672
4956
  className: cn("flex w-full flex-col items-start bg-(--cmp-tree-item-bg-default)", className),
4673
4957
  ...rest,
4674
- children: renderNodes(items)
4958
+ children: [
4959
+ showAllItem && /* @__PURE__ */ jsx(
4960
+ STreeAllItem,
4961
+ {
4962
+ selected: allSelected,
4963
+ indeterminate: allIndeterminate,
4964
+ border,
4965
+ size,
4966
+ onSelect: updateAllSelected
4967
+ }
4968
+ ),
4969
+ renderNodes(items)
4970
+ ]
4675
4971
  }
4676
4972
  );
4677
4973
  });
@@ -4851,14 +5147,14 @@ var STooltipImpl = /* @__PURE__ */ forwardRef(function STooltip({
4851
5147
  ...style,
4852
5148
  ...contentPropsStyle
4853
5149
  };
4854
- return /* @__PURE__ */ jsxs(RPopover9.Root, { open, onOpenChange: setOpenState, children: [
5150
+ return /* @__PURE__ */ jsxs(RPopover10.Root, { open, onOpenChange: setOpenState, children: [
4855
5151
  isNone ? (
4856
5152
  // 트리거 없음: children 을 래퍼 없이 그대로 앵커로 쓴다 (소비자 레이아웃 보존).
4857
5153
  // mouse/focus 핸들러가 없으므로 useClose 없이도 자동 닫힘이 발생하지 않는다.
4858
- /* @__PURE__ */ jsx(RPopover9.Anchor, { asChild: true, children: triggerNode })
5154
+ /* @__PURE__ */ jsx(RPopover10.Anchor, { asChild: true, children: triggerNode })
4859
5155
  ) : isHover ? (
4860
5156
  // hover: Anchor(위치 기준) + 직접 mouse 핸들러 — 클릭으로 토글되지 않도록 Trigger 미사용
4861
- /* @__PURE__ */ jsx(RPopover9.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
5157
+ /* @__PURE__ */ jsx(RPopover10.Anchor, { asChild: true, children: /* @__PURE__ */ jsx(
4862
5158
  "span",
4863
5159
  {
4864
5160
  className: "inline-flex",
@@ -4869,9 +5165,9 @@ var STooltipImpl = /* @__PURE__ */ forwardRef(function STooltip({
4869
5165
  children: triggerNode
4870
5166
  }
4871
5167
  ) })
4872
- ) : /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: triggerNode }),
4873
- /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
4874
- RPopover9.Content,
5168
+ ) : /* @__PURE__ */ jsx(RPopover10.Trigger, { asChild: true, children: triggerNode }),
5169
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
5170
+ RPopover10.Content,
4875
5171
  {
4876
5172
  side: placement,
4877
5173
  sideOffset,
@@ -4905,7 +5201,7 @@ var STooltipImpl = /* @__PURE__ */ forwardRef(function STooltip({
4905
5201
  }
4906
5202
  )
4907
5203
  ] }),
4908
- /* @__PURE__ */ jsx(RPopover9.Arrow, { width: 16, height: 12, style: { fill: bg } })
5204
+ /* @__PURE__ */ jsx(RPopover10.Arrow, { width: 16, height: 12, style: { fill: bg } })
4909
5205
  ]
4910
5206
  }
4911
5207
  ) })
@@ -4917,6 +5213,10 @@ var BODY_PADDING_CLASS = {
4917
5213
  wide: "p-(--sys-space-panel-padding-wide)",
4918
5214
  none: ""
4919
5215
  };
5216
+ var BODY_BACKGROUND_CLASS = {
5217
+ frame: "",
5218
+ neutral: "bg-(--sys-color-bg-neutralLight)"
5219
+ };
4920
5220
  var topBorderColorClass = {
4921
5221
  default: "bg-(--cmp-sectionHeaderCard-topBorder-color-default)",
4922
5222
  accent: "bg-(--cmp-sectionHeaderCard-topBorder-color-accent)"
@@ -4926,8 +5226,8 @@ var titleSizeClass = {
4926
5226
  sm: "typo-heading-sm"
4927
5227
  };
4928
5228
  var SSectionHeaderCardBodyImpl = /* @__PURE__ */ forwardRef(
4929
- function SSectionHeaderCardBody({ padding = "default", className, children, ...rest }, ref) {
4930
- return /* @__PURE__ */ jsx("div", { ref, className: cn(BODY_PADDING_CLASS[padding], className), ...rest, children });
5229
+ function SSectionHeaderCardBody({ padding = "default", background = "frame", children }, ref) {
5230
+ return /* @__PURE__ */ jsx("div", { ref, className: cn(BODY_PADDING_CLASS[padding], BODY_BACKGROUND_CLASS[background]), children });
4931
5231
  }
4932
5232
  );
4933
5233
  var SSectionHeaderCardHeaderImpl = /* @__PURE__ */ forwardRef(
@@ -4940,8 +5240,7 @@ var SSectionHeaderCardHeaderImpl = /* @__PURE__ */ forwardRef(
4940
5240
  subtitle,
4941
5241
  slot,
4942
5242
  thickness = false,
4943
- className,
4944
- ...rest
5243
+ className
4945
5244
  }, ref) {
4946
5245
  const hasHelp = helpText != null && helpText.length > 0;
4947
5246
  return /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -4962,7 +5261,6 @@ var SSectionHeaderCardHeaderImpl = /* @__PURE__ */ forwardRef(
4962
5261
  "box-border flex min-h-(--cmp-sectionHeaderCard-header-minHeight) w-full shrink-0 items-center gap-(--cmp-sectionHeaderCard-header-gap) overflow-hidden border-b border-(--cmp-sectionHeaderCard-header-border) bg-(--cmp-sectionHeaderCard-header-bg) px-(--cmp-sectionHeaderCard-header-paddingX) py-(--cmp-sectionHeaderCard-header-paddingY)",
4963
5262
  className
4964
5263
  ),
4965
- ...rest,
4966
5264
  children: [
4967
5265
  marker && /* @__PURE__ */ jsx(SBadge, { color: "darkblue", className: "shrink-0" }),
4968
5266
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 shrink-0 items-center gap-[8px]", children: [
@@ -5006,8 +5304,24 @@ var SSectionHeaderCardHeaderImpl = /* @__PURE__ */ forwardRef(
5006
5304
  }
5007
5305
  );
5008
5306
  var SSectionHeaderCardRootImpl = /* @__PURE__ */ forwardRef(
5009
- function SSectionHeaderCard({ className, children, ...rest }, ref) {
5010
- return /* @__PURE__ */ jsx(
5307
+ function SSectionHeaderCard({
5308
+ title,
5309
+ titleSize,
5310
+ marker,
5311
+ required,
5312
+ helpText,
5313
+ subtitle,
5314
+ slot,
5315
+ thickness,
5316
+ headerClassName,
5317
+ padding,
5318
+ background,
5319
+ className,
5320
+ children,
5321
+ ...rest
5322
+ }, ref) {
5323
+ const hasHeader = title != null;
5324
+ return /* @__PURE__ */ jsxs(
5011
5325
  "div",
5012
5326
  {
5013
5327
  ref,
@@ -5016,24 +5330,28 @@ var SSectionHeaderCardRootImpl = /* @__PURE__ */ forwardRef(
5016
5330
  className
5017
5331
  ),
5018
5332
  ...rest,
5019
- children
5333
+ children: [
5334
+ hasHeader && /* @__PURE__ */ jsx(
5335
+ SSectionHeaderCardHeaderImpl,
5336
+ {
5337
+ title,
5338
+ titleSize,
5339
+ marker,
5340
+ required,
5341
+ helpText,
5342
+ subtitle,
5343
+ slot,
5344
+ thickness,
5345
+ className: headerClassName
5346
+ }
5347
+ ),
5348
+ /* @__PURE__ */ jsx(SSectionHeaderCardBodyImpl, { padding, background, children })
5349
+ ]
5020
5350
  }
5021
5351
  );
5022
5352
  }
5023
5353
  );
5024
- var SSectionHeaderCardRoot = /* @__PURE__ */ withDisplayName(SSectionHeaderCardRootImpl, "SSectionHeaderCard");
5025
- var SSectionHeaderCardHeader2 = /* @__PURE__ */ withDisplayName(
5026
- SSectionHeaderCardHeaderImpl,
5027
- "SSectionHeaderCard.Header"
5028
- );
5029
- var SSectionHeaderCardBody2 = /* @__PURE__ */ withDisplayName(
5030
- SSectionHeaderCardBodyImpl,
5031
- "SSectionHeaderCard.Body"
5032
- );
5033
- var SSectionHeaderCard2 = /* @__PURE__ */ Object.assign(SSectionHeaderCardRoot, {
5034
- Header: SSectionHeaderCardHeader2,
5035
- Body: SSectionHeaderCardBody2
5036
- });
5354
+ var SSectionHeaderCard2 = /* @__PURE__ */ withDisplayName(SSectionHeaderCardRootImpl, "SSectionHeaderCard");
5037
5355
  var SDivider = /* @__PURE__ */ forwardRef(function SDivider2({ vertical = false, className, style, ...rest }, ref) {
5038
5356
  return /* @__PURE__ */ jsx(
5039
5357
  "hr",
@@ -5549,168 +5867,59 @@ var SCallout = /* @__PURE__ */ forwardRef(function SCallout2({ type = "default",
5549
5867
  }
5550
5868
  ),
5551
5869
  /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 flex-col justify-center gap-[var(--cmp-callout-body-gap)] px-[var(--cmp-callout-body-paddingX)] py-[var(--cmp-callout-body-paddingY)] text-[12px] font-normal leading-[20px]", children: children ?? /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col justify-center gap-[var(--cmp-callout-body-gap)] p-0", children: renderItems(message) }) })
5552
- ]
5553
- }
5554
- );
5555
- });
5556
- var FILL_COLOR = {
5557
- primary: "var(--cmp-progress-active-color)",
5558
- error: "var(--cmp-progress-error-color)",
5559
- complete: "var(--cmp-progress-success-color)"
5560
- };
5561
- var clamp = (v) => Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 0;
5562
- var SLinearProgress = /* @__PURE__ */ forwardRef(
5563
- function SLinearProgress2({ value = 0, type = "primary", indeterminate = false, label, className, style }, ref) {
5564
- const v = clamp(value);
5565
- const fill = FILL_COLOR[type];
5566
- const valueColor = v === 0 ? "var(--sys-color-fg-tertiary, #888888)" : fill;
5567
- const valueText = `${Math.round(v)}%`;
5568
- return /* @__PURE__ */ jsxs(
5569
- "div",
5570
- {
5571
- ref,
5572
- className: cn("inline-flex w-full flex-col gap-[12px]", className),
5573
- style,
5574
- children: [
5575
- /* @__PURE__ */ jsxs("div", { className: "relative h-[20px] w-full overflow-hidden rounded-full bg-[var(--cmp-progress-color-track)]", children: [
5576
- /* @__PURE__ */ jsx(
5577
- "div",
5578
- {
5579
- className: cn(
5580
- "absolute inset-y-0 left-0 h-full rounded-[inherit]",
5581
- indeterminate ? "w-full origin-left animate-linear-indeterminate" : "transition-[width] duration-[400ms] ease-linear"
5582
- ),
5583
- style: { background: fill, width: indeterminate ? void 0 : `${v}%` }
5584
- }
5585
- ),
5586
- !indeterminate && /* @__PURE__ */ jsxs(Fragment, { children: [
5587
- /* @__PURE__ */ jsx(
5588
- "span",
5589
- {
5590
- className: "pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 select-none text-center text-[12px] font-bold leading-[20px]",
5591
- style: { color: valueColor },
5592
- children: valueText
5593
- }
5594
- ),
5595
- /* @__PURE__ */ jsx(
5596
- "span",
5597
- {
5598
- className: "pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 select-none text-center text-[12px] font-bold leading-[20px] text-[color:var(--cmp-progress-bar-text)]",
5599
- style: { clipPath: `inset(0 ${100 - v}% 0 0)` },
5600
- children: valueText
5601
- }
5602
- )
5603
- ] })
5604
- ] }),
5605
- label && /* @__PURE__ */ jsx("span", { className: "text-center text-[12px] font-normal leading-[20px] text-[color:var(--cmp-progress-color-label)]", children: label })
5606
- ]
5607
- }
5608
- );
5609
- }
5610
- );
5611
- var VIEWBOX = 80;
5612
- var RADIUS = 34;
5613
- var STROKE = 12;
5614
- var CIRCUMFERENCE = 2 * Math.PI * RADIUS;
5615
- var COLOR_MAP = {
5616
- primary: { arc: "var(--cmp-progress-active-color)", track: "var(--cmp-progress-color-track)" },
5617
- // inverse: 어두운 배경용 — arc 흰색, track 은 흰색 40%(회색 링)
5618
- inverse: { arc: "var(--color-white)", track: "rgba(255,255,255,0.4)" },
5619
- error: { arc: "var(--color-red-75)", track: "var(--cmp-progress-color-track)" },
5620
- complete: { arc: "#01BB4B", track: "var(--cmp-progress-color-track)" }
5621
- };
5622
- var clamp2 = (v) => Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 0;
5623
- var SCircleProgress = /* @__PURE__ */ forwardRef(
5624
- function SCircleProgress2({
5625
- value = 0,
5626
- type = "primary",
5627
- indeterminate = false,
5628
- label,
5629
- innerValue = false,
5630
- className,
5631
- style
5632
- }, ref) {
5633
- const v = clamp2(value);
5634
- const colors = COLOR_MAP[type];
5635
- const c = VIEWBOX / 2;
5636
- const percentColor = v === 0 ? "var(--sys-color-fg-tertiary, #888888)" : colors.arc;
5637
- const filled = v / 100 * CIRCUMFERENCE;
5638
- const showPercent = !indeterminate;
5639
- const percentNode = /* @__PURE__ */ jsxs(
5640
- "span",
5641
- {
5642
- className: cn(
5643
- "text-center text-[16px] font-bold",
5644
- innerValue ? (
5645
- // leading-[normal]: 26px leading 은 폰트 content(19px) 위아래로 홀수(7px)만큼 남아
5646
- // 3px/4px 로 쪼개지고 글자가 링 중심보다 위로 뜬다. (leading-normal 은 1.5 라 동일 문제)
5647
- "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 leading-[normal]"
5648
- ) : "leading-[26px]"
5649
- ),
5650
- style: { color: percentColor },
5651
- children: [
5652
- Math.round(v),
5653
- "%"
5654
- ]
5655
- }
5656
- );
5657
- return /* @__PURE__ */ jsxs(
5658
- "div",
5659
- {
5660
- ref,
5661
- className: cn(
5662
- "inline-flex flex-col items-center gap-[var(--cmp-progress-circular-gap)]",
5663
- className
5664
- ),
5665
- style,
5666
- children: [
5667
- /* @__PURE__ */ jsxs("div", { className: cn(innerValue && "relative"), children: [
5668
- /* @__PURE__ */ jsxs(
5669
- "svg",
5670
- {
5671
- viewBox: `0 0 ${VIEWBOX} ${VIEWBOX}`,
5672
- xmlns: "http://www.w3.org/2000/svg",
5673
- className: cn(
5674
- "h-[80px] w-[80px]",
5675
- indeterminate && "origin-center animate-circle-spin"
5676
- ),
5677
- children: [
5678
- /* @__PURE__ */ jsx(
5679
- "circle",
5680
- {
5681
- cx: c,
5682
- cy: c,
5683
- r: RADIUS,
5684
- fill: "none",
5685
- strokeWidth: STROKE,
5686
- style: { stroke: colors.track }
5687
- }
5688
- ),
5689
- /* @__PURE__ */ jsx(
5690
- "circle",
5691
- {
5692
- cx: c,
5693
- cy: c,
5694
- r: RADIUS,
5695
- fill: "none",
5696
- strokeWidth: STROKE,
5697
- strokeLinecap: "round",
5698
- className: cn(
5699
- "origin-center",
5700
- indeterminate ? "animate-circle-arc" : "-rotate-90 transition-[stroke-dasharray] duration-[400ms] ease-linear"
5701
- ),
5702
- style: {
5703
- stroke: colors.arc,
5704
- ...indeterminate ? {} : { strokeDasharray: `${filled} ${CIRCUMFERENCE}`, strokeDashoffset: 0 }
5705
- }
5706
- }
5707
- )
5708
- ]
5870
+ ]
5871
+ }
5872
+ );
5873
+ });
5874
+ var FILL_COLOR = {
5875
+ primary: "var(--cmp-progress-color-active)",
5876
+ error: "var(--cmp-progress-color-error)",
5877
+ complete: "var(--cmp-progress-color-success)"
5878
+ };
5879
+ var clamp2 = (v) => Number.isFinite(v) ? Math.max(0, Math.min(100, v)) : 0;
5880
+ var SLinearProgress = /* @__PURE__ */ forwardRef(
5881
+ function SLinearProgress2({ value = 0, type = "primary", indeterminate = false, label, className, style }, ref) {
5882
+ const v = clamp2(value);
5883
+ const fill = FILL_COLOR[type];
5884
+ const valueColor = v === 0 ? "var(--sys-color-fg-tertiary, #888888)" : fill;
5885
+ const valueText = `${Math.round(v)}%`;
5886
+ return /* @__PURE__ */ jsxs(
5887
+ "div",
5888
+ {
5889
+ ref,
5890
+ className: cn("inline-flex w-full flex-col gap-[12px]", className),
5891
+ style,
5892
+ children: [
5893
+ /* @__PURE__ */ jsxs("div", { className: "relative h-[20px] w-full overflow-hidden rounded-full bg-[var(--cmp-progress-color-track)]", children: [
5894
+ /* @__PURE__ */ jsx(
5895
+ "div",
5896
+ {
5897
+ className: cn(
5898
+ "absolute inset-y-0 left-0 h-full rounded-[inherit]",
5899
+ indeterminate ? "w-full origin-left animate-linear-indeterminate" : "transition-[width] duration-[400ms] ease-linear"
5900
+ ),
5901
+ style: { background: fill, width: indeterminate ? void 0 : `${v}%` }
5709
5902
  }
5710
5903
  ),
5711
- showPercent && innerValue && percentNode
5904
+ !indeterminate && /* @__PURE__ */ jsxs(Fragment, { children: [
5905
+ /* @__PURE__ */ jsx(
5906
+ "span",
5907
+ {
5908
+ className: "pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 select-none text-center text-[12px] font-bold leading-[20px]",
5909
+ style: { color: valueColor },
5910
+ children: valueText
5911
+ }
5912
+ ),
5913
+ /* @__PURE__ */ jsx(
5914
+ "span",
5915
+ {
5916
+ className: "pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 select-none text-center text-[12px] font-bold leading-[20px] text-[color:var(--cmp-progress-bar-text)]",
5917
+ style: { clipPath: `inset(0 ${100 - v}% 0 0)` },
5918
+ children: valueText
5919
+ }
5920
+ )
5921
+ ] })
5712
5922
  ] }),
5713
- showPercent && !innerValue && percentNode,
5714
5923
  label && /* @__PURE__ */ jsx("span", { className: "text-center text-[12px] font-normal leading-[20px] text-[color:var(--cmp-progress-color-label)]", children: label })
5715
5924
  ]
5716
5925
  }
@@ -6202,10 +6411,15 @@ function SSwitch({
6202
6411
  }
6203
6412
  );
6204
6413
  }
6414
+ var SIZE_CLASSES = {
6415
+ xs: "h-[24px] px-[8px] text-[12px] font-normal leading-[20px]",
6416
+ sm: "h-[28px] px-[12px] text-[14px] font-medium leading-[24px]"
6417
+ };
6205
6418
  function SToggle({
6206
6419
  value = false,
6207
6420
  label = "",
6208
6421
  disabled = false,
6422
+ size = "sm",
6209
6423
  onValueChange,
6210
6424
  className,
6211
6425
  style
@@ -6221,7 +6435,8 @@ function SToggle({
6221
6435
  "aria-pressed": value,
6222
6436
  onClick: () => !disabled && onValueChange?.(!value),
6223
6437
  className: cn(
6224
- "box-border inline-flex h-[28px] items-center justify-center whitespace-nowrap rounded-full border border-solid px-[12px] text-[14px] font-medium leading-[24px] transition-colors",
6438
+ "box-border inline-flex items-center justify-center whitespace-nowrap rounded-full border border-solid transition-colors",
6439
+ SIZE_CLASSES[size],
6225
6440
  disabled ? "cursor-not-allowed" : "cursor-pointer",
6226
6441
  // 미선택(off) hover 시 파란 채움 + 흰 글자
6227
6442
  !disabled && !value && "hover:!bg-[var(--cmp-toggle-bg-hover)] hover:!text-[color:var(--cmp-toggle-content-hover)] hover:!border-[color:var(--cmp-toggle-bg-hover)]",
@@ -6273,10 +6488,10 @@ function SPopover({
6273
6488
  const descColor = type === "default" ? "var(--cmp-popover-default-description)" : "var(--cmp-popover-light-description)";
6274
6489
  const leftLinkLabelClass = type === "default" ? "text-[color:var(--cmp-popover-default-subAction)]" : "text-[color:var(--cmp-popover-light-subAction)]";
6275
6490
  const triggerNode = trigger ?? /* @__PURE__ */ jsx("span", { className: "inline-flex cursor-pointer", "aria-label": ariaLabel, children: /* @__PURE__ */ jsx(SIcon, { name: icon, size: iconSize, color }) });
6276
- return /* @__PURE__ */ jsxs(RPopover9.Root, { open: actualOpen, onOpenChange: setOpen, children: [
6277
- /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: triggerNode }),
6278
- /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
6279
- RPopover9.Content,
6491
+ return /* @__PURE__ */ jsxs(RPopover10.Root, { open: actualOpen, onOpenChange: setOpen, children: [
6492
+ /* @__PURE__ */ jsx(RPopover10.Trigger, { asChild: true, children: triggerNode }),
6493
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
6494
+ RPopover10.Content,
6280
6495
  {
6281
6496
  side: placement,
6282
6497
  sideOffset: 8,
@@ -6342,7 +6557,7 @@ function SPopover({
6342
6557
  ]
6343
6558
  }
6344
6559
  ),
6345
- /* @__PURE__ */ jsx(RPopover9.Arrow, { width: 16, height: 12, style: { fill: bg } })
6560
+ /* @__PURE__ */ jsx(RPopover10.Arrow, { width: 16, height: 12, style: { fill: bg } })
6346
6561
  ]
6347
6562
  }
6348
6563
  ) })
@@ -6373,7 +6588,7 @@ function SPortal({
6373
6588
  const portalContainer = usePortalContainer();
6374
6589
  const { minWidth: styleMinWidth, maxWidth: styleMaxWidth, ...restStyle } = style ?? {};
6375
6590
  return /* @__PURE__ */ jsxs(
6376
- RPopover9.Root,
6591
+ RPopover10.Root,
6377
6592
  {
6378
6593
  open,
6379
6594
  onOpenChange: (next) => {
@@ -6381,9 +6596,9 @@ function SPortal({
6381
6596
  if (!next) onClose?.();
6382
6597
  },
6383
6598
  children: [
6384
- /* @__PURE__ */ jsx(RPopover9.Anchor, { virtualRef: anchorRef }),
6385
- /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
6386
- RPopover9.Content,
6599
+ /* @__PURE__ */ jsx(RPopover10.Anchor, { virtualRef: anchorRef }),
6600
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
6601
+ RPopover10.Content,
6387
6602
  {
6388
6603
  side: placement,
6389
6604
  align,
@@ -6581,10 +6796,10 @@ function SGuide({
6581
6796
  );
6582
6797
  if (isNotion)
6583
6798
  return /* @__PURE__ */ jsx("span", { className, style, children: trigger });
6584
- return /* @__PURE__ */ jsxs(RPopover9.Root, { open, onOpenChange: setOpen, children: [
6585
- /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: trigger }),
6586
- /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
6587
- RPopover9.Content,
6799
+ return /* @__PURE__ */ jsxs(RPopover10.Root, { open, onOpenChange: setOpen, children: [
6800
+ /* @__PURE__ */ jsx(RPopover10.Trigger, { asChild: true, children: trigger }),
6801
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxs(
6802
+ RPopover10.Content,
6588
6803
  {
6589
6804
  side: "bottom",
6590
6805
  align: "start",
@@ -6767,7 +6982,7 @@ function SModalContainer({
6767
6982
  "div",
6768
6983
  {
6769
6984
  className: cn(
6770
- "relative flex max-h-[calc(100dvh-48px)] max-w-[calc(100dvw-48px)] flex-col overflow-hidden rounded-[var(--cmp-modal-radius)] bg-[var(--cmp-modal-bg)] shadow-normal-md",
6985
+ "relative flex max-h-[85dvh] max-w-[calc(100dvw-48px)] flex-col overflow-hidden rounded-[var(--cmp-modal-radius)] bg-[var(--cmp-modal-bg)] shadow-normal-md",
6771
6986
  shaking && "animate-modal-shake",
6772
6987
  className
6773
6988
  ),
@@ -7024,7 +7239,7 @@ function SActionModal({
7024
7239
  showClose: true,
7025
7240
  width,
7026
7241
  height,
7027
- className: "w-fit min-w-[min(480px,calc(100dvw-48px))] min-h-[min(320px,calc(100dvh-48px))]",
7242
+ className: "w-fit min-w-[min(480px,calc(100dvw-48px))] min-h-[min(320px,85dvh)]",
7028
7243
  children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-auto flex-col", children: [
7029
7244
  /* @__PURE__ */ jsx(SOverlayHeader, { title: modalTitle, description, reserveCloseSpace: true }),
7030
7245
  /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto px-[var(--cmp-modal-action-body-paddingX)] pb-[var(--cmp-modal-action-body-paddingBottom)]", children }),
@@ -7917,15 +8132,21 @@ var GNB_MENU = {
7917
8132
  depth2PaddingLeft: box("item-depth2-paddingLeft"),
7918
8133
  depth3PaddingLeft: box("item-depth3-paddingLeft"),
7919
8134
  depth1Icon: box("item-depth1-icon"),
8135
+ // box 는 depth1 전용 타이포 토큰이 없어 depth1 도 selected 타이포를 따른다
7920
8136
  depth1FontSize: 14,
7921
8137
  // token: gnb.box.item.typography.selected.fontSize
7922
8138
  depth1LineHeight: 24,
7923
8139
  // token: gnb.box.item.typography.selected.lineHeight
7924
- depth1FontWeight: 700,
8140
+ depth1FontWeight: 600,
8141
+ // token: gnb.box.item.typography.selected.fontWeight
7925
8142
  itemFontSize: 14,
7926
8143
  // token: gnb.box.item.typography.default.fontSize
7927
- itemLineHeight: 24
8144
+ itemLineHeight: 24,
7928
8145
  // token: gnb.box.item.typography.default.lineHeight
8146
+ itemFontWeight: 500,
8147
+ // token: gnb.box.item.typography.default.fontWeight
8148
+ itemFontWeightSelected: 600
8149
+ // token: gnb.box.item.typography.selected.fontWeight
7929
8150
  },
7930
8151
  belt: {
7931
8152
  bodyPaddingY: gnb("body-padding"),
@@ -7947,10 +8168,15 @@ var GNB_MENU = {
7947
8168
  depth1LineHeight: 22,
7948
8169
  // token: gnb.belt.item.depth1.typography.lineHeight
7949
8170
  depth1FontWeight: 700,
8171
+ // token: gnb.belt.item.depth1.typography.fontWeight
7950
8172
  itemFontSize: 12,
7951
8173
  // token: gnb.belt.item.typography.default.fontSize
7952
- itemLineHeight: 20
8174
+ itemLineHeight: 20,
7953
8175
  // token: gnb.belt.item.typography.default.lineHeight
8176
+ itemFontWeight: 500,
8177
+ // token: gnb.belt.item.typography.default.fontWeight
8178
+ itemFontWeightSelected: 700
8179
+ // token: gnb.belt.item.typography.selected.fontWeight
7954
8180
  }
7955
8181
  };
7956
8182
  var GNB_COLORS = {
@@ -8248,7 +8474,7 @@ var SGnb = /* @__PURE__ */ forwardRef(function SGnb2({
8248
8474
  fontFamily: "inherit",
8249
8475
  fontSize: depth === 1 ? M.depth1FontSize : M.itemFontSize,
8250
8476
  lineHeight: `${depth === 1 ? M.depth1LineHeight : M.itemLineHeight}px`,
8251
- fontWeight: depth === 1 || isSelected ? M.depth1FontWeight : 400,
8477
+ fontWeight: depth === 1 ? M.depth1FontWeight : isSelected ? M.itemFontWeightSelected : M.itemFontWeight,
8252
8478
  opacity: item.disabled ? 0.4 : 1,
8253
8479
  cursor: item.disabled ? "not-allowed" : "pointer"
8254
8480
  };
@@ -8665,10 +8891,10 @@ var SGnb = /* @__PURE__ */ forwardRef(function SGnb2({
8665
8891
  }
8666
8892
  ),
8667
8893
  header === "fix" && menuResizeHandle,
8668
- /* @__PURE__ */ jsxs(RPopover9.Root, { open: labelTip !== null, onOpenChange: (open) => !open && setLabelTip(null), children: [
8669
- /* @__PURE__ */ jsx(RPopover9.Anchor, { virtualRef: labelTipAnchorRef }),
8670
- /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
8671
- RPopover9.Content,
8894
+ /* @__PURE__ */ jsxs(RPopover10.Root, { open: labelTip !== null, onOpenChange: (open) => !open && setLabelTip(null), children: [
8895
+ /* @__PURE__ */ jsx(RPopover10.Anchor, { virtualRef: labelTipAnchorRef }),
8896
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
8897
+ RPopover10.Content,
8672
8898
  {
8673
8899
  side: "right",
8674
8900
  sideOffset: 4,
@@ -8739,13 +8965,156 @@ var SPageHeader = /* @__PURE__ */ withDisplayName(
8739
8965
  );
8740
8966
  var MIN_CONTENT_WIDTH = `calc(var(${SLAYOUT_MIN_WIDTH_VAR}, 0px) - 100cqw + 100%)`;
8741
8967
  var BACKGROUND_CLASS = {
8742
- frame: `bg-[var(--sys-color-bg-frame)] ${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_CLASS}`,
8743
- neutral: `bg-[var(--sys-color-bg-neutralLight)] ${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_WHITE_CLASS}`,
8744
- screen: `bg-[var(--sys-color-bg-screen)] ${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_CLASS}`
8968
+ frame: "bg-[var(--sys-color-bg-frame)]",
8969
+ neutral: "bg-[var(--sys-color-bg-neutralLight)]",
8970
+ screen: "bg-[var(--sys-color-bg-screen)]"
8971
+ };
8972
+ var NATIVE_SCROLLBAR_BACKGROUND_CLASS = {
8973
+ frame: `${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_CLASS}`,
8974
+ neutral: `${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_WHITE_CLASS}`,
8975
+ screen: `${SCROLLBAR_TRACK_BORDER_CLASS} ${SCROLLBAR_TRACK_BG_CLASS}`
8976
+ };
8977
+ var SCROLLBAR_SIZE = 12;
8978
+ var SCROLLBAR_MIN_THUMB_SIZE = 32;
8979
+ var getMetrics = (viewportSize, contentSize, scrollOffset) => {
8980
+ if (contentSize <= viewportSize) {
8981
+ return { visible: false, thumbSize: 0, thumbOffset: 0 };
8982
+ }
8983
+ const trackSize = viewportSize;
8984
+ const maxScrollOffset = contentSize - viewportSize;
8985
+ const thumbSize = Math.min(
8986
+ trackSize,
8987
+ Math.max(SCROLLBAR_MIN_THUMB_SIZE, viewportSize / contentSize * trackSize)
8988
+ );
8989
+ const maxThumbOffset = trackSize - thumbSize;
8990
+ const thumbOffset = maxScrollOffset > 0 ? scrollOffset / maxScrollOffset * maxThumbOffset : 0;
8991
+ return { visible: true, thumbSize, thumbOffset };
8745
8992
  };
8993
+ var setForwardedRef = (ref, node) => {
8994
+ if (typeof ref === "function") {
8995
+ ref(node);
8996
+ return;
8997
+ }
8998
+ if (ref) ref.current = node;
8999
+ };
9000
+ function SPageOverlayScrollbars({ scrollRef }) {
9001
+ const [vertical, setVertical] = useState({
9002
+ visible: false,
9003
+ thumbSize: 0,
9004
+ thumbOffset: 0
9005
+ });
9006
+ const [horizontal, setHorizontal] = useState({
9007
+ visible: false,
9008
+ thumbSize: 0,
9009
+ thumbOffset: 0
9010
+ });
9011
+ const update = useCallback(() => {
9012
+ const el = scrollRef.current;
9013
+ if (!el) return;
9014
+ setVertical(getMetrics(el.clientHeight, el.scrollHeight, el.scrollTop));
9015
+ setHorizontal(getMetrics(el.clientWidth, el.scrollWidth, el.scrollLeft));
9016
+ }, [scrollRef]);
9017
+ useEffect(() => {
9018
+ const el = scrollRef.current;
9019
+ if (!el) return;
9020
+ update();
9021
+ el.addEventListener("scroll", update, { passive: true });
9022
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(update);
9023
+ observer?.observe(el);
9024
+ if (el.firstElementChild) observer?.observe(el.firstElementChild);
9025
+ return () => {
9026
+ el.removeEventListener("scroll", update);
9027
+ observer?.disconnect();
9028
+ };
9029
+ }, [scrollRef, update]);
9030
+ const dragVertical = (startEvent) => {
9031
+ const el = scrollRef.current;
9032
+ if (!el || !vertical.visible) return;
9033
+ startEvent.preventDefault();
9034
+ startEvent.currentTarget.setPointerCapture(startEvent.pointerId);
9035
+ const startPointer = startEvent.clientY;
9036
+ const startScrollTop = el.scrollTop;
9037
+ const maxThumbOffset = el.clientHeight - vertical.thumbSize;
9038
+ const maxScrollTop = el.scrollHeight - el.clientHeight;
9039
+ if (maxThumbOffset <= 0 || maxScrollTop <= 0) return;
9040
+ const onPointerMove = (moveEvent) => {
9041
+ const delta = moveEvent.clientY - startPointer;
9042
+ el.scrollTop = startScrollTop + delta / maxThumbOffset * maxScrollTop;
9043
+ };
9044
+ const onPointerUp = () => {
9045
+ window.removeEventListener("pointermove", onPointerMove);
9046
+ window.removeEventListener("pointerup", onPointerUp);
9047
+ };
9048
+ window.addEventListener("pointermove", onPointerMove);
9049
+ window.addEventListener("pointerup", onPointerUp, { once: true });
9050
+ };
9051
+ const dragHorizontal = (startEvent) => {
9052
+ const el = scrollRef.current;
9053
+ if (!el || !horizontal.visible) return;
9054
+ startEvent.preventDefault();
9055
+ startEvent.currentTarget.setPointerCapture(startEvent.pointerId);
9056
+ const startPointer = startEvent.clientX;
9057
+ const startScrollLeft = el.scrollLeft;
9058
+ const maxThumbOffset = el.clientWidth - horizontal.thumbSize;
9059
+ const maxScrollLeft = el.scrollWidth - el.clientWidth;
9060
+ if (maxThumbOffset <= 0 || maxScrollLeft <= 0) return;
9061
+ const onPointerMove = (moveEvent) => {
9062
+ const delta = moveEvent.clientX - startPointer;
9063
+ el.scrollLeft = startScrollLeft + delta / maxThumbOffset * maxScrollLeft;
9064
+ };
9065
+ const onPointerUp = () => {
9066
+ window.removeEventListener("pointermove", onPointerMove);
9067
+ window.removeEventListener("pointerup", onPointerUp);
9068
+ };
9069
+ window.addEventListener("pointermove", onPointerMove);
9070
+ window.addEventListener("pointerup", onPointerUp, { once: true });
9071
+ };
9072
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
9073
+ vertical.visible && /* @__PURE__ */ jsx(
9074
+ "div",
9075
+ {
9076
+ "aria-hidden": "true",
9077
+ className: "pointer-events-none absolute bottom-0 right-0 top-0 z-[1] w-[12px]",
9078
+ style: { bottom: horizontal.visible ? SCROLLBAR_SIZE : 0 },
9079
+ children: /* @__PURE__ */ jsx(
9080
+ "div",
9081
+ {
9082
+ className: "pointer-events-auto absolute right-[2px] w-[8px] cursor-default touch-none rounded-full bg-[var(--color-grey-45)]",
9083
+ style: {
9084
+ height: vertical.thumbSize,
9085
+ transform: `translateY(${vertical.thumbOffset}px)`
9086
+ },
9087
+ onPointerDown: dragVertical
9088
+ }
9089
+ )
9090
+ }
9091
+ ),
9092
+ horizontal.visible && /* @__PURE__ */ jsx(
9093
+ "div",
9094
+ {
9095
+ "aria-hidden": "true",
9096
+ className: "pointer-events-none absolute bottom-0 left-0 right-0 z-[1] h-[12px]",
9097
+ style: { right: vertical.visible ? SCROLLBAR_SIZE : 0 },
9098
+ children: /* @__PURE__ */ jsx(
9099
+ "div",
9100
+ {
9101
+ className: "pointer-events-auto absolute bottom-[2px] h-[8px] cursor-default touch-none rounded-full bg-[var(--color-grey-45)]",
9102
+ style: {
9103
+ width: horizontal.thumbSize,
9104
+ transform: `translateX(${horizontal.thumbOffset}px)`
9105
+ },
9106
+ onPointerDown: dragHorizontal
9107
+ }
9108
+ )
9109
+ }
9110
+ )
9111
+ ] });
9112
+ }
8746
9113
  var SPage = /* @__PURE__ */ forwardRef(function SPage2({
8747
9114
  background = "frame",
8748
9115
  scrollEndSpacing = true,
9116
+ overlayScrollbar = false,
9117
+ contentHeight = "auto",
8749
9118
  header,
8750
9119
  className,
8751
9120
  style,
@@ -8755,29 +9124,71 @@ var SPage = /* @__PURE__ */ forwardRef(function SPage2({
8755
9124
  }, ref) {
8756
9125
  const layout = useContext(SLayoutContext);
8757
9126
  const gridStyle = layout?.header === "full" ? { gridArea: "page" } : void 0;
9127
+ const scrollRef = useRef(null);
9128
+ const setMainRef = useCallback(
9129
+ (node) => {
9130
+ scrollRef.current = node;
9131
+ setForwardedRef(ref, node);
9132
+ },
9133
+ [ref]
9134
+ );
8758
9135
  const [scrolled, setScrolled] = useState(false);
8759
9136
  const handleScroll = (e) => {
8760
9137
  onScroll?.(e);
8761
9138
  if (header != null && !header.fix) setScrolled(e.currentTarget.scrollTop > 0);
8762
9139
  };
9140
+ const fill = contentHeight === "fill";
8763
9141
  const main = /* @__PURE__ */ jsx(
8764
9142
  "main",
8765
9143
  {
8766
- ref,
9144
+ ref: setMainRef,
8767
9145
  className: cn(
8768
9146
  "box-border min-h-0 min-w-0 flex-1 overflow-auto p-[var(--cmp-pageBody-padding-default)]",
8769
- scrollEndSpacing && "pb-[var(--cmp-pageBody-padding-scrollEnd)]",
8770
- SCROLLBAR_CLASS,
9147
+ overlayScrollbar && "h-full",
9148
+ !fill && scrollEndSpacing && "pb-[var(--cmp-pageBody-padding-scrollEnd)]",
9149
+ overlayScrollbar ? SCROLLBAR_HIDDEN_CLASS : `${SCROLLBAR_CLASS} ${NATIVE_SCROLLBAR_BACKGROUND_CLASS[background]}`,
8771
9150
  BACKGROUND_CLASS[background],
8772
9151
  className
8773
9152
  ),
8774
- style: header == null ? { ...gridStyle, ...style } : style,
9153
+ style: header == null && !overlayScrollbar ? { ...gridStyle, ...style } : style,
8775
9154
  onScroll: handleScroll,
8776
9155
  ...rest,
8777
- children: /* @__PURE__ */ jsx("div", { style: { minWidth: MIN_CONTENT_WIDTH }, children })
9156
+ children: /* @__PURE__ */ jsx("div", { className: cn(fill && "h-full"), style: { minWidth: MIN_CONTENT_WIDTH }, children })
8778
9157
  }
8779
9158
  );
8780
- if (header == null) return main;
9159
+ if (!overlayScrollbar) {
9160
+ if (header == null) return main;
9161
+ return (
9162
+ // fix 가 아닌 헤더는 자기 배경이 투명이라(SPageHeader.tsx) 이 배경이 그대로 비친다.
9163
+ /* @__PURE__ */ jsxs(
9164
+ "div",
9165
+ {
9166
+ className: cn("flex min-h-0 min-w-0 flex-1 flex-col", BACKGROUND_CLASS[background]),
9167
+ style: gridStyle,
9168
+ children: [
9169
+ /* @__PURE__ */ jsx(SPageHeader, { ...header, scrolled }),
9170
+ main
9171
+ ]
9172
+ }
9173
+ )
9174
+ );
9175
+ }
9176
+ if (header == null) {
9177
+ return /* @__PURE__ */ jsxs(
9178
+ "div",
9179
+ {
9180
+ className: cn(
9181
+ "relative min-h-0 min-w-0 flex-1 overflow-hidden",
9182
+ BACKGROUND_CLASS[background]
9183
+ ),
9184
+ style: gridStyle,
9185
+ children: [
9186
+ main,
9187
+ /* @__PURE__ */ jsx(SPageOverlayScrollbars, { scrollRef })
9188
+ ]
9189
+ }
9190
+ );
9191
+ }
8781
9192
  return (
8782
9193
  // fix 가 아닌 헤더는 자기 배경이 투명이라(SPageHeader.tsx) 이 배경이 그대로 비친다 —
8783
9194
  // 그래서 SPage 의 background 를 main 뿐 아니라 여기에도 칠한다(스크롤바 관련 클래스는
@@ -8789,7 +9200,10 @@ var SPage = /* @__PURE__ */ forwardRef(function SPage2({
8789
9200
  style: gridStyle,
8790
9201
  children: [
8791
9202
  /* @__PURE__ */ jsx(SPageHeader, { ...header, scrolled }),
8792
- main
9203
+ /* @__PURE__ */ jsxs("div", { className: "relative min-h-0 min-w-0 flex-1 overflow-hidden", children: [
9204
+ main,
9205
+ /* @__PURE__ */ jsx(SPageOverlayScrollbars, { scrollRef })
9206
+ ] })
8793
9207
  ]
8794
9208
  }
8795
9209
  )
@@ -8798,12 +9212,10 @@ var SPage = /* @__PURE__ */ forwardRef(function SPage2({
8798
9212
 
8799
9213
  // src/components/SPage/page.config.ts
8800
9214
  var PAGE_BACKGROUNDS = ["frame", "neutral", "screen"];
9215
+ var PAGE_CONTENT_HEIGHTS = ["auto", "fill"];
8801
9216
 
8802
9217
  // src/components/STabs/tabs.config.ts
8803
- var TAB_MAIN_SIZE = {
8804
- md: { height: 36, paddingX: 24 },
8805
- lg: { height: 44, paddingX: 32 }
8806
- };
9218
+ var TAB_MAIN_SIZE = { height: 36, paddingX: 24 };
8807
9219
  var TAB_TYPOGRAPHY = { fontSize: 12, lineHeight: 20 };
8808
9220
  var TAB_MAIN_COLORS = {
8809
9221
  selected: {
@@ -8825,13 +9237,11 @@ function STabs({
8825
9237
  value,
8826
9238
  tabs,
8827
9239
  onValueChange,
8828
- size = "md",
8829
9240
  isSub = false,
8830
9241
  vertical = false,
8831
9242
  className,
8832
9243
  style
8833
9244
  }) {
8834
- const s = TAB_MAIN_SIZE[size];
8835
9245
  const blue = TAB_MAIN_COLORS.selected.border;
8836
9246
  const isVertical = isSub && vertical;
8837
9247
  const tabRefs = useRef([]);
@@ -8872,8 +9282,8 @@ function STabs({
8872
9282
  const border = selected ? TAB_MAIN_COLORS.selected.border : TAB_MAIN_COLORS.default.border;
8873
9283
  const selectedBg = TAB_MAIN_COLORS.selected.bg;
8874
9284
  tabStyle = {
8875
- height: s.height,
8876
- padding: `0 ${s.paddingX}px`,
9285
+ height: TAB_MAIN_SIZE.height,
9286
+ padding: `0 ${TAB_MAIN_SIZE.paddingX}px`,
8877
9287
  fontSize: TAB_TYPOGRAPHY.fontSize,
8878
9288
  lineHeight: `${TAB_TYPOGRAPHY.lineHeight}px`,
8879
9289
  background: selected ? TAB_MAIN_COLORS.selected.bg : TAB_MAIN_COLORS.default.bg,
@@ -9615,6 +10025,7 @@ var SField = /* @__PURE__ */ forwardRef(function SField2({
9615
10025
  readOnly = false,
9616
10026
  width,
9617
10027
  minWidth,
10028
+ maxWidth,
9618
10029
  multiline = false,
9619
10030
  borderless = false,
9620
10031
  children,
@@ -9650,7 +10061,10 @@ var SField = /* @__PURE__ */ forwardRef(function SField2({
9650
10061
  const resolvedSuccess = status === "pass";
9651
10062
  const showError = resolvedError && errorMessage !== "";
9652
10063
  const interactive = !disabled && !readOnly;
9653
- const hug = cssLen(width) !== void 0;
10064
+ const isPercentWidth = typeof width === "string" && width.trim().endsWith("%");
10065
+ const hug = cssLen(width) !== void 0 && !isPercentWidth;
10066
+ const addonWidth = effectiveAddonLabel !== "" ? cssLen(labelWidth) : void 0;
10067
+ const resolvedMaxWidth = cssLen(maxWidth) === void 0 || effectiveAddonLabel === "" ? cssLen(maxWidth) : addonWidth ? `calc(${cssLen(maxWidth)} + ${addonWidth})` : void 0;
9654
10068
  const borderColor = borderless ? "transparent" : disabled ? "var(--color-grey-45)" : interactive && focused ? "var(--sys-color-field-border-focus)" : resolvedSuccess ? "var(--sys-color-field-border-success)" : resolvedError ? "var(--sys-color-field-border-danger)" : "var(--sys-color-field-border-default)";
9655
10069
  const bgColor = borderless ? "transparent" : disabled ? "var(--color-grey-30)" : readOnly ? "var(--sys-color-field-bg-readOnly)" : "var(--sys-color-field-bg-default)";
9656
10070
  const textColor = disabled ? "var(--color-grey-65)" : readOnly ? "var(--sys-color-field-text-readOnly)" : "var(--sys-color-field-text-default)";
@@ -9706,8 +10120,12 @@ var SField = /* @__PURE__ */ forwardRef(function SField2({
9706
10120
  /* @__PURE__ */ jsxs(
9707
10121
  "div",
9708
10122
  {
9709
- className: cn("flex min-w-0 flex-col", !hug && "flex-1"),
9710
- style: { width: cssLen(width), minWidth: cssLen(minWidth) },
10123
+ className: cn("flex min-w-0 flex-col", !hug && !isPercentWidth && "flex-1"),
10124
+ style: {
10125
+ width: cssLen(width),
10126
+ minWidth: cssLen(minWidth),
10127
+ maxWidth: resolvedMaxWidth
10128
+ },
9711
10129
  children: [
9712
10130
  /* @__PURE__ */ jsxs(
9713
10131
  "div",
@@ -10462,6 +10880,7 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
10462
10880
  height,
10463
10881
  stickyColumn,
10464
10882
  radius = "default",
10883
+ bordered = true,
10465
10884
  noDataLabel = "\uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
10466
10885
  noDataSlot,
10467
10886
  isLoading = false,
@@ -10720,7 +11139,7 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
10720
11139
  };
10721
11140
  const radiusClass = radius === "full" ? "rounded-none" : radius === "useTop" ? "rounded-b-[var(--cmp-table-radius)] rounded-t-none" : "rounded-[var(--cmp-table-radius)]";
10722
11141
  const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-body-paddingY-default)";
10723
- const cellPadX = "var(--cmp-table-body-paddingX-wide)";
11142
+ const cellPadXOf = (col) => col.contentType === "control" ? "var(--cmp-table-body-paddingX-default)" : "var(--cmp-table-body-paddingX-wide)";
10724
11143
  const rowH = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-body-default-height)";
10725
11144
  const totalColSpan = visibleCols.length + (selectable ? 1 : 0);
10726
11145
  const stickyInfo = (i) => {
@@ -10774,7 +11193,8 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
10774
11193
  // 만으로는 스태킹 컨텍스트가 생기지 않아, 모달 안에 들어가면 30·31이 모달 Content
10775
11194
  // 레벨까지 올라가 같은 컨텍스트의 floating portal 밴드(z-2)를 덮는다.
10776
11195
  // 자손은 이미 overflow-hidden으로 클리핑되므로 테이블 내부 순서는 달라지지 않는다.
10777
- "bg-white relative isolate flex flex-col overflow-hidden border border-solid border-[color:var(--cmp-table-border-color)]",
11196
+ "bg-white relative isolate flex flex-col overflow-hidden",
11197
+ bordered && "border border-solid border-[color:var(--cmp-table-border-color)]",
10778
11198
  // height가 없으면 루트가 받은 높이를 끝까지 채운다. 호스트가 className으로
10779
11199
  // `flex-1 min-h-0` 등을 줘 루트 높이가 확정된 경우, 행이 적어도 테두리 박스가
10780
11200
  // 남은 영역을 그대로 차지한다. basis는 auto로 둬 높이가 확정되지 않은
@@ -10944,9 +11364,10 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
10944
11364
  stickyShadowClass(i),
10945
11365
  typeof col.tdClass === "function" ? col.tdClass(row) : col.tdClass
10946
11366
  );
11367
+ const tdPadX = cellPadXOf(col);
10947
11368
  const tdStyle = {
10948
- paddingLeft: cellPadX,
10949
- paddingRight: cellPadX,
11369
+ paddingLeft: tdPadX,
11370
+ paddingRight: tdPadX,
10950
11371
  paddingTop: cellPadY,
10951
11372
  paddingBottom: cellPadY,
10952
11373
  textAlign: col.align ?? "left",
@@ -10999,19 +11420,29 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
10999
11420
  // SPagination은 전체 폭 기준 중앙, rows-per-page SSelect는 우측에 absolute 배치해 중앙 정렬을 유지한다.
11000
11421
  // 루트 높이가 확정된 경우(테이블이 남은 영역을 채우는 배치) 페이지네이션 바가
11001
11422
  // 눌리지 않도록 shrink-0으로 48px를 보장한다.
11002
- /* @__PURE__ */ jsxs("div", { className: "relative -mt-px flex h-[48px] shrink-0 items-center justify-center rounded-[var(--cmp-table-radius)] border border-solid border-[color:var(--cmp-table-border-color)] bg-[var(--color-grey-05)]", children: [
11003
- /* @__PURE__ */ jsx(SPagination, { currentPage: displayPage, lastPage, onPageChange: changePage }),
11004
- useRowsPerPageSelect && /* @__PURE__ */ jsx("div", { className: "absolute right-[10px] top-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsx(
11005
- SSelect2,
11006
- {
11007
- options: rowsPerPageOption,
11008
- value: useInternalPagination ? innerRowsPerPage : pagination?.rowsPerPage,
11009
- emitValue: true,
11010
- onValueChange: (v) => changeRowsPerPage(Number(v)),
11011
- width: 128
11012
- }
11013
- ) })
11014
- ] })
11423
+ /* @__PURE__ */ jsxs(
11424
+ "div",
11425
+ {
11426
+ className: cn(
11427
+ "relative flex h-[48px] shrink-0 items-center justify-center rounded-[var(--cmp-table-radius)] bg-[var(--color-grey-05)]",
11428
+ // 테두리를 끄면 겹칠 대상이 없어진다 — -mt-px 를 남기면 1px 어긋남만 생긴다.
11429
+ bordered && "-mt-px border border-solid border-[color:var(--cmp-table-border-color)]"
11430
+ ),
11431
+ children: [
11432
+ /* @__PURE__ */ jsx(SPagination, { currentPage: displayPage, lastPage, onPageChange: changePage }),
11433
+ useRowsPerPageSelect && /* @__PURE__ */ jsx("div", { className: "absolute right-[10px] top-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsx(
11434
+ SSelect2,
11435
+ {
11436
+ options: rowsPerPageOption,
11437
+ value: useInternalPagination ? innerRowsPerPage : pagination?.rowsPerPage,
11438
+ emitValue: true,
11439
+ onValueChange: (v) => changeRowsPerPage(Number(v)),
11440
+ width: 128
11441
+ }
11442
+ ) })
11443
+ ]
11444
+ }
11445
+ )
11015
11446
  ]
11016
11447
  }
11017
11448
  );
@@ -11074,6 +11505,67 @@ var STableBar = /* @__PURE__ */ forwardRef(function STableBar2({ total, selected
11074
11505
 
11075
11506
  // src/lib/autofill.ts
11076
11507
  var AUTOFILL_RESET_CLASS = "autofill:[-webkit-text-fill-color:currentColor] autofill:[box-shadow:0_0_0_1000px_var(--sys-color-field-bg-default)_inset] autofill:[transition:background-color_5000s_ease-in-out_0s] [&:-webkit-autofill]:[-webkit-text-fill-color:currentColor] [&:-webkit-autofill]:[box-shadow:0_0_0_1000px_var(--sys-color-field-bg-default)_inset] [&:-webkit-autofill]:[transition:background-color_5000s_ease-in-out_0s]";
11508
+ var HIDE_DELAY_MS2 = 100;
11509
+ function mergeRefs(...refs) {
11510
+ return (node) => {
11511
+ for (const ref of refs) {
11512
+ if (!ref) continue;
11513
+ if (typeof ref === "function") ref(node);
11514
+ else ref.current = node;
11515
+ }
11516
+ };
11517
+ }
11518
+ function useTruncatedValueTooltip({
11519
+ enabled,
11520
+ text
11521
+ }) {
11522
+ const portalContainer = usePortalContainer();
11523
+ const elRef = useRef(null);
11524
+ const [open, setOpen] = useState(false);
11525
+ const hideTimer = useRef(void 0);
11526
+ const cancelHide = useCallback(() => {
11527
+ if (hideTimer.current) {
11528
+ clearTimeout(hideTimer.current);
11529
+ hideTimer.current = void 0;
11530
+ }
11531
+ }, []);
11532
+ const startHide = useCallback(() => {
11533
+ cancelHide();
11534
+ hideTimer.current = setTimeout(() => setOpen(false), HIDE_DELAY_MS2);
11535
+ }, [cancelHide]);
11536
+ const onMouseEnter = useCallback(() => {
11537
+ cancelHide();
11538
+ const el = elRef.current;
11539
+ if (!el) return;
11540
+ if (el.scrollWidth - el.clientWidth > 1) setOpen(true);
11541
+ }, [cancelHide]);
11542
+ useEffect(() => {
11543
+ if (!enabled || text === "") setOpen(false);
11544
+ }, [enabled, text]);
11545
+ useEffect(() => cancelHide, [cancelHide]);
11546
+ const active = enabled && text !== "";
11547
+ return {
11548
+ valueRef: elRef,
11549
+ onMouseEnter: active ? onMouseEnter : cancelHide,
11550
+ onMouseLeave: active ? startHide : cancelHide,
11551
+ tooltip: active ? /* @__PURE__ */ jsxs(RPopover10.Root, { open, onOpenChange: setOpen, children: [
11552
+ /* @__PURE__ */ jsx(RPopover10.Anchor, { virtualRef: elRef }),
11553
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
11554
+ RPopover10.Content,
11555
+ {
11556
+ side: "top",
11557
+ sideOffset: 4,
11558
+ "aria-hidden": true,
11559
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
11560
+ onOpenAutoFocus: (e) => e.preventDefault(),
11561
+ onMouseEnter: cancelHide,
11562
+ onMouseLeave: startHide,
11563
+ children: /* @__PURE__ */ jsx(STag, { shape: "square", size: "xs", color: "grey", label: text })
11564
+ }
11565
+ ) })
11566
+ ] }) : null
11567
+ };
11568
+ }
11077
11569
  var FONT = {
11078
11570
  sm: { fontSize: 12, lineHeight: 20, paddingX: "var(--cmp-textinput-sm-paddingX)" },
11079
11571
  md: { fontSize: 14, lineHeight: 24, paddingX: "var(--cmp-textinput-md-paddingX)" }
@@ -11131,6 +11623,10 @@ var SInput = /* @__PURE__ */ forwardRef(function SInput2({
11131
11623
  const resolvedError = error || ruleError !== "";
11132
11624
  const resolvedErrorMessage = ruleError !== "" ? ruleError : errorMessage;
11133
11625
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
11626
+ const valueTooltip = useTruncatedValueTooltip({
11627
+ enabled: readOnly && !disabled,
11628
+ text: String(value ?? "")
11629
+ });
11134
11630
  return /* @__PURE__ */ jsx(
11135
11631
  SField,
11136
11632
  {
@@ -11171,7 +11667,7 @@ var SInput = /* @__PURE__ */ forwardRef(function SInput2({
11171
11667
  /* @__PURE__ */ jsx(
11172
11668
  "input",
11173
11669
  {
11174
- ref,
11670
+ ref: mergeRefs(ref, valueTooltip.valueRef),
11175
11671
  type: inputType,
11176
11672
  value: value ?? "",
11177
11673
  disabled,
@@ -11196,9 +11692,18 @@ var SInput = /* @__PURE__ */ forwardRef(function SInput2({
11196
11692
  color: disabled ? "var(--cmp-textinput-text-disabled)" : "var(--sys-color-field-text-default)",
11197
11693
  ...inputStyle
11198
11694
  },
11199
- ...rest
11695
+ ...rest,
11696
+ onMouseEnter: (e) => {
11697
+ rest.onMouseEnter?.(e);
11698
+ valueTooltip.onMouseEnter();
11699
+ },
11700
+ onMouseLeave: (e) => {
11701
+ rest.onMouseLeave?.(e);
11702
+ valueTooltip.onMouseLeave();
11703
+ }
11200
11704
  }
11201
11705
  ),
11706
+ valueTooltip.tooltip,
11202
11707
  suffix != null && /* @__PURE__ */ jsx("span", { className: "flex flex-shrink-0 items-center", children: suffix }),
11203
11708
  clearable && hasValue && !disabled && !readOnly && /* @__PURE__ */ jsx(
11204
11709
  SGhostButton,
@@ -11432,8 +11937,10 @@ var SNumberInput = /* @__PURE__ */ forwardRef(function SNumberInput2({
11432
11937
  const [display, setDisplay] = useState(() => formatWithCommas(numValue));
11433
11938
  const [ruleError, setRuleError] = useState("");
11434
11939
  const innerRef = useRef(null);
11940
+ const valueTooltip = useTruncatedValueTooltip({ enabled: readOnly && !disabled, text: display });
11435
11941
  const setRefs2 = (el) => {
11436
11942
  innerRef.current = el;
11943
+ valueTooltip.valueRef.current = el;
11437
11944
  if (typeof ref === "function") ref(el);
11438
11945
  else if (ref) ref.current = el;
11439
11946
  };
@@ -11647,9 +12154,12 @@ var SNumberInput = /* @__PURE__ */ forwardRef(function SNumberInput2({
11647
12154
  textAlign: useButton ? "center" : "right",
11648
12155
  color: disabled ? "var(--cmp-numberInput-text-disabled)" : "var(--cmp-numberInput-text-default)",
11649
12156
  ...inputStyle
11650
- }
12157
+ },
12158
+ onMouseEnter: valueTooltip.onMouseEnter,
12159
+ onMouseLeave: valueTooltip.onMouseLeave
11651
12160
  }
11652
12161
  ),
12162
+ valueTooltip.tooltip,
11653
12163
  inputSuffix !== "" && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", style: { color: affixColor }, children: inputSuffix }),
11654
12164
  useButton && stepper("inc")
11655
12165
  ]
@@ -12028,12 +12538,275 @@ function SCalendar({
12028
12538
  }
12029
12539
  );
12030
12540
  }
12541
+ var DATEPICKER_MODE_LIST_HEIGHT = 240;
12542
+ var DATEPICKER_MODE_ITEM_HEIGHT = 28;
12543
+ var YEAR_LIST_EDGE_BUFFER = 24;
12544
+ var YEAR_LIST_EXPAND_SIZE = 80;
12545
+ var MONTHS = Array.from({ length: 12 }, (_, i) => i + 1);
12546
+ var getTodayParts = () => {
12547
+ const today = /* @__PURE__ */ new Date();
12548
+ return {
12549
+ year: today.getFullYear(),
12550
+ month: today.getMonth() + 1
12551
+ };
12552
+ };
12553
+ var parseYear = (value) => {
12554
+ const year = Number(value?.slice(0, 4));
12555
+ return Number.isFinite(year) && year > 0 ? year : void 0;
12556
+ };
12557
+ var parseMonth = (value) => {
12558
+ const month = Number(value?.slice(5, 7));
12559
+ return Number.isFinite(month) && month >= 1 && month <= 12 ? month : void 0;
12560
+ };
12561
+ var formatMonthValue = (year, month) => `${year}-${String(month).padStart(2, "0")}`;
12562
+ var isYearDisabled = (year, selectable) => {
12563
+ if (!selectable) return false;
12564
+ const [start, end] = selectable;
12565
+ if (start !== "" && year < Number(start.slice(0, 4))) return true;
12566
+ if (end !== "" && year > Number(end.slice(0, 4))) return true;
12567
+ return false;
12568
+ };
12569
+ var isMonthDisabled = (year, month, selectable) => {
12570
+ if (!selectable) return false;
12571
+ const value = formatMonthValue(year, month);
12572
+ const [start, end] = selectable;
12573
+ if (start !== "" && value < start.slice(0, 7)) return true;
12574
+ if (end !== "" && value > end.slice(0, 7)) return true;
12575
+ return false;
12576
+ };
12577
+ function YearWheel({ value, anchorYear, selectable, onCommit }) {
12578
+ const listRef = useRef(null);
12579
+ const syncingScrollRef = useRef(false);
12580
+ const anchorYearRef = useRef(anchorYear);
12581
+ const [range, setRange] = useState(() => ({
12582
+ start: anchorYear - YEAR_LIST_EXPAND_SIZE,
12583
+ end: anchorYear + YEAR_LIST_EXPAND_SIZE
12584
+ }));
12585
+ const rangeStartRef = useRef(range.start);
12586
+ rangeStartRef.current = range.start;
12587
+ const years = Array.from({ length: range.end - range.start + 1 }, (_, i) => range.start + i);
12588
+ useLayoutEffect(() => {
12589
+ const list = listRef.current;
12590
+ if (!list) return;
12591
+ const index = anchorYearRef.current - rangeStartRef.current;
12592
+ syncingScrollRef.current = true;
12593
+ list.scrollTop = index * DATEPICKER_MODE_ITEM_HEIGHT;
12594
+ requestAnimationFrame(() => {
12595
+ syncingScrollRef.current = false;
12596
+ });
12597
+ }, []);
12598
+ const handleScroll = () => {
12599
+ const list = listRef.current;
12600
+ if (!list) return;
12601
+ if (syncingScrollRef.current) return;
12602
+ if (list.scrollTop < DATEPICKER_MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
12603
+ const previousHeight = list.scrollHeight;
12604
+ setRange((current) => ({
12605
+ start: current.start - YEAR_LIST_EXPAND_SIZE,
12606
+ end: current.end
12607
+ }));
12608
+ requestAnimationFrame(() => {
12609
+ if (!listRef.current) return;
12610
+ syncingScrollRef.current = true;
12611
+ listRef.current.scrollTop += listRef.current.scrollHeight - previousHeight;
12612
+ requestAnimationFrame(() => {
12613
+ syncingScrollRef.current = false;
12614
+ });
12615
+ });
12616
+ return;
12617
+ }
12618
+ if (list.scrollHeight - list.scrollTop - list.clientHeight < DATEPICKER_MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
12619
+ setRange((current) => ({
12620
+ start: current.start,
12621
+ end: current.end + YEAR_LIST_EXPAND_SIZE
12622
+ }));
12623
+ }
12624
+ };
12625
+ return /* @__PURE__ */ jsx("div", { className: "h-full w-full overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsx(
12626
+ "div",
12627
+ {
12628
+ ref: listRef,
12629
+ role: "listbox",
12630
+ "aria-label": "year",
12631
+ className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
12632
+ style: {
12633
+ paddingTop: `calc((${DATEPICKER_MODE_LIST_HEIGHT}px - ${DATEPICKER_MODE_ITEM_HEIGHT}px) / 2)`,
12634
+ paddingBottom: `calc((${DATEPICKER_MODE_LIST_HEIGHT}px - ${DATEPICKER_MODE_ITEM_HEIGHT}px) / 2)`
12635
+ },
12636
+ onScroll: handleScroll,
12637
+ children: years.map((year) => {
12638
+ const selected = year === value;
12639
+ const disabled = isYearDisabled(year, selectable);
12640
+ return /* @__PURE__ */ jsx(
12641
+ "button",
12642
+ {
12643
+ type: "button",
12644
+ role: "option",
12645
+ "aria-selected": selected,
12646
+ disabled,
12647
+ className: cn(
12648
+ "flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
12649
+ disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
12650
+ !disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
12651
+ !disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
12652
+ ),
12653
+ onClick: () => !disabled && onCommit(year),
12654
+ children: year
12655
+ },
12656
+ year
12657
+ );
12658
+ })
12659
+ }
12660
+ ) });
12661
+ }
12662
+ function SDatePickerYearListbox({
12663
+ value,
12664
+ anchorYear,
12665
+ selectable,
12666
+ onValueChange,
12667
+ className,
12668
+ style
12669
+ }) {
12670
+ const today = getTodayParts();
12671
+ const resolvedAnchorYear = anchorYear ?? today.year;
12672
+ const [year, setYear] = useState(() => parseYear(value) ?? resolvedAnchorYear);
12673
+ useEffect(() => {
12674
+ setYear(parseYear(value) ?? resolvedAnchorYear);
12675
+ }, [value, resolvedAnchorYear]);
12676
+ const commitYear = (nextYear) => {
12677
+ setYear(nextYear);
12678
+ onValueChange?.(String(nextYear));
12679
+ };
12680
+ return /* @__PURE__ */ jsx(
12681
+ "div",
12682
+ {
12683
+ className: cn(
12684
+ "inline-flex h-[240px] w-[var(--cmp-datepicker-mode-year-width)] overflow-hidden rounded-[var(--cmp-datepicker-mode-radius)] bg-[var(--cmp-datepicker-mode-bg)] shadow-normal-sm",
12685
+ className
12686
+ ),
12687
+ style,
12688
+ children: /* @__PURE__ */ jsx(
12689
+ YearWheel,
12690
+ {
12691
+ value: year,
12692
+ anchorYear: resolvedAnchorYear,
12693
+ selectable,
12694
+ onCommit: commitYear
12695
+ }
12696
+ )
12697
+ }
12698
+ );
12699
+ }
12700
+ function MonthWheel({ year, value, selectable, onCommit }) {
12701
+ const listRef = useRef(null);
12702
+ const initialValueRef = useRef(value);
12703
+ useLayoutEffect(() => {
12704
+ const list = listRef.current;
12705
+ if (!list) return;
12706
+ list.scrollTop = Math.max(
12707
+ 0,
12708
+ (initialValueRef.current - 1) * DATEPICKER_MODE_ITEM_HEIGHT - (list.clientHeight - DATEPICKER_MODE_ITEM_HEIGHT) / 2
12709
+ );
12710
+ }, []);
12711
+ return /* @__PURE__ */ jsx("div", { className: "h-full w-full overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsx(
12712
+ "div",
12713
+ {
12714
+ ref: listRef,
12715
+ role: "listbox",
12716
+ "aria-label": "month",
12717
+ className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
12718
+ children: MONTHS.map((month) => {
12719
+ const selected = month === value;
12720
+ const disabled = isMonthDisabled(year, month, selectable);
12721
+ return /* @__PURE__ */ jsxs(
12722
+ "button",
12723
+ {
12724
+ type: "button",
12725
+ role: "option",
12726
+ "aria-selected": selected,
12727
+ disabled,
12728
+ className: cn(
12729
+ "flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
12730
+ disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
12731
+ !disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
12732
+ !disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
12733
+ ),
12734
+ onClick: () => !disabled && onCommit(month),
12735
+ children: [
12736
+ month,
12737
+ "\uC6D4"
12738
+ ]
12739
+ },
12740
+ month
12741
+ );
12742
+ })
12743
+ }
12744
+ ) });
12745
+ }
12746
+ function SDatePickerMonthListbox({
12747
+ value,
12748
+ anchorYear,
12749
+ selectable,
12750
+ onValueChange,
12751
+ onMonthSelect,
12752
+ className,
12753
+ style
12754
+ }) {
12755
+ const today = getTodayParts();
12756
+ const resolvedAnchorYear = anchorYear ?? today.year;
12757
+ const [year, setYear] = useState(() => parseYear(value) ?? resolvedAnchorYear);
12758
+ const [month, setMonth] = useState(() => parseMonth(value) ?? today.month);
12759
+ useEffect(() => {
12760
+ setYear(parseYear(value) ?? resolvedAnchorYear);
12761
+ setMonth(parseMonth(value) ?? today.month);
12762
+ }, [value, resolvedAnchorYear, today.month]);
12763
+ const commitYear = (nextYear) => {
12764
+ setYear(nextYear);
12765
+ onValueChange?.(formatMonthValue(nextYear, month));
12766
+ };
12767
+ const commitMonth = (nextMonth) => {
12768
+ setMonth(nextMonth);
12769
+ const nextValue = formatMonthValue(year, nextMonth);
12770
+ onValueChange?.(nextValue);
12771
+ onMonthSelect?.(nextValue);
12772
+ };
12773
+ return /* @__PURE__ */ jsxs(
12774
+ "div",
12775
+ {
12776
+ className: cn(
12777
+ "inline-flex h-[240px] gap-[var(--cmp-datepicker-mode-yearMonth-gap)] overflow-hidden rounded-[var(--cmp-datepicker-mode-radius)] bg-[var(--cmp-datepicker-mode-bg)] shadow-normal-sm",
12778
+ className
12779
+ ),
12780
+ style,
12781
+ children: [
12782
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 w-[var(--cmp-datepicker-mode-yearMonth-column-width)]", children: /* @__PURE__ */ jsx(
12783
+ YearWheel,
12784
+ {
12785
+ value: year,
12786
+ anchorYear: resolvedAnchorYear,
12787
+ selectable,
12788
+ onCommit: commitYear
12789
+ }
12790
+ ) }),
12791
+ /* @__PURE__ */ jsx(
12792
+ "div",
12793
+ {
12794
+ "aria-hidden": true,
12795
+ className: "w-[var(--border-width-1)] self-stretch bg-[var(--sys-color-divider-default)]"
12796
+ }
12797
+ ),
12798
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 w-[var(--cmp-datepicker-mode-yearMonth-column-width)]", children: /* @__PURE__ */ jsx(MonthWheel, { year, value: month, selectable, onCommit: commitMonth }) })
12799
+ ]
12800
+ }
12801
+ );
12802
+ }
12031
12803
  function SDatePicker({
12032
12804
  value,
12033
12805
  onValueChange,
12034
12806
  onViewChange,
12807
+ mode = "date",
12035
12808
  size = "sm",
12036
- placeholder = "YYYY-MM-DD",
12809
+ placeholder,
12037
12810
  selectable,
12038
12811
  disabled = false,
12039
12812
  width,
@@ -12059,6 +12832,7 @@ function SDatePicker({
12059
12832
  const [focused, setFocused] = useState(false);
12060
12833
  const [ruleError, setRuleError] = useState("");
12061
12834
  const hasValue = value != null && value !== "";
12835
+ const resolvedPlaceholder = placeholder ?? (mode === "year" ? "YYYY" : mode === "month" ? "YYYY-MM" : "YYYY-MM-DD");
12062
12836
  useFormField({
12063
12837
  name,
12064
12838
  validate: () => {
@@ -12070,12 +12844,39 @@ function SDatePicker({
12070
12844
  });
12071
12845
  const resolvedError = error || ruleError !== "";
12072
12846
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
12073
- 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 };
12847
+ const dims = size === "md" ? {
12848
+ padX: 16,
12849
+ gap: 12,
12850
+ icon: 20,
12851
+ minW: 104,
12852
+ fieldMinW: 168,
12853
+ // 상한 등급 lg — 값 길이가 YYYY-MM-DD 로 정해져 있어, width="100%" 를 받아도
12854
+ // 행 전체로 늘어나지 않게 막는다 (SKeyValueTable 이 모든 컨트롤에 100% 를 넘긴다)
12855
+ fieldMaxW: 240,
12856
+ fontSize: 14,
12857
+ lineHeight: 24,
12858
+ radius: 6
12859
+ } : {
12860
+ padX: 12,
12861
+ gap: 8,
12862
+ icon: 16,
12863
+ minW: 80,
12864
+ fieldMinW: 128,
12865
+ // 상한 등급 md
12866
+ fieldMaxW: 160,
12867
+ fontSize: 12,
12868
+ lineHeight: 20,
12869
+ radius: 4
12870
+ };
12074
12871
  const select = (date) => {
12075
12872
  onValueChange?.(date);
12076
12873
  if (rules && rules.length > 0) setRuleError(runRules(date, rules));
12077
12874
  setOpen(false);
12078
12875
  };
12876
+ const selectListboxValue = (nextValue) => {
12877
+ onValueChange?.(nextValue);
12878
+ if (rules && rules.length > 0) setRuleError(runRules(nextValue, rules));
12879
+ };
12079
12880
  return /* @__PURE__ */ jsx(
12080
12881
  SField,
12081
12882
  {
@@ -12094,11 +12895,13 @@ function SDatePicker({
12094
12895
  focused: focused || open,
12095
12896
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
12096
12897
  width,
12898
+ minWidth: dims.fieldMinW,
12899
+ maxWidth: dims.fieldMaxW,
12097
12900
  disabled,
12098
12901
  className,
12099
12902
  style,
12100
- children: /* @__PURE__ */ jsxs(RPopover9.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
12101
- /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
12903
+ children: /* @__PURE__ */ jsxs(RPopover10.Root, { open, onOpenChange: (o) => !disabled && setOpen(o), children: [
12904
+ /* @__PURE__ */ jsx(RPopover10.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
12102
12905
  "button",
12103
12906
  {
12104
12907
  type: "button",
@@ -12128,23 +12931,23 @@ function SDatePicker({
12128
12931
  lineHeight: `${dims.lineHeight}px`,
12129
12932
  color: disabled ? "var(--cmp-datepicker-text-disabled)" : hasValue ? "var(--cmp-datepicker-text-default)" : "var(--cmp-field-hint-color)"
12130
12933
  },
12131
- children: hasValue ? value : placeholder
12934
+ children: hasValue ? value : resolvedPlaceholder
12132
12935
  }
12133
12936
  )
12134
12937
  ]
12135
12938
  }
12136
12939
  ) }),
12137
- /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
12138
- RPopover9.Content,
12940
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
12941
+ RPopover10.Content,
12139
12942
  {
12140
12943
  side: "bottom",
12141
- align: "start",
12944
+ align: mode === "date" ? "start" : "center",
12142
12945
  sideOffset: 4,
12143
12946
  className: cn(
12144
12947
  "z-50 max-w-[var(--radix-popover-content-available-width)]",
12145
12948
  FLOATING_SLIDE_ANIM
12146
12949
  ),
12147
- children: /* @__PURE__ */ jsx(
12950
+ children: mode === "date" ? /* @__PURE__ */ jsx(
12148
12951
  SCalendar,
12149
12952
  {
12150
12953
  value: value ?? void 0,
@@ -12153,6 +12956,24 @@ function SDatePicker({
12153
12956
  onValueChange: select,
12154
12957
  onViewChange
12155
12958
  }
12959
+ ) : mode === "year" ? /* @__PURE__ */ jsx(
12960
+ SDatePickerYearListbox,
12961
+ {
12962
+ value,
12963
+ selectable,
12964
+ onValueChange: (nextValue) => {
12965
+ selectListboxValue(nextValue);
12966
+ setOpen(false);
12967
+ }
12968
+ }
12969
+ ) : /* @__PURE__ */ jsx(
12970
+ SDatePickerMonthListbox,
12971
+ {
12972
+ value,
12973
+ selectable,
12974
+ onValueChange: selectListboxValue,
12975
+ onMonthSelect: () => setOpen(false)
12976
+ }
12156
12977
  )
12157
12978
  }
12158
12979
  ) })
@@ -12534,6 +13355,9 @@ function SDateRangePicker({
12534
13355
  icon: 20,
12535
13356
  minW: 104,
12536
13357
  fieldMinW: 260,
13358
+ // 상한 등급 xl — 값이 `YYYY-MM-DD ~ YYYY-MM-DD` 라 단일 날짜의 두 배가 넘는다.
13359
+ // 최소너비(260)가 이미 lg(240)를 넘으므로 상한도 한 등급 위여야 한다
13360
+ fieldMaxW: 480,
12537
13361
  fontSize: 14,
12538
13362
  lineHeight: 24,
12539
13363
  radius: 6
@@ -12543,6 +13367,8 @@ function SDateRangePicker({
12543
13367
  icon: 16,
12544
13368
  minW: 80,
12545
13369
  fieldMinW: 216,
13370
+ // 상한 등급 lg — 최소너비 216 이 md(160)를 넘어 md 로는 담기지 않는다
13371
+ fieldMaxW: 240,
12546
13372
  fontSize: 12,
12547
13373
  lineHeight: 20,
12548
13374
  radius: 4
@@ -12573,11 +13399,12 @@ function SDateRangePicker({
12573
13399
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
12574
13400
  width,
12575
13401
  minWidth: dims.fieldMinW,
13402
+ maxWidth: dims.fieldMaxW,
12576
13403
  disabled,
12577
13404
  className,
12578
13405
  style,
12579
13406
  children: /* @__PURE__ */ jsxs(
12580
- RPopover9.Root,
13407
+ RPopover10.Root,
12581
13408
  {
12582
13409
  open,
12583
13410
  onOpenChange: (o) => {
@@ -12586,7 +13413,7 @@ function SDateRangePicker({
12586
13413
  if (!o) setPendingStart(null);
12587
13414
  },
12588
13415
  children: [
12589
- /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
13416
+ /* @__PURE__ */ jsx(RPopover10.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
12590
13417
  "button",
12591
13418
  {
12592
13419
  type: "button",
@@ -12627,8 +13454,8 @@ function SDateRangePicker({
12627
13454
  ]
12628
13455
  }
12629
13456
  ) }),
12630
- /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
12631
- RPopover9.Content,
13457
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
13458
+ RPopover10.Content,
12632
13459
  {
12633
13460
  side: "bottom",
12634
13461
  align: "start",
@@ -13037,6 +13864,7 @@ function SKeyValueTable({
13037
13864
  values = {},
13038
13865
  search = false,
13039
13866
  radius = "default",
13867
+ bordered = true,
13040
13868
  onChange,
13041
13869
  onSearch,
13042
13870
  className,
@@ -13101,7 +13929,8 @@ function SKeyValueTable({
13101
13929
  });
13102
13930
  const radiusClass = radius === "full" ? "rounded-none" : radius === "useTop" ? "rounded-b-[8px] rounded-t-none" : "rounded-[8px]";
13103
13931
  const frameClass = cn(
13104
- "overflow-hidden border border-solid border-[color:var(--cmp-table-border-color)]",
13932
+ "overflow-hidden",
13933
+ bordered && "border border-solid border-[color:var(--cmp-table-border-color)]",
13105
13934
  radiusClass
13106
13935
  );
13107
13936
  const table = /* @__PURE__ */ jsxs(
@@ -13646,6 +14475,119 @@ function SRadioButton({
13646
14475
  }
13647
14476
  );
13648
14477
  }
14478
+ var FONT2 = {
14479
+ sm: {
14480
+ fontSize: 12,
14481
+ lineHeight: 20,
14482
+ paddingX: "var(--cmp-textinput-sm-paddingX)",
14483
+ gap: "var(--cmp-textinput-sm-gap)"
14484
+ },
14485
+ md: {
14486
+ fontSize: 14,
14487
+ lineHeight: 24,
14488
+ paddingX: "var(--cmp-textinput-md-paddingX)",
14489
+ gap: "var(--cmp-textinput-md-gap)"
14490
+ }
14491
+ };
14492
+ var SSearchInput = /* @__PURE__ */ forwardRef(function SSearchInput2({
14493
+ value,
14494
+ onValueChange,
14495
+ onChange,
14496
+ onSearch,
14497
+ size = "sm",
14498
+ placeholder = "\uACB0\uACFC \uB0B4 \uAC80\uC0C9",
14499
+ clearable = false,
14500
+ disabled = false,
14501
+ width,
14502
+ focused,
14503
+ hovered,
14504
+ inputClass,
14505
+ inputStyle,
14506
+ className,
14507
+ style,
14508
+ onKeyDown,
14509
+ ...rest
14510
+ }, ref) {
14511
+ const f = FONT2[size];
14512
+ const hasValue = value !== void 0 && value !== null && String(value) !== "";
14513
+ return /* @__PURE__ */ jsx(
14514
+ SField,
14515
+ {
14516
+ size,
14517
+ width,
14518
+ focused,
14519
+ hovered,
14520
+ disabled,
14521
+ className,
14522
+ style,
14523
+ children: /* @__PURE__ */ jsxs(
14524
+ "div",
14525
+ {
14526
+ className: "flex h-full w-full items-center",
14527
+ style: {
14528
+ gap: f.gap,
14529
+ padding: `0 ${f.paddingX}`,
14530
+ fontSize: f.fontSize,
14531
+ lineHeight: `${f.lineHeight}px`
14532
+ },
14533
+ children: [
14534
+ /* @__PURE__ */ jsx(
14535
+ SIcon,
14536
+ {
14537
+ name: "search",
14538
+ size: 16,
14539
+ color: disabled ? "var(--sys-color-disabled-icon)" : "var(--cmp-textinput-icon-default)",
14540
+ className: "flex-shrink-0"
14541
+ }
14542
+ ),
14543
+ /* @__PURE__ */ jsx(
14544
+ "input",
14545
+ {
14546
+ ref,
14547
+ type: "search",
14548
+ value: value ?? "",
14549
+ disabled,
14550
+ placeholder,
14551
+ onChange: (e) => {
14552
+ onChange?.(e);
14553
+ onValueChange?.(e.target.value);
14554
+ },
14555
+ onKeyDown: (e) => {
14556
+ onKeyDown?.(e);
14557
+ if (e.key === "Enter" && !e.nativeEvent.isComposing) {
14558
+ onSearch?.(e.currentTarget.value);
14559
+ }
14560
+ },
14561
+ className: cn(
14562
+ // type="search" 의 브라우저 기본 취소 버튼은 clearable 과 겹치므로 지운다
14563
+ "h-full min-w-0 flex-1 border-0 bg-transparent p-0 outline-none placeholder:text-[color:var(--sys-color-field-text-placeholder)] disabled:cursor-not-allowed [&::-webkit-search-cancel-button]:hidden",
14564
+ AUTOFILL_RESET_CLASS,
14565
+ inputClass
14566
+ ),
14567
+ style: {
14568
+ // fontSize/lineHeight 는 컨테이너에서 상속 (원본 font-size: inherit)
14569
+ color: disabled ? "var(--cmp-textinput-text-disabled)" : "var(--sys-color-field-text-default)",
14570
+ ...inputStyle
14571
+ },
14572
+ ...rest
14573
+ }
14574
+ ),
14575
+ clearable && hasValue && !disabled && /* @__PURE__ */ jsx(
14576
+ SGhostButton,
14577
+ {
14578
+ icon: "close",
14579
+ size: "xxs",
14580
+ ariaLabel: "\uC9C0\uC6B0\uAE30",
14581
+ className: "flex-shrink-0",
14582
+ onClick: () => onValueChange?.("")
14583
+ }
14584
+ )
14585
+ ]
14586
+ }
14587
+ )
14588
+ }
14589
+ );
14590
+ });
13649
14591
  var CHIPFILTER_LISTBOX_WIDTH = 240;
13650
14592
  var CHIPFILTER_DATE_PRESET_LISTBOX_WIDTH = 246;
13651
14593
  var CHIPFILTER = {
@@ -13686,7 +14628,16 @@ var CHIPFILTER = {
13686
14628
  itemTextDefault: "var(--cmp-chipFilter-item-text-default)",
13687
14629
  itemTextSelected: "var(--cmp-chipFilter-item-text-selected)"
13688
14630
  };
14631
+ var DEFAULT_PERIOD_PRESETS = [
14632
+ { value: "day", label: "\uC77C\uBCC4" },
14633
+ { value: "month", label: "\uC6D4\uBCC4" },
14634
+ { value: "quarter", label: "\uBD84\uAE30\uBCC4" },
14635
+ { value: "half", label: "\uBC18\uAE30\uBCC4" },
14636
+ { value: "year", label: "\uC5F0\uB3C4\uBCC4" },
14637
+ { value: "custom", label: "\uC0AC\uC6A9\uC790 \uC9C0\uC815", custom: true }
14638
+ ];
13689
14639
  var isKeywordValue = (value) => typeof value === "object" && value != null && !Array.isArray(value) && "keywords" in value;
14640
+ var isPeriodValue = (value) => typeof value === "object" && value != null && !Array.isArray(value) && "unit" in value;
13690
14641
  var keywordEntries = (value) => {
13691
14642
  if (isKeywordValue(value)) return value.keywords;
13692
14643
  if (Array.isArray(value)) {
@@ -13710,12 +14661,33 @@ var todayIso = () => {
13710
14661
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
13711
14662
  };
13712
14663
  var isInlineDateField = (field) => field.type === "date" && field.radioButton === true && (field.presets?.length ?? 0) > 0;
14664
+ var getFieldPresets = (field) => {
14665
+ if (field.type === "period") return field.presets ?? DEFAULT_PERIOD_PRESETS;
14666
+ return field.presets ?? [];
14667
+ };
14668
+ var getDefaultPeriodInputValue = (unit) => {
14669
+ const today = todayIso();
14670
+ if (unit === "day") return today;
14671
+ if (unit === "month") return today.slice(0, 7);
14672
+ if (unit === "quarter") return 1;
14673
+ if (unit === "half") return "first";
14674
+ if (unit === "year") return today.slice(0, 4);
14675
+ return [today, today];
14676
+ };
14677
+ var createPeriodValue = (unit) => ({
14678
+ unit,
14679
+ value: getDefaultPeriodInputValue(unit)
14680
+ });
13713
14681
  var resolveFieldDefault = (field) => {
13714
14682
  if (field.defaultValue !== void 0) return field.defaultValue;
13715
14683
  if (isInlineDateField(field)) return todayIso();
13716
14684
  if (!field.required) return void 0;
13717
14685
  if (field.type === "single") return field.options?.[0]?.value;
13718
14686
  if (field.type === "date") return todayIso();
14687
+ if (field.type === "period") {
14688
+ const unit = getFieldPresets(field).find((preset) => !preset.custom)?.value;
14689
+ return unit ? createPeriodValue(unit) : void 0;
14690
+ }
13719
14691
  return void 0;
13720
14692
  };
13721
14693
  var valuesEqual = (a, b) => {
@@ -13739,6 +14711,8 @@ var isAtDefaultValue = (field, value) => {
13739
14711
  return def !== void 0 && valuesEqual(value, def);
13740
14712
  };
13741
14713
  var isInlineField = (field) => isInlineDateField(field) || field.type === "custom";
14714
+ var isFixedField = (field) => field.fix === true || field.required === true;
14715
+ var isAddableField = (field) => !isFixedField(field);
13742
14716
  var isGroupedFields = (fields) => fields.length > 0 && "fields" in fields[0];
13743
14717
  var isGroupSatisfied = (group, values) => {
13744
14718
  if (!group.rule) return true;
@@ -13765,9 +14739,10 @@ var defaultGroupTooltipMessage = (rule, fields) => {
13765
14739
  };
13766
14740
  var matchPresetId = (presets, value) => {
13767
14741
  if (isEmptyValue(value)) return null;
14742
+ if (isPeriodValue(value)) return value.unit;
13768
14743
  for (const preset of presets) {
13769
- if (preset.custom || !preset.resolve) continue;
13770
- const resolved = preset.resolve();
14744
+ if (preset.custom) continue;
14745
+ const resolved = preset.resolve ? preset.resolve() : preset.value;
13771
14746
  if (Array.isArray(resolved) && Array.isArray(value)) {
13772
14747
  if (resolved[0] === value[0] && resolved[1] === value[1]) return preset.value;
13773
14748
  } else if (!Array.isArray(resolved) && !Array.isArray(value) && resolved === value) {
@@ -13783,18 +14758,36 @@ var formatValueList = (labels) => {
13783
14758
  };
13784
14759
  var displayValue = (field, value) => {
13785
14760
  if (isEmptyValue(value)) return "";
13786
- if (field.type === "multi")
13787
- return formatValueList(toArrayValue(value).map((v) => optionLabel(field, v)));
14761
+ if (field.type === "multi") {
14762
+ const values = toArrayValue(value);
14763
+ const selectableValues = (field.options ?? []).filter((option) => !option.disabled).map((option) => option.value);
14764
+ if (selectableValues.length > 0 && selectableValues.every((selectableValue) => values.includes(selectableValue))) {
14765
+ return "\uC804\uCCB4";
14766
+ }
14767
+ return formatValueList(values.map((v) => optionLabel(field, v)));
14768
+ }
13788
14769
  if (field.type === "single") return optionLabel(field, value);
13789
14770
  if (field.type === "date")
13790
14771
  return Array.isArray(value) ? `${value[0]} ~ ${value[1]}` : String(value);
14772
+ if (field.type === "period") {
14773
+ if (!isPeriodValue(value)) {
14774
+ const preset2 = getFieldPresets(field).find((p) => p.value === value);
14775
+ return preset2?.label ?? String(value);
14776
+ }
14777
+ const preset = getFieldPresets(field).find((p) => p.value === value.unit);
14778
+ const label = preset?.label ?? value.unit;
14779
+ if (value.unit === "quarter") return `${label}(${value.value ?? 1}\uBD84\uAE30)`;
14780
+ if (value.unit === "half") return `${label}(${value.value === "second" ? "\uD558\uBC18\uAE30" : "\uC0C1\uBC18\uAE30"})`;
14781
+ if (Array.isArray(value.value)) return `${value.value[0]} ~ ${value.value[1]}`;
14782
+ return value.value != null && value.value !== "" ? String(value.value) : label;
14783
+ }
13791
14784
  if (field.type === "keyword") return formatValueList(keywordEntries(value));
13792
14785
  return String(value);
13793
14786
  };
13794
14787
  var chipDisplayValue = (field, value) => {
13795
14788
  const text = displayValue(field, value);
13796
14789
  if (field.type !== "date" || isEmptyValue(value)) return text;
13797
- const presets = field.presets ?? [];
14790
+ const presets = getFieldPresets(field);
13798
14791
  if (presets.length === 0) return text;
13799
14792
  const preset = presets.find((p) => p.value === matchPresetId(presets, value));
13800
14793
  return preset && !preset.custom ? `${preset.label}(${text})` : text;
@@ -13811,7 +14804,6 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
13811
14804
  onAddFilter,
13812
14805
  label = "\uAC80\uC0C9 \uD544\uD130",
13813
14806
  showLabel = false,
13814
- showAddButton = true,
13815
14807
  showReset = true,
13816
14808
  disabled = false,
13817
14809
  className,
@@ -13826,7 +14818,7 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
13826
14818
  const values = normalizeValues(value);
13827
14819
  const portalContainer = usePortalContainer();
13828
14820
  const [internalActiveKeys, setInternalActiveKeys] = useState(
13829
- () => fields.filter((field) => field.defaultActive !== false).map((field) => field.key)
14821
+ () => fields.filter((field) => isFixedField(field) || field.defaultActive !== false).map((field) => field.key)
13830
14822
  );
13831
14823
  const [openKey, setOpenKey] = useState(null);
13832
14824
  const [addOpen, setAddOpen] = useState(false);
@@ -13868,7 +14860,9 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
13868
14860
  setInternalActiveKeys((current) => {
13869
14861
  const known = new Set(fields.map((field) => field.key));
13870
14862
  const kept = current.filter((key) => known.has(key));
13871
- const additions = fields.filter((field) => field.defaultActive !== false && !kept.includes(field.key)).map((field) => field.key);
14863
+ const additions = fields.filter(
14864
+ (field) => (isFixedField(field) || field.defaultActive !== false) && !kept.includes(field.key)
14865
+ ).map((field) => field.key);
13872
14866
  return [...kept, ...additions];
13873
14867
  });
13874
14868
  }, [activeKeys, fields]);
@@ -13889,25 +14883,31 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
13889
14883
  });
13890
14884
  if (changed) onValueChange?.({ ...values, ...patch });
13891
14885
  }, [fields, values, onValueChange]);
13892
- const resolvedActiveKeys = activeKeys ?? internalActiveKeys;
14886
+ const fixedKeys = fields.filter(isFixedField).map((field) => field.key);
14887
+ const resolvedActiveKeys = Array.from(
14888
+ /* @__PURE__ */ new Set([...activeKeys ?? internalActiveKeys, ...fixedKeys])
14889
+ );
13893
14890
  const activeFields = fields.filter((field) => resolvedActiveKeys.includes(field.key));
13894
- const inactiveFields = fields.filter((field) => !resolvedActiveKeys.includes(field.key));
14891
+ const inactiveFields = fields.filter(
14892
+ (field) => isAddableField(field) && !resolvedActiveKeys.includes(field.key)
14893
+ );
13895
14894
  const groupIndexByKey = /* @__PURE__ */ new Map();
13896
14895
  groups.forEach((group, groupIndex) => {
13897
14896
  group.fields.forEach((field) => groupIndexByKey.set(field.key, groupIndex));
13898
14897
  });
13899
14898
  const hasValue = fields.some((field) => !isEmptyValue(values[field.key]));
13900
- const hasRightActions = showAddButton || showReset;
14899
+ const hasAddButton = fields.some(isAddableField);
14900
+ const hasRightActions = hasAddButton || showReset;
13901
14901
  const currentOpenField = fields.find((field) => field.key === openKey);
13902
14902
  const editorPopoverStyle = useMemo(() => {
13903
14903
  if (currentOpenField?.type === "keyword") {
13904
14904
  return { width: CHIPFILTER_LISTBOX_WIDTH };
13905
14905
  }
13906
- if (currentOpenField?.type === "date" && (currentOpenField.presets?.length ?? 0) > 0) {
14906
+ if ((currentOpenField?.type === "date" || currentOpenField?.type === "period") && getFieldPresets(currentOpenField).length > 0) {
13907
14907
  return { width: CHIPFILTER_DATE_PRESET_LISTBOX_WIDTH };
13908
14908
  }
13909
14909
  return { width: CHIPFILTER_LISTBOX_WIDTH };
13910
- }, [currentOpenField?.type, currentOpenField?.presets?.length]);
14910
+ }, [currentOpenField]);
13911
14911
  const setActive = (next) => {
13912
14912
  if (!activeKeys) setInternalActiveKeys(next);
13913
14913
  onActiveKeysChange?.(next);
@@ -13926,6 +14926,12 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
13926
14926
  const def = field ? resolveFieldDefault(field) : void 0;
13927
14927
  runSearch(emitValue(key, def !== void 0 ? def : null));
13928
14928
  };
14929
+ const removeField = (key) => {
14930
+ const nextValues = emitValue(key, null);
14931
+ setActive(resolvedActiveKeys.filter((activeKey) => activeKey !== key));
14932
+ if (openKey === key) setOpenKey(null);
14933
+ runSearch(nextValues);
14934
+ };
13929
14935
  const reset = useCallback(() => {
13930
14936
  const next = fields.reduce((acc, field) => {
13931
14937
  const def = resolveFieldDefault(field);
@@ -14016,7 +15022,15 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14016
15022
  flushRun();
14017
15023
  runGroupIndex = groupIndex;
14018
15024
  }
14019
- const showClear = !isAtDefaultValue(field, values[field.key]);
15025
+ const addable = isAddableField(field);
15026
+ const showClear = addable || !isAtDefaultValue(field, values[field.key]);
15027
+ const handleClear = () => {
15028
+ if (addable) {
15029
+ removeField(field.key);
15030
+ return;
15031
+ }
15032
+ clearValue(field.key);
15033
+ };
14020
15034
  const node = isInlineDateField(field) ? /* @__PURE__ */ jsx(
14021
15035
  InlineDatePresetFilter,
14022
15036
  {
@@ -14026,15 +15040,27 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14026
15040
  warning,
14027
15041
  showClear,
14028
15042
  onValueChange: (next) => emitValueAndSearch(field.key, next),
14029
- onClear: () => clearValue(field.key)
15043
+ onClear: handleClear
14030
15044
  },
14031
15045
  field.key
14032
- ) : field.type === "custom" ? /* @__PURE__ */ jsx(Fragment$1, { children: field.render?.({
14033
- value: values[field.key],
14034
- disabled: disabled || field.disabled,
14035
- warning,
14036
- onValueChange: (next) => emitValueAndSearch(field.key, next)
14037
- }) }, field.key) : /* @__PURE__ */ jsx(
15046
+ ) : field.type === "custom" ? /* @__PURE__ */ jsxs("div", { className: "inline-flex shrink-0 items-center gap-[4px]", children: [
15047
+ field.render?.({
15048
+ value: values[field.key],
15049
+ disabled: disabled || field.disabled,
15050
+ warning,
15051
+ onValueChange: (next) => emitValueAndSearch(field.key, next)
15052
+ }),
15053
+ addable && !disabled && !field.disabled && showClear && /* @__PURE__ */ jsx(
15054
+ "button",
15055
+ {
15056
+ type: "button",
15057
+ "aria-label": `${field.label} \uD544\uD130 \uC81C\uAC70`,
15058
+ className: "inline-flex size-[16px] shrink-0 items-center justify-center rounded-[var(--cmp-ghostButton-radius-sm)] border-none bg-transparent p-0",
15059
+ onClick: handleClear,
15060
+ children: /* @__PURE__ */ jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
15061
+ }
15062
+ )
15063
+ ] }, field.key) : /* @__PURE__ */ jsx(
14038
15064
  FilterChip,
14039
15065
  {
14040
15066
  field,
@@ -14044,9 +15070,10 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14044
15070
  disabled: disabled || field.disabled,
14045
15071
  warning,
14046
15072
  showClear,
15073
+ showClearWhenEmpty: addable,
14047
15074
  refCallback: (node2) => triggerRefs.current.set(field.key, node2),
14048
15075
  onOpen: () => setOpenKey(openKey === field.key ? null : field.key),
14049
- onClear: () => clearValue(field.key)
15076
+ onClear: handleClear
14050
15077
  },
14051
15078
  field.key
14052
15079
  );
@@ -14067,8 +15094,8 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14067
15094
  })(),
14068
15095
  hasRightActions && /* @__PURE__ */ jsxs(Fragment, { children: [
14069
15096
  /* @__PURE__ */ jsx(Divider, { inset: true }),
14070
- showAddButton && /* @__PURE__ */ jsxs(RPopover9.Root, { open: addOpen, onOpenChange: setAddOpen, children: [
14071
- /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
15097
+ hasAddButton && /* @__PURE__ */ jsxs(RPopover10.Root, { open: addOpen, onOpenChange: setAddOpen, children: [
15098
+ /* @__PURE__ */ jsx(RPopover10.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
14072
15099
  "button",
14073
15100
  {
14074
15101
  type: "button",
@@ -14093,8 +15120,8 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14093
15120
  ]
14094
15121
  }
14095
15122
  ) }),
14096
- /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
14097
- RPopover9.Content,
15123
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
15124
+ RPopover10.Content,
14098
15125
  {
14099
15126
  side: "bottom",
14100
15127
  align: "start",
@@ -14114,7 +15141,7 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14114
15141
  "inline-flex shrink-0 cursor-pointer items-center gap-[4px] border-none bg-transparent p-0 text-[12px] font-medium leading-[20px] disabled:cursor-not-allowed",
14115
15142
  // 부모 flex gap(6px)은 칩 사이 간격 기준이라, 필터 추가 버튼과의 12px 간격을 맞추려면
14116
15143
  // 부족한 만큼(12px - gap)을 추가로 띄운다. 필터 추가가 없을 땐 divider와 이미 12px이라 불필요.
14117
- showAddButton && "ml-[calc(12px-var(--cmp-chipFilter-chip-group))]"
15144
+ hasAddButton && "ml-[calc(12px-var(--cmp-chipFilter-chip-group))]"
14118
15145
  ),
14119
15146
  style: {
14120
15147
  color: disabled || !hasValue ? "var(--cmp-textLink-content-disabled)" : "var(--cmp-textLink-content-default)"
@@ -14134,17 +15161,17 @@ var SChipFilter = /* @__PURE__ */ forwardRef(function SChipFilter2({
14134
15161
  )
14135
15162
  ] })
14136
15163
  ] }),
14137
- /* @__PURE__ */ jsxs(RPopover9.Root, { open: openKey != null, onOpenChange: (next) => !next && setOpenKey(null), children: [
15164
+ /* @__PURE__ */ jsxs(RPopover10.Root, { open: openKey != null, onOpenChange: (next) => !next && setOpenKey(null), children: [
14138
15165
  /* @__PURE__ */ jsx(
14139
- RPopover9.Anchor,
15166
+ RPopover10.Anchor,
14140
15167
  {
14141
15168
  virtualRef: {
14142
15169
  current: openKey ? triggerRefs.current.get(openKey) : null
14143
15170
  }
14144
15171
  }
14145
15172
  ),
14146
- /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
14147
- RPopover9.Content,
15173
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
15174
+ RPopover10.Content,
14148
15175
  {
14149
15176
  side: "bottom",
14150
15177
  align: "start",
@@ -14198,6 +15225,7 @@ function FilterChip({
14198
15225
  disabled,
14199
15226
  warning,
14200
15227
  showClear,
15228
+ showClearWhenEmpty,
14201
15229
  refCallback,
14202
15230
  onOpen,
14203
15231
  onClear
@@ -14267,7 +15295,7 @@ function FilterChip({
14267
15295
  }
14268
15296
  )
14269
15297
  ] }),
14270
- applied && !disabled && showClear && /* @__PURE__ */ jsx(
15298
+ !disabled && showClear && (applied || showClearWhenEmpty) && /* @__PURE__ */ jsx(
14271
15299
  "span",
14272
15300
  {
14273
15301
  role: "button",
@@ -14299,7 +15327,7 @@ function EditorPanel({
14299
15327
  onValueChange,
14300
15328
  onClose
14301
15329
  }) {
14302
- const wrapsInPaddedTop = field.type === "date";
15330
+ const wrapsInPaddedTop = field.type === "date" || field.type === "period";
14303
15331
  const isMulti = field.type === "multi";
14304
15332
  const selectableValues = isMulti ? (field.options ?? []).filter((option) => !option.disabled).map((option) => option.value) : [];
14305
15333
  const selectedValues = isMulti ? toArrayValue(value) : [];
@@ -14378,6 +15406,17 @@ function FilterEditor({
14378
15406
  disabled,
14379
15407
  onValueChange
14380
15408
  }) {
15409
+ if (field.type === "period") {
15410
+ return /* @__PURE__ */ jsx(
15411
+ PeriodPresetEditor,
15412
+ {
15413
+ field,
15414
+ value,
15415
+ disabled,
15416
+ onValueChange
15417
+ }
15418
+ );
15419
+ }
14381
15420
  if (field.type === "date") {
14382
15421
  return /* @__PURE__ */ jsx(
14383
15422
  DatePresetEditor,
@@ -14417,7 +15456,7 @@ function DatePresetEditor({
14417
15456
  disabled,
14418
15457
  onValueChange
14419
15458
  }) {
14420
- const presets = field.presets ?? [];
15459
+ const presets = getFieldPresets(field);
14421
15460
  const [selectedId, setSelectedId] = useState(() => matchPresetId(presets, value));
14422
15461
  const syncedInitialValueRef = useRef(!isEmptyValue(value));
14423
15462
  useEffect(() => {
@@ -14428,9 +15467,9 @@ function DatePresetEditor({
14428
15467
  const selectedPreset = presets.find((preset) => preset.value === selectedId);
14429
15468
  const selectPreset = (preset) => {
14430
15469
  setSelectedId(preset.value);
14431
- if (!preset.custom) onValueChange(preset.resolve ? preset.resolve() : null);
15470
+ if (!preset.custom) onValueChange(preset.resolve ? preset.resolve() : preset.value);
14432
15471
  };
14433
- if (presets.length === 0) {
15472
+ if (field.type === "date" && presets.length === 0) {
14434
15473
  return /* @__PURE__ */ jsx(
14435
15474
  SDatePicker,
14436
15475
  {
@@ -14466,6 +15505,130 @@ function DatePresetEditor({
14466
15505
  )
14467
15506
  ] });
14468
15507
  }
15508
+ function PeriodPresetEditor({
15509
+ field,
15510
+ value,
15511
+ disabled,
15512
+ onValueChange
15513
+ }) {
15514
+ const presets = getFieldPresets(field);
15515
+ const firstUnit = presets.find((preset) => !preset.custom)?.value ?? "day";
15516
+ const [selectedId, setSelectedId] = useState(() => matchPresetId(presets, value) ?? firstUnit);
15517
+ const periodValue = isPeriodValue(value) ? value : createPeriodValue(selectedId ?? firstUnit);
15518
+ const selectedUnit = selectedId ?? periodValue.unit;
15519
+ useEffect(() => {
15520
+ if (isEmptyValue(value)) return;
15521
+ setSelectedId(matchPresetId(presets, value) ?? firstUnit);
15522
+ }, [firstUnit, presets, value]);
15523
+ const selectPreset = (preset) => {
15524
+ const nextUnit = preset.value;
15525
+ setSelectedId(nextUnit);
15526
+ onValueChange(createPeriodValue(nextUnit));
15527
+ };
15528
+ const updateInputValue = (nextValue) => {
15529
+ onValueChange({ unit: selectedUnit, value: nextValue });
15530
+ };
15531
+ const renderInput = () => {
15532
+ if (selectedUnit === "day") {
15533
+ return /* @__PURE__ */ jsx(
15534
+ SDatePicker,
15535
+ {
15536
+ value: typeof periodValue.value === "string" ? periodValue.value : null,
15537
+ onValueChange: updateInputValue,
15538
+ selectable: field.selectable,
15539
+ disabled,
15540
+ width: "100%"
15541
+ }
15542
+ );
15543
+ }
15544
+ if (selectedUnit === "month") {
15545
+ return /* @__PURE__ */ jsx(
15546
+ SDatePicker,
15547
+ {
15548
+ mode: "month",
15549
+ value: typeof periodValue.value === "string" ? periodValue.value : null,
15550
+ onValueChange: updateInputValue,
15551
+ selectable: field.selectable,
15552
+ disabled,
15553
+ width: "100%"
15554
+ }
15555
+ );
15556
+ }
15557
+ if (selectedUnit === "quarter") {
15558
+ return /* @__PURE__ */ jsx(
15559
+ SSelect2,
15560
+ {
15561
+ value: typeof periodValue.value === "number" ? periodValue.value : 1,
15562
+ onValueChange: updateInputValue,
15563
+ emitValue: true,
15564
+ disabled,
15565
+ width: "100%",
15566
+ options: [
15567
+ { value: 1, label: "1\uBD84\uAE30" },
15568
+ { value: 2, label: "2\uBD84\uAE30" },
15569
+ { value: 3, label: "3\uBD84\uAE30" },
15570
+ { value: 4, label: "4\uBD84\uAE30" }
15571
+ ]
15572
+ }
15573
+ );
15574
+ }
15575
+ if (selectedUnit === "half") {
15576
+ return /* @__PURE__ */ jsx(
15577
+ SSelect2,
15578
+ {
15579
+ value: typeof periodValue.value === "string" ? periodValue.value : "first",
15580
+ onValueChange: updateInputValue,
15581
+ emitValue: true,
15582
+ disabled,
15583
+ width: "100%",
15584
+ options: [
15585
+ { value: "first", label: "\uC0C1\uBC18\uAE30" },
15586
+ { value: "second", label: "\uD558\uBC18\uAE30" }
15587
+ ]
15588
+ }
15589
+ );
15590
+ }
15591
+ if (selectedUnit === "year") {
15592
+ return /* @__PURE__ */ jsx(
15593
+ SDatePicker,
15594
+ {
15595
+ mode: "year",
15596
+ value: typeof periodValue.value === "string" ? periodValue.value : null,
15597
+ onValueChange: updateInputValue,
15598
+ selectable: field.selectable,
15599
+ disabled,
15600
+ width: "100%"
15601
+ }
15602
+ );
15603
+ }
15604
+ return /* @__PURE__ */ jsx(
15605
+ SDateRangePicker,
15606
+ {
15607
+ value: Array.isArray(periodValue.value) && periodValue.value.length === 2 ? periodValue.value : null,
15608
+ onValueChange: updateInputValue,
15609
+ selectable: field.selectable,
15610
+ maxRange: field.maxRange,
15611
+ disabled,
15612
+ width: "100%"
15613
+ }
15614
+ );
15615
+ };
15616
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-[var(--cmp-chipFilter-listbox-top-gap)]", children: [
15617
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-[8px]", children: presets.map((preset) => /* @__PURE__ */ jsx(
15618
+ SRadio,
15619
+ {
15620
+ val: preset.value,
15621
+ value: selectedId,
15622
+ label: preset.label,
15623
+ disabled,
15624
+ onValueChange: () => selectPreset(preset)
15625
+ },
15626
+ preset.value
15627
+ )) }),
15628
+ /* @__PURE__ */ jsx("div", { "aria-hidden": true, className: "h-px w-full", style: { background: CHIPFILTER.listboxBorder } }),
15629
+ /* @__PURE__ */ jsx("div", { className: "w-full [&>*]:w-full", children: renderInput() })
15630
+ ] });
15631
+ }
14469
15632
  function InlineDatePresetFilter({
14470
15633
  field,
14471
15634
  value,
@@ -14548,8 +15711,8 @@ function InlineDatePresetFilter({
14548
15711
  applied && (rangeValue ? (
14549
15712
  // 뱃지 모양은 그대로 두고, 눌렀을 때 캘린더가 열려 기간을 직접 다시 고를 수 있게 한다.
14550
15713
  // 직접 고르면 프리셋 선택은 해제된다.
14551
- /* @__PURE__ */ jsxs(RPopover9.Root, { open: pickerOpen, onOpenChange: setPickerOpen, children: [
14552
- /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
15714
+ /* @__PURE__ */ jsxs(RPopover10.Root, { open: pickerOpen, onOpenChange: setPickerOpen, children: [
15715
+ /* @__PURE__ */ jsx(RPopover10.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
14553
15716
  "button",
14554
15717
  {
14555
15718
  type: "button",
@@ -14558,8 +15721,8 @@ function InlineDatePresetFilter({
14558
15721
  children: badge
14559
15722
  }
14560
15723
  ) }),
14561
- /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
14562
- RPopover9.Content,
15724
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
15725
+ RPopover10.Content,
14563
15726
  {
14564
15727
  side: "bottom",
14565
15728
  align: "start",
@@ -15308,7 +16471,7 @@ var SChipInput = /* @__PURE__ */ forwardRef(function SChipInput2({
15308
16471
  }
15309
16472
  );
15310
16473
  });
15311
- var FONT2 = {
16474
+ var FONT3 = {
15312
16475
  sm: {
15313
16476
  fontSize: 12,
15314
16477
  paddingX: "var(--cmp-textinput-sm-paddingX)",
@@ -15356,7 +16519,7 @@ var SBarcodeInput = /* @__PURE__ */ forwardRef(
15356
16519
  className,
15357
16520
  style
15358
16521
  }, ref) {
15359
- const f = FONT2[size];
16522
+ const f = FONT3[size];
15360
16523
  const [ruleError, setRuleError] = useState("");
15361
16524
  const hasValue = value !== void 0 && value !== null && String(value) !== "";
15362
16525
  useFormField({
@@ -15370,6 +16533,10 @@ var SBarcodeInput = /* @__PURE__ */ forwardRef(
15370
16533
  });
15371
16534
  const resolvedError = error || ruleError !== "";
15372
16535
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
16536
+ const valueTooltip = useTruncatedValueTooltip({
16537
+ enabled: readOnly && !disabled,
16538
+ text: String(value ?? "")
16539
+ });
15373
16540
  return /* @__PURE__ */ jsx(
15374
16541
  SField,
15375
16542
  {
@@ -15406,7 +16573,7 @@ var SBarcodeInput = /* @__PURE__ */ forwardRef(
15406
16573
  /* @__PURE__ */ jsx(
15407
16574
  "input",
15408
16575
  {
15409
- ref,
16576
+ ref: mergeRefs(ref, valueTooltip.valueRef),
15410
16577
  name,
15411
16578
  type: "text",
15412
16579
  value: value ?? "",
@@ -15432,9 +16599,12 @@ var SBarcodeInput = /* @__PURE__ */ forwardRef(
15432
16599
  fontSize: f.fontSize,
15433
16600
  color: disabled ? "var(--cmp-textinput-text-disabled)" : "var(--cmp-textinput-text-default)",
15434
16601
  ...inputStyle
15435
- }
16602
+ },
16603
+ onMouseEnter: valueTooltip.onMouseEnter,
16604
+ onMouseLeave: valueTooltip.onMouseLeave
15436
16605
  }
15437
16606
  ),
16607
+ valueTooltip.tooltip,
15438
16608
  suffix,
15439
16609
  clearable && hasValue && !disabled && /* @__PURE__ */ jsx(
15440
16610
  SGhostButton,
@@ -15470,7 +16640,9 @@ var TIMEPICKER_SIZE_CONFIG = {
15470
16640
  fontSize: 12,
15471
16641
  lineHeight: 20,
15472
16642
  minWidth: 80,
15473
- fieldMinWidth: 128
16643
+ fieldMinWidth: 128,
16644
+ // 상한 등급 md
16645
+ fieldMaxWidth: 160
15474
16646
  },
15475
16647
  md: {
15476
16648
  height: V3("md-height"),
@@ -15482,7 +16654,9 @@ var TIMEPICKER_SIZE_CONFIG = {
15482
16654
  fontSize: 14,
15483
16655
  lineHeight: 24,
15484
16656
  minWidth: 96,
15485
- fieldMinWidth: 160
16657
+ fieldMinWidth: 160,
16658
+ // 상한 등급 lg
16659
+ fieldMaxWidth: 240
15486
16660
  }
15487
16661
  };
15488
16662
  function clampTimePart(value, min, max) {
@@ -15806,11 +16980,12 @@ function STimePicker({
15806
16980
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
15807
16981
  width,
15808
16982
  minWidth: dims.fieldMinWidth,
16983
+ maxWidth: dims.fieldMaxWidth,
15809
16984
  disabled,
15810
16985
  className,
15811
16986
  style,
15812
- children: /* @__PURE__ */ jsxs(RPopover9.Root, { open, onOpenChange: commitOpen, children: [
15813
- /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
16987
+ children: /* @__PURE__ */ jsxs(RPopover10.Root, { open, onOpenChange: commitOpen, children: [
16988
+ /* @__PURE__ */ jsx(RPopover10.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
15814
16989
  "div",
15815
16990
  {
15816
16991
  ref: triggerRef,
@@ -15870,8 +17045,8 @@ function STimePicker({
15870
17045
  ]
15871
17046
  }
15872
17047
  ) }),
15873
- /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
15874
- RPopover9.Content,
17048
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
17049
+ RPopover10.Content,
15875
17050
  {
15876
17051
  side: "bottom",
15877
17052
  align: "start",
@@ -15922,6 +17097,10 @@ var MIDDAY_FIELD_MIN_WIDTH = {
15922
17097
  sm: 178,
15923
17098
  md: 216
15924
17099
  };
17100
+ var MIDDAY_FIELD_MAX_WIDTH = {
17101
+ sm: 240,
17102
+ md: 240
17103
+ };
15925
17104
  function RangeTimeSelector({
15926
17105
  value,
15927
17106
  useMeridiem,
@@ -16268,11 +17447,12 @@ function STimeRangePicker({
16268
17447
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
16269
17448
  width,
16270
17449
  minWidth: effectiveUseMeridiem ? MIDDAY_FIELD_MIN_WIDTH[size] : dims.fieldMinWidth,
17450
+ maxWidth: effectiveUseMeridiem ? MIDDAY_FIELD_MAX_WIDTH[size] : dims.fieldMaxWidth,
16271
17451
  disabled,
16272
17452
  className,
16273
17453
  style,
16274
- children: /* @__PURE__ */ jsxs(RPopover9.Root, { open, onOpenChange: commitOpen, children: [
16275
- /* @__PURE__ */ jsx(RPopover9.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
17454
+ children: /* @__PURE__ */ jsxs(RPopover10.Root, { open, onOpenChange: commitOpen, children: [
17455
+ /* @__PURE__ */ jsx(RPopover10.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
16276
17456
  "div",
16277
17457
  {
16278
17458
  ref: triggerRef,
@@ -16332,8 +17512,8 @@ function STimeRangePicker({
16332
17512
  ]
16333
17513
  }
16334
17514
  ) }),
16335
- /* @__PURE__ */ jsx(RPopover9.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
16336
- RPopover9.Content,
17515
+ /* @__PURE__ */ jsx(RPopover10.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx(
17516
+ RPopover10.Content,
16337
17517
  {
16338
17518
  side: "bottom",
16339
17519
  align: "start",
@@ -16360,6 +17540,6 @@ function STimeRangePicker({
16360
17540
  );
16361
17541
  }
16362
17542
 
16363
- export { BADGE_COLORS, BUTTON_COLORS, BUTTON_SIZES, COLOR_KEYS, GNB_COLORS, GNB_COMMON, GNB_FOLD_MS, GNB_FULL_LOGO_WIDTH, GNB_HEADER, GNB_MENU, GNB_MENU_WIDTH, GNB_RAIL, GNB_RAIL_WIDTH, ICONS, PAGE_BACKGROUNDS, RangeCalendar, SActionModal, SBadge, SBarcodeInput, SButton, SCROLL_AREA_AXES, SCalendar, SCallout, SCard, SCheckbox, SChip2 as SChip, SChipFilter, SChipInput, SCircleProgress, SConfirmModal, SDatePicker, SDateRangePicker, SDivider, SDraggableGroup, SDraggableItem2 as SDraggableItem, SDraggableList, SDraggableProvider, SDrawer, SDropdownButton2 as SDropdownButton, SExpansionItem, SExpansionList2 as SExpansionList, SField, SFilePicker, SForm, SGhostButton, SGnb, SGuide, SIcon, SInput, SKeyValueTable, SLAYOUT_MIN_WIDTH, SLayout, SLayoutContext, SLinearProgress, SList2 as SList, SListItem2 as SListItem, SLoadingContainer, SLoadingModal, SLoadingViewport, SModal, SModalOutlet, SNumberInput, SPage, SPagination, SPopover, SPopup, SPortal, SRadio, SRadioButton, SRadioGroup, SSPLITTER_UNITS, SScrollArea, SSectionHeaderCard2 as SSectionHeaderCard, SSelect2 as SSelect, SSplitter2 as SSplitter, SStepper, SSwitch, STEPPER_SIZES, STable, STableBar, STabs, STag, STextLink, STextarea, STimePicker, STimeRangePicker, SToast, SToastContainer, SToggle, STooltip2 as STooltip, STree2 as STree, STreeItem2 as STreeItem, TAG_COLORS, TAG_SHAPES, TAG_SIZES, TIMEPICKER_SIZES, TIMEPICKER_SIZE_CONFIG, buttonPreset, cn, findGnbAncestors, findGnbRailValue, isColorKey, loading, resolveColor, useSLayout };
17543
+ export { BADGE_COLORS, BUTTON_COLORS, BUTTON_SIZES, COLOR_KEYS, GNB_COLORS, GNB_COMMON, GNB_FOLD_MS, GNB_FULL_LOGO_WIDTH, GNB_HEADER, GNB_MENU, GNB_MENU_WIDTH, GNB_RAIL, GNB_RAIL_WIDTH, ICONS, IMAGE_FITS, PAGE_BACKGROUNDS, PAGE_CONTENT_HEIGHTS, RangeCalendar, SActionModal, SBadge, SBarcodeInput, SButton, SCROLL_AREA_AXES, SCalendar, SCallout, SCard, SCheckbox, SChip2 as SChip, SChipFilter, SChipInput, SCircleProgress, SConfirmModal, SDatePicker, SDatePickerMonthListbox, SDatePickerYearListbox, SDateRangePicker, SDivider, SDraggableGroup, SDraggableItem2 as SDraggableItem, SDraggableList, SDraggableProvider, SDrawer, SDropdownButton2 as SDropdownButton, SExpansionItem, SExpansionList2 as SExpansionList, SField, SFilePicker, SForm, SGhostButton, SGnb, SGuide, SIcon, SImage, SInput, SKeyValueTable, SLAYOUT_MIN_WIDTH, SLayout, SLayoutContext, SLinearProgress, SList2 as SList, SListItem2 as SListItem, SLoadingContainer, SLoadingModal, SLoadingViewport, SModal, SModalOutlet, SNumberInput, SPage, SPagination, SPopover, SPopup, SPortal, SRadio, SRadioButton, SRadioGroup, SSPLITTER_UNITS, SScrollArea, SSearchInput, SSectionHeaderCard2 as SSectionHeaderCard, SSelect2 as SSelect, SSplitter2 as SSplitter, SStepper, SSwitch, STEPPER_SIZES, STable, STableBar, STabs, STag, STextLink, STextarea, STimePicker, STimeRangePicker, SToast, SToastContainer, SToggle, STooltip2 as STooltip, STree2 as STree, STreeItem2 as STreeItem, TAG_COLORS, TAG_SHAPES, TAG_SIZES, TIMEPICKER_SIZES, TIMEPICKER_SIZE_CONFIG, buttonPreset, cn, findGnbAncestors, findGnbRailValue, isColorKey, loading, resolveColor, useSLayout };
16364
17544
  //# sourceMappingURL=index.js.map
16365
17545
  //# sourceMappingURL=index.js.map