sellmate-design-system-react 9.0.0-beta.45 → 9.0.0-beta.46

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.
package/dist/index.cjs CHANGED
@@ -5,9 +5,9 @@ var clsx = require('clsx');
5
5
  var tailwindMerge = require('tailwind-merge');
6
6
  var react = require('react');
7
7
  var jsxRuntime = require('react/jsx-runtime');
8
+ var RPopover12 = require('@radix-ui/react-popover');
8
9
  var reactDom = require('react-dom');
9
10
  var RCheckbox = require('@radix-ui/react-checkbox');
10
- var RPopover12 = require('@radix-ui/react-popover');
11
11
  var client = require('react-dom/client');
12
12
  var RTooltip = require('@radix-ui/react-tooltip');
13
13
  var RDropdown2 = require('@radix-ui/react-dropdown-menu');
@@ -33,8 +33,8 @@ function _interopNamespace(e) {
33
33
  return Object.freeze(n);
34
34
  }
35
35
 
36
- var RCheckbox__namespace = /*#__PURE__*/_interopNamespace(RCheckbox);
37
36
  var RPopover12__namespace = /*#__PURE__*/_interopNamespace(RPopover12);
37
+ var RCheckbox__namespace = /*#__PURE__*/_interopNamespace(RCheckbox);
38
38
  var RTooltip__namespace = /*#__PURE__*/_interopNamespace(RTooltip);
39
39
  var RDropdown2__namespace = /*#__PURE__*/_interopNamespace(RDropdown2);
40
40
  var RSwitch__namespace = /*#__PURE__*/_interopNamespace(RSwitch);
@@ -2757,6 +2757,38 @@ var Icon_star = (p) => {
2757
2757
  }
2758
2758
  );
2759
2759
  };
2760
+ var Icon_stop = (p) => {
2761
+ const { size = 24, ...rest } = p;
2762
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2763
+ "svg",
2764
+ {
2765
+ width: size,
2766
+ height: size,
2767
+ viewBox: "0 0 24 24",
2768
+ fill: "none",
2769
+ xmlns: "http://www.w3.org/2000/svg",
2770
+ ...rest,
2771
+ children: [
2772
+ /* @__PURE__ */ jsxRuntime.jsx(
2773
+ "path",
2774
+ {
2775
+ d: "M15.5 7.5C16.0523 7.5 16.5 7.94772 16.5 8.5V15.5C16.5 16.0523 16.0523 16.5 15.5 16.5H8.5C7.94772 16.5 7.5 16.0523 7.5 15.5V8.5C7.5 7.94772 7.94772 7.5 8.5 7.5H15.5Z",
2776
+ fill: "currentColor"
2777
+ }
2778
+ ),
2779
+ /* @__PURE__ */ jsxRuntime.jsx(
2780
+ "path",
2781
+ {
2782
+ fillRule: "evenodd",
2783
+ clipRule: "evenodd",
2784
+ d: "M12 1.0498C18.0474 1.04991 22.9501 5.95263 22.9502 12C22.9501 18.0474 18.0474 22.9501 12 22.9502C5.95266 22.9501 1.04991 18.0473 1.0498 12C1.0499 5.95265 5.95266 1.04994 12 1.0498ZM12 2.5498C6.78108 2.54994 2.5499 6.78108 2.5498 12C2.54991 17.2189 6.78109 21.4501 12 21.4502C17.2189 21.4501 21.4501 17.2189 21.4502 12C21.4501 6.78106 17.2189 2.54991 12 2.5498Z",
2785
+ fill: "currentColor"
2786
+ }
2787
+ )
2788
+ ]
2789
+ }
2790
+ );
2791
+ };
2760
2792
  var Icon_store = (p) => {
2761
2793
  const { size = 24, ...rest } = p;
2762
2794
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -3256,6 +3288,7 @@ var ICONS = {
3256
3288
  shield: Icon_shield,
3257
3289
  sidebar: Icon_sidebar,
3258
3290
  star: Icon_star,
3291
+ stop: Icon_stop,
3259
3292
  store: Icon_store,
3260
3293
  synchronize: Icon_synchronize,
3261
3294
  thumbsDown: Icon_thumbsDown,
@@ -3766,409 +3799,1474 @@ function withDisplayName(component, name) {
3766
3799
  return component;
3767
3800
  }
3768
3801
 
3769
- // src/lib/z-index.ts
3770
- var Z_INDEX = {
3771
- baseFloating: 50,
3772
- modalHost: 1e3,
3773
- toast: 1100,
3774
- loading: 1200,
3775
- drag: 1300
3802
+ // src/lib/floating-anim.ts
3803
+ var FLOATING_SLIDE_ANIM = [
3804
+ "data-[state=open]:data-[side=top]:animate-floating-in-top",
3805
+ "data-[state=delayed-open]:data-[side=top]:animate-floating-in-top",
3806
+ "data-[state=instant-open]:data-[side=top]:animate-floating-in-top",
3807
+ "data-[state=closed]:data-[side=top]:animate-floating-out-top",
3808
+ "data-[state=open]:data-[side=bottom]:animate-floating-in-bottom",
3809
+ "data-[state=delayed-open]:data-[side=bottom]:animate-floating-in-bottom",
3810
+ "data-[state=instant-open]:data-[side=bottom]:animate-floating-in-bottom",
3811
+ "data-[state=closed]:data-[side=bottom]:animate-floating-out-bottom",
3812
+ "data-[state=open]:data-[side=left]:animate-floating-in-left",
3813
+ "data-[state=delayed-open]:data-[side=left]:animate-floating-in-left",
3814
+ "data-[state=instant-open]:data-[side=left]:animate-floating-in-left",
3815
+ "data-[state=closed]:data-[side=left]:animate-floating-out-left",
3816
+ "data-[state=open]:data-[side=right]:animate-floating-in-right",
3817
+ "data-[state=delayed-open]:data-[side=right]:animate-floating-in-right",
3818
+ "data-[state=instant-open]:data-[side=right]:animate-floating-in-right",
3819
+ "data-[state=closed]:data-[side=right]:animate-floating-out-right"
3820
+ ].join(" ");
3821
+ var PortalContainerContext = /* @__PURE__ */ react.createContext(null);
3822
+ var InsideModalContext = /* @__PURE__ */ react.createContext(false);
3823
+ var PortalContainerProvider = PortalContainerContext.Provider;
3824
+ var InsideModalProvider = InsideModalContext.Provider;
3825
+ function usePortalContainer() {
3826
+ return react.useContext(PortalContainerContext) ?? void 0;
3827
+ }
3828
+ function useInsideModal() {
3829
+ return react.useContext(InsideModalContext);
3830
+ }
3831
+
3832
+ // src/components/STag/tag.config.ts
3833
+ var TAG_SHAPES = ["square", "pill"];
3834
+ var TAG_SIZES = ["xs", "sm", "md"];
3835
+ var TAG_COLORS = [
3836
+ "grey",
3837
+ "red",
3838
+ "orange",
3839
+ "yellow",
3840
+ "green",
3841
+ "blue",
3842
+ "darkblue",
3843
+ "indigo"
3844
+ ];
3845
+ var TAG_SIZE_CONFIG = {
3846
+ xs: {
3847
+ height: 20,
3848
+ paddingX: 8,
3849
+ gap: 4,
3850
+ radius: 6,
3851
+ iconSize: 12,
3852
+ fontSize: 11,
3853
+ fontWeight: 500,
3854
+ lineHeight: 18
3855
+ },
3856
+ sm: {
3857
+ height: 24,
3858
+ paddingX: 8,
3859
+ gap: 4,
3860
+ radius: 6,
3861
+ iconSize: 16,
3862
+ fontSize: 12,
3863
+ fontWeight: 700,
3864
+ lineHeight: 20
3865
+ },
3866
+ md: {
3867
+ height: 28,
3868
+ paddingX: 12,
3869
+ gap: 6,
3870
+ radius: 8,
3871
+ iconSize: 16,
3872
+ fontSize: 14,
3873
+ fontWeight: 700,
3874
+ lineHeight: 24
3875
+ }
3776
3876
  };
3777
- var setRefs = (refs, value) => {
3778
- refs.forEach((ref) => {
3779
- if (typeof ref === "function") {
3780
- ref(value);
3781
- return;
3782
- }
3783
- if (ref) {
3784
- ref.current = value;
3877
+ var STag = /* @__PURE__ */ react.forwardRef(function STag2({
3878
+ shape = "square",
3879
+ size = "sm",
3880
+ color = "grey",
3881
+ deep = false,
3882
+ label = "",
3883
+ icon,
3884
+ iconLeft = true,
3885
+ width,
3886
+ className,
3887
+ style,
3888
+ ...rest
3889
+ }, ref) {
3890
+ const s = TAG_SIZE_CONFIG[size];
3891
+ const contentColor = deep ? `var(--cmp-tag-${color}-deep-content, var(--cmp-tag-deep-content))` : `var(--cmp-tag-${color}-content)`;
3892
+ const bgColor = deep ? `var(--cmp-tag-${color}-deep-bg)` : `var(--cmp-tag-${color}-bg)`;
3893
+ const iconNode = icon ? /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.iconSize, color: contentColor }) : null;
3894
+ const resolvedWidth = typeof width === "number" ? `${width}px` : width;
3895
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3896
+ "span",
3897
+ {
3898
+ ref,
3899
+ "aria-label": label || "tag",
3900
+ className: cn(
3901
+ "box-border inline-flex w-fit select-none items-center justify-center whitespace-nowrap",
3902
+ className
3903
+ ),
3904
+ style: {
3905
+ minHeight: s.height,
3906
+ padding: `0 ${s.paddingX}px`,
3907
+ gap: s.gap,
3908
+ borderRadius: shape === "pill" ? 999 : s.radius,
3909
+ fontSize: s.fontSize,
3910
+ fontWeight: s.fontWeight,
3911
+ lineHeight: `${s.lineHeight}px`,
3912
+ background: bgColor,
3913
+ color: contentColor,
3914
+ ...resolvedWidth != null ? { width: resolvedWidth } : {},
3915
+ ...style
3916
+ },
3917
+ ...rest,
3918
+ children: [
3919
+ icon && iconLeft && iconNode,
3920
+ label && // leading-none: 태그는 min-height 로 높이가 정해지고 flex 가 세로 중앙 정렬하므로
3921
+ // 레이블에 line-height 가 필요 없다. 오히려 line-height 를 주면 브라우저가
3922
+ // 폰트 ascent/descent 를 정수 px 로 반올림한 뒤 남는 half-leading 을 내림 처리해
3923
+ // 베이스라인이 최대 1px 위로 밀린다(xs 에서 특히 눈에 띈다).
3924
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex min-w-0 items-center justify-center leading-none", children: label }),
3925
+ icon && !iconLeft && iconNode
3926
+ ]
3785
3927
  }
3786
- });
3928
+ );
3929
+ });
3930
+ var SIZE = {
3931
+ xxs: { icon: 12, radius: "sm" },
3932
+ xs: { icon: 16, radius: "sm" },
3933
+ sm: { icon: 20, radius: "sm" },
3934
+ md: { icon: 24, radius: "md" },
3935
+ lg: { icon: 32, radius: "md" }
3787
3936
  };
3788
- function renderSlot(slot, state2) {
3789
- return typeof slot === "function" ? slot(state2) : slot;
3790
- }
3791
- var SDraggableItemView = /* @__PURE__ */ react.forwardRef(
3792
- function SDraggableItemView2({
3793
- title,
3794
- supportingText,
3795
- supportingTextPosition,
3796
- trailing,
3797
- depth,
3798
- accentStripe,
3799
- bordered,
3800
- selected,
3801
- dense,
3802
- size,
3803
- dragging,
3804
- disabled,
3805
- hovered,
3806
- dragHandleProps,
3807
- onDragHandleMouseDown,
3808
- className,
3809
- style,
3810
- ...rest
3811
- }, ref) {
3812
- const renderState = { hovered, dragging, disabled };
3813
- const titleNode = renderSlot(title, renderState);
3814
- const supportingTextNode = renderSlot(supportingText, renderState);
3815
- const hasSupportingText = supportingTextNode != null && supportingTextNode !== "";
3816
- const trailingNode = renderSlot(trailing, renderState);
3817
- const titleTypo = size === "sm" ? selected ? "typo-item-sm-selected" : "typo-item-sm-default" : selected ? "typo-item-md-selected" : "typo-item-md-default";
3818
- const paddingYClass = dense ? size === "sm" ? "py-(--cmp-listDraggableItem-paddingY-sm-dense)" : "py-(--cmp-listDraggableItem-paddingY-md-dense)" : size === "sm" ? "py-(--cmp-listDraggableItem-paddingY-sm-normal)" : "py-(--cmp-listDraggableItem-paddingY-md-normal)";
3819
- const normalizedDepth = Math.max(1, Math.floor(depth));
3820
- const depthOffset = (normalizedDepth - 1) * 20;
3821
- const depthStyle = depthOffset === 0 ? style : bordered ? {
3822
- ...style,
3823
- marginLeft: depthOffset,
3824
- width: `calc(100% - ${depthOffset}px)`
3825
- } : {
3826
- ...style,
3827
- paddingLeft: `calc(var(--cmp-listDraggableItem-paddingX) + ${depthOffset}px)`
3828
- };
3829
- return /* @__PURE__ */ jsxRuntime.jsxs(
3830
- "div",
3831
- {
3832
- ref,
3833
- "aria-disabled": disabled || void 0,
3834
- "data-dragging": dragging || void 0,
3835
- style: depthStyle,
3836
- className: cn(
3837
- "relative flex w-full items-center gap-(--cmp-listDraggableItem-gap) overflow-hidden px-(--cmp-listDraggableItem-paddingX)",
3838
- titleTypo,
3839
- disabled ? "bg-(--cmp-listDraggableItem-bg-default) text-(--sys-color-disabled-text)" : selected ? "bg-(--cmp-listDraggableItem-bg-selected) text-(--cmp-listDraggableItem-text-selected)" : "bg-(--cmp-listDraggableItem-bg-default) text-(--cmp-listDraggableItem-text-default)",
3840
- !disabled && !selected && !dragging && cn(
3841
- "hover:bg-(--cmp-listDraggableItem-bg-hover)",
3842
- // 리스트가 드래그 중이면(SDraggableList 가 data-drag-active 를 붙인다) hover 를 무효화한다.
3843
- "[[data-drag-active]_&]:hover:bg-(--cmp-listDraggableItem-bg-default)"
3844
- ),
3845
- // 높이는 세로 패딩과 내용이 정한다 — 항목에 높이를 못 박지 않는다 (SListItem 과 동일)
3846
- paddingYClass,
3847
- // 항목 사이 구분선은 리스트가 그린다.
3848
- bordered && cn(
3849
- "rounded-(--cmp-listDraggableItem-radius-bordered) border border-(--cmp-listDraggableItem-border-default)",
3850
- !disabled && !selected && !dragging && cn(
3851
- "hover:border-(--cmp-listDraggableItem-bordered-border-hover)",
3852
- "[[data-drag-active]_&]:hover:border-(--cmp-listDraggableItem-border-default)"
3853
- ),
3854
- !disabled && selected && !dragging && "border-(--cmp-listDraggableItem-bordered-border-selected) bg-(--cmp-listDraggableItem-bordered-bg-selected)",
3855
- !disabled && selected && dragging && "border-(--cmp-listDraggableItem-bordered-dragging-border-color) bg-(--cmp-listDraggableItem-bordered-dragging-bg)"
3856
- ),
3857
- disabled && "cursor-not-allowed",
3858
- className
3859
- ),
3860
- ...rest,
3861
- children: [
3862
- accentStripe && selected && !bordered && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
3863
- "span",
3864
- {
3865
- "aria-hidden": "true",
3866
- className: "pointer-events-none absolute bottom-[-1px] left-0 top-0 w-(--cmp-listDraggableItem-accentStripe-width) bg-(--cmp-listDraggableItem-accentStripe-color)"
3867
- }
3868
- ),
3869
- /* @__PURE__ */ jsxRuntime.jsx(
3870
- "div",
3871
- {
3872
- ...dragHandleProps,
3873
- role: dragHandleProps?.role ?? "button",
3874
- tabIndex: dragHandleProps?.tabIndex ?? (disabled ? -1 : 0),
3875
- "aria-label": dragHandleProps?.["aria-label"] ?? "\uB4DC\uB798\uADF8 \uD578\uB4E4",
3876
- "aria-disabled": disabled || void 0,
3877
- onMouseDown: onDragHandleMouseDown,
3878
- className: cn(
3879
- "flex size-[24px] shrink-0 select-none items-center justify-center rounded-[4px]",
3880
- disabled ? "cursor-not-allowed opacity-0" : "cursor-grab hover:bg-black/5 active:cursor-grabbing",
3881
- !disabled && !dragging && "[[data-drag-active]_&]:hover:bg-transparent",
3882
- dragging && "bg-black/5",
3883
- dragHandleProps?.className
3884
- ),
3885
- children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "drag", size: 16, color: "grey_65" })
3886
- }
3887
- ),
3888
- /* @__PURE__ */ jsxRuntime.jsxs(
3889
- "div",
3890
- {
3891
- className: cn(
3892
- "flex min-w-0 flex-1 items-center gap-[8px]",
3893
- dragging && "opacity-(--cmp-listDraggableItem-dragging-content-opacity)"
3894
- ),
3895
- children: [
3896
- /* @__PURE__ */ jsxRuntime.jsxs(
3897
- "span",
3898
- {
3899
- className: cn(
3900
- "min-w-0 flex-1",
3901
- supportingTextPosition === "bottom" ? "flex flex-col" : "flex items-center gap-[8px] whitespace-nowrap"
3902
- ),
3903
- children: [
3904
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: titleNode }),
3905
- hasSupportingText && /* @__PURE__ */ jsxRuntime.jsx(
3906
- "span",
3907
- {
3908
- className: cn(
3909
- "typo-body-sm-default min-w-0 truncate text-(--sys-color-fg-secondary)",
3910
- supportingTextPosition === "right" && "flex-1"
3911
- ),
3912
- children: supportingTextNode
3913
- }
3914
- )
3915
- ]
3916
- }
3917
- ),
3918
- trailingNode
3919
- ]
3920
- }
3921
- )
3922
- ]
3923
- }
3924
- );
3925
- }
3926
- );
3927
- var SDraggableItemImpl = /* @__PURE__ */ react.forwardRef(function SDraggableItem({
3928
- title,
3929
- supportingText,
3930
- supportingTextPosition = "right",
3931
- trailing,
3932
- depth = 1,
3933
- accentStripe = false,
3934
- bordered = false,
3935
- selected = false,
3936
- dragging = false,
3937
- dense = false,
3937
+ var TOOLTIP_HIDE_DELAY_MS = 100;
3938
+ function SGhostButton({
3939
+ icon,
3938
3940
  size = "sm",
3941
+ intent = "default",
3942
+ ariaLabel = "",
3943
+ ariaPressed,
3944
+ ariaExpanded,
3945
+ tooltipText,
3939
3946
  disabled = false,
3940
- dragOverlay = true,
3941
- dragOverlayOpacity = 0.75,
3942
- dragHandleProps,
3943
- onDragHandleMouseDown,
3947
+ onClick,
3948
+ anchorClassName,
3949
+ anchorStyle,
3944
3950
  className,
3945
- onMouseEnter,
3946
- onMouseLeave,
3947
- ...rest
3948
- }, ref) {
3949
- const [hovered, setHovered] = react.useState(false);
3950
- const [overlay, setOverlay] = react.useState(null);
3951
- const rootRef = react.useRef(null);
3952
- const overlayVisible = overlay != null;
3953
- const handleMouseEnter = (event) => {
3954
- if (!disabled) setHovered(true);
3955
- onMouseEnter?.(event);
3951
+ style
3952
+ }) {
3953
+ const portalContainer = usePortalContainer();
3954
+ const [open, setOpen] = react.useState(false);
3955
+ const hideTimer = react.useRef(void 0);
3956
+ const cancelHide = () => {
3957
+ if (hideTimer.current) {
3958
+ clearTimeout(hideTimer.current);
3959
+ hideTimer.current = void 0;
3960
+ }
3956
3961
  };
3957
- const handleMouseLeave = (event) => {
3958
- setHovered(false);
3959
- onMouseLeave?.(event);
3962
+ const openNow = () => {
3963
+ cancelHide();
3964
+ setOpen(true);
3960
3965
  };
3961
- const handleDragMouseDown = (event) => {
3962
- if (disabled) return;
3963
- const rect = rootRef.current?.getBoundingClientRect();
3964
- if (dragOverlay && rect) {
3965
- setOverlay({
3966
- offsetX: event.clientX - rect.left,
3967
- offsetY: event.clientY - rect.top,
3968
- rect,
3969
- x: event.clientX,
3970
- y: event.clientY
3971
- });
3972
- }
3973
- dragHandleProps?.onMouseDown?.(event);
3974
- onDragHandleMouseDown?.(event);
3966
+ const startHide = () => {
3967
+ cancelHide();
3968
+ hideTimer.current = setTimeout(() => setOpen(false), TOOLTIP_HIDE_DELAY_MS);
3975
3969
  };
3976
- react.useEffect(() => {
3977
- if (!overlayVisible) return void 0;
3978
- const handleMouseMove = (event) => {
3979
- setOverlay(
3980
- (current) => current ? {
3981
- ...current,
3982
- x: event.clientX,
3983
- y: event.clientY
3984
- } : current
3985
- );
3986
- };
3987
- const handleMouseUp = () => {
3988
- setOverlay(null);
3989
- };
3990
- window.addEventListener("mousemove", handleMouseMove);
3991
- window.addEventListener("mouseup", handleMouseUp);
3992
- return () => {
3993
- window.removeEventListener("mousemove", handleMouseMove);
3994
- window.removeEventListener("mouseup", handleMouseUp);
3995
- };
3996
- }, [overlayVisible]);
3997
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3998
- /* @__PURE__ */ jsxRuntime.jsx(
3999
- SDraggableItemView,
4000
- {
4001
- ref: (node) => setRefs([rootRef, ref], node),
4002
- title,
4003
- supportingText,
4004
- supportingTextPosition,
4005
- trailing,
4006
- depth,
4007
- accentStripe,
4008
- bordered,
4009
- selected,
4010
- dense,
4011
- size,
4012
- dragging: dragging || overlayVisible,
4013
- disabled,
4014
- hovered,
4015
- dragHandleProps,
4016
- onDragHandleMouseDown: handleDragMouseDown,
4017
- onMouseEnter: handleMouseEnter,
4018
- onMouseLeave: handleMouseLeave,
4019
- className,
4020
- ...rest
4021
- }
4022
- ),
4023
- overlay && typeof document !== "undefined" && reactDom.createPortal(
4024
- /* @__PURE__ */ jsxRuntime.jsx(
4025
- "div",
4026
- {
4027
- className: "pointer-events-none fixed",
4028
- style: {
4029
- left: overlay.x - overlay.offsetX,
4030
- opacity: dragOverlayOpacity,
4031
- top: overlay.y - overlay.offsetY,
4032
- width: overlay.rect.width,
4033
- zIndex: Z_INDEX.drag
4034
- },
4035
- children: /* @__PURE__ */ jsxRuntime.jsx(
4036
- SDraggableItemView,
4037
- {
4038
- title,
4039
- supportingText,
4040
- supportingTextPosition,
4041
- trailing,
4042
- depth,
4043
- accentStripe,
4044
- bordered,
4045
- selected,
4046
- dense,
4047
- size,
4048
- dragging: true,
4049
- disabled,
4050
- hovered: true,
4051
- dragHandleProps,
4052
- className
4053
- }
4054
- )
4055
- }
4056
- ),
4057
- document.body
4058
- )
4059
- ] });
4060
- });
4061
- var SDraggableItem2 = /* @__PURE__ */ withDisplayName(SDraggableItemImpl, "SDraggableItem");
4062
- var SDivider = /* @__PURE__ */ react.forwardRef(function SDivider2({ vertical = false, height, className, style, ...rest }, ref) {
4063
- const fixedHeight = vertical && height !== void 0;
4064
- return /* @__PURE__ */ jsxRuntime.jsx(
4065
- "hr",
3970
+ const s = SIZE[size];
3971
+ const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
3972
+ const text = (tooltipText ?? "").trim();
3973
+ const hasTooltip = text !== "";
3974
+ const btn2 = /* @__PURE__ */ jsxRuntime.jsxs(
3975
+ "button",
4066
3976
  {
4067
- ref,
4068
- "aria-orientation": vertical ? "vertical" : "horizontal",
3977
+ type: "button",
3978
+ disabled,
3979
+ "aria-disabled": disabled ? "true" : void 0,
3980
+ "aria-label": ariaLabel.trim() || void 0,
3981
+ "aria-pressed": ariaPressed,
3982
+ "aria-expanded": ariaExpanded,
3983
+ tabIndex: disabled ? -1 : void 0,
3984
+ onClick: (e) => {
3985
+ if (disabled) {
3986
+ e.preventDefault();
3987
+ return;
3988
+ }
3989
+ onClick?.(e);
3990
+ },
4069
3991
  className: cn(
4070
- "shrink-0 border-0",
4071
- vertical ? cn("inline-block w-px", fixedHeight ? "self-center" : "h-auto self-stretch") : "block h-px w-full",
3992
+ "group/ghost-button relative box-border inline-flex items-center justify-center border-none bg-transparent p-0 transition-shadow",
3993
+ disabled ? "cursor-not-allowed" : "cursor-pointer",
3994
+ "focus-visible:outline-none focus-visible:shadow-[0_0_0_2px_var(--cmp-ghostButton-action-hover-bg)]",
4072
3995
  className
4073
3996
  ),
4074
3997
  style: {
4075
- backgroundColor: "var(--sys-color-divider-default, #E1E1E1)",
4076
- ...fixedHeight ? { height: typeof height === "number" ? `${height}px` : height } : null,
3998
+ width: `var(--cmp-ghostButton-${size}-size)`,
3999
+ height: `var(--cmp-ghostButton-${size}-size)`,
4000
+ borderRadius: `var(--cmp-ghostButton-radius-${s.radius})`,
4077
4001
  ...style
4078
4002
  },
4079
- ...rest
4003
+ children: [
4004
+ !disabled && /* @__PURE__ */ jsxRuntime.jsx(
4005
+ "span",
4006
+ {
4007
+ "aria-hidden": true,
4008
+ className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover/ghost-button:opacity-5",
4009
+ style: { background: `var(--cmp-ghostButton-${intent}-hover-bg)` }
4010
+ }
4011
+ ),
4012
+ /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: contentColor })
4013
+ ]
4080
4014
  }
4081
4015
  );
4082
- });
4083
- var PADDING_CLASS = {
4084
- default: "p-(--cmp-list-group-padding)",
4085
- none: ""
4016
+ if (!hasTooltip) return btn2;
4017
+ return /* @__PURE__ */ jsxRuntime.jsxs(RPopover12__namespace.Root, { open, onOpenChange: setOpen, children: [
4018
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4019
+ "span",
4020
+ {
4021
+ className: cn("inline-flex", anchorClassName),
4022
+ style: anchorStyle,
4023
+ onMouseEnter: openNow,
4024
+ onMouseLeave: startHide,
4025
+ onFocus: openNow,
4026
+ onBlur: startHide,
4027
+ children: btn2
4028
+ }
4029
+ ) }),
4030
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
4031
+ RPopover12__namespace.Content,
4032
+ {
4033
+ side: "top",
4034
+ sideOffset: 4,
4035
+ className: cn("z-50", FLOATING_SLIDE_ANIM),
4036
+ "aria-hidden": true,
4037
+ onOpenAutoFocus: (e) => e.preventDefault(),
4038
+ onMouseEnter: cancelHide,
4039
+ onMouseLeave: startHide,
4040
+ children: /* @__PURE__ */ jsxRuntime.jsx(STag, { shape: "square", size: "xs", color: "grey", label: text })
4041
+ }
4042
+ ) })
4043
+ ] });
4044
+ }
4045
+
4046
+ // src/lib/sanitize-inline-html.ts
4047
+ var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
4048
+ var ALLOWED_ATTRS_BY_TAG = {
4049
+ A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
4086
4050
  };
4087
- var dividerStyle = { backgroundColor: "var(--cmp-listItem-border-default)" };
4088
- function hasBorderedChild(children) {
4089
- let bordered = false;
4090
- react.Children.forEach(children, (child) => {
4091
- if (!react.isValidElement(child)) return;
4092
- if (child.props.bordered === true) {
4093
- bordered = true;
4094
- return;
4051
+ var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
4052
+ var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
4053
+ var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
4054
+ "SCRIPT",
4055
+ "STYLE",
4056
+ "IFRAME",
4057
+ "OBJECT",
4058
+ "EMBED",
4059
+ "META",
4060
+ "LINK",
4061
+ "BASE",
4062
+ "NOSCRIPT",
4063
+ // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
4064
+ // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
4065
+ "SVG",
4066
+ "MATH",
4067
+ "TEMPLATE"
4068
+ ]);
4069
+ var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4070
+ var sanitizeNode = (node) => {
4071
+ if (node.nodeType === Node.COMMENT_NODE) {
4072
+ node.remove();
4073
+ return;
4074
+ }
4075
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
4076
+ const element = node;
4077
+ const tagName = element.tagName.toUpperCase();
4078
+ if (DROP_CONTENT_TAGS.has(tagName)) {
4079
+ element.remove();
4080
+ return;
4081
+ }
4082
+ Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
4083
+ if (ALLOWED_INLINE_TAGS.has(tagName)) {
4084
+ if (element.hasAttribute("className")) {
4085
+ const val = element.getAttribute("className");
4086
+ element.removeAttribute("className");
4087
+ if (!element.hasAttribute("class")) element.setAttribute("class", val);
4095
4088
  }
4096
- react.Children.forEach(child.props.children, (wrapped) => {
4097
- if (react.isValidElement(wrapped) && wrapped.props.bordered === true) {
4098
- bordered = true;
4099
- }
4089
+ const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
4090
+ Array.from(element.attributes).forEach((attr) => {
4091
+ if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
4100
4092
  });
4101
- });
4102
- return bordered;
4103
- }
4104
- function withDividers(children) {
4105
- return react.Children.toArray(children).flatMap(
4106
- (child, index) => index === 0 ? [child] : [
4107
- /* @__PURE__ */ jsxRuntime.jsx(SDivider, { "aria-hidden": "true", style: dividerStyle }, `s-list-divider-${index}`),
4108
- child
4109
- ]
4110
- );
4111
- }
4112
- var SListImpl = /* @__PURE__ */ react.forwardRef(function SList({ className, children, gap = false, padding = "none", ...rest }, ref) {
4113
- const showDivider = !gap && !hasBorderedChild(children);
4114
- return /* @__PURE__ */ jsxRuntime.jsx(
4093
+ if (tagName === "A") {
4094
+ const href = element.getAttribute("href");
4095
+ if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
4096
+ if (element.hasAttribute("target")) {
4097
+ element.setAttribute("rel", "noopener noreferrer");
4098
+ }
4099
+ }
4100
+ return;
4101
+ }
4102
+ const parent = element.parentNode;
4103
+ if (parent == null) return;
4104
+ while (element.firstChild) parent.insertBefore(element.firstChild, element);
4105
+ parent.removeChild(element);
4106
+ };
4107
+ var sanitizeInlineHtml = (value) => {
4108
+ if (typeof document === "undefined") return escapeHtml(value);
4109
+ const template = document.createElement("template");
4110
+ template.innerHTML = value;
4111
+ Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
4112
+ return template.innerHTML;
4113
+ };
4114
+ var HIDE_DELAY_MS = 100;
4115
+ function useBody(content, message = []) {
4116
+ if (content != null) return content;
4117
+ if (message.length === 0) return null;
4118
+ const singleLine = message.length === 1;
4119
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsxRuntime.jsx(
4115
4120
  "div",
4116
4121
  {
4117
- ref,
4118
- className: cn(
4119
- gap && "flex flex-col gap-(--cmp-list-group-gap)",
4120
- PADDING_CLASS[padding],
4121
- className
4122
- ),
4123
- ...rest,
4124
- children: showDivider ? withDividers(children) : children
4125
- }
4126
- );
4127
- });
4128
- var SList2 = /* @__PURE__ */ withDisplayName(SListImpl, "SList");
4129
- var DraggableGroupContext = /* @__PURE__ */ react.createContext(null);
4130
- function useSDraggableGroup() {
4131
- return react.useContext(DraggableGroupContext);
4122
+ className: singleLine ? "leading-[24px]" : void 0,
4123
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(line) }
4124
+ },
4125
+ i
4126
+ )) });
4132
4127
  }
4133
- function SDraggableProviderInternal({
4128
+ var STooltipImpl = /* @__PURE__ */ react.forwardRef(function STooltip({
4129
+ content,
4130
+ message = [],
4131
+ open: openProp,
4132
+ onOpenChange,
4133
+ trigger = "hover",
4134
+ placement = "top",
4135
+ sideOffset = 8,
4136
+ tooltipType = "default",
4137
+ showClose = false,
4138
+ disabled = false,
4134
4139
  children,
4135
- defaultGroup,
4136
- legacyOnMove
4137
- }) {
4138
- const listsRef = react.useRef(/* @__PURE__ */ new Map());
4139
- const legacyOnMoveRef = react.useRef(legacyOnMove);
4140
- const pointerRef = react.useRef(null);
4141
- const [dragging, setDragging] = react.useState(null);
4142
- const [drop, setDrop] = react.useState(null);
4143
- const dropRef = react.useRef(drop);
4144
- legacyOnMoveRef.current = legacyOnMove;
4145
- dropRef.current = drop;
4146
- const registerList = react.useCallback(
4147
- (group, listId, element, items, onChange) => {
4148
- const key = `${group}:${listId}`;
4149
- if (element) listsRef.current.set(key, { element, items, onChange });
4150
- else listsRef.current.delete(key);
4140
+ icon = "helpOutline",
4141
+ iconSize = 12,
4142
+ color = "#01BB4B",
4143
+ ariaLabel,
4144
+ className,
4145
+ triggerClassName,
4146
+ style,
4147
+ contentProps
4148
+ }, ref) {
4149
+ const portalContainer = usePortalContainer();
4150
+ const [internalOpen, setInternalOpen] = react.useState(false);
4151
+ const isControlled = openProp !== void 0;
4152
+ const open = isControlled ? openProp : internalOpen;
4153
+ const hideTimer = react.useRef(void 0);
4154
+ const cancelHide = react.useCallback(() => {
4155
+ if (hideTimer.current) {
4156
+ clearTimeout(hideTimer.current);
4157
+ hideTimer.current = void 0;
4158
+ }
4159
+ }, []);
4160
+ const setOpenState = react.useCallback(
4161
+ (next) => {
4162
+ if (!isControlled) setInternalOpen(next);
4163
+ onOpenChange?.(next);
4151
4164
  },
4152
- []
4165
+ [isControlled, onOpenChange]
4153
4166
  );
4154
- const startDrag = react.useCallback(
4155
- (event, group, listId, index, itemKey) => {
4156
- event.preventDefault();
4157
- setDragging({ group, listId, index, itemKey });
4158
- setDrop({ group, listId, index });
4159
- },
4160
- []
4167
+ const openNow = () => {
4168
+ cancelHide();
4169
+ setOpenState(true);
4170
+ };
4171
+ const startHide = () => {
4172
+ if (showClose) return;
4173
+ cancelHide();
4174
+ hideTimer.current = setTimeout(() => setOpenState(false), HIDE_DELAY_MS);
4175
+ };
4176
+ react.useImperativeHandle(
4177
+ ref,
4178
+ () => ({
4179
+ show: () => {
4180
+ cancelHide();
4181
+ setOpenState(true);
4182
+ },
4183
+ hide: () => {
4184
+ cancelHide();
4185
+ setOpenState(false);
4186
+ }
4187
+ }),
4188
+ [cancelHide, setOpenState]
4161
4189
  );
4162
- react.useEffect(() => {
4163
- if (!dragging) return void 0;
4164
- let autoScrollFrame = null;
4165
- const findListAtPointer = (clientX, clientY) => {
4166
- const element = document.elementFromPoint(clientX, clientY);
4167
- const listElement = element?.closest("[data-draggable-list-id]");
4168
- const group = listElement?.dataset.draggableGroup;
4169
- const listId = listElement?.dataset.draggableListId;
4170
- const list = group && listId ? listsRef.current.get(`${group}:${listId}`) : void 0;
4171
- return group && listId && list ? { group, listId, list } : null;
4190
+ const isHover = trigger === "hover";
4191
+ const isNone = trigger === "none";
4192
+ if (isNone && children == null && typeof console !== "undefined") {
4193
+ console.warn(
4194
+ '[STooltip] trigger="none" \uC740 \uC704\uCE58 \uAE30\uC900\uC774 \uB420 children \uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uC544\uC774\uCF58 \uD2B8\uB9AC\uAC70\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.'
4195
+ );
4196
+ }
4197
+ const body = useBody(content, message);
4198
+ const bg = `var(--cmp-tooltip-${tooltipType}-bg)`;
4199
+ const fg = `var(--cmp-tooltip-${tooltipType}-content)`;
4200
+ const triggerNode = children ?? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("inline-flex cursor-pointer", triggerClassName), "aria-label": ariaLabel, children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: iconSize, color }) });
4201
+ if (disabled || body == null) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: triggerNode });
4202
+ const {
4203
+ className: contentPropsClassName,
4204
+ style: contentPropsStyle,
4205
+ ...restContentProps
4206
+ } = contentProps ?? {};
4207
+ const contentClass = cn(
4208
+ "z-50 w-fit max-w-[var(--radix-popover-content-available-width)] rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px] shadow-normal-sm",
4209
+ FLOATING_SLIDE_ANIM,
4210
+ className,
4211
+ contentPropsClassName
4212
+ );
4213
+ const contentStyle = {
4214
+ background: bg,
4215
+ color: fg,
4216
+ fontWeight: tooltipType === "default" ? 500 : 700,
4217
+ ...style,
4218
+ ...contentPropsStyle
4219
+ };
4220
+ return /* @__PURE__ */ jsxRuntime.jsxs(RPopover12__namespace.Root, { open, onOpenChange: setOpenState, children: [
4221
+ isNone ? (
4222
+ // 트리거 없음: children 을 래퍼 없이 그대로 앵커로 쓴다 (소비자 레이아웃 보존).
4223
+ // mouse/focus 핸들러가 없으므로 showClose 없이도 자동 닫힘이 발생하지 않는다.
4224
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Anchor, { asChild: true, children: triggerNode })
4225
+ ) : isHover ? (
4226
+ // hover: Anchor(위치 기준) + 직접 mouse 핸들러 — 클릭으로 토글되지 않도록 Trigger 미사용
4227
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4228
+ "span",
4229
+ {
4230
+ className: "inline-flex",
4231
+ onMouseEnter: openNow,
4232
+ onMouseLeave: startHide,
4233
+ onFocus: openNow,
4234
+ onBlur: startHide,
4235
+ children: triggerNode
4236
+ }
4237
+ ) })
4238
+ ) : /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Trigger, { asChild: true, children: triggerNode }),
4239
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
4240
+ RPopover12__namespace.Content,
4241
+ {
4242
+ side: placement,
4243
+ sideOffset,
4244
+ className: contentClass,
4245
+ style: contentStyle,
4246
+ onOpenAutoFocus: (e) => e.preventDefault(),
4247
+ onInteractOutside: (e) => {
4248
+ if (showClose || isNone) e.preventDefault();
4249
+ },
4250
+ onEscapeKeyDown: (e) => {
4251
+ if (showClose || isNone) e.preventDefault();
4252
+ },
4253
+ onMouseEnter: isHover ? cancelHide : void 0,
4254
+ onMouseLeave: isHover ? startHide : void 0,
4255
+ ...restContentProps,
4256
+ children: [
4257
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-[12px]", children: [
4258
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: body }),
4259
+ showClose && !isNone && /* @__PURE__ */ jsxRuntime.jsx(
4260
+ SGhostButton,
4261
+ {
4262
+ icon: "close",
4263
+ size: "xs",
4264
+ intent: tooltipType === "default" ? "inverse" : "default",
4265
+ ariaLabel: "\uB2EB\uAE30",
4266
+ className: "shrink-0",
4267
+ onClick: () => {
4268
+ cancelHide();
4269
+ setOpenState(false);
4270
+ }
4271
+ }
4272
+ )
4273
+ ] }),
4274
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Arrow, { width: 16, height: 12, style: { fill: bg } })
4275
+ ]
4276
+ }
4277
+ ) })
4278
+ ] });
4279
+ });
4280
+ var STooltip2 = /* @__PURE__ */ withDisplayName(STooltipImpl, "STooltip");
4281
+ function runRules(value, rules) {
4282
+ if (!rules || rules.length === 0) return "";
4283
+ for (const rule of rules) {
4284
+ const result = rule(value ?? "");
4285
+ if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
4286
+ }
4287
+ return "";
4288
+ }
4289
+ var FormContext = /* @__PURE__ */ react.createContext(null);
4290
+ function useFormContext() {
4291
+ return react.useContext(FormContext);
4292
+ }
4293
+ function useFormField(field) {
4294
+ const ctx = useFormContext();
4295
+ const cbRef = react.useRef(field);
4296
+ cbRef.current = field;
4297
+ const name = field.name ?? "";
4298
+ react.useEffect(() => {
4299
+ if (!ctx) return;
4300
+ return ctx.register({
4301
+ name,
4302
+ validate: () => cbRef.current.validate(),
4303
+ resetValidation: () => cbRef.current.resetValidation()
4304
+ });
4305
+ }, [ctx, name]);
4306
+ }
4307
+ var FIELD_SIZE = {
4308
+ sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 16 },
4309
+ md: { height: 36, radius: 6, fieldGap: 16, labelGap: 8, fontSize: 14, lineHeight: 24, icon: 16 }
4310
+ };
4311
+ var ALIGN = {
4312
+ start: "flex-start",
4313
+ center: "center",
4314
+ end: "flex-end"
4315
+ };
4316
+ var FOCUS_RING_SHADOW = "0px 0px 4px 0px #0071ff66";
4317
+ var cssLen = (v) => v === void 0 || v === "" || v === 0 ? void 0 : typeof v === "number" ? `${v}px` : v;
4318
+ var SField = /* @__PURE__ */ react.forwardRef(function SField2({
4319
+ name,
4320
+ label = "",
4321
+ icon,
4322
+ iconColor,
4323
+ labelTooltip = "",
4324
+ labelTooltipProps,
4325
+ labelWidth,
4326
+ labelHeight,
4327
+ size = "sm",
4328
+ addonLabel = "",
4329
+ addonAlign = "start",
4330
+ error = false,
4331
+ errorMessage = "",
4332
+ status,
4333
+ focused = false,
4334
+ hovered = false,
4335
+ rules: _rules,
4336
+ hint = "",
4337
+ disabled = false,
4338
+ readOnly = false,
4339
+ width,
4340
+ minWidth,
4341
+ maxWidth,
4342
+ multiline = false,
4343
+ borderless = false,
4344
+ children,
4345
+ className,
4346
+ style,
4347
+ onMouseEnter,
4348
+ onMouseLeave
4349
+ }, ref) {
4350
+ const rootRef = react.useRef(null);
4351
+ react.useImperativeHandle(
4352
+ ref,
4353
+ () => ({
4354
+ focus: () => {
4355
+ const root = rootRef.current;
4356
+ if (!root) return;
4357
+ const focusable = root.querySelector(
4358
+ "input,textarea,select,button,[tabindex]"
4359
+ );
4360
+ focusable?.focus();
4361
+ root.scrollIntoView({ block: "nearest" });
4362
+ }
4363
+ }),
4364
+ []
4365
+ );
4366
+ const s = FIELD_SIZE[size];
4367
+ if (label !== "" && addonLabel !== "" && typeof console !== "undefined") {
4368
+ console.warn(
4369
+ "[SField] label \uACFC addonLabel \uC740 \uD568\uAED8 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. addonLabel \uC744 \uBB34\uC2DC\uD569\uB2C8\uB2E4."
4370
+ );
4371
+ }
4372
+ const effectiveAddonLabel = label !== "" ? "" : addonLabel;
4373
+ const resolvedError = status === "error" || status === void 0 && error;
4374
+ const resolvedSuccess = status === "pass";
4375
+ const showError = resolvedError && errorMessage !== "";
4376
+ const interactive = !disabled && !readOnly;
4377
+ const isPercentWidth = typeof width === "string" && width.trim().endsWith("%");
4378
+ const resolvedWidth = fieldWidthValue(width);
4379
+ const hug = resolvedWidth !== void 0 && !isPercentWidth;
4380
+ const addonWidth = effectiveAddonLabel !== "" ? cssLen(labelWidth) : void 0;
4381
+ const maxWidthValue = fieldWidthValue(maxWidth);
4382
+ const resolvedMaxWidth = maxWidthValue === void 0 || effectiveAddonLabel === "" ? maxWidthValue : addonWidth ? `calc(${maxWidthValue} + ${addonWidth})` : void 0;
4383
+ const borderColor = borderless ? "transparent" : disabled ? "var(--color-grey-45)" : (
4384
+ // focus·hover(제어 prop) 가 error/pass 보다 우선 — 실제 :hover/:focus-within 클래스도
4385
+ // 같은 순서로 --field-bc 를 덮으므로, prop 으로 흉내 낸 상태도 그와 같게 둔다.
4386
+ interactive && (focused || hovered) ? "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)"
4387
+ );
4388
+ const bgColor = borderless ? "transparent" : disabled ? "var(--color-grey-30)" : readOnly ? "var(--sys-color-field-bg-readOnly)" : "var(--sys-color-field-bg-default)";
4389
+ const textColor = disabled ? "var(--color-grey-65)" : readOnly ? "var(--sys-color-field-text-readOnly)" : "var(--sys-color-field-text-default)";
4390
+ return /* @__PURE__ */ jsxRuntime.jsx(
4391
+ "div",
4392
+ {
4393
+ ref: rootRef,
4394
+ "data-name": name || void 0,
4395
+ className: cn(
4396
+ "h-fit flex-col",
4397
+ // hug 시 inline-flex 를 유지하면 기존에 세로로 쌓여 있던 필드들이 한 줄에 붙어버리므로
4398
+ // 블록 레벨(flex)로 두어 쌓임은 그대로 유지한다. 나란히 놓으려면 부모를 flex 로.
4399
+ hug ? "flex w-fit max-w-full" : "inline-flex w-full",
4400
+ className
4401
+ ),
4402
+ style,
4403
+ onMouseEnter,
4404
+ onMouseLeave,
4405
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-start", children: [
4406
+ label !== "" && /* @__PURE__ */ jsxRuntime.jsxs(
4407
+ "div",
4408
+ {
4409
+ className: "flex shrink-0 items-center font-bold text-(--sys-color-field-text-default)",
4410
+ style: {
4411
+ height: cssLen(labelHeight) ?? s.height,
4412
+ marginRight: s.fieldGap,
4413
+ width: cssLen(labelWidth),
4414
+ fontSize: s.fontSize,
4415
+ lineHeight: `${s.lineHeight}px`
4416
+ },
4417
+ children: [
4418
+ icon && /* @__PURE__ */ jsxRuntime.jsx(
4419
+ SIcon,
4420
+ {
4421
+ name: icon,
4422
+ size: s.icon,
4423
+ color: iconColor,
4424
+ style: { marginRight: s.labelGap }
4425
+ }
4426
+ ),
4427
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
4428
+ (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 flex items-center", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
4429
+ STooltip2,
4430
+ {
4431
+ message: labelTooltip !== "" ? [labelTooltip] : void 0,
4432
+ iconSize: s.icon,
4433
+ ...labelTooltipProps
4434
+ }
4435
+ ) })
4436
+ ]
4437
+ }
4438
+ ),
4439
+ /* @__PURE__ */ jsxRuntime.jsxs(
4440
+ "div",
4441
+ {
4442
+ className: cn("flex min-w-0 flex-col", !hug && !isPercentWidth && "flex-1"),
4443
+ style: {
4444
+ width: resolvedWidth,
4445
+ minWidth: fieldWidthValue(minWidth),
4446
+ maxWidth: resolvedMaxWidth
4447
+ },
4448
+ children: [
4449
+ /* @__PURE__ */ jsxRuntime.jsxs(
4450
+ "div",
4451
+ {
4452
+ className: cn(
4453
+ "relative box-border flex min-w-0 items-stretch border border-solid border-(--field-bc)",
4454
+ // 둘 다 넘친 것을 잘라 내지만 clip 은 스크롤 상자를 만들지 않는다. hidden 이면
4455
+ // 이 상자가 스크롤포트가 되어 안쪽의 position:sticky 가 페이지 스크롤을 따라가지
4456
+ // 못한다 (SEditor 의 고정 툴바). 여러 줄 필드에서만 바꿔 파급을 좁힌다.
4457
+ multiline ? "overflow-clip" : "overflow-hidden",
4458
+ interactive && !borderless && "hover:border-(--sys-color-field-border-focus) hover:shadow-[0px_0px_4px_0px_#0071ff66] focus-within:border-(--sys-color-field-border-focus) focus-within:shadow-[0px_0px_4px_0px_#0071ff66]",
4459
+ disabled && "cursor-not-allowed"
4460
+ ),
4461
+ style: {
4462
+ // @ts-expect-error CSS 변수
4463
+ "--field-bc": borderColor,
4464
+ // focused/hovered prop이 명시된 경우 강조를 즉시 반영
4465
+ ...interactive && !borderless && (focused || hovered) ? { boxShadow: FOCUS_RING_SHADOW } : {},
4466
+ ...multiline ? { minHeight: s.height } : { height: s.height },
4467
+ "borderRadius": s.radius,
4468
+ "background": bgColor,
4469
+ "color": textColor
4470
+ },
4471
+ "data-focused": focused || void 0,
4472
+ "data-hovered": hovered || void 0,
4473
+ children: [
4474
+ effectiveAddonLabel !== "" && /* @__PURE__ */ jsxRuntime.jsxs(
4475
+ "div",
4476
+ {
4477
+ className: "flex shrink-0 items-center gap-(--cmp-field-addonLabel-gap) whitespace-nowrap border-r border-solid px-(--cmp-field-addonLabel-paddingX) text-12 font-medium leading-sd-20",
4478
+ style: {
4479
+ // 원본 sd-field: labelWidth가 addon 폭(--sd-field-addon-width)에도 적용됨
4480
+ width: cssLen(labelWidth),
4481
+ justifyContent: ALIGN[addonAlign],
4482
+ background: "var(--cmp-field-addonLabel-bg)",
4483
+ borderRightColor: disabled ? "var(--cmp-field-addonLabel-border-disabled)" : "var(--cmp-field-addonLabel-border-default)",
4484
+ borderTopLeftRadius: s.radius - 1,
4485
+ borderBottomLeftRadius: s.radius - 1
4486
+ },
4487
+ children: [
4488
+ icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
4489
+ effectiveAddonLabel
4490
+ ]
4491
+ }
4492
+ ),
4493
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex min-w-0 flex-1", multiline && "bg-inherit"), children })
4494
+ ]
4495
+ }
4496
+ ),
4497
+ showError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-(--cmp-field-contents-gap) px-(--cmp-field-message-paddingX) text-12 font-normal leading-sd-20 text-(--cmp-field-errorMessage-color)", children: errorMessage }) : hint !== "" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-(--cmp-field-contents-gap) px-(--cmp-field-message-paddingX) text-12 font-normal leading-sd-20 text-(--cmp-field-hint-color)", children: hint }) : null
4498
+ ]
4499
+ }
4500
+ )
4501
+ ] })
4502
+ }
4503
+ );
4504
+ });
4505
+
4506
+ // src/lib/autofill.ts
4507
+ 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]";
4508
+ var TEXTAREA_SIZE = {
4509
+ sm: {
4510
+ fontSize: 12,
4511
+ lineHeight: 20,
4512
+ paddingX: "var(--cmp-textinput-sm-paddingX)",
4513
+ paddingY: "var(--cmp-textinput-sm-paddingY)"
4514
+ },
4515
+ md: {
4516
+ fontSize: 14,
4517
+ lineHeight: 24,
4518
+ paddingX: "var(--cmp-textinput-md-paddingX)",
4519
+ paddingY: "var(--cmp-textinput-md-paddingY)"
4520
+ }
4521
+ };
4522
+ var MIN_ROWS = 2;
4523
+ var STextarea = /* @__PURE__ */ react.forwardRef(function STextarea2({
4524
+ value,
4525
+ onValueChange,
4526
+ onChange,
4527
+ size = "sm",
4528
+ rows = MIN_ROWS,
4529
+ autogrow = false,
4530
+ maxRows,
4531
+ rules,
4532
+ status,
4533
+ focused,
4534
+ hovered,
4535
+ textareaClassName,
4536
+ textareaStyle,
4537
+ label,
4538
+ labelWidth,
4539
+ icon,
4540
+ iconColor,
4541
+ labelTooltip,
4542
+ labelTooltipProps,
4543
+ addonLabel,
4544
+ addonAlign,
4545
+ hint,
4546
+ error,
4547
+ errorMessage,
4548
+ width,
4549
+ disabled = false,
4550
+ readOnly = false,
4551
+ placeholder = "\uC785\uB825\uD574 \uC8FC\uC138\uC694.",
4552
+ onBlur,
4553
+ className,
4554
+ style,
4555
+ ...rest
4556
+ }, ref) {
4557
+ const p = TEXTAREA_SIZE[size];
4558
+ const heightForRows = (n) => `calc(${p.lineHeight * n}px + ${p.paddingY} * 2)`;
4559
+ const minHeight = heightForRows(MIN_ROWS);
4560
+ const maxHeight = autogrow && maxRows != null ? heightForRows(maxRows) : void 0;
4561
+ const [ruleError, setRuleError] = react.useState("");
4562
+ const innerRef = react.useRef(null);
4563
+ const setRefs2 = react.useCallback(
4564
+ (node) => {
4565
+ innerRef.current = node;
4566
+ if (typeof ref === "function") ref(node);
4567
+ else if (ref) ref.current = node;
4568
+ },
4569
+ [ref]
4570
+ );
4571
+ react.useLayoutEffect(() => {
4572
+ const el = innerRef.current;
4573
+ if (el == null) return;
4574
+ if (!autogrow) {
4575
+ el.style.height = "";
4576
+ return;
4577
+ }
4578
+ el.style.height = "auto";
4579
+ el.style.height = `${el.scrollHeight}px`;
4580
+ }, [autogrow, maxHeight, minHeight, rows, value]);
4581
+ useFormField({
4582
+ name: rest.name,
4583
+ validate: () => {
4584
+ const msg = runRules(value, rules);
4585
+ setRuleError(msg);
4586
+ return msg === "";
4587
+ },
4588
+ resetValidation: () => setRuleError("")
4589
+ });
4590
+ const resolvedError = error || ruleError !== "";
4591
+ const resolvedErrorMessage = ruleError !== "" ? ruleError : errorMessage;
4592
+ const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
4593
+ return /* @__PURE__ */ jsxRuntime.jsx(
4594
+ SField,
4595
+ {
4596
+ label,
4597
+ labelWidth,
4598
+ icon,
4599
+ iconColor,
4600
+ labelTooltip,
4601
+ labelTooltipProps,
4602
+ addonLabel,
4603
+ addonAlign,
4604
+ hint,
4605
+ size,
4606
+ error: resolvedError,
4607
+ status: resolvedStatus,
4608
+ focused,
4609
+ hovered,
4610
+ errorMessage: resolvedErrorMessage,
4611
+ width,
4612
+ disabled,
4613
+ readOnly,
4614
+ multiline: true,
4615
+ className,
4616
+ style,
4617
+ children: /* @__PURE__ */ jsxRuntime.jsx(
4618
+ "textarea",
4619
+ {
4620
+ ref: setRefs2,
4621
+ value: value ?? "",
4622
+ rows,
4623
+ disabled,
4624
+ readOnly,
4625
+ placeholder,
4626
+ onChange: (e) => {
4627
+ onChange?.(e);
4628
+ onValueChange?.(e.target.value);
4629
+ if (ruleError !== "") setRuleError(runRules(e.target.value, rules));
4630
+ },
4631
+ onBlur: (e) => {
4632
+ onBlur?.(e);
4633
+ if (rules && rules.length > 0) setRuleError(runRules(e.target.value, rules));
4634
+ },
4635
+ className: cn(
4636
+ "w-full border-0 bg-transparent outline-none placeholder:text-[color:var(--sys-color-field-text-placeholder)] disabled:cursor-not-allowed",
4637
+ // autogrow 와 끌어서 늘리기는 같은 높이를 두고 다투므로 함께 두지 않는다
4638
+ autogrow ? "resize-none" : "resize-y",
4639
+ AUTOFILL_RESET_CLASS,
4640
+ textareaClassName
4641
+ ),
4642
+ style: {
4643
+ minHeight,
4644
+ maxHeight,
4645
+ // 상한이 있으면 거기 닿았을 때 안에서 스크롤한다. 상한이 없으면 높이가 늘 내용과
4646
+ // 같으므로 스크롤바가 나올 일이 없고, 반올림으로 1px 이 넘쳐 깜빡이는 것만 막는다.
4647
+ overflowY: autogrow ? maxRows != null ? "auto" : "hidden" : void 0,
4648
+ padding: `${p.paddingY} ${p.paddingX}`,
4649
+ fontSize: p.fontSize,
4650
+ lineHeight: `${p.lineHeight}px`,
4651
+ color: disabled ? "var(--cmp-textinput-text-disabled)" : "var(--sys-color-field-text-default)",
4652
+ ...textareaStyle
4653
+ },
4654
+ ...rest
4655
+ }
4656
+ )
4657
+ }
4658
+ );
4659
+ });
4660
+ var DEFAULT_PLACEHOLDER = "\uBA54\uC2DC\uC9C0\uB97C \uC785\uB825\uD574 \uC8FC\uC138\uC694...";
4661
+ var FIELD_MIN_HEIGHT = 48;
4662
+ var FIELD_BORDER = 1;
4663
+ var TEXTAREA_MIN_HEIGHT = FIELD_MIN_HEIGHT - FIELD_BORDER * 2;
4664
+ var BUTTON_BOTTOM_INSET = `calc((${FIELD_MIN_HEIGHT}px - var(--cmp-ghostButton-md-size)) / 2)`;
4665
+ var DEFAULT_MAX_ROWS = 5;
4666
+ var SChatInputImpl = /* @__PURE__ */ react.forwardRef(function SChatInput({
4667
+ value = "",
4668
+ onValueChange,
4669
+ placeholder = DEFAULT_PLACEHOLDER,
4670
+ disabled = false,
4671
+ onSend,
4672
+ onAttach,
4673
+ busy = false,
4674
+ onStop,
4675
+ footerAction,
4676
+ maxRows = DEFAULT_MAX_ROWS,
4677
+ className,
4678
+ style,
4679
+ textareaClassName,
4680
+ ...rest
4681
+ }, ref) {
4682
+ const canSend = !disabled && !busy && value.trim() !== "";
4683
+ const send = react.useCallback(() => {
4684
+ if (!canSend) return;
4685
+ onSend?.(value);
4686
+ }, [canSend, onSend, value]);
4687
+ const handleKeyDown = (e) => {
4688
+ rest.onKeyDown?.(e);
4689
+ if (e.key !== "Enter" || e.shiftKey || e.nativeEvent.isComposing) return;
4690
+ e.preventDefault();
4691
+ send();
4692
+ };
4693
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col bg-(--sys-color-field-bg-default)", className), style, children: [
4694
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-sd-8 p-sd-12", children: [
4695
+ onAttach != null && /* @__PURE__ */ jsxRuntime.jsx(
4696
+ SGhostButton,
4697
+ {
4698
+ icon: "attachFile",
4699
+ size: "md",
4700
+ ariaLabel: "\uD30C\uC77C \uCCA8\uBD80",
4701
+ disabled,
4702
+ onClick: onAttach,
4703
+ style: { marginBottom: BUTTON_BOTTOM_INSET }
4704
+ }
4705
+ ),
4706
+ /* @__PURE__ */ jsxRuntime.jsx(
4707
+ STextarea,
4708
+ {
4709
+ ref,
4710
+ size: "md",
4711
+ rows: 1,
4712
+ autogrow: true,
4713
+ maxRows,
4714
+ width: "100%",
4715
+ value,
4716
+ onValueChange,
4717
+ placeholder,
4718
+ disabled,
4719
+ className: "min-w-0 flex-1",
4720
+ textareaClassName,
4721
+ textareaStyle: { minHeight: TEXTAREA_MIN_HEIGHT },
4722
+ ...rest,
4723
+ onKeyDown: handleKeyDown
4724
+ }
4725
+ ),
4726
+ busy ? /* @__PURE__ */ jsxRuntime.jsx(
4727
+ SGhostButton,
4728
+ {
4729
+ icon: "stop",
4730
+ size: "md",
4731
+ intent: "danger",
4732
+ ariaLabel: "\uC751\uB2F5 \uC911\uC9C0",
4733
+ onClick: onStop,
4734
+ style: { marginBottom: BUTTON_BOTTOM_INSET }
4735
+ }
4736
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
4737
+ SGhostButton,
4738
+ {
4739
+ icon: "send",
4740
+ size: "md",
4741
+ intent: "subAction",
4742
+ ariaLabel: "\uC804\uC1A1",
4743
+ disabled: !canSend,
4744
+ onClick: send,
4745
+ style: { marginBottom: BUTTON_BOTTOM_INSET }
4746
+ }
4747
+ )
4748
+ ] }),
4749
+ footerAction != null && // 보조 액션은 입력창 아래 가운데에 선다 — 주 동작(전송)과 층이 다름을 자리로 드러낸다.
4750
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center", children: footerAction })
4751
+ ] });
4752
+ });
4753
+ var SChatInput2 = /* @__PURE__ */ withDisplayName(SChatInputImpl, "SChatInput");
4754
+ var CHAT_MESSAGE_DIRECTIONS = ["incoming", "outgoing"];
4755
+ var CHAT_MESSAGE_VARIANTS = ["default", "template"];
4756
+ var BUBBLE_SURFACE = {
4757
+ incoming: "bg-(--sys-color-bg-screen) text-(--sys-color-fg-primary)",
4758
+ outgoing: "bg-(--sys-color-bg-brand) text-(--sys-color-fg-inverse)",
4759
+ template: "bg-(--sys-color-bg-accentFaint) text-(--sys-color-fg-deep)"
4760
+ };
4761
+ var CHAT_MESSAGE_READ_STATUSES = ["read", "unread"];
4762
+ var READ_STATUS_LABEL = {
4763
+ read: "\uC77D\uC74C",
4764
+ unread: "\uC548 \uC77D\uC74C"
4765
+ };
4766
+ var READ_STATUS_COLOR = {
4767
+ read: "text-(--sys-color-fg-tertiary)",
4768
+ unread: "text-(--sys-color-fg-warning)"
4769
+ };
4770
+ var AVATAR_SIZE = 32;
4771
+ var AVATAR_ROW_HEIGHT = 40;
4772
+ var AVATAR_ICON_SIZE = 40;
4773
+ function Avatar({ src, alt, placeholder }) {
4774
+ if (placeholder) {
4775
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0", style: { width: AVATAR_SIZE, height: AVATAR_ROW_HEIGHT } });
4776
+ }
4777
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 items-center", style: { height: AVATAR_ROW_HEIGHT }, children: /* @__PURE__ */ jsxRuntime.jsx(
4778
+ "div",
4779
+ {
4780
+ className: "relative overflow-hidden rounded-full bg-(--sys-color-bg-screen)",
4781
+ style: { width: AVATAR_SIZE, height: AVATAR_SIZE },
4782
+ children: src != null ? /* @__PURE__ */ jsxRuntime.jsx(SImage, { src, alt: alt ?? "", fit: "cover", className: "h-full w-full" }) : (
4783
+ // 원 밖으로 나가는 만큼을 좌우로 나눠 가운데를 맞추고, 아래는 원이 자른다.
4784
+ /* @__PURE__ */ jsxRuntime.jsx(
4785
+ SIcon,
4786
+ {
4787
+ name: "user",
4788
+ size: AVATAR_ICON_SIZE,
4789
+ color: "var(--sys-color-fg-quaternary)",
4790
+ className: "absolute top-0",
4791
+ style: { left: (AVATAR_SIZE - AVATAR_ICON_SIZE) / 2 }
4792
+ }
4793
+ )
4794
+ )
4795
+ }
4796
+ ) });
4797
+ }
4798
+ var SChatMessageImpl = /* @__PURE__ */ react.forwardRef(function SChatMessage({
4799
+ direction = "incoming",
4800
+ sender,
4801
+ time,
4802
+ readStatus,
4803
+ avatarSrc,
4804
+ avatarAlt,
4805
+ continued = false,
4806
+ variant = "default",
4807
+ actions,
4808
+ messages,
4809
+ className,
4810
+ ...rest
4811
+ }, ref) {
4812
+ const outgoing = direction === "outgoing";
4813
+ const surface = variant === "template" ? "template" : direction;
4814
+ const bubbles = messages ?? [];
4815
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4816
+ "div",
4817
+ {
4818
+ ref,
4819
+ className: cn(
4820
+ // 아바타와 본문 열이 같은 줄에 서고, 보낸 메시지는 좌우를 뒤집는다.
4821
+ // items-start 라 이름이 있든 없든 아바타는 언제나 첫 줄에 붙는다.
4822
+ "flex items-start gap-sd-12",
4823
+ outgoing && "flex-row-reverse",
4824
+ className
4825
+ ),
4826
+ ...rest,
4827
+ children: [
4828
+ !outgoing && /* @__PURE__ */ jsxRuntime.jsx(Avatar, { src: avatarSrc, alt: avatarAlt, placeholder: continued }),
4829
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex min-w-0 flex-col gap-sd-4", outgoing ? "items-end" : "items-start"), children: [
4830
+ !outgoing && !continued && sender != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-sm-default px-sd-8 text-(--sys-color-fg-secondary)", children: sender }),
4831
+ bubbles.length > 0 && // 말풍선끼리는 이름·시각과의 간격(4px)보다 벌려 한 덩어리 안의 여러 발화로 읽히게 한다.
4832
+ /* @__PURE__ */ jsxRuntime.jsx(
4833
+ "div",
4834
+ {
4835
+ className: cn("flex min-w-0 flex-col gap-sd-8", outgoing ? "items-end" : "items-start"),
4836
+ children: bubbles.map((message, index) => /* @__PURE__ */ jsxRuntime.jsx(
4837
+ "div",
4838
+ {
4839
+ className: cn(
4840
+ // 라운드 12px 은 대응 토큰이 없어 값으로 둔다 (디자인팀 토큰 추가 요청 대상).
4841
+ "typo-body-md-default max-w-full rounded-[12px] px-sd-16 py-sd-8",
4842
+ // 한글은 기본 규칙으로 두면 어절 한가운데서도 줄이 바뀐다 — 띄어쓰기에서만 끊고,
4843
+ // 끊을 자리가 없는 긴 토큰(URL·주문번호)만 흘려보낸다.
4844
+ "break-keep wrap-break-word",
4845
+ BUBBLE_SURFACE[surface]
4846
+ ),
4847
+ children: message
4848
+ },
4849
+ index
4850
+ ))
4851
+ }
4852
+ ),
4853
+ (readStatus != null || time != null) && // 읽음 표시와 시각은 한 줄에 선다 — 읽음이 먼저, 시각이 뒤다.
4854
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "typo-body-sm-default flex items-center gap-sd-4 px-sd-8", children: [
4855
+ readStatus != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: READ_STATUS_COLOR[readStatus], children: READ_STATUS_LABEL[readStatus] }),
4856
+ time != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-(--sys-color-fg-tertiary)", children: time })
4857
+ ] }),
4858
+ actions != null && // 버튼이 많아 한 줄에 안 들어가면 감싼다 — 넘쳐서 잘리는 것보다 낫다.
4859
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex flex-wrap gap-sd-8 px-sd-8", outgoing && "justify-end"), children: actions })
4860
+ ] })
4861
+ ]
4862
+ }
4863
+ );
4864
+ });
4865
+ var SChatMessage2 = /* @__PURE__ */ withDisplayName(SChatMessageImpl, "SChatMessage");
4866
+
4867
+ // src/lib/z-index.ts
4868
+ var Z_INDEX = {
4869
+ baseFloating: 50,
4870
+ modalHost: 1e3,
4871
+ toast: 1100,
4872
+ loading: 1200,
4873
+ drag: 1300
4874
+ };
4875
+ var setRefs = (refs, value) => {
4876
+ refs.forEach((ref) => {
4877
+ if (typeof ref === "function") {
4878
+ ref(value);
4879
+ return;
4880
+ }
4881
+ if (ref) {
4882
+ ref.current = value;
4883
+ }
4884
+ });
4885
+ };
4886
+ function renderSlot(slot, state2) {
4887
+ return typeof slot === "function" ? slot(state2) : slot;
4888
+ }
4889
+ var SDraggableItemView = /* @__PURE__ */ react.forwardRef(
4890
+ function SDraggableItemView2({
4891
+ title,
4892
+ supportingText,
4893
+ supportingTextPosition,
4894
+ trailing,
4895
+ depth,
4896
+ accentStripe,
4897
+ bordered,
4898
+ selected,
4899
+ dense,
4900
+ size,
4901
+ dragging,
4902
+ disabled,
4903
+ hovered,
4904
+ dragHandleProps,
4905
+ onDragHandleMouseDown,
4906
+ className,
4907
+ style,
4908
+ ...rest
4909
+ }, ref) {
4910
+ const renderState = { hovered, dragging, disabled };
4911
+ const titleNode = renderSlot(title, renderState);
4912
+ const supportingTextNode = renderSlot(supportingText, renderState);
4913
+ const hasSupportingText = supportingTextNode != null && supportingTextNode !== "";
4914
+ const trailingNode = renderSlot(trailing, renderState);
4915
+ const titleTypo = size === "sm" ? selected ? "typo-item-sm-selected" : "typo-item-sm-default" : selected ? "typo-item-md-selected" : "typo-item-md-default";
4916
+ const paddingYClass = dense ? size === "sm" ? "py-(--cmp-listDraggableItem-paddingY-sm-dense)" : "py-(--cmp-listDraggableItem-paddingY-md-dense)" : size === "sm" ? "py-(--cmp-listDraggableItem-paddingY-sm-normal)" : "py-(--cmp-listDraggableItem-paddingY-md-normal)";
4917
+ const normalizedDepth = Math.max(1, Math.floor(depth));
4918
+ const depthOffset = (normalizedDepth - 1) * 20;
4919
+ const depthStyle = depthOffset === 0 ? style : bordered ? {
4920
+ ...style,
4921
+ marginLeft: depthOffset,
4922
+ width: `calc(100% - ${depthOffset}px)`
4923
+ } : {
4924
+ ...style,
4925
+ paddingLeft: `calc(var(--cmp-listDraggableItem-paddingX) + ${depthOffset}px)`
4926
+ };
4927
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4928
+ "div",
4929
+ {
4930
+ ref,
4931
+ "aria-disabled": disabled || void 0,
4932
+ "data-dragging": dragging || void 0,
4933
+ style: depthStyle,
4934
+ className: cn(
4935
+ "relative flex w-full items-center gap-(--cmp-listDraggableItem-gap) overflow-hidden px-(--cmp-listDraggableItem-paddingX)",
4936
+ titleTypo,
4937
+ disabled ? "bg-(--cmp-listDraggableItem-bg-default) text-(--sys-color-disabled-text)" : selected ? "bg-(--cmp-listDraggableItem-bg-selected) text-(--cmp-listDraggableItem-text-selected)" : "bg-(--cmp-listDraggableItem-bg-default) text-(--cmp-listDraggableItem-text-default)",
4938
+ !disabled && !selected && !dragging && cn(
4939
+ "hover:bg-(--cmp-listDraggableItem-bg-hover)",
4940
+ // 리스트가 드래그 중이면(SDraggableList 가 data-drag-active 를 붙인다) hover 를 무효화한다.
4941
+ "[[data-drag-active]_&]:hover:bg-(--cmp-listDraggableItem-bg-default)"
4942
+ ),
4943
+ // 높이는 세로 패딩과 내용이 정한다 — 항목에 높이를 못 박지 않는다 (SListItem 과 동일)
4944
+ paddingYClass,
4945
+ // 항목 사이 구분선은 리스트가 그린다.
4946
+ bordered && cn(
4947
+ "rounded-(--cmp-listDraggableItem-radius-bordered) border border-(--cmp-listDraggableItem-border-default)",
4948
+ !disabled && !selected && !dragging && cn(
4949
+ "hover:border-(--cmp-listDraggableItem-bordered-border-hover)",
4950
+ "[[data-drag-active]_&]:hover:border-(--cmp-listDraggableItem-border-default)"
4951
+ ),
4952
+ !disabled && selected && !dragging && "border-(--cmp-listDraggableItem-bordered-border-selected) bg-(--cmp-listDraggableItem-bordered-bg-selected)",
4953
+ !disabled && selected && dragging && "border-(--cmp-listDraggableItem-bordered-dragging-border-color) bg-(--cmp-listDraggableItem-bordered-dragging-bg)"
4954
+ ),
4955
+ disabled && "cursor-not-allowed",
4956
+ className
4957
+ ),
4958
+ ...rest,
4959
+ children: [
4960
+ accentStripe && selected && !bordered && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
4961
+ "span",
4962
+ {
4963
+ "aria-hidden": "true",
4964
+ className: "pointer-events-none absolute bottom-[-1px] left-0 top-0 w-(--cmp-listDraggableItem-accentStripe-width) bg-(--cmp-listDraggableItem-accentStripe-color)"
4965
+ }
4966
+ ),
4967
+ /* @__PURE__ */ jsxRuntime.jsx(
4968
+ "div",
4969
+ {
4970
+ ...dragHandleProps,
4971
+ role: dragHandleProps?.role ?? "button",
4972
+ tabIndex: dragHandleProps?.tabIndex ?? (disabled ? -1 : 0),
4973
+ "aria-label": dragHandleProps?.["aria-label"] ?? "\uB4DC\uB798\uADF8 \uD578\uB4E4",
4974
+ "aria-disabled": disabled || void 0,
4975
+ onMouseDown: onDragHandleMouseDown,
4976
+ className: cn(
4977
+ "flex size-[24px] shrink-0 select-none items-center justify-center rounded-[4px]",
4978
+ disabled ? "cursor-not-allowed opacity-0" : "cursor-grab hover:bg-black/5 active:cursor-grabbing",
4979
+ !disabled && !dragging && "[[data-drag-active]_&]:hover:bg-transparent",
4980
+ dragging && "bg-black/5",
4981
+ dragHandleProps?.className
4982
+ ),
4983
+ children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "drag", size: 16, color: "grey_65" })
4984
+ }
4985
+ ),
4986
+ /* @__PURE__ */ jsxRuntime.jsxs(
4987
+ "div",
4988
+ {
4989
+ className: cn(
4990
+ "flex min-w-0 flex-1 items-center gap-[8px]",
4991
+ dragging && "opacity-(--cmp-listDraggableItem-dragging-content-opacity)"
4992
+ ),
4993
+ children: [
4994
+ /* @__PURE__ */ jsxRuntime.jsxs(
4995
+ "span",
4996
+ {
4997
+ className: cn(
4998
+ "min-w-0 flex-1",
4999
+ supportingTextPosition === "bottom" ? "flex flex-col" : "flex items-center gap-[8px] whitespace-nowrap"
5000
+ ),
5001
+ children: [
5002
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: titleNode }),
5003
+ hasSupportingText && /* @__PURE__ */ jsxRuntime.jsx(
5004
+ "span",
5005
+ {
5006
+ className: cn(
5007
+ "typo-body-sm-default min-w-0 truncate text-(--sys-color-fg-secondary)",
5008
+ supportingTextPosition === "right" && "flex-1"
5009
+ ),
5010
+ children: supportingTextNode
5011
+ }
5012
+ )
5013
+ ]
5014
+ }
5015
+ ),
5016
+ trailingNode
5017
+ ]
5018
+ }
5019
+ )
5020
+ ]
5021
+ }
5022
+ );
5023
+ }
5024
+ );
5025
+ var SDraggableItemImpl = /* @__PURE__ */ react.forwardRef(function SDraggableItem({
5026
+ title,
5027
+ supportingText,
5028
+ supportingTextPosition = "right",
5029
+ trailing,
5030
+ depth = 1,
5031
+ accentStripe = false,
5032
+ bordered = false,
5033
+ selected = false,
5034
+ dragging = false,
5035
+ dense = false,
5036
+ size = "sm",
5037
+ disabled = false,
5038
+ dragOverlay = true,
5039
+ dragOverlayOpacity = 0.75,
5040
+ dragHandleProps,
5041
+ onDragHandleMouseDown,
5042
+ className,
5043
+ onMouseEnter,
5044
+ onMouseLeave,
5045
+ ...rest
5046
+ }, ref) {
5047
+ const [hovered, setHovered] = react.useState(false);
5048
+ const [overlay, setOverlay] = react.useState(null);
5049
+ const rootRef = react.useRef(null);
5050
+ const overlayVisible = overlay != null;
5051
+ const handleMouseEnter = (event) => {
5052
+ if (!disabled) setHovered(true);
5053
+ onMouseEnter?.(event);
5054
+ };
5055
+ const handleMouseLeave = (event) => {
5056
+ setHovered(false);
5057
+ onMouseLeave?.(event);
5058
+ };
5059
+ const handleDragMouseDown = (event) => {
5060
+ if (disabled) return;
5061
+ const rect = rootRef.current?.getBoundingClientRect();
5062
+ if (dragOverlay && rect) {
5063
+ setOverlay({
5064
+ offsetX: event.clientX - rect.left,
5065
+ offsetY: event.clientY - rect.top,
5066
+ rect,
5067
+ x: event.clientX,
5068
+ y: event.clientY
5069
+ });
5070
+ }
5071
+ dragHandleProps?.onMouseDown?.(event);
5072
+ onDragHandleMouseDown?.(event);
5073
+ };
5074
+ react.useEffect(() => {
5075
+ if (!overlayVisible) return void 0;
5076
+ const handleMouseMove = (event) => {
5077
+ setOverlay(
5078
+ (current) => current ? {
5079
+ ...current,
5080
+ x: event.clientX,
5081
+ y: event.clientY
5082
+ } : current
5083
+ );
5084
+ };
5085
+ const handleMouseUp = () => {
5086
+ setOverlay(null);
5087
+ };
5088
+ window.addEventListener("mousemove", handleMouseMove);
5089
+ window.addEventListener("mouseup", handleMouseUp);
5090
+ return () => {
5091
+ window.removeEventListener("mousemove", handleMouseMove);
5092
+ window.removeEventListener("mouseup", handleMouseUp);
5093
+ };
5094
+ }, [overlayVisible]);
5095
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5096
+ /* @__PURE__ */ jsxRuntime.jsx(
5097
+ SDraggableItemView,
5098
+ {
5099
+ ref: (node) => setRefs([rootRef, ref], node),
5100
+ title,
5101
+ supportingText,
5102
+ supportingTextPosition,
5103
+ trailing,
5104
+ depth,
5105
+ accentStripe,
5106
+ bordered,
5107
+ selected,
5108
+ dense,
5109
+ size,
5110
+ dragging: dragging || overlayVisible,
5111
+ disabled,
5112
+ hovered,
5113
+ dragHandleProps,
5114
+ onDragHandleMouseDown: handleDragMouseDown,
5115
+ onMouseEnter: handleMouseEnter,
5116
+ onMouseLeave: handleMouseLeave,
5117
+ className,
5118
+ ...rest
5119
+ }
5120
+ ),
5121
+ overlay && typeof document !== "undefined" && reactDom.createPortal(
5122
+ /* @__PURE__ */ jsxRuntime.jsx(
5123
+ "div",
5124
+ {
5125
+ className: "pointer-events-none fixed",
5126
+ style: {
5127
+ left: overlay.x - overlay.offsetX,
5128
+ opacity: dragOverlayOpacity,
5129
+ top: overlay.y - overlay.offsetY,
5130
+ width: overlay.rect.width,
5131
+ zIndex: Z_INDEX.drag
5132
+ },
5133
+ children: /* @__PURE__ */ jsxRuntime.jsx(
5134
+ SDraggableItemView,
5135
+ {
5136
+ title,
5137
+ supportingText,
5138
+ supportingTextPosition,
5139
+ trailing,
5140
+ depth,
5141
+ accentStripe,
5142
+ bordered,
5143
+ selected,
5144
+ dense,
5145
+ size,
5146
+ dragging: true,
5147
+ disabled,
5148
+ hovered: true,
5149
+ dragHandleProps,
5150
+ className
5151
+ }
5152
+ )
5153
+ }
5154
+ ),
5155
+ document.body
5156
+ )
5157
+ ] });
5158
+ });
5159
+ var SDraggableItem2 = /* @__PURE__ */ withDisplayName(SDraggableItemImpl, "SDraggableItem");
5160
+ var SDivider = /* @__PURE__ */ react.forwardRef(function SDivider2({ vertical = false, height, className, style, ...rest }, ref) {
5161
+ const fixedHeight = vertical && height !== void 0;
5162
+ return /* @__PURE__ */ jsxRuntime.jsx(
5163
+ "hr",
5164
+ {
5165
+ ref,
5166
+ "aria-orientation": vertical ? "vertical" : "horizontal",
5167
+ className: cn(
5168
+ "shrink-0 border-0",
5169
+ vertical ? cn("inline-block w-px", fixedHeight ? "self-center" : "h-auto self-stretch") : "block h-px w-full",
5170
+ className
5171
+ ),
5172
+ style: {
5173
+ backgroundColor: "var(--sys-color-divider-default, #E1E1E1)",
5174
+ ...fixedHeight ? { height: typeof height === "number" ? `${height}px` : height } : null,
5175
+ ...style
5176
+ },
5177
+ ...rest
5178
+ }
5179
+ );
5180
+ });
5181
+ var PADDING_CLASS = {
5182
+ default: "p-(--cmp-list-group-padding)",
5183
+ none: ""
5184
+ };
5185
+ var dividerStyle = { backgroundColor: "var(--cmp-listItem-border-default)" };
5186
+ function hasBorderedChild(children) {
5187
+ let bordered = false;
5188
+ react.Children.forEach(children, (child) => {
5189
+ if (!react.isValidElement(child)) return;
5190
+ if (child.props.bordered === true) {
5191
+ bordered = true;
5192
+ return;
5193
+ }
5194
+ react.Children.forEach(child.props.children, (wrapped) => {
5195
+ if (react.isValidElement(wrapped) && wrapped.props.bordered === true) {
5196
+ bordered = true;
5197
+ }
5198
+ });
5199
+ });
5200
+ return bordered;
5201
+ }
5202
+ function withDividers(children) {
5203
+ return react.Children.toArray(children).flatMap(
5204
+ (child, index) => index === 0 ? [child] : [
5205
+ /* @__PURE__ */ jsxRuntime.jsx(SDivider, { "aria-hidden": "true", style: dividerStyle }, `s-list-divider-${index}`),
5206
+ child
5207
+ ]
5208
+ );
5209
+ }
5210
+ var SListImpl = /* @__PURE__ */ react.forwardRef(function SList({ className, children, gap = false, padding = "none", ...rest }, ref) {
5211
+ const showDivider = !gap && !hasBorderedChild(children);
5212
+ return /* @__PURE__ */ jsxRuntime.jsx(
5213
+ "div",
5214
+ {
5215
+ ref,
5216
+ className: cn(
5217
+ gap && "flex flex-col gap-(--cmp-list-group-gap)",
5218
+ PADDING_CLASS[padding],
5219
+ className
5220
+ ),
5221
+ ...rest,
5222
+ children: showDivider ? withDividers(children) : children
5223
+ }
5224
+ );
5225
+ });
5226
+ var SList2 = /* @__PURE__ */ withDisplayName(SListImpl, "SList");
5227
+ var DraggableGroupContext = /* @__PURE__ */ react.createContext(null);
5228
+ function useSDraggableGroup() {
5229
+ return react.useContext(DraggableGroupContext);
5230
+ }
5231
+ function SDraggableProviderInternal({
5232
+ children,
5233
+ defaultGroup,
5234
+ legacyOnMove
5235
+ }) {
5236
+ const listsRef = react.useRef(/* @__PURE__ */ new Map());
5237
+ const legacyOnMoveRef = react.useRef(legacyOnMove);
5238
+ const pointerRef = react.useRef(null);
5239
+ const [dragging, setDragging] = react.useState(null);
5240
+ const [drop, setDrop] = react.useState(null);
5241
+ const dropRef = react.useRef(drop);
5242
+ legacyOnMoveRef.current = legacyOnMove;
5243
+ dropRef.current = drop;
5244
+ const registerList = react.useCallback(
5245
+ (group, listId, element, items, onChange) => {
5246
+ const key = `${group}:${listId}`;
5247
+ if (element) listsRef.current.set(key, { element, items, onChange });
5248
+ else listsRef.current.delete(key);
5249
+ },
5250
+ []
5251
+ );
5252
+ const startDrag = react.useCallback(
5253
+ (event, group, listId, index, itemKey) => {
5254
+ event.preventDefault();
5255
+ setDragging({ group, listId, index, itemKey });
5256
+ setDrop({ group, listId, index });
5257
+ },
5258
+ []
5259
+ );
5260
+ react.useEffect(() => {
5261
+ if (!dragging) return void 0;
5262
+ let autoScrollFrame = null;
5263
+ const findListAtPointer = (clientX, clientY) => {
5264
+ const element = document.elementFromPoint(clientX, clientY);
5265
+ const listElement = element?.closest("[data-draggable-list-id]");
5266
+ const group = listElement?.dataset.draggableGroup;
5267
+ const listId = listElement?.dataset.draggableListId;
5268
+ const list = group && listId ? listsRef.current.get(`${group}:${listId}`) : void 0;
5269
+ return group && listId && list ? { group, listId, list } : null;
4172
5270
  };
4173
5271
  const updateDropTarget = (clientX, clientY) => {
4174
5272
  const target = findListAtPointer(clientX, clientY);
@@ -4913,23 +6011,104 @@ var SExpansionListImpl = /* @__PURE__ */ react.forwardRef(function SExpansionLis
4913
6011
  if (isListItemElement(child)) {
4914
6012
  return react.cloneElement(child, { clickable: child.props.clickable ?? true });
4915
6013
  }
4916
- if (!isExpansionItemElement(child)) return child;
4917
- const depth = child.props.depth ?? 1;
4918
- const itemKey = String(child.key ?? index);
4919
- return react.cloneElement(child, {
4920
- expanded: openKeys[depth] === itemKey,
4921
- onToggle: (next, event) => {
4922
- setOpenKeys((current) => ({
4923
- ...current,
4924
- [depth]: next ? itemKey : current[depth] === itemKey ? void 0 : current[depth]
4925
- }));
4926
- child.props.onToggle?.(next, event);
4927
- }
4928
- });
4929
- });
4930
- return /* @__PURE__ */ jsxRuntime.jsx(SList2, { ref, ...rest, children: listChildren });
6014
+ if (!isExpansionItemElement(child)) return child;
6015
+ const depth = child.props.depth ?? 1;
6016
+ const itemKey = String(child.key ?? index);
6017
+ return react.cloneElement(child, {
6018
+ expanded: openKeys[depth] === itemKey,
6019
+ onToggle: (next, event) => {
6020
+ setOpenKeys((current) => ({
6021
+ ...current,
6022
+ [depth]: next ? itemKey : current[depth] === itemKey ? void 0 : current[depth]
6023
+ }));
6024
+ child.props.onToggle?.(next, event);
6025
+ }
6026
+ });
6027
+ });
6028
+ return /* @__PURE__ */ jsxRuntime.jsx(SList2, { ref, ...rest, children: listChildren });
6029
+ });
6030
+ var SExpansionList2 = /* @__PURE__ */ withDisplayName(SExpansionListImpl, "SExpansionList");
6031
+ function SellmateWordmark({ className }) {
6032
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6033
+ "svg",
6034
+ {
6035
+ className,
6036
+ width: "276",
6037
+ height: "40",
6038
+ viewBox: "0 0 276 40",
6039
+ fill: "#00275F",
6040
+ xmlns: "http://www.w3.org/2000/svg",
6041
+ role: "img",
6042
+ "aria-label": "Sellmate",
6043
+ children: [
6044
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M22.6953 15.17H13.0352C10.9045 15.17 9.36459 14.8464 8.56779 14.2353C7.81576 13.6602 7.47556 12.7615 7.47556 11.3325C7.47556 9.90362 7.81576 8.98695 8.56779 8.4028C9.5526 7.62992 10.8418 7.48613 13.0352 7.48613H33.7072V0H13.1516C4.91507 0 0 4.23285 0 11.3325C0 18.4322 4.91507 22.6561 13.1516 22.6561H22.8206C24.1187 22.6292 26.0973 22.6561 27.2701 23.5908C27.9953 24.1659 28.3713 25.2444 28.3713 26.7811C28.3713 28.2011 28.0669 29.0908 27.3955 29.6569C26.1331 30.7174 23.6711 31.2476 20.0721 31.2476C6.75038 31.2476 0.877371 27.2125 0.823654 27.1676L0 26.5834V34.2852L0.196961 34.438C0.402874 34.5998 5.29108 38.3743 19.9557 38.3743C30.3499 38.3743 35.8469 34.3661 35.8469 26.7721C35.8469 19.1782 30.9318 15.17 22.6953 15.17Z" }),
6045
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M94.6954 0H87.4258V38.3833H94.6954V0Z" }),
6046
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M109.874 0H102.604V38.3833H109.874V0Z" }),
6047
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M160.887 10.728C158.675 8.97557 153.885 8.35547 150.466 8.35547C147.314 8.35547 143.035 9.04746 140.752 10.5483C138.558 9.03848 134.261 8.35547 131.056 8.35547C127.394 8.35547 122.631 9.17328 120.635 10.728C118.54 12.3636 117.475 14.844 117.475 18.1063V38.7224H124.744V18.1063C124.744 17.3514 125.058 16.7762 125.702 16.3539C126.669 15.7158 129.203 15.4911 131.083 15.4911C134.395 15.4911 137.135 15.6888 137.135 18.1063V38.7224H144.405V18.1063C144.405 15.9135 146.625 15.4911 150.457 15.4911C152.552 15.4911 155.067 15.7787 155.81 16.3359C156.482 16.8302 156.786 17.3963 156.786 18.1063V38.7224H164.056V18.1063C164.056 14.871 162.99 12.3906 160.896 10.728H160.887Z" }),
6048
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M195.015 8.35547H174.648V15.4911H194.854C197.074 15.4911 198.453 15.599 199.348 16.1741C199.895 16.5246 200.208 17.1537 200.289 18.0704H183.314C175.095 18.0704 171.264 21.3506 171.264 28.3964C171.264 34.9569 175.865 38.7224 183.887 38.7224H195.024C209.053 38.7224 207.639 35.9454 207.639 28.9715V18.1063C207.639 11.5458 203.512 8.35547 195.024 8.35547H195.015ZM200.369 28.9715C200.369 29.6276 200.441 30.5982 199.689 31.1194C198.847 31.7036 196.895 31.5867 194.854 31.5867H184.048C180.288 31.5867 178.533 30.5443 178.533 28.2975C178.533 25.4217 181.255 25.188 184.048 25.188H200.369V28.9715Z" }),
6049
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M227.692 0.521243L227.683 0H220.413V8.01636H212.866V15.143H220.413V37.862L220.422 38.3833H227.692V15.143H235.239V8.01636H227.692V0.521243Z" }),
6050
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M75.2766 10.728C73.2801 9.15531 70.084 8.35547 65.7688 8.35547H56.3146C47.9438 8.35547 43.7002 11.6357 43.7002 18.1063V29.2052C43.7002 34.4536 47.9528 38.7224 53.1812 38.7224H76.6553V31.6856H54.4793C52.5366 31.6856 50.9609 30.1039 50.9609 28.1537V27.4527H74.9006C76.2793 27.4527 77.1925 27.1472 77.7117 26.5181C78.1683 25.9519 78.3922 25.0802 78.3922 23.858V18.0973C78.3922 14.844 77.3447 12.3547 75.2766 10.719V10.728ZM50.9609 18.1063C50.9609 17.4233 51.31 16.8751 52.0621 16.3628C52.9126 15.7877 54.4346 15.4821 56.4758 15.4821H65.6076C67.6936 15.4821 69.2335 15.7607 70.075 16.282C70.7913 16.7223 71.1225 17.2975 71.1225 18.0973V20.416H50.9609V18.0973V18.1063Z" }),
6051
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M271.48 10.728C269.483 9.15531 266.287 8.35547 261.972 8.35547H252.518C244.147 8.35547 239.895 11.6357 239.895 18.1063V29.2052C239.895 34.4536 244.147 38.7224 249.376 38.7224H272.85V31.6856H250.674C248.731 31.6856 247.155 30.1039 247.155 28.1537V27.4527H271.095C272.474 27.4527 273.387 27.1472 273.906 26.5181C274.363 25.9519 274.586 25.0802 274.586 23.858V18.0973C274.586 14.844 273.539 12.3547 271.471 10.719L271.48 10.728ZM247.164 20.416V18.0973C247.164 17.4143 247.513 16.8661 248.265 16.3539C249.116 15.7787 250.638 15.4731 252.679 15.4731H261.811C263.897 15.4731 265.437 15.7517 266.278 16.273C266.995 16.7133 267.326 17.2885 267.326 18.0883V20.407H247.164V20.416Z" })
6052
+ ]
6053
+ }
6054
+ );
6055
+ }
6056
+ var BUTTON_LABEL = "\uD1B5\uD569 \uACC4\uC815\uC73C\uB85C \uB85C\uADF8\uC778";
6057
+ var SLoginCardImpl = /* @__PURE__ */ react.forwardRef(function SLoginCard({ product, title, description, onLogin, footerNote, className, ...rest }, ref) {
6058
+ const hasIntro = title != null || description != null;
6059
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6060
+ "div",
6061
+ {
6062
+ ref,
6063
+ className: cn(
6064
+ // 폭도 높이도 부모가 준 만큼 채우고, 그 안에서 내용을 가운데 세운다.
6065
+ // 폭에만 상하한이 있다 — 640px 을 넘으면 소개 문장의 읽기 폭이 늘어지고, 368px 아래로
6066
+ // 내려가면 버튼(320px)이 안쪽 여백을 밀어낸다. 그 사이에서는 부모를 그대로 따른다.
6067
+ // 부모가 높이를 주지 않으면 h-full 이 auto 로 풀려 카드가 내용 높이로 줄어든다 —
6068
+ // 로그인 화면 밖(모달·스토리)에서도 그대로 쓸 수 있는 이유다.
6069
+ "flex h-full w-full max-w-[640px] min-w-[368px] shrink-0 flex-col items-center justify-center gap-sd-60",
6070
+ // 라운드 24px·폭 상하한(640/368px)은 대응 토큰이 없어 값으로 둔다 (디자인팀 토큰 추가 요청 대상).
6071
+ "rounded-[24px] bg-(--sys-color-bg-frame) p-sd-24 text-center",
6072
+ // 한글은 기본 규칙으로 두면 어절 한가운데서도 줄이 바뀐다 — `break-keep` 으로 띄어쓰기에서만
6073
+ // 끊는다. 끊을 자리가 아예 없는 긴 토큰(URL·주문번호)만 `wrap-break-word` 로 흘려보낸다.
6074
+ "break-keep wrap-break-word",
6075
+ className
6076
+ ),
6077
+ ...rest,
6078
+ children: [
6079
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-sd-32", children: [
6080
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-sd-8", children: [
6081
+ /* @__PURE__ */ jsxRuntime.jsx(SellmateWordmark, {}),
6082
+ product != null && // 24px 는 typo 프리셋(최대 18px)에 없는 등급이다 — 제품명은 로고의 일부처럼 읽혀야
6083
+ // 해서 본문 스케일 위에 선다. 여기만 값으로 두고, 프리셋이 생기면 갈아탄다.
6084
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-[24px] leading-[36px] font-bold text-(--sys-color-fg-accentLight)", children: product })
6085
+ ] }),
6086
+ hasIntro && // 소개와 보조 설명은 한 문단으로 읽힌다.
6087
+ // `text-balance` 는 상속되므로 여기 한 번만 걸어 둘을 함께 덮는다 — 가운데 정렬에서는
6088
+ // 마지막 줄에 한 어절만 남는 모양이 가장 눈에 띄어서, 줄 길이를 고르게 나눠야 한다.
6089
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-sd-4 text-balance", children: [
6090
+ title != null && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "typo-body-lg-medium m-0 text-(--sys-color-fg-secondary)", children: title }),
6091
+ description != null && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "typo-body-md-default m-0 text-(--sys-color-fg-tertiary)", children: description })
6092
+ ] })
6093
+ ] }),
6094
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-sd-12", children: [
6095
+ /* @__PURE__ */ jsxRuntime.jsx(
6096
+ SButton,
6097
+ {
6098
+ color: "secondary",
6099
+ size: "lg",
6100
+ label: BUTTON_LABEL,
6101
+ onClick: onLogin,
6102
+ className: "w-[320px]"
6103
+ }
6104
+ ),
6105
+ footerNote != null && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "typo-body-sm-default m-0 text-(--sys-color-fg-tertiary)", children: footerNote })
6106
+ ] })
6107
+ ]
6108
+ }
6109
+ );
4931
6110
  });
4932
- var SExpansionList2 = /* @__PURE__ */ withDisplayName(SExpansionListImpl, "SExpansionList");
6111
+ var SLoginCard2 = /* @__PURE__ */ withDisplayName(SLoginCardImpl, "SLoginCard");
4933
6112
  function SCheckbox({
4934
6113
  value = false,
4935
6114
  val,
@@ -4957,292 +6136,48 @@ function SCheckbox({
4957
6136
  };
4958
6137
  const boxBg = disabled ? "var(--cmp-checkbox-bg-disabled)" : inverse ? "transparent" : isChecked ? "var(--cmp-checkbox-checked-bg-default)" : "var(--cmp-checkbox-unchecked-bg-default)";
4959
6138
  const boxBorder = disabled ? "var(--cmp-checkbox-border-disabled)" : inverse ? "var(--cmp-checkbox-checked-border-inverse)" : isChecked ? "var(--cmp-checkbox-checked-bg-default)" : "var(--cmp-checkbox-unchecked-border-default)";
4960
- const iconColor = disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)";
4961
- const labelColor = disabled ? "var(--cmp-checkbox-label-disabled)" : inverse ? "var(--cmp-checkbox-label-inverse)" : "var(--cmp-checkbox-label-default)";
4962
- return /* @__PURE__ */ jsxRuntime.jsxs(
4963
- "label",
4964
- {
4965
- className: cn(
4966
- "inline-flex items-center gap-[var(--cmp-checkbox-gap)] align-middle",
4967
- disabled ? "cursor-not-allowed" : "cursor-pointer",
4968
- className
4969
- ),
4970
- style,
4971
- children: [
4972
- /* @__PURE__ */ jsxRuntime.jsx(
4973
- RCheckbox__namespace.Root,
4974
- {
4975
- checked: state2,
4976
- disabled,
4977
- onCheckedChange: handleChange,
4978
- className: cn(
4979
- "flex h-[var(--cmp-checkbox-size)] w-[var(--cmp-checkbox-size)] items-center justify-center rounded-[var(--cmp-checkbox-radius)] border-[length:var(--cmp-checkbox-borderWidth)] border-solid bg-[var(--cb-bg)] border-[color:var(--cb-border)] outline-none",
4980
- disabled ? "cursor-not-allowed" : "cursor-pointer",
4981
- !disabled && !isChecked && !inverse && "hover:border-[color:var(--cmp-checkbox-unchecked-border-hover)] hover:bg-[var(--cmp-checkbox-unchecked-bg-hover)]"
4982
- ),
4983
- style: { "--cb-bg": boxBg, "--cb-border": boxBorder },
4984
- children: /* @__PURE__ */ jsxRuntime.jsx(RCheckbox__namespace.Indicator, { className: "flex items-center justify-center leading-none", children: /* @__PURE__ */ jsxRuntime.jsx(
4985
- SIcon,
4986
- {
4987
- name: state2 === "indeterminate" ? "checkboxIndeterminate" : "checkboxCheck",
4988
- size: 12,
4989
- color: iconColor
4990
- }
4991
- ) })
4992
- }
4993
- ),
4994
- label && // 고스트(500) 텍스트로 너비를 고정 → 두께 400↔500 전환 시 레이아웃이 밀리지 않음
4995
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "grid text-nowrap text-[12px] leading-[20px]", style: { color: labelColor }, children: [
4996
- /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "invisible font-medium", style: { gridArea: "1 / 1" }, children: label }),
4997
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { gridArea: "1 / 1", fontWeight: isChecked ? 500 : 400 }, children: label })
4998
- ] })
4999
- ]
5000
- }
5001
- );
5002
- }
5003
-
5004
- // src/lib/floating-anim.ts
5005
- var FLOATING_SLIDE_ANIM = [
5006
- "data-[state=open]:data-[side=top]:animate-floating-in-top",
5007
- "data-[state=delayed-open]:data-[side=top]:animate-floating-in-top",
5008
- "data-[state=instant-open]:data-[side=top]:animate-floating-in-top",
5009
- "data-[state=closed]:data-[side=top]:animate-floating-out-top",
5010
- "data-[state=open]:data-[side=bottom]:animate-floating-in-bottom",
5011
- "data-[state=delayed-open]:data-[side=bottom]:animate-floating-in-bottom",
5012
- "data-[state=instant-open]:data-[side=bottom]:animate-floating-in-bottom",
5013
- "data-[state=closed]:data-[side=bottom]:animate-floating-out-bottom",
5014
- "data-[state=open]:data-[side=left]:animate-floating-in-left",
5015
- "data-[state=delayed-open]:data-[side=left]:animate-floating-in-left",
5016
- "data-[state=instant-open]:data-[side=left]:animate-floating-in-left",
5017
- "data-[state=closed]:data-[side=left]:animate-floating-out-left",
5018
- "data-[state=open]:data-[side=right]:animate-floating-in-right",
5019
- "data-[state=delayed-open]:data-[side=right]:animate-floating-in-right",
5020
- "data-[state=instant-open]:data-[side=right]:animate-floating-in-right",
5021
- "data-[state=closed]:data-[side=right]:animate-floating-out-right"
5022
- ].join(" ");
5023
- var PortalContainerContext = /* @__PURE__ */ react.createContext(null);
5024
- var InsideModalContext = /* @__PURE__ */ react.createContext(false);
5025
- var PortalContainerProvider = PortalContainerContext.Provider;
5026
- var InsideModalProvider = InsideModalContext.Provider;
5027
- function usePortalContainer() {
5028
- return react.useContext(PortalContainerContext) ?? void 0;
5029
- }
5030
- function useInsideModal() {
5031
- return react.useContext(InsideModalContext);
5032
- }
5033
-
5034
- // src/components/STag/tag.config.ts
5035
- var TAG_SHAPES = ["square", "pill"];
5036
- var TAG_SIZES = ["xs", "sm", "md"];
5037
- var TAG_COLORS = [
5038
- "grey",
5039
- "red",
5040
- "orange",
5041
- "yellow",
5042
- "green",
5043
- "blue",
5044
- "darkblue",
5045
- "indigo"
5046
- ];
5047
- var TAG_SIZE_CONFIG = {
5048
- xs: {
5049
- height: 20,
5050
- paddingX: 8,
5051
- gap: 4,
5052
- radius: 6,
5053
- iconSize: 12,
5054
- fontSize: 11,
5055
- fontWeight: 500,
5056
- lineHeight: 18
5057
- },
5058
- sm: {
5059
- height: 24,
5060
- paddingX: 8,
5061
- gap: 4,
5062
- radius: 6,
5063
- iconSize: 16,
5064
- fontSize: 12,
5065
- fontWeight: 700,
5066
- lineHeight: 20
5067
- },
5068
- md: {
5069
- height: 28,
5070
- paddingX: 12,
5071
- gap: 6,
5072
- radius: 8,
5073
- iconSize: 16,
5074
- fontSize: 14,
5075
- fontWeight: 700,
5076
- lineHeight: 24
5077
- }
5078
- };
5079
- var STag = /* @__PURE__ */ react.forwardRef(function STag2({
5080
- shape = "square",
5081
- size = "sm",
5082
- color = "grey",
5083
- deep = false,
5084
- label = "",
5085
- icon,
5086
- iconLeft = true,
5087
- width,
5088
- className,
5089
- style,
5090
- ...rest
5091
- }, ref) {
5092
- const s = TAG_SIZE_CONFIG[size];
5093
- const contentColor = deep ? `var(--cmp-tag-${color}-deep-content, var(--cmp-tag-deep-content))` : `var(--cmp-tag-${color}-content)`;
5094
- const bgColor = deep ? `var(--cmp-tag-${color}-deep-bg)` : `var(--cmp-tag-${color}-bg)`;
5095
- const iconNode = icon ? /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.iconSize, color: contentColor }) : null;
5096
- const resolvedWidth = typeof width === "number" ? `${width}px` : width;
5097
- return /* @__PURE__ */ jsxRuntime.jsxs(
5098
- "span",
5099
- {
5100
- ref,
5101
- "aria-label": label || "tag",
5102
- className: cn(
5103
- "box-border inline-flex w-fit select-none items-center justify-center whitespace-nowrap",
5104
- className
5105
- ),
5106
- style: {
5107
- minHeight: s.height,
5108
- padding: `0 ${s.paddingX}px`,
5109
- gap: s.gap,
5110
- borderRadius: shape === "pill" ? 999 : s.radius,
5111
- fontSize: s.fontSize,
5112
- fontWeight: s.fontWeight,
5113
- lineHeight: `${s.lineHeight}px`,
5114
- background: bgColor,
5115
- color: contentColor,
5116
- ...resolvedWidth != null ? { width: resolvedWidth } : {},
5117
- ...style
5118
- },
5119
- ...rest,
5120
- children: [
5121
- icon && iconLeft && iconNode,
5122
- label && // leading-none: 태그는 min-height 로 높이가 정해지고 flex 가 세로 중앙 정렬하므로
5123
- // 레이블에 line-height 가 필요 없다. 오히려 line-height 를 주면 브라우저가
5124
- // 폰트 ascent/descent 를 정수 px 로 반올림한 뒤 남는 half-leading 을 내림 처리해
5125
- // 베이스라인이 최대 1px 위로 밀린다(xs 에서 특히 눈에 띈다).
5126
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex min-w-0 items-center justify-center leading-none", children: label }),
5127
- icon && !iconLeft && iconNode
5128
- ]
5129
- }
5130
- );
5131
- });
5132
- var SIZE = {
5133
- xxs: { icon: 12, radius: "sm" },
5134
- xs: { icon: 16, radius: "sm" },
5135
- sm: { icon: 20, radius: "sm" },
5136
- md: { icon: 24, radius: "md" },
5137
- lg: { icon: 32, radius: "md" }
5138
- };
5139
- var TOOLTIP_HIDE_DELAY_MS = 100;
5140
- function SGhostButton({
5141
- icon,
5142
- size = "sm",
5143
- intent = "default",
5144
- ariaLabel = "",
5145
- ariaPressed,
5146
- ariaExpanded,
5147
- tooltipText,
5148
- disabled = false,
5149
- onClick,
5150
- anchorClassName,
5151
- anchorStyle,
5152
- className,
5153
- style
5154
- }) {
5155
- const portalContainer = usePortalContainer();
5156
- const [open, setOpen] = react.useState(false);
5157
- const hideTimer = react.useRef(void 0);
5158
- const cancelHide = () => {
5159
- if (hideTimer.current) {
5160
- clearTimeout(hideTimer.current);
5161
- hideTimer.current = void 0;
5162
- }
5163
- };
5164
- const openNow = () => {
5165
- cancelHide();
5166
- setOpen(true);
5167
- };
5168
- const startHide = () => {
5169
- cancelHide();
5170
- hideTimer.current = setTimeout(() => setOpen(false), TOOLTIP_HIDE_DELAY_MS);
5171
- };
5172
- const s = SIZE[size];
5173
- const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
5174
- const text = (tooltipText ?? "").trim();
5175
- const hasTooltip = text !== "";
5176
- const btn2 = /* @__PURE__ */ jsxRuntime.jsxs(
5177
- "button",
5178
- {
5179
- type: "button",
5180
- disabled,
5181
- "aria-disabled": disabled ? "true" : void 0,
5182
- "aria-label": ariaLabel.trim() || void 0,
5183
- "aria-pressed": ariaPressed,
5184
- "aria-expanded": ariaExpanded,
5185
- tabIndex: disabled ? -1 : void 0,
5186
- onClick: (e) => {
5187
- if (disabled) {
5188
- e.preventDefault();
5189
- return;
5190
- }
5191
- onClick?.(e);
5192
- },
6139
+ const iconColor = disabled ? "var(--cmp-checkbox-checked-icon-disabled)" : "var(--cmp-checkbox-checked-icon-default)";
6140
+ const labelColor = disabled ? "var(--cmp-checkbox-label-disabled)" : inverse ? "var(--cmp-checkbox-label-inverse)" : "var(--cmp-checkbox-label-default)";
6141
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6142
+ "label",
6143
+ {
5193
6144
  className: cn(
5194
- "group/ghost-button relative box-border inline-flex items-center justify-center border-none bg-transparent p-0 transition-shadow",
6145
+ "inline-flex items-center gap-[var(--cmp-checkbox-gap)] align-middle",
5195
6146
  disabled ? "cursor-not-allowed" : "cursor-pointer",
5196
- "focus-visible:outline-none focus-visible:shadow-[0_0_0_2px_var(--cmp-ghostButton-action-hover-bg)]",
5197
6147
  className
5198
6148
  ),
5199
- style: {
5200
- width: `var(--cmp-ghostButton-${size}-size)`,
5201
- height: `var(--cmp-ghostButton-${size}-size)`,
5202
- borderRadius: `var(--cmp-ghostButton-radius-${s.radius})`,
5203
- ...style
5204
- },
6149
+ style,
5205
6150
  children: [
5206
- !disabled && /* @__PURE__ */ jsxRuntime.jsx(
5207
- "span",
6151
+ /* @__PURE__ */ jsxRuntime.jsx(
6152
+ RCheckbox__namespace.Root,
5208
6153
  {
5209
- "aria-hidden": true,
5210
- className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover/ghost-button:opacity-5",
5211
- style: { background: `var(--cmp-ghostButton-${intent}-hover-bg)` }
6154
+ checked: state2,
6155
+ disabled,
6156
+ onCheckedChange: handleChange,
6157
+ className: cn(
6158
+ "flex h-[var(--cmp-checkbox-size)] w-[var(--cmp-checkbox-size)] items-center justify-center rounded-[var(--cmp-checkbox-radius)] border-[length:var(--cmp-checkbox-borderWidth)] border-solid bg-[var(--cb-bg)] border-[color:var(--cb-border)] outline-none",
6159
+ disabled ? "cursor-not-allowed" : "cursor-pointer",
6160
+ !disabled && !isChecked && !inverse && "hover:border-[color:var(--cmp-checkbox-unchecked-border-hover)] hover:bg-[var(--cmp-checkbox-unchecked-bg-hover)]"
6161
+ ),
6162
+ style: { "--cb-bg": boxBg, "--cb-border": boxBorder },
6163
+ children: /* @__PURE__ */ jsxRuntime.jsx(RCheckbox__namespace.Indicator, { className: "flex items-center justify-center leading-none", children: /* @__PURE__ */ jsxRuntime.jsx(
6164
+ SIcon,
6165
+ {
6166
+ name: state2 === "indeterminate" ? "checkboxIndeterminate" : "checkboxCheck",
6167
+ size: 12,
6168
+ color: iconColor
6169
+ }
6170
+ ) })
5212
6171
  }
5213
6172
  ),
5214
- /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: contentColor })
6173
+ label && // 고스트(500) 텍스트로 너비를 고정 → 두께 400↔500 전환 시 레이아웃이 밀리지 않음
6174
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "grid text-nowrap text-[12px] leading-[20px]", style: { color: labelColor }, children: [
6175
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "invisible font-medium", style: { gridArea: "1 / 1" }, children: label }),
6176
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { gridArea: "1 / 1", fontWeight: isChecked ? 500 : 400 }, children: label })
6177
+ ] })
5215
6178
  ]
5216
6179
  }
5217
6180
  );
5218
- if (!hasTooltip) return btn2;
5219
- return /* @__PURE__ */ jsxRuntime.jsxs(RPopover12__namespace.Root, { open, onOpenChange: setOpen, children: [
5220
- /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
5221
- "span",
5222
- {
5223
- className: cn("inline-flex", anchorClassName),
5224
- style: anchorStyle,
5225
- onMouseEnter: openNow,
5226
- onMouseLeave: startHide,
5227
- onFocus: openNow,
5228
- onBlur: startHide,
5229
- children: btn2
5230
- }
5231
- ) }),
5232
- /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
5233
- RPopover12__namespace.Content,
5234
- {
5235
- side: "top",
5236
- sideOffset: 4,
5237
- className: cn("z-50", FLOATING_SLIDE_ANIM),
5238
- "aria-hidden": true,
5239
- onOpenAutoFocus: (e) => e.preventDefault(),
5240
- onMouseEnter: cancelHide,
5241
- onMouseLeave: startHide,
5242
- children: /* @__PURE__ */ jsxRuntime.jsx(STag, { shape: "square", size: "xs", color: "grey", label: text })
5243
- }
5244
- ) })
5245
- ] });
5246
6181
  }
5247
6182
  var TREE_LABEL_TYPOGRAPHY = {
5248
6183
  sm: {
@@ -5430,443 +6365,207 @@ var STreeItemImpl = /* @__PURE__ */ react.forwardRef(function STreeItem({
5430
6365
  "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",
5431
6366
  typography.rowHeight,
5432
6367
  border && "border-b border-(--cmp-tree-item-border-default)",
5433
- !node.disabled && "hover:bg-(--cmp-tree-item-bg-hover)",
5434
- node.disabled && "cursor-not-allowed opacity-60",
5435
- !node.disabled && hasChildren && "cursor-pointer",
5436
- className
5437
- ),
5438
- ...rest,
5439
- children: [
5440
- /* @__PURE__ */ jsxRuntime.jsx(
5441
- TreeConnector,
5442
- {
5443
- guideline,
5444
- size,
5445
- depth,
5446
- hasChildren,
5447
- isLast: last,
5448
- ancestorLast
5449
- }
5450
- ),
5451
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "box-border flex min-w-0 flex-1 items-center gap-(--cmp-tree-item-contentGap)", children: [
5452
- hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
5453
- SGhostButton,
5454
- {
5455
- icon: expanded ? "caretDown" : "caretRight",
5456
- size: "xxs",
5457
- ariaLabel: expanded ? "collapse tree item" : "expand tree item",
5458
- ariaExpanded: expanded,
5459
- disabled: node.disabled,
5460
- onClick: (event) => {
5461
- event.stopPropagation();
5462
- if (node.disabled) return;
5463
- onToggle?.(node, event);
5464
- }
5465
- }
5466
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-(--cmp-tree-item-arrow-icon) shrink-0", "aria-hidden": "true" }),
5467
- leadingNode,
5468
- selectable && /* @__PURE__ */ jsxRuntime.jsx(
5469
- "span",
5470
- {
5471
- onClick: (event) => event.stopPropagation(),
5472
- className: "flex shrink-0",
5473
- "aria-hidden": node.disabled ? "true" : void 0,
5474
- children: /* @__PURE__ */ jsxRuntime.jsx(
5475
- SCheckbox,
5476
- {
5477
- value: checkboxValue,
5478
- disabled: node.disabled,
5479
- onValueChange: () => handleSelect()
5480
- }
5481
- )
5482
- }
5483
- ),
5484
- /* @__PURE__ */ jsxRuntime.jsx(
5485
- "span",
5486
- {
5487
- onClick: (event) => {
5488
- if (!selectable) return;
5489
- event.stopPropagation();
5490
- if (!node.disabled && selectable) onTreeSelect?.(node, event);
5491
- },
5492
- className: cn(
5493
- "block min-w-0 flex-1 truncate py-(--cmp-tree-item-paddingY) text-(--cmp-tree-item-text-default)",
5494
- labelTypo,
5495
- node.disabled ? "cursor-not-allowed" : selectable ? "cursor-pointer" : void 0
5496
- ),
5497
- children: node.label
5498
- }
5499
- ),
5500
- trailingNode
5501
- ] })
5502
- ]
5503
- }
5504
- );
5505
- });
5506
- var STreeItem2 = /* @__PURE__ */ withDisplayName(STreeItemImpl, "STreeItem");
5507
- var STreeImpl = /* @__PURE__ */ react.forwardRef(function STree({
5508
- items,
5509
- value,
5510
- defaultValue,
5511
- expandedIds,
5512
- defaultExpandedIds,
5513
- selectable = true,
5514
- size = "sm",
5515
- showSelectAll = false,
5516
- cascadeSelection = true,
5517
- guideline = true,
5518
- border = true,
5519
- leading,
5520
- trailing,
5521
- onValueChange,
5522
- onExpandedChange,
5523
- className,
5524
- ...rest
5525
- }, ref) {
5526
- const [internalValue, setInternalValue] = react.useState(defaultValue ?? []);
5527
- const [internalExpandedIds, setInternalExpandedIds] = react.useState(
5528
- defaultExpandedIds ?? items.filter((item4) => item4.children?.length).map((item4) => item4.id)
5529
- );
5530
- const currentValue = value ?? internalValue;
5531
- const currentExpandedIds = expandedIds ?? internalExpandedIds;
5532
- const selectedSet = react.useMemo(() => toSelectedSet(currentValue), [currentValue]);
5533
- const expandedSet = react.useMemo(() => new Set(currentExpandedIds), [currentExpandedIds]);
5534
- const selectableIds = react.useMemo(() => items.flatMap(collectSelectableIds), [items]);
5535
- const allSelected = selectableIds.length > 0 && selectableIds.every((id) => selectedSet.has(id));
5536
- const someSelected = selectableIds.some((id) => selectedSet.has(id));
5537
- const allIndeterminate = someSelected && !allSelected;
5538
- const showAllItem = selectable && showSelectAll;
5539
- const updateExpanded = (node) => {
5540
- const next = new Set(currentExpandedIds);
5541
- if (next.has(node.id)) next.delete(node.id);
5542
- else next.add(node.id);
5543
- const nextValue = Array.from(next);
5544
- if (expandedIds === void 0) setInternalExpandedIds(nextValue);
5545
- onExpandedChange?.(nextValue, node);
5546
- };
5547
- const updateSelected = (node) => {
5548
- if (node.disabled) return;
5549
- const targetIds = cascadeSelection ? collectSelectableIds(node) : [node.id];
5550
- const everySelected = getSelectionState(node, selectedSet, cascadeSelection).selected;
5551
- const next = new Set(selectedSet);
5552
- targetIds.forEach((id) => {
5553
- if (everySelected) next.delete(id);
5554
- else next.add(id);
5555
- });
5556
- const orderedIds = collectIds(items).filter((id) => next.has(id));
5557
- if (value === void 0) setInternalValue(orderedIds);
5558
- onValueChange?.(orderedIds, node);
5559
- };
5560
- const updateAllSelected = (event) => {
5561
- event?.stopPropagation();
5562
- if (selectableIds.length === 0) return;
5563
- const next = new Set(selectedSet);
5564
- selectableIds.forEach((id) => {
5565
- if (allSelected) next.delete(id);
5566
- else next.add(id);
5567
- });
5568
- const orderedIds = collectIds(items).filter((id) => next.has(id));
5569
- if (value === void 0) setInternalValue(orderedIds);
5570
- onValueChange?.(orderedIds, TREE_ALL_NODE);
5571
- };
5572
- const renderNodes = (nodes, depth = 1, ancestorLast = []) => nodes.map((node, index) => {
5573
- const isLast = index === nodes.length - 1;
5574
- const isExpanded = expandedSet.has(node.id);
5575
- const { selected, indeterminate } = getSelectionState(node, selectedSet, cascadeSelection);
5576
- return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
5577
- /* @__PURE__ */ jsxRuntime.jsx(
5578
- STreeItem2,
5579
- {
5580
- node,
5581
- depth,
5582
- last: isLast,
5583
- ancestorLast,
5584
- expanded: isExpanded,
5585
- selected,
5586
- indeterminate,
5587
- selectable,
5588
- size,
5589
- guideline,
5590
- border,
5591
- leading,
5592
- trailing,
5593
- onToggle: updateExpanded,
5594
- onTreeSelect: updateSelected
5595
- }
5596
- ),
5597
- node.children?.length ? /* @__PURE__ */ jsxRuntime.jsx(
5598
- "div",
5599
- {
5600
- "aria-hidden": !isExpanded,
5601
- className: cn(
5602
- "grid w-full overflow-hidden transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none",
5603
- isExpanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
5604
- ),
5605
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 overflow-hidden", children: renderNodes(node.children, depth + 1, [...ancestorLast, isLast]) })
5606
- }
5607
- ) : null
5608
- ] }, node.id);
5609
- });
5610
- return /* @__PURE__ */ jsxRuntime.jsxs(
5611
- "div",
5612
- {
5613
- ref,
5614
- role: "tree",
5615
- className: cn("flex w-full flex-col items-start bg-(--cmp-tree-item-bg-default)", className),
6368
+ !node.disabled && "hover:bg-(--cmp-tree-item-bg-hover)",
6369
+ node.disabled && "cursor-not-allowed opacity-60",
6370
+ !node.disabled && hasChildren && "cursor-pointer",
6371
+ className
6372
+ ),
5616
6373
  ...rest,
5617
6374
  children: [
5618
- showAllItem && /* @__PURE__ */ jsxRuntime.jsx(
5619
- STreeAllItem,
6375
+ /* @__PURE__ */ jsxRuntime.jsx(
6376
+ TreeConnector,
5620
6377
  {
5621
- selected: allSelected,
5622
- indeterminate: allIndeterminate,
5623
- border,
6378
+ guideline,
5624
6379
  size,
5625
- onSelect: updateAllSelected
6380
+ depth,
6381
+ hasChildren,
6382
+ isLast: last,
6383
+ ancestorLast
5626
6384
  }
5627
6385
  ),
5628
- renderNodes(items)
6386
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "box-border flex min-w-0 flex-1 items-center gap-(--cmp-tree-item-contentGap)", children: [
6387
+ hasChildren ? /* @__PURE__ */ jsxRuntime.jsx(
6388
+ SGhostButton,
6389
+ {
6390
+ icon: expanded ? "caretDown" : "caretRight",
6391
+ size: "xxs",
6392
+ ariaLabel: expanded ? "collapse tree item" : "expand tree item",
6393
+ ariaExpanded: expanded,
6394
+ disabled: node.disabled,
6395
+ onClick: (event) => {
6396
+ event.stopPropagation();
6397
+ if (node.disabled) return;
6398
+ onToggle?.(node, event);
6399
+ }
6400
+ }
6401
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-(--cmp-tree-item-arrow-icon) shrink-0", "aria-hidden": "true" }),
6402
+ leadingNode,
6403
+ selectable && /* @__PURE__ */ jsxRuntime.jsx(
6404
+ "span",
6405
+ {
6406
+ onClick: (event) => event.stopPropagation(),
6407
+ className: "flex shrink-0",
6408
+ "aria-hidden": node.disabled ? "true" : void 0,
6409
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6410
+ SCheckbox,
6411
+ {
6412
+ value: checkboxValue,
6413
+ disabled: node.disabled,
6414
+ onValueChange: () => handleSelect()
6415
+ }
6416
+ )
6417
+ }
6418
+ ),
6419
+ /* @__PURE__ */ jsxRuntime.jsx(
6420
+ "span",
6421
+ {
6422
+ onClick: (event) => {
6423
+ if (!selectable) return;
6424
+ event.stopPropagation();
6425
+ if (!node.disabled && selectable) onTreeSelect?.(node, event);
6426
+ },
6427
+ className: cn(
6428
+ "block min-w-0 flex-1 truncate py-(--cmp-tree-item-paddingY) text-(--cmp-tree-item-text-default)",
6429
+ labelTypo,
6430
+ node.disabled ? "cursor-not-allowed" : selectable ? "cursor-pointer" : void 0
6431
+ ),
6432
+ children: node.label
6433
+ }
6434
+ ),
6435
+ trailingNode
6436
+ ] })
5629
6437
  ]
5630
6438
  }
5631
6439
  );
5632
6440
  });
5633
- var STree2 = /* @__PURE__ */ withDisplayName(STreeImpl, "STree");
5634
-
5635
- // src/lib/sanitize-inline-html.ts
5636
- var ALLOWED_INLINE_TAGS = /* @__PURE__ */ new Set(["A", "B", "STRONG", "I", "EM", "BR", "SPAN"]);
5637
- var ALLOWED_ATTRS_BY_TAG = {
5638
- A: /* @__PURE__ */ new Set(["href", "target", "rel", "class"])
5639
- };
5640
- var COMMON_ALLOWED_ATTRS = /* @__PURE__ */ new Set(["class"]);
5641
- var SAFE_HREF_RE = /^(https?:|mailto:|tel:|\/|#|\.)/i;
5642
- var DROP_CONTENT_TAGS = /* @__PURE__ */ new Set([
5643
- "SCRIPT",
5644
- "STYLE",
5645
- "IFRAME",
5646
- "OBJECT",
5647
- "EMBED",
5648
- "META",
5649
- "LINK",
5650
- "BASE",
5651
- "NOSCRIPT",
5652
- // SVG/MATH 는 foreign-content 파싱 모드로 전환되어 재직렬화 시 다른 트리로
5653
- // 재파싱될 수 있다(mXSS 벡터). 인라인 라벨에 필요 없으므로 통째로 제거한다.
5654
- "SVG",
5655
- "MATH",
5656
- "TEMPLATE"
5657
- ]);
5658
- var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
5659
- var sanitizeNode = (node) => {
5660
- if (node.nodeType === Node.COMMENT_NODE) {
5661
- node.remove();
5662
- return;
5663
- }
5664
- if (node.nodeType !== Node.ELEMENT_NODE) return;
5665
- const element = node;
5666
- const tagName = element.tagName.toUpperCase();
5667
- if (DROP_CONTENT_TAGS.has(tagName)) {
5668
- element.remove();
5669
- return;
5670
- }
5671
- Array.from(element.childNodes).forEach((child) => sanitizeNode(child));
5672
- if (ALLOWED_INLINE_TAGS.has(tagName)) {
5673
- if (element.hasAttribute("className")) {
5674
- const val = element.getAttribute("className");
5675
- element.removeAttribute("className");
5676
- if (!element.hasAttribute("class")) element.setAttribute("class", val);
5677
- }
5678
- const allowedAttrs = ALLOWED_ATTRS_BY_TAG[tagName] ?? COMMON_ALLOWED_ATTRS;
5679
- Array.from(element.attributes).forEach((attr) => {
5680
- if (!allowedAttrs.has(attr.name)) element.removeAttribute(attr.name);
5681
- });
5682
- if (tagName === "A") {
5683
- const href = element.getAttribute("href");
5684
- if (href !== null && href !== "" && !SAFE_HREF_RE.test(href)) element.removeAttribute("href");
5685
- if (element.hasAttribute("target")) {
5686
- element.setAttribute("rel", "noopener noreferrer");
5687
- }
5688
- }
5689
- return;
5690
- }
5691
- const parent = element.parentNode;
5692
- if (parent == null) return;
5693
- while (element.firstChild) parent.insertBefore(element.firstChild, element);
5694
- parent.removeChild(element);
5695
- };
5696
- var sanitizeInlineHtml = (value) => {
5697
- if (typeof document === "undefined") return escapeHtml(value);
5698
- const template = document.createElement("template");
5699
- template.innerHTML = value;
5700
- Array.from(template.content.childNodes).forEach((child) => sanitizeNode(child));
5701
- return template.innerHTML;
5702
- };
5703
- var HIDE_DELAY_MS = 100;
5704
- function useBody(content, message = []) {
5705
- if (content != null) return content;
5706
- if (message.length === 0) return null;
5707
- const singleLine = message.length === 1;
5708
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: message.map((line, i) => /* @__PURE__ */ jsxRuntime.jsx(
5709
- "div",
5710
- {
5711
- className: singleLine ? "leading-[24px]" : void 0,
5712
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(line) }
5713
- },
5714
- i
5715
- )) });
5716
- }
5717
- var STooltipImpl = /* @__PURE__ */ react.forwardRef(function STooltip({
5718
- content,
5719
- message = [],
5720
- open: openProp,
5721
- onOpenChange,
5722
- trigger = "hover",
5723
- placement = "top",
5724
- sideOffset = 8,
5725
- tooltipType = "default",
5726
- showClose = false,
5727
- disabled = false,
5728
- children,
5729
- icon = "helpOutline",
5730
- iconSize = 12,
5731
- color = "#01BB4B",
5732
- ariaLabel,
6441
+ var STreeItem2 = /* @__PURE__ */ withDisplayName(STreeItemImpl, "STreeItem");
6442
+ var STreeImpl = /* @__PURE__ */ react.forwardRef(function STree({
6443
+ items,
6444
+ value,
6445
+ defaultValue,
6446
+ expandedIds,
6447
+ defaultExpandedIds,
6448
+ selectable = true,
6449
+ size = "sm",
6450
+ showSelectAll = false,
6451
+ cascadeSelection = true,
6452
+ guideline = true,
6453
+ border = true,
6454
+ leading,
6455
+ trailing,
6456
+ onValueChange,
6457
+ onExpandedChange,
5733
6458
  className,
5734
- triggerClassName,
5735
- style,
5736
- contentProps
6459
+ ...rest
5737
6460
  }, ref) {
5738
- const portalContainer = usePortalContainer();
5739
- const [internalOpen, setInternalOpen] = react.useState(false);
5740
- const isControlled = openProp !== void 0;
5741
- const open = isControlled ? openProp : internalOpen;
5742
- const hideTimer = react.useRef(void 0);
5743
- const cancelHide = react.useCallback(() => {
5744
- if (hideTimer.current) {
5745
- clearTimeout(hideTimer.current);
5746
- hideTimer.current = void 0;
5747
- }
5748
- }, []);
5749
- const setOpenState = react.useCallback(
5750
- (next) => {
5751
- if (!isControlled) setInternalOpen(next);
5752
- onOpenChange?.(next);
5753
- },
5754
- [isControlled, onOpenChange]
5755
- );
5756
- const openNow = () => {
5757
- cancelHide();
5758
- setOpenState(true);
5759
- };
5760
- const startHide = () => {
5761
- if (showClose) return;
5762
- cancelHide();
5763
- hideTimer.current = setTimeout(() => setOpenState(false), HIDE_DELAY_MS);
5764
- };
5765
- react.useImperativeHandle(
5766
- ref,
5767
- () => ({
5768
- show: () => {
5769
- cancelHide();
5770
- setOpenState(true);
5771
- },
5772
- hide: () => {
5773
- cancelHide();
5774
- setOpenState(false);
5775
- }
5776
- }),
5777
- [cancelHide, setOpenState]
5778
- );
5779
- const isHover = trigger === "hover";
5780
- const isNone = trigger === "none";
5781
- if (isNone && children == null && typeof console !== "undefined") {
5782
- console.warn(
5783
- '[STooltip] trigger="none" \uC740 \uC704\uCE58 \uAE30\uC900\uC774 \uB420 children \uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uC544\uC774\uCF58 \uD2B8\uB9AC\uAC70\uB85C \uD3F4\uBC31\uD569\uB2C8\uB2E4.'
5784
- );
5785
- }
5786
- const body = useBody(content, message);
5787
- const bg = `var(--cmp-tooltip-${tooltipType}-bg)`;
5788
- const fg = `var(--cmp-tooltip-${tooltipType}-content)`;
5789
- const triggerNode = children ?? /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("inline-flex cursor-pointer", triggerClassName), "aria-label": ariaLabel, children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: iconSize, color }) });
5790
- if (disabled || body == null) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: triggerNode });
5791
- const {
5792
- className: contentPropsClassName,
5793
- style: contentPropsStyle,
5794
- ...restContentProps
5795
- } = contentProps ?? {};
5796
- const contentClass = cn(
5797
- "z-50 w-fit max-w-[var(--radix-popover-content-available-width)] rounded-[6px] px-[16px] py-[12px] text-[12px] leading-[20px] shadow-normal-sm",
5798
- FLOATING_SLIDE_ANIM,
5799
- className,
5800
- contentPropsClassName
6461
+ const [internalValue, setInternalValue] = react.useState(defaultValue ?? []);
6462
+ const [internalExpandedIds, setInternalExpandedIds] = react.useState(
6463
+ defaultExpandedIds ?? items.filter((item4) => item4.children?.length).map((item4) => item4.id)
5801
6464
  );
5802
- const contentStyle = {
5803
- background: bg,
5804
- color: fg,
5805
- fontWeight: tooltipType === "default" ? 500 : 700,
5806
- ...style,
5807
- ...contentPropsStyle
6465
+ const currentValue = value ?? internalValue;
6466
+ const currentExpandedIds = expandedIds ?? internalExpandedIds;
6467
+ const selectedSet = react.useMemo(() => toSelectedSet(currentValue), [currentValue]);
6468
+ const expandedSet = react.useMemo(() => new Set(currentExpandedIds), [currentExpandedIds]);
6469
+ const selectableIds = react.useMemo(() => items.flatMap(collectSelectableIds), [items]);
6470
+ const allSelected = selectableIds.length > 0 && selectableIds.every((id) => selectedSet.has(id));
6471
+ const someSelected = selectableIds.some((id) => selectedSet.has(id));
6472
+ const allIndeterminate = someSelected && !allSelected;
6473
+ const showAllItem = selectable && showSelectAll;
6474
+ const updateExpanded = (node) => {
6475
+ const next = new Set(currentExpandedIds);
6476
+ if (next.has(node.id)) next.delete(node.id);
6477
+ else next.add(node.id);
6478
+ const nextValue = Array.from(next);
6479
+ if (expandedIds === void 0) setInternalExpandedIds(nextValue);
6480
+ onExpandedChange?.(nextValue, node);
5808
6481
  };
5809
- return /* @__PURE__ */ jsxRuntime.jsxs(RPopover12__namespace.Root, { open, onOpenChange: setOpenState, children: [
5810
- isNone ? (
5811
- // 트리거 없음: children 을 래퍼 없이 그대로 앵커로 쓴다 (소비자 레이아웃 보존).
5812
- // mouse/focus 핸들러가 없으므로 showClose 없이도 자동 닫힘이 발생하지 않는다.
5813
- /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Anchor, { asChild: true, children: triggerNode })
5814
- ) : isHover ? (
5815
- // hover: Anchor(위치 기준) + 직접 mouse 핸들러 — 클릭으로 토글되지 않도록 Trigger 미사용
5816
- /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
5817
- "span",
6482
+ const updateSelected = (node) => {
6483
+ if (node.disabled) return;
6484
+ const targetIds = cascadeSelection ? collectSelectableIds(node) : [node.id];
6485
+ const everySelected = getSelectionState(node, selectedSet, cascadeSelection).selected;
6486
+ const next = new Set(selectedSet);
6487
+ targetIds.forEach((id) => {
6488
+ if (everySelected) next.delete(id);
6489
+ else next.add(id);
6490
+ });
6491
+ const orderedIds = collectIds(items).filter((id) => next.has(id));
6492
+ if (value === void 0) setInternalValue(orderedIds);
6493
+ onValueChange?.(orderedIds, node);
6494
+ };
6495
+ const updateAllSelected = (event) => {
6496
+ event?.stopPropagation();
6497
+ if (selectableIds.length === 0) return;
6498
+ const next = new Set(selectedSet);
6499
+ selectableIds.forEach((id) => {
6500
+ if (allSelected) next.delete(id);
6501
+ else next.add(id);
6502
+ });
6503
+ const orderedIds = collectIds(items).filter((id) => next.has(id));
6504
+ if (value === void 0) setInternalValue(orderedIds);
6505
+ onValueChange?.(orderedIds, TREE_ALL_NODE);
6506
+ };
6507
+ const renderNodes = (nodes, depth = 1, ancestorLast = []) => nodes.map((node, index) => {
6508
+ const isLast = index === nodes.length - 1;
6509
+ const isExpanded = expandedSet.has(node.id);
6510
+ const { selected, indeterminate } = getSelectionState(node, selectedSet, cascadeSelection);
6511
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
6512
+ /* @__PURE__ */ jsxRuntime.jsx(
6513
+ STreeItem2,
5818
6514
  {
5819
- className: "inline-flex",
5820
- onMouseEnter: openNow,
5821
- onMouseLeave: startHide,
5822
- onFocus: openNow,
5823
- onBlur: startHide,
5824
- children: triggerNode
6515
+ node,
6516
+ depth,
6517
+ last: isLast,
6518
+ ancestorLast,
6519
+ expanded: isExpanded,
6520
+ selected,
6521
+ indeterminate,
6522
+ selectable,
6523
+ size,
6524
+ guideline,
6525
+ border,
6526
+ leading,
6527
+ trailing,
6528
+ onToggle: updateExpanded,
6529
+ onTreeSelect: updateSelected
5825
6530
  }
5826
- ) })
5827
- ) : /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Trigger, { asChild: true, children: triggerNode }),
5828
- /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsxs(
5829
- RPopover12__namespace.Content,
5830
- {
5831
- side: placement,
5832
- sideOffset,
5833
- className: contentClass,
5834
- style: contentStyle,
5835
- onOpenAutoFocus: (e) => e.preventDefault(),
5836
- onInteractOutside: (e) => {
5837
- if (showClose || isNone) e.preventDefault();
5838
- },
5839
- onEscapeKeyDown: (e) => {
5840
- if (showClose || isNone) e.preventDefault();
5841
- },
5842
- onMouseEnter: isHover ? cancelHide : void 0,
5843
- onMouseLeave: isHover ? startHide : void 0,
5844
- ...restContentProps,
5845
- children: [
5846
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-[12px]", children: [
5847
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: body }),
5848
- showClose && !isNone && /* @__PURE__ */ jsxRuntime.jsx(
5849
- SGhostButton,
5850
- {
5851
- icon: "close",
5852
- size: "xs",
5853
- intent: tooltipType === "default" ? "inverse" : "default",
5854
- ariaLabel: "\uB2EB\uAE30",
5855
- className: "shrink-0",
5856
- onClick: () => {
5857
- cancelHide();
5858
- setOpenState(false);
5859
- }
5860
- }
5861
- )
5862
- ] }),
5863
- /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Arrow, { width: 16, height: 12, style: { fill: bg } })
5864
- ]
5865
- }
5866
- ) })
5867
- ] });
6531
+ ),
6532
+ node.children?.length ? /* @__PURE__ */ jsxRuntime.jsx(
6533
+ "div",
6534
+ {
6535
+ "aria-hidden": !isExpanded,
6536
+ className: cn(
6537
+ "grid w-full overflow-hidden transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none",
6538
+ isExpanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
6539
+ ),
6540
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 overflow-hidden", children: renderNodes(node.children, depth + 1, [...ancestorLast, isLast]) })
6541
+ }
6542
+ ) : null
6543
+ ] }, node.id);
6544
+ });
6545
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6546
+ "div",
6547
+ {
6548
+ ref,
6549
+ role: "tree",
6550
+ className: cn("flex w-full flex-col items-start bg-(--cmp-tree-item-bg-default)", className),
6551
+ ...rest,
6552
+ children: [
6553
+ showAllItem && /* @__PURE__ */ jsxRuntime.jsx(
6554
+ STreeAllItem,
6555
+ {
6556
+ selected: allSelected,
6557
+ indeterminate: allIndeterminate,
6558
+ border,
6559
+ size,
6560
+ onSelect: updateAllSelected
6561
+ }
6562
+ ),
6563
+ renderNodes(items)
6564
+ ]
6565
+ }
6566
+ );
5868
6567
  });
5869
- var STooltip2 = /* @__PURE__ */ withDisplayName(STooltipImpl, "STooltip");
6568
+ var STree2 = /* @__PURE__ */ withDisplayName(STreeImpl, "STree");
5870
6569
  var BODY_PADDING_CLASS = {
5871
6570
  default: "p-(--sys-space-panel-padding-default)",
5872
6571
  wide: "p-(--sys-space-panel-padding-wide)",
@@ -11670,931 +12369,704 @@ var SStepper = /* @__PURE__ */ react.forwardRef(function SStepper2({
11670
12369
  ]
11671
12370
  },
11672
12371
  `${groupKey}-${group.startIndex}`
11673
- );
11674
- })
11675
- ]
11676
- }
11677
- );
11678
- }
11679
- return /* @__PURE__ */ jsxRuntime.jsx(
11680
- "div",
11681
- {
11682
- role: "list",
11683
- "aria-label": ariaLabel,
11684
- className: cn("inline-flex items-center", className),
11685
- style: {
11686
- gap: STEPPER_LAYOUT.containerGap,
11687
- fontSize: config.fontSize,
11688
- lineHeight: config.lineHeight,
11689
- ...style
11690
- },
11691
- children: items.map((item4, index) => {
11692
- const state2 = getState(item4, index);
11693
- const content = isClickable ? /* @__PURE__ */ jsxRuntime.jsx("span", { role: "listitem", "aria-current": state2 === "active" ? "step" : void 0, children: renderClickableStepContent(item4, index) }) : renderStepContent(item4, index);
11694
- return /* @__PURE__ */ jsxRuntime.jsxs(
11695
- "span",
11696
- {
11697
- className: "inline-flex items-center",
11698
- style: { gap: STEPPER_LAYOUT.containerGap },
11699
- children: [
11700
- content,
11701
- index < items.length - 1 && /* @__PURE__ */ jsxRuntime.jsx(
11702
- "span",
11703
- {
11704
- "aria-hidden": true,
11705
- className: "inline-flex h-px shrink-0 items-center",
11706
- style: { width: STEPPER_LAYOUT.dashWidth, color: STEPPER_COLORS.dash },
11707
- children: /* @__PURE__ */ jsxRuntime.jsxs(
11708
- "svg",
11709
- {
11710
- width: "20",
11711
- height: "1",
11712
- viewBox: "0 0 20 1",
11713
- fill: "none",
11714
- xmlns: "http://www.w3.org/2000/svg",
11715
- children: [
11716
- /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "1.6", height: "1", fill: "currentColor" }),
11717
- /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", width: "3.3", height: "1", fill: "currentColor" }),
11718
- /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "11.7", width: "3.3", height: "1", fill: "currentColor" }),
11719
- /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "18.4", width: "1.6", height: "1", fill: "currentColor" })
11720
- ]
11721
- }
11722
- )
11723
- }
11724
- )
11725
- ]
11726
- },
11727
- getItemValue(item4, index)
11728
- );
11729
- })
11730
- }
11731
- );
11732
- });
11733
-
11734
- // src/lib/date.ts
11735
- function todayStr() {
11736
- const d = /* @__PURE__ */ new Date();
11737
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
11738
- }
11739
- function formatDate(year, month, day) {
11740
- return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
11741
- }
11742
- function calcYearMonth(year, month, dir) {
11743
- if (dir === "prev")
11744
- return month === 1 ? { year: year - 1, month: 12 } : { year, month: month - 1 };
11745
- return month === 12 ? { year: year + 1, month: 1 } : { year, month: month + 1 };
11746
- }
11747
- function monthCells(year, month) {
11748
- const firstDayIndex = new Date(year, month - 1, 1).getDay();
11749
- const daysInCurrent = new Date(year, month, 0).getDate();
11750
- const prev = calcYearMonth(year, month, "prev");
11751
- const daysInPrev = new Date(prev.year, prev.month, 0).getDate();
11752
- const next = calcYearMonth(year, month, "next");
11753
- const cells = [];
11754
- for (let i = firstDayIndex - 1; i >= 0; i--) {
11755
- const day = daysInPrev - i;
11756
- cells.push({ date: formatDate(prev.year, prev.month, day), day, inCurrentMonth: false });
11757
- }
11758
- for (let day = 1; day <= daysInCurrent; day++) {
11759
- cells.push({ date: formatDate(year, month, day), day, inCurrentMonth: true });
11760
- }
11761
- const remaining = (7 - cells.length % 7) % 7;
11762
- for (let day = 1; day <= remaining; day++) {
11763
- cells.push({ date: formatDate(next.year, next.month, day), day, inCurrentMonth: false });
12372
+ );
12373
+ })
12374
+ ]
12375
+ }
12376
+ );
11764
12377
  }
11765
- return cells;
11766
- }
11767
- var WEEK_LABELS = ["\uC77C", "\uC6D4", "\uD654", "\uC218", "\uBAA9", "\uAE08", "\uD1A0"];
11768
-
11769
- // src/components/SCalendarBoard/calendarBoard.config.ts
11770
- var CALENDAR_BOARD_EVENT_COLORS = [
11771
- "default",
11772
- "blue",
11773
- "oceanBlue",
11774
- "lightBlue",
11775
- "green",
11776
- "yellow",
11777
- "orange",
11778
- "red",
11779
- "white"
11780
- ];
11781
- var BG = (name) => `var(--cmp-calendarBoard-eventButton-bg-${name})`;
11782
- var CALENDAR_BOARD_EVENT_PRESETS = {
11783
- default: { bg: BG("default"), dot: "indigo" },
11784
- blue: { bg: BG("blue"), dot: "blue" },
11785
- oceanBlue: { bg: BG("oceanBlue"), dot: "darkblue" },
11786
- lightBlue: { bg: BG("lightBlue"), dot: "lightblue" },
11787
- green: { bg: BG("green"), dot: "green" },
11788
- yellow: { bg: BG("yellow"), dot: "yellow" },
11789
- orange: { bg: BG("orange"), dot: "orange" },
11790
- red: { bg: BG("red"), dot: "red" },
11791
- white: { bg: BG("white"), dot: "grey" }
11792
- };
11793
- var CALENDAR_BOARD_MAX_VISIBLE_EVENTS = 4;
11794
- var CALENDAR_BOARD_DAY_COLORS = [
11795
- "default",
11796
- "danger",
11797
- "accent",
11798
- "complete",
11799
- "dim"
11800
- ];
11801
- var CALENDAR_BOARD_DAY_BG = {
11802
- default: "var(--cmp-calendarBoard-dayCard-bg)",
11803
- danger: "var(--color-red-30)",
11804
- accent: "var(--color-brilliantblue-40)",
11805
- complete: "var(--color-green-45)",
11806
- dim: "var(--color-grey-25)"
11807
- };
11808
- function CalendarEventButton({
11809
- event,
11810
- onSelect,
11811
- className
11812
- }) {
11813
- const preset = CALENDAR_BOARD_EVENT_PRESETS[event.color ?? "default"];
11814
- const dot = event.dotColor ?? preset.dot;
11815
- const disabled = event.disabled === true;
11816
- const hasDescription = event.description != null && event.description !== "";
11817
- const hasValue = event.value != null && event.value !== "";
11818
- const title = [event.label.replace(/<[^>]*>/g, ""), event.description, event.value].filter((v) => v != null && v !== "").join(" ") || void 0;
11819
- return /* @__PURE__ */ jsxRuntime.jsxs(
11820
- "button",
11821
- {
11822
- type: "button",
11823
- disabled,
11824
- title,
11825
- className: cn(
11826
- // hover 는 배경 위에 8% 오버레이를 얹는다 — 프리셋 배경이 9종이라 색마다 hover 색을 두지 않는다.
11827
- "group relative flex w-full min-w-0 items-center overflow-hidden text-left",
11828
- "h-(--cmp-calendarBoard-item-height) shrink-0 rounded-(--cmp-calendarBoard-item-radius)",
11829
- "gap-(--cmp-calendarBoard-eventButton-inline-gap) px-(--cmp-calendarBoard-item-paddingX)",
11830
- "bg-(--ceb-bg) text-(--cmp-calendarBoard-eventButton-text-color-default)",
11831
- disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
11832
- className
11833
- ),
11834
- style: { "--ceb-bg": preset.bg },
11835
- onClick: (e) => {
11836
- if (disabled) return;
11837
- onSelect?.(event, e);
11838
- },
11839
- children: [
11840
- !disabled && /* @__PURE__ */ jsxRuntime.jsx(
11841
- "span",
11842
- {
11843
- "aria-hidden": true,
11844
- className: "pointer-events-none absolute inset-0 rounded-(--cmp-calendarBoard-item-radius) bg-(--cmp-calendarBoard-eventButton-hover-overlay-color) opacity-0 transition-opacity group-hover:opacity-(--cmp-calendarBoard-eventButton-hover-overlay-opacity)"
11845
- }
11846
- ),
11847
- /* @__PURE__ */ jsxRuntime.jsx(SBadge, { "aria-hidden": true, color: dot, className: "shrink-0" }),
11848
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-(--cmp-calendarBoard-eventButton-label-gap)", children: [
11849
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "typo-body-sm-default min-w-0 flex-1 truncate", children: [
11850
- /* @__PURE__ */ jsxRuntime.jsx(
11851
- "span",
11852
- {
11853
- className: cn(
11854
- "typo-body-sm-semibold",
11855
- hasDescription && "me-(--cmp-calendarBoard-eventButton-label-gap)"
11856
- ),
11857
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(event.label) }
11858
- }
11859
- ),
11860
- hasDescription && event.description
11861
- ] }),
11862
- hasValue && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex shrink-0 items-center gap-(--cmp-calendarBoard-eventButton-label-gap)", children: [
11863
- /* @__PURE__ */ jsxRuntime.jsx(
11864
- "span",
11865
- {
11866
- "aria-hidden": true,
11867
- className: "typo-body-sm-default text-(--cmp-calendarBoard-eventButton-text-color-separator)",
11868
- children: "/"
11869
- }
11870
- ),
11871
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-sm-default whitespace-nowrap", children: event.value })
11872
- ] })
11873
- ] })
11874
- ]
11875
- }
11876
- );
11877
- }
11878
- var CalendarMoreButton = /* @__PURE__ */ react.forwardRef(function CalendarMoreButton2({ count, label = "\uB354\uBCF4\uAE30", onClick, className }, ref) {
11879
- return /* @__PURE__ */ jsxRuntime.jsxs(
11880
- "button",
11881
- {
11882
- ref,
11883
- type: "button",
11884
- className: cn(
11885
- "group relative flex w-full min-w-0 cursor-pointer items-center overflow-hidden text-left",
11886
- "h-(--cmp-calendarBoard-item-height) shrink-0 rounded-(--cmp-calendarBoard-item-radius)",
11887
- "gap-(--cmp-calendarBoard-moreButton-gap) px-(--cmp-calendarBoard-item-paddingX)",
11888
- "bg-(--cmp-calendarBoard-moreButton-bg)",
11889
- className
11890
- ),
11891
- onClick,
11892
- children: [
11893
- /* @__PURE__ */ jsxRuntime.jsx(
11894
- "span",
11895
- {
11896
- "aria-hidden": true,
11897
- className: "pointer-events-none absolute inset-0 rounded-(--cmp-calendarBoard-item-radius) bg-(--cmp-calendarBoard-moreButton-hover-overlay-color) opacity-0 transition-opacity group-hover:opacity-(--cmp-calendarBoard-moreButton-hover-overlay-opacity)"
11898
- }
11899
- ),
11900
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "typo-body-sm-semibold shrink-0 text-(--cmp-calendarBoard-moreButton-count-color)", children: [
11901
- count,
11902
- "\uAC1C"
11903
- ] }),
11904
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-sm-default min-w-0 truncate text-(--cmp-calendarBoard-moreButton-label-color)", children: label })
11905
- ]
11906
- }
11907
- );
11908
- });
11909
- var CalendarDayCard = /* @__PURE__ */ react.forwardRef(function CalendarDayCard2({
11910
- date,
11911
- day,
11912
- inCurrentMonth,
11913
- isToday,
11914
- isSelected,
11915
- selectable,
11916
- color,
11917
- events,
11918
- body,
11919
- hiddenCount,
11920
- isOpen,
11921
- moreLabel,
11922
- onDaySelect,
11923
- onEventClick,
11924
- onOpenEvents,
11925
- ...rest
11926
- }, ref) {
11927
- const hasEvents = events.length + hiddenCount > 0;
11928
- const interactive = selectable || hasEvents;
11929
- const activate = () => {
11930
- if (hasEvents) onOpenEvents?.();
11931
- if (selectable) onDaySelect?.(date);
11932
- };
11933
- return /* @__PURE__ */ jsxRuntime.jsxs(
12378
+ return /* @__PURE__ */ jsxRuntime.jsx(
11934
12379
  "div",
11935
12380
  {
11936
- ...rest,
11937
- ref,
11938
- role: "gridcell",
11939
- "data-date": date,
11940
- "aria-selected": selectable ? isSelected : void 0,
11941
- "aria-current": isToday ? "date" : void 0,
11942
- "aria-haspopup": hasEvents ? "dialog" : void 0,
11943
- "aria-expanded": hasEvents ? isOpen : void 0,
11944
- tabIndex: interactive ? 0 : void 0,
11945
- onClick: interactive ? activate : void 0,
11946
- onKeyDown: interactive ? (e) => {
11947
- if (e.key === "Enter" || e.key === " ") {
11948
- e.preventDefault();
11949
- activate();
11950
- }
11951
- } : void 0,
11952
- className: cn(
11953
- "relative flex min-h-(--cmp-calendarBoard-dayCard-minHeight) min-w-(--cmp-calendarBoard-dayCard-minWidth) flex-col bg-(--cdc-bg) px-(--cmp-calendarBoard-dayCard-paddingX) outline-none",
11954
- interactive && "cursor-pointer"
11955
- ),
11956
- style: { ...rest.style, "--cdc-bg": CALENDAR_BOARD_DAY_BG[color] },
11957
- children: [
11958
- isSelected && /* @__PURE__ */ jsxRuntime.jsx(
11959
- "span",
11960
- {
11961
- "aria-hidden": true,
11962
- className: "pointer-events-none absolute inset-[1px] rounded-(--cmp-calendarBoard-dayCard-focus-border-radius) border-(length:--cmp-calendarBoard-dayCard-focus-border-width) border-solid border-(--cmp-calendarBoard-dayCard-focus-border-color)"
11963
- }
11964
- ),
11965
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 pt-(--cmp-calendarBoard-dayCard-header-paddingTop) pb-(--cmp-calendarBoard-dayCard-header-paddingBottom)", children: /* @__PURE__ */ jsxRuntime.jsx(
12381
+ role: "list",
12382
+ "aria-label": ariaLabel,
12383
+ className: cn("inline-flex items-center", className),
12384
+ style: {
12385
+ gap: STEPPER_LAYOUT.containerGap,
12386
+ fontSize: config.fontSize,
12387
+ lineHeight: config.lineHeight,
12388
+ ...style
12389
+ },
12390
+ children: items.map((item4, index) => {
12391
+ const state2 = getState(item4, index);
12392
+ const content = isClickable ? /* @__PURE__ */ jsxRuntime.jsx("span", { role: "listitem", "aria-current": state2 === "active" ? "step" : void 0, children: renderClickableStepContent(item4, index) }) : renderStepContent(item4, index);
12393
+ return /* @__PURE__ */ jsxRuntime.jsxs(
11966
12394
  "span",
11967
12395
  {
11968
- className: cn(
11969
- "inline-flex size-(--cmp-calendarBoard-daySequence-size) items-center justify-center rounded-(--cmp-calendarBoard-daySequence-radius)",
11970
- isToday ? "typo-body-md-bold bg-(--cmp-calendarBoard-daySequence-bg-today) text-(--cmp-calendarBoard-daySequence-text-today)" : cn(
11971
- "typo-body-md-medium",
11972
- inCurrentMonth ? "text-(--cmp-calendarBoard-daySequence-text-default)" : "text-(--cmp-calendarBoard-daySequence-text-otherMonth)"
11973
- ),
11974
- // 오늘이 아니면서 선택된 날은 테두리 원으로 표시한다 (오늘은 이미 채운 원이라 겹치지 않는다).
11975
- // 굵기는 1px 로 고정한다 — 칸을 감싸는 focus 테두리(2px)와 달리 얇은 원이고, 대응 토큰이 없다.
11976
- isSelected && !isToday && "border border-solid border-(--cmp-calendarBoard-daySequence-border-selected)"
11977
- ),
11978
- children: day
11979
- }
11980
- ) }),
11981
- /* @__PURE__ */ jsxRuntime.jsxs(
11982
- "div",
11983
- {
11984
- className: cn(
11985
- "flex min-h-0 flex-col gap-(--cmp-calendarBoard-dayCard-grid-gap) pb-(--cmp-calendarBoard-dayCard-grid-paddingBottom)",
11986
- // 슬롯은 남는 세로를 다 갖는다 — 안에서 h-full 로 칸을 채울 수 있어야 한다.
11987
- body != null && "flex-1"
11988
- ),
12396
+ className: "inline-flex items-center",
12397
+ style: { gap: STEPPER_LAYOUT.containerGap },
11989
12398
  children: [
11990
- body,
11991
- body == null && events.map((event, i) => /* @__PURE__ */ jsxRuntime.jsx(
11992
- CalendarEventButton,
11993
- {
11994
- event,
11995
- onSelect: (ev, e) => {
11996
- e.stopPropagation();
11997
- onEventClick?.(ev, e);
11998
- }
11999
- },
12000
- event.id ?? `${event.date}-${i}`
12001
- )),
12002
- hiddenCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(
12003
- CalendarMoreButton,
12399
+ content,
12400
+ index < items.length - 1 && /* @__PURE__ */ jsxRuntime.jsx(
12401
+ "span",
12004
12402
  {
12005
- count: hiddenCount,
12006
- label: moreLabel,
12007
- onClick: (e) => {
12008
- e.stopPropagation();
12009
- onOpenEvents?.();
12010
- }
12403
+ "aria-hidden": true,
12404
+ className: "inline-flex h-px shrink-0 items-center",
12405
+ style: { width: STEPPER_LAYOUT.dashWidth, color: STEPPER_COLORS.dash },
12406
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
12407
+ "svg",
12408
+ {
12409
+ width: "20",
12410
+ height: "1",
12411
+ viewBox: "0 0 20 1",
12412
+ fill: "none",
12413
+ xmlns: "http://www.w3.org/2000/svg",
12414
+ children: [
12415
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "1.6", height: "1", fill: "currentColor" }),
12416
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", width: "3.3", height: "1", fill: "currentColor" }),
12417
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "11.7", width: "3.3", height: "1", fill: "currentColor" }),
12418
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "18.4", width: "1.6", height: "1", fill: "currentColor" })
12419
+ ]
12420
+ }
12421
+ )
12011
12422
  }
12012
12423
  )
12013
12424
  ]
12014
- }
12015
- )
12016
- ]
12017
- }
12018
- );
12019
- });
12020
- var MAX_HEIGHT = 480;
12021
- var MAX_WIDTH = "calc(100vw - 16px)";
12022
- function CalendarDayEventListBox({
12023
- date,
12024
- events,
12025
- onEventClick,
12026
- minWidth,
12027
- minHeight,
12028
- onClose,
12029
- closeLabel = "\uB2EB\uAE30"
12030
- }) {
12031
- const [y, m, d] = date.split("-").map(Number);
12032
- const weekday = WEEK_LABELS[new Date(y, m - 1, d).getDay()];
12033
- return /* @__PURE__ */ jsxRuntime.jsxs(
12034
- "div",
12035
- {
12036
- className: "flex w-max flex-col overflow-hidden rounded-(--cmp-calendarBoard-radius) bg-(--cmp-calendarBoard-bg) shadow-(--shadow-overlay)",
12037
- style: { minWidth, maxWidth: MAX_WIDTH, minHeight, maxHeight: MAX_HEIGHT },
12038
- children: [
12039
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-(--cmp-calendarBoard-dayEvent-header-gap) px-(--cmp-calendarBoard-dayEvent-header-paddingX) py-(--cmp-calendarBoard-dayEvent-header-paddingY)", children: [
12040
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "typo-heading-md text-(--cmp-calendarBoard-header-content-color)", children: [
12041
- d,
12042
- "\uC77C"
12043
- ] }),
12044
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "typo-body-lg-default flex-1 text-(--cmp-calendarBoard-header-content-color)", children: [
12045
- "(",
12046
- weekday,
12047
- ")"
12048
- ] }),
12049
- /* @__PURE__ */ jsxRuntime.jsx(
12050
- SGhostButton,
12051
- {
12052
- icon: "close",
12053
- size: "xs",
12054
- ariaLabel: closeLabel,
12055
- className: "shrink-0",
12056
- onClick: onClose
12057
- }
12058
- )
12059
- ] }),
12060
- /* @__PURE__ */ jsxRuntime.jsx(SScrollArea, { axis: "vertical", className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-(--cmp-calendarBoard-dayEvent-body-gap) px-(--cmp-calendarBoard-dayEvent-body-paddingX) pb-(--cmp-calendarBoard-dayEvent-body-paddingBottom)", children: events.map((event, i) => /* @__PURE__ */ jsxRuntime.jsx(
12061
- CalendarEventButton,
12062
- {
12063
- event,
12064
- onSelect: onEventClick
12065
12425
  },
12066
- event.id ?? `${event.date}-${i}`
12067
- )) }) })
12068
- ]
12426
+ getItemValue(item4, index)
12427
+ );
12428
+ })
12069
12429
  }
12070
12430
  );
12431
+ });
12432
+
12433
+ // src/lib/date.ts
12434
+ function todayStr() {
12435
+ const d = /* @__PURE__ */ new Date();
12436
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
12071
12437
  }
12072
- var DAY_EVENT_OUTDENT = 16;
12073
- var VIEWPORT_PADDING2 = 8;
12074
- function shiftIntoView(desired, size, viewport) {
12075
- const max = Math.max(viewport - size - VIEWPORT_PADDING2, VIEWPORT_PADDING2);
12076
- return Math.min(Math.max(desired, VIEWPORT_PADDING2), max) - desired;
12438
+ function formatDate(year, month, day) {
12439
+ return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
12077
12440
  }
12078
- function measureRowCapacity(cell) {
12079
- const cs = getComputedStyle(cell);
12080
- const v = (name) => Number.parseFloat(cs.getPropertyValue(name)) || 0;
12081
- const gap = v("--cmp-calendarBoard-dayCard-grid-gap");
12082
- const row = v("--cmp-calendarBoard-item-height") + gap;
12083
- if (row <= 0) return null;
12084
- const header = v("--cmp-calendarBoard-dayCard-header-paddingTop") + v("--cmp-calendarBoard-daySequence-size") + v("--cmp-calendarBoard-dayCard-header-paddingBottom");
12085
- const free = cell.getBoundingClientRect().height - header - v("--cmp-calendarBoard-dayCard-grid-paddingBottom") + gap;
12086
- if (free <= 0) return null;
12087
- return Math.max(1, Math.floor(free / row));
12441
+ function calcYearMonth(year, month, dir) {
12442
+ if (dir === "prev")
12443
+ return month === 1 ? { year: year - 1, month: 12 } : { year, month: month - 1 };
12444
+ return month === 12 ? { year: year + 1, month: 1 } : { year, month: month + 1 };
12088
12445
  }
12089
- function SCalendarBoard({
12090
- year,
12091
- month,
12092
- defaultYear,
12093
- defaultMonth,
12094
- onViewChange,
12095
- selectable = false,
12096
- value,
12097
- onValueChange,
12098
- events,
12099
- dayColors,
12100
- dayContent,
12101
- onEventClick,
12102
- onMoreClick,
12103
- todayLabel = "\uC624\uB298",
12104
- moreLabel,
12105
- headerRight,
12106
- className,
12107
- style
12108
- }) {
12109
- const today = todayStr();
12110
- const [ty, tm] = [Number(today.slice(0, 4)), Number(today.slice(5, 7))];
12111
- const [innerView, setInnerView] = react.useState(() => ({
12112
- year: defaultYear ?? ty,
12113
- month: defaultMonth ?? tm
12114
- }));
12115
- const controlled = year != null && month != null;
12116
- const view = controlled ? { year, month } : innerView;
12117
- const [openDate, setOpenDate] = react.useState(null);
12118
- const [placement, setPlacement] = react.useState({ cellWidth: 0, cellHeight: 0, side: 0, align: 0 });
12119
- const [panelEl, setPanelEl] = react.useState(null);
12120
- const portalContainer = usePortalContainer();
12121
- const setView = react.useCallback(
12122
- (next) => {
12123
- setOpenDate(null);
12124
- if (!controlled) setInnerView(next);
12125
- onViewChange?.(next);
12126
- },
12127
- [controlled, onViewChange]
12128
- );
12129
- const cells = react.useMemo(() => monthCells(view.year, view.month), [view.year, view.month]);
12130
- const rowCount = cells.length / 7;
12131
- const gridRef = react.useRef(null);
12132
- const [rowCapacity, setRowCapacity] = react.useState(CALENDAR_BOARD_MAX_VISIBLE_EVENTS);
12133
- react.useEffect(() => {
12134
- const grid = gridRef.current;
12135
- if (grid == null || typeof ResizeObserver === "undefined") return;
12136
- const cell = grid.querySelector('[role="gridcell"]');
12137
- if (cell == null) return;
12138
- const apply = () => {
12139
- const next = measureRowCapacity(cell);
12140
- if (next != null) setRowCapacity(next);
12141
- };
12142
- apply();
12143
- const observer = new ResizeObserver(apply);
12144
- observer.observe(cell);
12145
- return () => observer.disconnect();
12146
- }, [rowCount]);
12147
- const eventMap = react.useMemo(() => {
12148
- const map = /* @__PURE__ */ new Map();
12149
- (events ?? []).forEach((event) => {
12150
- const list = map.get(event.date);
12151
- if (list) list.push(event);
12152
- else map.set(event.date, [event]);
12153
- });
12154
- return map;
12155
- }, [events]);
12156
- const openEvents = openDate ? eventMap.get(openDate) ?? [] : [];
12157
- react.useLayoutEffect(() => {
12158
- if (openDate == null || panelEl == null) return;
12159
- const cell = gridRef.current?.querySelector(`[data-date="${openDate}"]`);
12160
- if (cell == null) return;
12161
- const adjust = () => {
12162
- const cellRect = cell.getBoundingClientRect();
12163
- const panelRect = panelEl.getBoundingClientRect();
12164
- const next = {
12165
- cellWidth: cellRect.width,
12166
- cellHeight: cellRect.height,
12167
- side: -(cellRect.height + DAY_EVENT_OUTDENT) + shiftIntoView(cellRect.top - DAY_EVENT_OUTDENT, panelRect.height, window.innerHeight),
12168
- align: -DAY_EVENT_OUTDENT + shiftIntoView(cellRect.left - DAY_EVENT_OUTDENT, panelRect.width, window.innerWidth)
12169
- };
12170
- setPlacement(
12171
- (prev) => prev.cellWidth === next.cellWidth && prev.cellHeight === next.cellHeight && prev.side === next.side && prev.align === next.align ? prev : next
12172
- );
12173
- };
12174
- adjust();
12175
- const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(adjust);
12176
- observer?.observe(panelEl);
12177
- window.addEventListener("scroll", adjust, true);
12178
- window.addEventListener("resize", adjust);
12179
- return () => {
12180
- observer?.disconnect();
12181
- window.removeEventListener("scroll", adjust, true);
12182
- window.removeEventListener("resize", adjust);
12183
- };
12184
- }, [openDate, panelEl]);
12185
- const columns = "repeat(7, minmax(var(--cmp-calendarBoard-dayCard-minWidth), 1fr))";
12186
- const openEventList = (date, list) => {
12187
- if (onMoreClick) {
12188
- onMoreClick({ date, events: list });
12189
- return;
12190
- }
12191
- setOpenDate((prev) => prev === date ? null : date);
12192
- };
12446
+ function monthCells(year, month) {
12447
+ const firstDayIndex = new Date(year, month - 1, 1).getDay();
12448
+ const daysInCurrent = new Date(year, month, 0).getDate();
12449
+ const prev = calcYearMonth(year, month, "prev");
12450
+ const daysInPrev = new Date(prev.year, prev.month, 0).getDate();
12451
+ const next = calcYearMonth(year, month, "next");
12452
+ const cells = [];
12453
+ for (let i = firstDayIndex - 1; i >= 0; i--) {
12454
+ const day = daysInPrev - i;
12455
+ cells.push({ date: formatDate(prev.year, prev.month, day), day, inCurrentMonth: false });
12456
+ }
12457
+ for (let day = 1; day <= daysInCurrent; day++) {
12458
+ cells.push({ date: formatDate(year, month, day), day, inCurrentMonth: true });
12459
+ }
12460
+ const remaining = (7 - cells.length % 7) % 7;
12461
+ for (let day = 1; day <= remaining; day++) {
12462
+ cells.push({ date: formatDate(next.year, next.month, day), day, inCurrentMonth: false });
12463
+ }
12464
+ return cells;
12465
+ }
12466
+ var WEEK_LABELS = ["\uC77C", "\uC6D4", "\uD654", "\uC218", "\uBAA9", "\uAE08", "\uD1A0"];
12467
+
12468
+ // src/components/SCalendarBoard/calendarBoard.config.ts
12469
+ var CALENDAR_BOARD_EVENT_COLORS = [
12470
+ "default",
12471
+ "blue",
12472
+ "oceanBlue",
12473
+ "lightBlue",
12474
+ "green",
12475
+ "yellow",
12476
+ "orange",
12477
+ "red",
12478
+ "white"
12479
+ ];
12480
+ var BG = (name) => `var(--cmp-calendarBoard-eventButton-bg-${name})`;
12481
+ var CALENDAR_BOARD_EVENT_PRESETS = {
12482
+ default: { bg: BG("default"), dot: "indigo" },
12483
+ blue: { bg: BG("blue"), dot: "blue" },
12484
+ oceanBlue: { bg: BG("oceanBlue"), dot: "darkblue" },
12485
+ lightBlue: { bg: BG("lightBlue"), dot: "lightblue" },
12486
+ green: { bg: BG("green"), dot: "green" },
12487
+ yellow: { bg: BG("yellow"), dot: "yellow" },
12488
+ orange: { bg: BG("orange"), dot: "orange" },
12489
+ red: { bg: BG("red"), dot: "red" },
12490
+ white: { bg: BG("white"), dot: "grey" }
12491
+ };
12492
+ var CALENDAR_BOARD_MAX_VISIBLE_EVENTS = 4;
12493
+ var CALENDAR_BOARD_DAY_COLORS = [
12494
+ "default",
12495
+ "danger",
12496
+ "accent",
12497
+ "complete",
12498
+ "dim"
12499
+ ];
12500
+ var CALENDAR_BOARD_DAY_BG = {
12501
+ default: "var(--cmp-calendarBoard-dayCard-bg)",
12502
+ danger: "var(--color-red-30)",
12503
+ accent: "var(--color-brilliantblue-40)",
12504
+ complete: "var(--color-green-45)",
12505
+ dim: "var(--color-grey-25)"
12506
+ };
12507
+ function CalendarEventButton({
12508
+ event,
12509
+ onSelect,
12510
+ className
12511
+ }) {
12512
+ const preset = CALENDAR_BOARD_EVENT_PRESETS[event.color ?? "default"];
12513
+ const dot = event.dotColor ?? preset.dot;
12514
+ const disabled = event.disabled === true;
12515
+ const hasDescription = event.description != null && event.description !== "";
12516
+ const hasValue = event.value != null && event.value !== "";
12517
+ const title = [event.label.replace(/<[^>]*>/g, ""), event.description, event.value].filter((v) => v != null && v !== "").join(" ") || void 0;
12193
12518
  return /* @__PURE__ */ jsxRuntime.jsxs(
12194
- "div",
12519
+ "button",
12195
12520
  {
12521
+ type: "button",
12522
+ disabled,
12523
+ title,
12196
12524
  className: cn(
12197
- // 판 높이는 부모가 정한다 — 행이 그 높이를 균등 분할하고, 칸 높이가 다시 "몇 줄 보일지" 를 정한다.
12198
- "flex h-full w-full flex-col overflow-hidden rounded-(--cmp-calendarBoard-radius) border border-solid border-(--cmp-calendarBoard-border) bg-(--cmp-calendarBoard-bg)",
12525
+ // hover 는 배경 위에 8% 오버레이를 얹는다 — 프리셋 배경이 9종이라 색마다 hover 색을 두지 않는다.
12526
+ "group relative flex w-full min-w-0 items-center overflow-hidden text-left",
12527
+ "h-(--cmp-calendarBoard-item-height) shrink-0 rounded-(--cmp-calendarBoard-item-radius)",
12528
+ "gap-(--cmp-calendarBoard-eventButton-inline-gap) px-(--cmp-calendarBoard-item-paddingX)",
12529
+ "bg-(--ceb-bg) text-(--cmp-calendarBoard-eventButton-text-color-default)",
12530
+ disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
12199
12531
  className
12200
12532
  ),
12201
- style,
12533
+ style: { "--ceb-bg": preset.bg },
12534
+ onClick: (e) => {
12535
+ if (disabled) return;
12536
+ onSelect?.(event, e);
12537
+ },
12202
12538
  children: [
12203
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-(--cmp-calendarBoard-header-gap) px-sd-16 py-sd-12", children: [
12204
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-(--cmp-calendarBoard-header-content-gap)", children: [
12539
+ !disabled && /* @__PURE__ */ jsxRuntime.jsx(
12540
+ "span",
12541
+ {
12542
+ "aria-hidden": true,
12543
+ className: "pointer-events-none absolute inset-0 rounded-(--cmp-calendarBoard-item-radius) bg-(--cmp-calendarBoard-eventButton-hover-overlay-color) opacity-0 transition-opacity group-hover:opacity-(--cmp-calendarBoard-eventButton-hover-overlay-opacity)"
12544
+ }
12545
+ ),
12546
+ /* @__PURE__ */ jsxRuntime.jsx(SBadge, { "aria-hidden": true, color: dot, className: "shrink-0" }),
12547
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-(--cmp-calendarBoard-eventButton-label-gap)", children: [
12548
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "typo-body-sm-default min-w-0 flex-1 truncate", children: [
12205
12549
  /* @__PURE__ */ jsxRuntime.jsx(
12206
- SGhostButton,
12550
+ "span",
12207
12551
  {
12208
- icon: "chevronLeft",
12209
- size: "xxs",
12210
- ariaLabel: "\uC774\uC804 \uD574",
12211
- onClick: () => setView({ year: view.year - 1, month: view.month })
12552
+ className: cn(
12553
+ "typo-body-sm-semibold",
12554
+ hasDescription && "me-(--cmp-calendarBoard-eventButton-label-gap)"
12555
+ ),
12556
+ dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(event.label) }
12212
12557
  }
12213
12558
  ),
12214
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-heading-lg inline-flex min-w-[48px] justify-center text-(--cmp-calendarBoard-header-content-color)", children: view.year }),
12215
- /* @__PURE__ */ jsxRuntime.jsx(
12216
- SGhostButton,
12217
- {
12218
- icon: "chevronRight",
12219
- size: "xxs",
12220
- ariaLabel: "\uB2E4\uC74C \uD574",
12221
- onClick: () => setView({ year: view.year + 1, month: view.month })
12222
- }
12223
- )
12559
+ hasDescription && event.description
12224
12560
  ] }),
12225
- /* @__PURE__ */ jsxRuntime.jsx(SDivider, { vertical: true, height: "var(--cmp-calendarBoard-header-content-divider-height)" }),
12226
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-(--cmp-calendarBoard-header-content-gap)", children: [
12561
+ hasValue && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex shrink-0 items-center gap-(--cmp-calendarBoard-eventButton-label-gap)", children: [
12227
12562
  /* @__PURE__ */ jsxRuntime.jsx(
12228
- SGhostButton,
12563
+ "span",
12229
12564
  {
12230
- icon: "chevronLeft",
12231
- size: "xxs",
12232
- ariaLabel: "\uC774\uC804 \uB2EC",
12233
- onClick: () => setView(calcYearMonth(view.year, view.month, "prev"))
12565
+ "aria-hidden": true,
12566
+ className: "typo-body-sm-default text-(--cmp-calendarBoard-eventButton-text-color-separator)",
12567
+ children: "/"
12234
12568
  }
12235
12569
  ),
12236
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "typo-heading-lg inline-flex min-w-[48px] justify-center text-(--cmp-calendarBoard-header-content-color)", children: [
12237
- view.month,
12238
- "\uC6D4"
12239
- ] }),
12240
- /* @__PURE__ */ jsxRuntime.jsx(
12241
- SGhostButton,
12242
- {
12243
- icon: "chevronRight",
12244
- size: "xxs",
12245
- ariaLabel: "\uB2E4\uC74C \uB2EC",
12246
- onClick: () => setView(calcYearMonth(view.year, view.month, "next"))
12247
- }
12248
- )
12249
- ] }),
12250
- todayLabel !== "" && /* @__PURE__ */ jsxRuntime.jsx(
12251
- SButton,
12252
- {
12253
- color: "neutral",
12254
- outline: true,
12255
- size: "xs",
12256
- label: todayLabel,
12257
- onClick: () => setView({ year: ty, month: tm })
12258
- }
12259
- ),
12260
- headerRight != null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex items-center", children: headerRight })
12261
- ] }),
12262
- /* @__PURE__ */ jsxRuntime.jsx(SDivider, {}),
12570
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-sm-default whitespace-nowrap", children: event.value })
12571
+ ] })
12572
+ ] })
12573
+ ]
12574
+ }
12575
+ );
12576
+ }
12577
+ var CalendarMoreButton = /* @__PURE__ */ react.forwardRef(function CalendarMoreButton2({ count, label = "\uB354\uBCF4\uAE30", onClick, className }, ref) {
12578
+ return /* @__PURE__ */ jsxRuntime.jsxs(
12579
+ "button",
12580
+ {
12581
+ ref,
12582
+ type: "button",
12583
+ className: cn(
12584
+ "group relative flex w-full min-w-0 cursor-pointer items-center overflow-hidden text-left",
12585
+ "h-(--cmp-calendarBoard-item-height) shrink-0 rounded-(--cmp-calendarBoard-item-radius)",
12586
+ "gap-(--cmp-calendarBoard-moreButton-gap) px-(--cmp-calendarBoard-item-paddingX)",
12587
+ "bg-(--cmp-calendarBoard-moreButton-bg)",
12588
+ className
12589
+ ),
12590
+ onClick,
12591
+ children: [
12263
12592
  /* @__PURE__ */ jsxRuntime.jsx(
12264
- "div",
12593
+ "span",
12265
12594
  {
12266
- className: "grid shrink-0 bg-(--cmp-calendarBoard-grid-bg) pt-(--cmp-calendarBoard-grid-paddingTop) pb-(--cmp-calendarBoard-grid-gap)",
12267
- style: { gridTemplateColumns: columns },
12268
- children: WEEK_LABELS.map((label) => /* @__PURE__ */ jsxRuntime.jsx(
12269
- "div",
12270
- {
12271
- className: "typo-body-md-default flex justify-center text-(--cmp-calendarBoard-grid-weekRow-color)",
12272
- children: label
12273
- },
12274
- label
12275
- ))
12595
+ "aria-hidden": true,
12596
+ className: "pointer-events-none absolute inset-0 rounded-(--cmp-calendarBoard-item-radius) bg-(--cmp-calendarBoard-moreButton-hover-overlay-color) opacity-0 transition-opacity group-hover:opacity-(--cmp-calendarBoard-moreButton-hover-overlay-opacity)"
12276
12597
  }
12277
12598
  ),
12278
- /* @__PURE__ */ jsxRuntime.jsxs(
12279
- RPopover12__namespace.Root,
12280
- {
12281
- open: openDate !== null,
12282
- onOpenChange: (open) => {
12283
- if (!open) setOpenDate(null);
12284
- },
12285
- children: [
12286
- /* @__PURE__ */ jsxRuntime.jsx(
12287
- "div",
12288
- {
12289
- ref: gridRef,
12290
- role: "grid",
12291
- className: "grid min-h-0 flex-1 gap-px overflow-auto border-t border-solid border-(--cmp-calendarBoard-grid-month-border) bg-(--cmp-calendarBoard-grid-month-border)",
12292
- style: {
12293
- gridTemplateColumns: columns,
12294
- // 행은 판 높이를 균등하게 나눠 갖는다 — 일정이 많은 주만 길어지면 격자가 깨진다.
12295
- gridTemplateRows: `repeat(${rowCount}, minmax(var(--cmp-calendarBoard-dayCard-minHeight), 1fr))`
12296
- },
12297
- children: cells.map((cell) => {
12298
- const content = dayContent?.[cell.date];
12299
- const list = content == null ? eventMap.get(cell.date) ?? [] : [];
12300
- const folded = list.length > rowCapacity;
12301
- const visible = folded ? list.slice(0, rowCapacity - 1) : list;
12302
- const card = /* @__PURE__ */ jsxRuntime.jsx(
12303
- CalendarDayCard,
12304
- {
12305
- date: cell.date,
12306
- day: cell.day,
12307
- inCurrentMonth: cell.inCurrentMonth,
12308
- isToday: cell.date === today,
12309
- isSelected: value != null && value === cell.date,
12310
- selectable,
12311
- color: dayColors?.[cell.date] ?? "default",
12312
- events: visible,
12313
- body: content,
12314
- hiddenCount: folded ? list.length - (rowCapacity - 1) : 0,
12315
- isOpen: openDate === cell.date,
12316
- moreLabel,
12317
- onDaySelect: (date) => onValueChange?.(date, list),
12318
- onEventClick,
12319
- onOpenEvents: () => openEventList(cell.date, list)
12320
- },
12321
- cell.date
12322
- );
12323
- return cell.date === openDate ? /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Anchor, { asChild: true, children: card }, cell.date) : card;
12324
- })
12325
- }
12599
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "typo-body-sm-semibold shrink-0 text-(--cmp-calendarBoard-moreButton-count-color)", children: [
12600
+ count,
12601
+ "\uAC1C"
12602
+ ] }),
12603
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-sm-default min-w-0 truncate text-(--cmp-calendarBoard-moreButton-label-color)", children: label })
12604
+ ]
12605
+ }
12606
+ );
12607
+ });
12608
+ var CalendarDayCard = /* @__PURE__ */ react.forwardRef(function CalendarDayCard2({
12609
+ date,
12610
+ day,
12611
+ inCurrentMonth,
12612
+ isToday,
12613
+ isSelected,
12614
+ selectable,
12615
+ color,
12616
+ events,
12617
+ body,
12618
+ hiddenCount,
12619
+ isOpen,
12620
+ moreLabel,
12621
+ onDaySelect,
12622
+ onEventClick,
12623
+ onOpenEvents,
12624
+ ...rest
12625
+ }, ref) {
12626
+ const hasEvents = events.length + hiddenCount > 0;
12627
+ const interactive = selectable || hasEvents;
12628
+ const activate = () => {
12629
+ if (hasEvents) onOpenEvents?.();
12630
+ if (selectable) onDaySelect?.(date);
12631
+ };
12632
+ return /* @__PURE__ */ jsxRuntime.jsxs(
12633
+ "div",
12634
+ {
12635
+ ...rest,
12636
+ ref,
12637
+ role: "gridcell",
12638
+ "data-date": date,
12639
+ "aria-selected": selectable ? isSelected : void 0,
12640
+ "aria-current": isToday ? "date" : void 0,
12641
+ "aria-haspopup": hasEvents ? "dialog" : void 0,
12642
+ "aria-expanded": hasEvents ? isOpen : void 0,
12643
+ tabIndex: interactive ? 0 : void 0,
12644
+ onClick: interactive ? activate : void 0,
12645
+ onKeyDown: interactive ? (e) => {
12646
+ if (e.key === "Enter" || e.key === " ") {
12647
+ e.preventDefault();
12648
+ activate();
12649
+ }
12650
+ } : void 0,
12651
+ className: cn(
12652
+ "relative flex min-h-(--cmp-calendarBoard-dayCard-minHeight) min-w-(--cmp-calendarBoard-dayCard-minWidth) flex-col bg-(--cdc-bg) px-(--cmp-calendarBoard-dayCard-paddingX) outline-none",
12653
+ interactive && "cursor-pointer"
12654
+ ),
12655
+ style: { ...rest.style, "--cdc-bg": CALENDAR_BOARD_DAY_BG[color] },
12656
+ children: [
12657
+ isSelected && /* @__PURE__ */ jsxRuntime.jsx(
12658
+ "span",
12659
+ {
12660
+ "aria-hidden": true,
12661
+ className: "pointer-events-none absolute inset-[1px] rounded-(--cmp-calendarBoard-dayCard-focus-border-radius) border-(length:--cmp-calendarBoard-dayCard-focus-border-width) border-solid border-(--cmp-calendarBoard-dayCard-focus-border-color)"
12662
+ }
12663
+ ),
12664
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 pt-(--cmp-calendarBoard-dayCard-header-paddingTop) pb-(--cmp-calendarBoard-dayCard-header-paddingBottom)", children: /* @__PURE__ */ jsxRuntime.jsx(
12665
+ "span",
12666
+ {
12667
+ className: cn(
12668
+ "inline-flex size-(--cmp-calendarBoard-daySequence-size) items-center justify-center rounded-(--cmp-calendarBoard-daySequence-radius)",
12669
+ isToday ? "typo-body-md-bold bg-(--cmp-calendarBoard-daySequence-bg-today) text-(--cmp-calendarBoard-daySequence-text-today)" : cn(
12670
+ "typo-body-md-medium",
12671
+ inCurrentMonth ? "text-(--cmp-calendarBoard-daySequence-text-default)" : "text-(--cmp-calendarBoard-daySequence-text-otherMonth)"
12326
12672
  ),
12327
- /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
12328
- RPopover12__namespace.Content,
12673
+ // 오늘이 아니면서 선택된 날은 테두리 원으로 표시한다 (오늘은 이미 채운 원이라 겹치지 않는다).
12674
+ // 굵기는 1px 로 고정한다 — 칸을 감싸는 focus 테두리(2px)와 달리 얇은 원이고, 대응 토큰이 없다.
12675
+ isSelected && !isToday && "border border-solid border-(--cmp-calendarBoard-daySequence-border-selected)"
12676
+ ),
12677
+ children: day
12678
+ }
12679
+ ) }),
12680
+ /* @__PURE__ */ jsxRuntime.jsxs(
12681
+ "div",
12682
+ {
12683
+ className: cn(
12684
+ "flex min-h-0 flex-col gap-(--cmp-calendarBoard-dayCard-grid-gap) pb-(--cmp-calendarBoard-dayCard-grid-paddingBottom)",
12685
+ // 슬롯은 남는 세로를 다 갖는다 — 안에서 h-full 로 칸을 채울 수 있어야 한다.
12686
+ body != null && "flex-1"
12687
+ ),
12688
+ children: [
12689
+ body,
12690
+ body == null && events.map((event, i) => /* @__PURE__ */ jsxRuntime.jsx(
12691
+ CalendarEventButton,
12329
12692
  {
12330
- onInteractOutside: (e) => {
12331
- const target = e.detail.originalEvent.target;
12332
- if (target instanceof Node && gridRef.current?.contains(target)) e.preventDefault();
12333
- },
12334
- side: "bottom",
12335
- align: "start",
12336
- ref: setPanelEl,
12337
- sideOffset: placement.side,
12338
- alignOffset: placement.align,
12339
- avoidCollisions: false,
12340
- className: cn("z-50 outline-none", FLOATING_SLIDE_ANIM),
12341
- onOpenAutoFocus: (e) => e.preventDefault(),
12342
- children: openDate != null && /* @__PURE__ */ jsxRuntime.jsx(
12343
- CalendarDayEventListBox,
12344
- {
12345
- date: openDate,
12346
- minWidth: placement.cellWidth > 0 ? placement.cellWidth + DAY_EVENT_OUTDENT * 2 : void 0,
12347
- minHeight: placement.cellHeight > 0 ? placement.cellHeight + DAY_EVENT_OUTDENT * 2 : void 0,
12348
- events: openEvents,
12349
- onEventClick,
12350
- onClose: () => setOpenDate(null)
12351
- }
12352
- )
12693
+ event,
12694
+ onSelect: (ev, e) => {
12695
+ e.stopPropagation();
12696
+ onEventClick?.(ev, e);
12697
+ }
12353
12698
  },
12354
- openDate
12355
- ) })
12699
+ event.id ?? `${event.date}-${i}`
12700
+ )),
12701
+ hiddenCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(
12702
+ CalendarMoreButton,
12703
+ {
12704
+ count: hiddenCount,
12705
+ label: moreLabel,
12706
+ onClick: (e) => {
12707
+ e.stopPropagation();
12708
+ onOpenEvents?.();
12709
+ }
12710
+ }
12711
+ )
12356
12712
  ]
12357
12713
  }
12358
12714
  )
12359
12715
  ]
12360
12716
  }
12361
12717
  );
12362
- }
12363
-
12364
- // src/lib/floating-height.ts
12365
- var POPOVER_AVAILABLE_HEIGHT = "var(--radix-popover-content-available-height)";
12366
- function clampToAvailableHeight(value, availableHeight) {
12367
- if (value == null || value === "") return availableHeight;
12368
- const css = typeof value === "number" ? `${value}px` : value;
12369
- return `min(${css}, ${availableHeight})`;
12370
- }
12371
- function runRules(value, rules) {
12372
- if (!rules || rules.length === 0) return "";
12373
- for (const rule of rules) {
12374
- const result = rule(value ?? "");
12375
- if (result !== true) return typeof result === "string" ? result : "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4.";
12376
- }
12377
- return "";
12378
- }
12379
- var FormContext = /* @__PURE__ */ react.createContext(null);
12380
- function useFormContext() {
12381
- return react.useContext(FormContext);
12382
- }
12383
- function useFormField(field) {
12384
- const ctx = useFormContext();
12385
- const cbRef = react.useRef(field);
12386
- cbRef.current = field;
12387
- const name = field.name ?? "";
12388
- react.useEffect(() => {
12389
- if (!ctx) return;
12390
- return ctx.register({
12391
- name,
12392
- validate: () => cbRef.current.validate(),
12393
- resetValidation: () => cbRef.current.resetValidation()
12394
- });
12395
- }, [ctx, name]);
12396
- }
12397
- var FIELD_SIZE = {
12398
- sm: { height: 28, radius: 4, fieldGap: 12, labelGap: 6, fontSize: 12, lineHeight: 20, icon: 16 },
12399
- md: { height: 36, radius: 6, fieldGap: 16, labelGap: 8, fontSize: 14, lineHeight: 24, icon: 16 }
12400
- };
12401
- var ALIGN = {
12402
- start: "flex-start",
12403
- center: "center",
12404
- end: "flex-end"
12405
- };
12406
- var FOCUS_RING_SHADOW = "0px 0px 4px 0px #0071ff66";
12407
- var cssLen = (v) => v === void 0 || v === "" || v === 0 ? void 0 : typeof v === "number" ? `${v}px` : v;
12408
- var SField = /* @__PURE__ */ react.forwardRef(function SField2({
12409
- name,
12410
- label = "",
12411
- icon,
12412
- iconColor,
12413
- labelTooltip = "",
12414
- labelTooltipProps,
12415
- labelWidth,
12416
- labelHeight,
12417
- size = "sm",
12418
- addonLabel = "",
12419
- addonAlign = "start",
12420
- error = false,
12421
- errorMessage = "",
12422
- status,
12423
- focused = false,
12424
- hovered = false,
12425
- rules: _rules,
12426
- hint = "",
12427
- disabled = false,
12428
- readOnly = false,
12429
- width,
12718
+ });
12719
+ var MAX_HEIGHT = 480;
12720
+ var MAX_WIDTH = "calc(100vw - 16px)";
12721
+ function CalendarDayEventListBox({
12722
+ date,
12723
+ events,
12724
+ onEventClick,
12430
12725
  minWidth,
12431
- maxWidth,
12432
- multiline = false,
12433
- borderless = false,
12434
- children,
12435
- className,
12436
- style,
12437
- onMouseEnter,
12438
- onMouseLeave
12439
- }, ref) {
12440
- const rootRef = react.useRef(null);
12441
- react.useImperativeHandle(
12442
- ref,
12443
- () => ({
12444
- focus: () => {
12445
- const root = rootRef.current;
12446
- if (!root) return;
12447
- const focusable = root.querySelector(
12448
- "input,textarea,select,button,[tabindex]"
12449
- );
12450
- focusable?.focus();
12451
- root.scrollIntoView({ block: "nearest" });
12452
- }
12453
- }),
12454
- []
12726
+ minHeight,
12727
+ onClose,
12728
+ closeLabel = "\uB2EB\uAE30"
12729
+ }) {
12730
+ const [y, m, d] = date.split("-").map(Number);
12731
+ const weekday = WEEK_LABELS[new Date(y, m - 1, d).getDay()];
12732
+ return /* @__PURE__ */ jsxRuntime.jsxs(
12733
+ "div",
12734
+ {
12735
+ className: "flex w-max flex-col overflow-hidden rounded-(--cmp-calendarBoard-radius) bg-(--cmp-calendarBoard-bg) shadow-(--shadow-overlay)",
12736
+ style: { minWidth, maxWidth: MAX_WIDTH, minHeight, maxHeight: MAX_HEIGHT },
12737
+ children: [
12738
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-(--cmp-calendarBoard-dayEvent-header-gap) px-(--cmp-calendarBoard-dayEvent-header-paddingX) py-(--cmp-calendarBoard-dayEvent-header-paddingY)", children: [
12739
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "typo-heading-md text-(--cmp-calendarBoard-header-content-color)", children: [
12740
+ d,
12741
+ "\uC77C"
12742
+ ] }),
12743
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "typo-body-lg-default flex-1 text-(--cmp-calendarBoard-header-content-color)", children: [
12744
+ "(",
12745
+ weekday,
12746
+ ")"
12747
+ ] }),
12748
+ /* @__PURE__ */ jsxRuntime.jsx(
12749
+ SGhostButton,
12750
+ {
12751
+ icon: "close",
12752
+ size: "xs",
12753
+ ariaLabel: closeLabel,
12754
+ className: "shrink-0",
12755
+ onClick: onClose
12756
+ }
12757
+ )
12758
+ ] }),
12759
+ /* @__PURE__ */ jsxRuntime.jsx(SScrollArea, { axis: "vertical", className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-(--cmp-calendarBoard-dayEvent-body-gap) px-(--cmp-calendarBoard-dayEvent-body-paddingX) pb-(--cmp-calendarBoard-dayEvent-body-paddingBottom)", children: events.map((event, i) => /* @__PURE__ */ jsxRuntime.jsx(
12760
+ CalendarEventButton,
12761
+ {
12762
+ event,
12763
+ onSelect: onEventClick
12764
+ },
12765
+ event.id ?? `${event.date}-${i}`
12766
+ )) }) })
12767
+ ]
12768
+ }
12455
12769
  );
12456
- const s = FIELD_SIZE[size];
12457
- if (label !== "" && addonLabel !== "" && typeof console !== "undefined") {
12458
- console.warn(
12459
- "[SField] label \uACFC addonLabel \uC740 \uD568\uAED8 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. addonLabel \uC744 \uBB34\uC2DC\uD569\uB2C8\uB2E4."
12460
- );
12461
- }
12462
- const effectiveAddonLabel = label !== "" ? "" : addonLabel;
12463
- const resolvedError = status === "error" || status === void 0 && error;
12464
- const resolvedSuccess = status === "pass";
12465
- const showError = resolvedError && errorMessage !== "";
12466
- const interactive = !disabled && !readOnly;
12467
- const isPercentWidth = typeof width === "string" && width.trim().endsWith("%");
12468
- const resolvedWidth = fieldWidthValue(width);
12469
- const hug = resolvedWidth !== void 0 && !isPercentWidth;
12470
- const addonWidth = effectiveAddonLabel !== "" ? cssLen(labelWidth) : void 0;
12471
- const maxWidthValue = fieldWidthValue(maxWidth);
12472
- const resolvedMaxWidth = maxWidthValue === void 0 || effectiveAddonLabel === "" ? maxWidthValue : addonWidth ? `calc(${maxWidthValue} + ${addonWidth})` : void 0;
12473
- const borderColor = borderless ? "transparent" : disabled ? "var(--color-grey-45)" : (
12474
- // focus·hover(제어 prop) 가 error/pass 보다 우선 — 실제 :hover/:focus-within 클래스도
12475
- // 같은 순서로 --field-bc 를 덮으므로, prop 으로 흉내 낸 상태도 그와 같게 둔다.
12476
- interactive && (focused || hovered) ? "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)"
12770
+ }
12771
+ var DAY_EVENT_OUTDENT = 16;
12772
+ var VIEWPORT_PADDING2 = 8;
12773
+ function shiftIntoView(desired, size, viewport) {
12774
+ const max = Math.max(viewport - size - VIEWPORT_PADDING2, VIEWPORT_PADDING2);
12775
+ return Math.min(Math.max(desired, VIEWPORT_PADDING2), max) - desired;
12776
+ }
12777
+ function measureRowCapacity(cell) {
12778
+ const cs = getComputedStyle(cell);
12779
+ const v = (name) => Number.parseFloat(cs.getPropertyValue(name)) || 0;
12780
+ const gap = v("--cmp-calendarBoard-dayCard-grid-gap");
12781
+ const row = v("--cmp-calendarBoard-item-height") + gap;
12782
+ if (row <= 0) return null;
12783
+ const header = v("--cmp-calendarBoard-dayCard-header-paddingTop") + v("--cmp-calendarBoard-daySequence-size") + v("--cmp-calendarBoard-dayCard-header-paddingBottom");
12784
+ const free = cell.getBoundingClientRect().height - header - v("--cmp-calendarBoard-dayCard-grid-paddingBottom") + gap;
12785
+ if (free <= 0) return null;
12786
+ return Math.max(1, Math.floor(free / row));
12787
+ }
12788
+ function SCalendarBoard({
12789
+ year,
12790
+ month,
12791
+ defaultYear,
12792
+ defaultMonth,
12793
+ onViewChange,
12794
+ selectable = false,
12795
+ value,
12796
+ onValueChange,
12797
+ events,
12798
+ dayColors,
12799
+ dayContent,
12800
+ onEventClick,
12801
+ onMoreClick,
12802
+ todayLabel = "\uC624\uB298",
12803
+ moreLabel,
12804
+ headerRight,
12805
+ className,
12806
+ style
12807
+ }) {
12808
+ const today = todayStr();
12809
+ const [ty, tm] = [Number(today.slice(0, 4)), Number(today.slice(5, 7))];
12810
+ const [innerView, setInnerView] = react.useState(() => ({
12811
+ year: defaultYear ?? ty,
12812
+ month: defaultMonth ?? tm
12813
+ }));
12814
+ const controlled = year != null && month != null;
12815
+ const view = controlled ? { year, month } : innerView;
12816
+ const [openDate, setOpenDate] = react.useState(null);
12817
+ const [placement, setPlacement] = react.useState({ cellWidth: 0, cellHeight: 0, side: 0, align: 0 });
12818
+ const [panelEl, setPanelEl] = react.useState(null);
12819
+ const portalContainer = usePortalContainer();
12820
+ const setView = react.useCallback(
12821
+ (next) => {
12822
+ setOpenDate(null);
12823
+ if (!controlled) setInnerView(next);
12824
+ onViewChange?.(next);
12825
+ },
12826
+ [controlled, onViewChange]
12477
12827
  );
12478
- const bgColor = borderless ? "transparent" : disabled ? "var(--color-grey-30)" : readOnly ? "var(--sys-color-field-bg-readOnly)" : "var(--sys-color-field-bg-default)";
12479
- const textColor = disabled ? "var(--color-grey-65)" : readOnly ? "var(--sys-color-field-text-readOnly)" : "var(--sys-color-field-text-default)";
12480
- return /* @__PURE__ */ jsxRuntime.jsx(
12828
+ const cells = react.useMemo(() => monthCells(view.year, view.month), [view.year, view.month]);
12829
+ const rowCount = cells.length / 7;
12830
+ const gridRef = react.useRef(null);
12831
+ const [rowCapacity, setRowCapacity] = react.useState(CALENDAR_BOARD_MAX_VISIBLE_EVENTS);
12832
+ react.useEffect(() => {
12833
+ const grid = gridRef.current;
12834
+ if (grid == null || typeof ResizeObserver === "undefined") return;
12835
+ const cell = grid.querySelector('[role="gridcell"]');
12836
+ if (cell == null) return;
12837
+ const apply = () => {
12838
+ const next = measureRowCapacity(cell);
12839
+ if (next != null) setRowCapacity(next);
12840
+ };
12841
+ apply();
12842
+ const observer = new ResizeObserver(apply);
12843
+ observer.observe(cell);
12844
+ return () => observer.disconnect();
12845
+ }, [rowCount]);
12846
+ const eventMap = react.useMemo(() => {
12847
+ const map = /* @__PURE__ */ new Map();
12848
+ (events ?? []).forEach((event) => {
12849
+ const list = map.get(event.date);
12850
+ if (list) list.push(event);
12851
+ else map.set(event.date, [event]);
12852
+ });
12853
+ return map;
12854
+ }, [events]);
12855
+ const openEvents = openDate ? eventMap.get(openDate) ?? [] : [];
12856
+ react.useLayoutEffect(() => {
12857
+ if (openDate == null || panelEl == null) return;
12858
+ const cell = gridRef.current?.querySelector(`[data-date="${openDate}"]`);
12859
+ if (cell == null) return;
12860
+ const adjust = () => {
12861
+ const cellRect = cell.getBoundingClientRect();
12862
+ const panelRect = panelEl.getBoundingClientRect();
12863
+ const next = {
12864
+ cellWidth: cellRect.width,
12865
+ cellHeight: cellRect.height,
12866
+ side: -(cellRect.height + DAY_EVENT_OUTDENT) + shiftIntoView(cellRect.top - DAY_EVENT_OUTDENT, panelRect.height, window.innerHeight),
12867
+ align: -DAY_EVENT_OUTDENT + shiftIntoView(cellRect.left - DAY_EVENT_OUTDENT, panelRect.width, window.innerWidth)
12868
+ };
12869
+ setPlacement(
12870
+ (prev) => prev.cellWidth === next.cellWidth && prev.cellHeight === next.cellHeight && prev.side === next.side && prev.align === next.align ? prev : next
12871
+ );
12872
+ };
12873
+ adjust();
12874
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(adjust);
12875
+ observer?.observe(panelEl);
12876
+ window.addEventListener("scroll", adjust, true);
12877
+ window.addEventListener("resize", adjust);
12878
+ return () => {
12879
+ observer?.disconnect();
12880
+ window.removeEventListener("scroll", adjust, true);
12881
+ window.removeEventListener("resize", adjust);
12882
+ };
12883
+ }, [openDate, panelEl]);
12884
+ const columns = "repeat(7, minmax(var(--cmp-calendarBoard-dayCard-minWidth), 1fr))";
12885
+ const openEventList = (date, list) => {
12886
+ if (onMoreClick) {
12887
+ onMoreClick({ date, events: list });
12888
+ return;
12889
+ }
12890
+ setOpenDate((prev) => prev === date ? null : date);
12891
+ };
12892
+ return /* @__PURE__ */ jsxRuntime.jsxs(
12481
12893
  "div",
12482
12894
  {
12483
- ref: rootRef,
12484
- "data-name": name || void 0,
12485
12895
  className: cn(
12486
- "h-fit flex-col",
12487
- // hug 시 inline-flex 를 유지하면 기존에 세로로 쌓여 있던 필드들이 한 줄에 붙어버리므로
12488
- // 블록 레벨(flex)로 두어 쌓임은 그대로 유지한다. 나란히 놓으려면 부모를 flex 로.
12489
- hug ? "flex w-fit max-w-full" : "inline-flex w-full",
12896
+ // 판 높이는 부모가 정한다 — 행이 그 높이를 균등 분할하고, 칸 높이가 다시 "몇 줄 보일지" 를 정한다.
12897
+ "flex h-full w-full flex-col overflow-hidden rounded-(--cmp-calendarBoard-radius) border border-solid border-(--cmp-calendarBoard-border) bg-(--cmp-calendarBoard-bg)",
12490
12898
  className
12491
12899
  ),
12492
12900
  style,
12493
- onMouseEnter,
12494
- onMouseLeave,
12495
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-start", children: [
12496
- label !== "" && /* @__PURE__ */ jsxRuntime.jsxs(
12901
+ children: [
12902
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-(--cmp-calendarBoard-header-gap) px-sd-16 py-sd-12", children: [
12903
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-(--cmp-calendarBoard-header-content-gap)", children: [
12904
+ /* @__PURE__ */ jsxRuntime.jsx(
12905
+ SGhostButton,
12906
+ {
12907
+ icon: "chevronLeft",
12908
+ size: "xxs",
12909
+ ariaLabel: "\uC774\uC804 \uD574",
12910
+ onClick: () => setView({ year: view.year - 1, month: view.month })
12911
+ }
12912
+ ),
12913
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-heading-lg inline-flex min-w-[48px] justify-center text-(--cmp-calendarBoard-header-content-color)", children: view.year }),
12914
+ /* @__PURE__ */ jsxRuntime.jsx(
12915
+ SGhostButton,
12916
+ {
12917
+ icon: "chevronRight",
12918
+ size: "xxs",
12919
+ ariaLabel: "\uB2E4\uC74C \uD574",
12920
+ onClick: () => setView({ year: view.year + 1, month: view.month })
12921
+ }
12922
+ )
12923
+ ] }),
12924
+ /* @__PURE__ */ jsxRuntime.jsx(SDivider, { vertical: true, height: "var(--cmp-calendarBoard-header-content-divider-height)" }),
12925
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-(--cmp-calendarBoard-header-content-gap)", children: [
12926
+ /* @__PURE__ */ jsxRuntime.jsx(
12927
+ SGhostButton,
12928
+ {
12929
+ icon: "chevronLeft",
12930
+ size: "xxs",
12931
+ ariaLabel: "\uC774\uC804 \uB2EC",
12932
+ onClick: () => setView(calcYearMonth(view.year, view.month, "prev"))
12933
+ }
12934
+ ),
12935
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "typo-heading-lg inline-flex min-w-[48px] justify-center text-(--cmp-calendarBoard-header-content-color)", children: [
12936
+ view.month,
12937
+ "\uC6D4"
12938
+ ] }),
12939
+ /* @__PURE__ */ jsxRuntime.jsx(
12940
+ SGhostButton,
12941
+ {
12942
+ icon: "chevronRight",
12943
+ size: "xxs",
12944
+ ariaLabel: "\uB2E4\uC74C \uB2EC",
12945
+ onClick: () => setView(calcYearMonth(view.year, view.month, "next"))
12946
+ }
12947
+ )
12948
+ ] }),
12949
+ todayLabel !== "" && /* @__PURE__ */ jsxRuntime.jsx(
12950
+ SButton,
12951
+ {
12952
+ color: "neutral",
12953
+ outline: true,
12954
+ size: "xs",
12955
+ label: todayLabel,
12956
+ onClick: () => setView({ year: ty, month: tm })
12957
+ }
12958
+ ),
12959
+ headerRight != null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-auto flex items-center", children: headerRight })
12960
+ ] }),
12961
+ /* @__PURE__ */ jsxRuntime.jsx(SDivider, {}),
12962
+ /* @__PURE__ */ jsxRuntime.jsx(
12497
12963
  "div",
12498
12964
  {
12499
- className: "flex shrink-0 items-center font-bold text-(--sys-color-field-text-default)",
12500
- style: {
12501
- height: cssLen(labelHeight) ?? s.height,
12502
- marginRight: s.fieldGap,
12503
- width: cssLen(labelWidth),
12504
- fontSize: s.fontSize,
12505
- lineHeight: `${s.lineHeight}px`
12506
- },
12507
- children: [
12508
- icon && /* @__PURE__ */ jsxRuntime.jsx(
12509
- SIcon,
12510
- {
12511
- name: icon,
12512
- size: s.icon,
12513
- color: iconColor,
12514
- style: { marginRight: s.labelGap }
12515
- }
12516
- ),
12517
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap", children: label }),
12518
- (labelTooltip !== "" || labelTooltipProps) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 flex items-center", style: { marginLeft: s.labelGap }, children: /* @__PURE__ */ jsxRuntime.jsx(
12519
- STooltip2,
12520
- {
12521
- message: labelTooltip !== "" ? [labelTooltip] : void 0,
12522
- iconSize: s.icon,
12523
- ...labelTooltipProps
12524
- }
12525
- ) })
12526
- ]
12965
+ className: "grid shrink-0 bg-(--cmp-calendarBoard-grid-bg) pt-(--cmp-calendarBoard-grid-paddingTop) pb-(--cmp-calendarBoard-grid-gap)",
12966
+ style: { gridTemplateColumns: columns },
12967
+ children: WEEK_LABELS.map((label) => /* @__PURE__ */ jsxRuntime.jsx(
12968
+ "div",
12969
+ {
12970
+ className: "typo-body-md-default flex justify-center text-(--cmp-calendarBoard-grid-weekRow-color)",
12971
+ children: label
12972
+ },
12973
+ label
12974
+ ))
12527
12975
  }
12528
12976
  ),
12529
12977
  /* @__PURE__ */ jsxRuntime.jsxs(
12530
- "div",
12978
+ RPopover12__namespace.Root,
12531
12979
  {
12532
- className: cn("flex min-w-0 flex-col", !hug && !isPercentWidth && "flex-1"),
12533
- style: {
12534
- width: resolvedWidth,
12535
- minWidth: fieldWidthValue(minWidth),
12536
- maxWidth: resolvedMaxWidth
12980
+ open: openDate !== null,
12981
+ onOpenChange: (open) => {
12982
+ if (!open) setOpenDate(null);
12537
12983
  },
12538
12984
  children: [
12539
- /* @__PURE__ */ jsxRuntime.jsxs(
12985
+ /* @__PURE__ */ jsxRuntime.jsx(
12540
12986
  "div",
12541
12987
  {
12542
- className: cn(
12543
- "relative box-border flex min-w-0 items-stretch border border-solid border-(--field-bc)",
12544
- // 둘 다 넘친 것을 잘라 내지만 clip 은 스크롤 상자를 만들지 않는다. hidden 이면
12545
- // 이 상자가 스크롤포트가 되어 안쪽의 position:sticky 가 페이지 스크롤을 따라가지
12546
- // 못한다 (SEditor 의 고정 툴바). 여러 줄 필드에서만 바꿔 파급을 좁힌다.
12547
- multiline ? "overflow-clip" : "overflow-hidden",
12548
- interactive && !borderless && "hover:border-(--sys-color-field-border-focus) hover:shadow-[0px_0px_4px_0px_#0071ff66] focus-within:border-(--sys-color-field-border-focus) focus-within:shadow-[0px_0px_4px_0px_#0071ff66]",
12549
- disabled && "cursor-not-allowed"
12550
- ),
12988
+ ref: gridRef,
12989
+ role: "grid",
12990
+ className: "grid min-h-0 flex-1 gap-px overflow-auto border-t border-solid border-(--cmp-calendarBoard-grid-month-border) bg-(--cmp-calendarBoard-grid-month-border)",
12551
12991
  style: {
12552
- // @ts-expect-error CSS 변수
12553
- "--field-bc": borderColor,
12554
- // focused/hovered prop이 명시된 경우 강조를 즉시 반영
12555
- ...interactive && !borderless && (focused || hovered) ? { boxShadow: FOCUS_RING_SHADOW } : {},
12556
- ...multiline ? { minHeight: s.height } : { height: s.height },
12557
- "borderRadius": s.radius,
12558
- "background": bgColor,
12559
- "color": textColor
12992
+ gridTemplateColumns: columns,
12993
+ // 행은 판 높이를 균등하게 나눠 갖는다 — 일정이 많은 주만 길어지면 격자가 깨진다.
12994
+ gridTemplateRows: `repeat(${rowCount}, minmax(var(--cmp-calendarBoard-dayCard-minHeight), 1fr))`
12560
12995
  },
12561
- "data-focused": focused || void 0,
12562
- "data-hovered": hovered || void 0,
12563
- children: [
12564
- effectiveAddonLabel !== "" && /* @__PURE__ */ jsxRuntime.jsxs(
12565
- "div",
12996
+ children: cells.map((cell) => {
12997
+ const content = dayContent?.[cell.date];
12998
+ const list = content == null ? eventMap.get(cell.date) ?? [] : [];
12999
+ const folded = list.length > rowCapacity;
13000
+ const visible = folded ? list.slice(0, rowCapacity - 1) : list;
13001
+ const card = /* @__PURE__ */ jsxRuntime.jsx(
13002
+ CalendarDayCard,
12566
13003
  {
12567
- className: "flex shrink-0 items-center gap-(--cmp-field-addonLabel-gap) whitespace-nowrap border-r border-solid px-(--cmp-field-addonLabel-paddingX) text-12 font-medium leading-sd-20",
12568
- style: {
12569
- // 원본 sd-field: labelWidth가 addon 폭(--sd-field-addon-width)에도 적용됨
12570
- width: cssLen(labelWidth),
12571
- justifyContent: ALIGN[addonAlign],
12572
- background: "var(--cmp-field-addonLabel-bg)",
12573
- borderRightColor: disabled ? "var(--cmp-field-addonLabel-border-disabled)" : "var(--cmp-field-addonLabel-border-default)",
12574
- borderTopLeftRadius: s.radius - 1,
12575
- borderBottomLeftRadius: s.radius - 1
12576
- },
12577
- children: [
12578
- icon && !label && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: iconColor }),
12579
- effectiveAddonLabel
12580
- ]
12581
- }
12582
- ),
12583
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex min-w-0 flex-1", multiline && "bg-inherit"), children })
12584
- ]
13004
+ date: cell.date,
13005
+ day: cell.day,
13006
+ inCurrentMonth: cell.inCurrentMonth,
13007
+ isToday: cell.date === today,
13008
+ isSelected: value != null && value === cell.date,
13009
+ selectable,
13010
+ color: dayColors?.[cell.date] ?? "default",
13011
+ events: visible,
13012
+ body: content,
13013
+ hiddenCount: folded ? list.length - (rowCapacity - 1) : 0,
13014
+ isOpen: openDate === cell.date,
13015
+ moreLabel,
13016
+ onDaySelect: (date) => onValueChange?.(date, list),
13017
+ onEventClick,
13018
+ onOpenEvents: () => openEventList(cell.date, list)
13019
+ },
13020
+ cell.date
13021
+ );
13022
+ return cell.date === openDate ? /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Anchor, { asChild: true, children: card }, cell.date) : card;
13023
+ })
12585
13024
  }
12586
13025
  ),
12587
- showError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-(--cmp-field-contents-gap) px-(--cmp-field-message-paddingX) text-12 font-normal leading-sd-20 text-(--cmp-field-errorMessage-color)", children: errorMessage }) : hint !== "" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-(--cmp-field-contents-gap) px-(--cmp-field-message-paddingX) text-12 font-normal leading-sd-20 text-(--cmp-field-hint-color)", children: hint }) : null
13026
+ /* @__PURE__ */ jsxRuntime.jsx(RPopover12__namespace.Portal, { container: portalContainer, children: /* @__PURE__ */ jsxRuntime.jsx(
13027
+ RPopover12__namespace.Content,
13028
+ {
13029
+ onInteractOutside: (e) => {
13030
+ const target = e.detail.originalEvent.target;
13031
+ if (target instanceof Node && gridRef.current?.contains(target)) e.preventDefault();
13032
+ },
13033
+ side: "bottom",
13034
+ align: "start",
13035
+ ref: setPanelEl,
13036
+ sideOffset: placement.side,
13037
+ alignOffset: placement.align,
13038
+ avoidCollisions: false,
13039
+ className: cn("z-50 outline-none", FLOATING_SLIDE_ANIM),
13040
+ onOpenAutoFocus: (e) => e.preventDefault(),
13041
+ children: openDate != null && /* @__PURE__ */ jsxRuntime.jsx(
13042
+ CalendarDayEventListBox,
13043
+ {
13044
+ date: openDate,
13045
+ minWidth: placement.cellWidth > 0 ? placement.cellWidth + DAY_EVENT_OUTDENT * 2 : void 0,
13046
+ minHeight: placement.cellHeight > 0 ? placement.cellHeight + DAY_EVENT_OUTDENT * 2 : void 0,
13047
+ events: openEvents,
13048
+ onEventClick,
13049
+ onClose: () => setOpenDate(null)
13050
+ }
13051
+ )
13052
+ },
13053
+ openDate
13054
+ ) })
12588
13055
  ]
12589
13056
  }
12590
13057
  )
12591
- ] })
13058
+ ]
12592
13059
  }
12593
13060
  );
12594
- });
13061
+ }
12595
13062
 
12596
- // src/lib/autofill.ts
12597
- 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]";
13063
+ // src/lib/floating-height.ts
13064
+ var POPOVER_AVAILABLE_HEIGHT = "var(--radix-popover-content-available-height)";
13065
+ function clampToAvailableHeight(value, availableHeight) {
13066
+ if (value == null || value === "") return availableHeight;
13067
+ const css = typeof value === "number" ? `${value}px` : value;
13068
+ return `min(${css}, ${availableHeight})`;
13069
+ }
12598
13070
  var FONT = {
12599
13071
  sm: {
12600
13072
  fontSize: 12,
@@ -14879,158 +15351,6 @@ var SInput = /* @__PURE__ */ react.forwardRef(function SInput2({
14879
15351
  }
14880
15352
  );
14881
15353
  });
14882
- var TEXTAREA_SIZE = {
14883
- sm: {
14884
- fontSize: 12,
14885
- lineHeight: 20,
14886
- paddingX: "var(--cmp-textinput-sm-paddingX)",
14887
- paddingY: "var(--cmp-textinput-sm-paddingY)"
14888
- },
14889
- md: {
14890
- fontSize: 14,
14891
- lineHeight: 24,
14892
- paddingX: "var(--cmp-textinput-md-paddingX)",
14893
- paddingY: "var(--cmp-textinput-md-paddingY)"
14894
- }
14895
- };
14896
- var MIN_ROWS = 2;
14897
- var STextarea = /* @__PURE__ */ react.forwardRef(function STextarea2({
14898
- value,
14899
- onValueChange,
14900
- onChange,
14901
- size = "sm",
14902
- rows = MIN_ROWS,
14903
- autogrow = false,
14904
- maxRows,
14905
- rules,
14906
- status,
14907
- focused,
14908
- hovered,
14909
- textareaClassName,
14910
- textareaStyle,
14911
- label,
14912
- labelWidth,
14913
- icon,
14914
- iconColor,
14915
- labelTooltip,
14916
- labelTooltipProps,
14917
- addonLabel,
14918
- addonAlign,
14919
- hint,
14920
- error,
14921
- errorMessage,
14922
- width,
14923
- disabled = false,
14924
- readOnly = false,
14925
- placeholder = "\uC785\uB825\uD574 \uC8FC\uC138\uC694.",
14926
- onBlur,
14927
- className,
14928
- style,
14929
- ...rest
14930
- }, ref) {
14931
- const p = TEXTAREA_SIZE[size];
14932
- const heightForRows = (n) => `calc(${p.lineHeight * n}px + ${p.paddingY} * 2)`;
14933
- const minHeight = heightForRows(MIN_ROWS);
14934
- const maxHeight = autogrow && maxRows != null ? heightForRows(maxRows) : void 0;
14935
- const [ruleError, setRuleError] = react.useState("");
14936
- const innerRef = react.useRef(null);
14937
- const setRefs2 = react.useCallback(
14938
- (node) => {
14939
- innerRef.current = node;
14940
- if (typeof ref === "function") ref(node);
14941
- else if (ref) ref.current = node;
14942
- },
14943
- [ref]
14944
- );
14945
- react.useLayoutEffect(() => {
14946
- const el = innerRef.current;
14947
- if (el == null) return;
14948
- if (!autogrow) {
14949
- el.style.height = "";
14950
- return;
14951
- }
14952
- el.style.height = "auto";
14953
- el.style.height = `${el.scrollHeight}px`;
14954
- }, [autogrow, maxHeight, minHeight, rows, value]);
14955
- useFormField({
14956
- name: rest.name,
14957
- validate: () => {
14958
- const msg = runRules(value, rules);
14959
- setRuleError(msg);
14960
- return msg === "";
14961
- },
14962
- resetValidation: () => setRuleError("")
14963
- });
14964
- const resolvedError = error || ruleError !== "";
14965
- const resolvedErrorMessage = ruleError !== "" ? ruleError : errorMessage;
14966
- const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
14967
- return /* @__PURE__ */ jsxRuntime.jsx(
14968
- SField,
14969
- {
14970
- label,
14971
- labelWidth,
14972
- icon,
14973
- iconColor,
14974
- labelTooltip,
14975
- labelTooltipProps,
14976
- addonLabel,
14977
- addonAlign,
14978
- hint,
14979
- size,
14980
- error: resolvedError,
14981
- status: resolvedStatus,
14982
- focused,
14983
- hovered,
14984
- errorMessage: resolvedErrorMessage,
14985
- width,
14986
- disabled,
14987
- readOnly,
14988
- multiline: true,
14989
- className,
14990
- style,
14991
- children: /* @__PURE__ */ jsxRuntime.jsx(
14992
- "textarea",
14993
- {
14994
- ref: setRefs2,
14995
- value: value ?? "",
14996
- rows,
14997
- disabled,
14998
- readOnly,
14999
- placeholder,
15000
- onChange: (e) => {
15001
- onChange?.(e);
15002
- onValueChange?.(e.target.value);
15003
- if (ruleError !== "") setRuleError(runRules(e.target.value, rules));
15004
- },
15005
- onBlur: (e) => {
15006
- onBlur?.(e);
15007
- if (rules && rules.length > 0) setRuleError(runRules(e.target.value, rules));
15008
- },
15009
- className: cn(
15010
- "w-full border-0 bg-transparent outline-none placeholder:text-[color:var(--sys-color-field-text-placeholder)] disabled:cursor-not-allowed",
15011
- // autogrow 와 끌어서 늘리기는 같은 높이를 두고 다투므로 함께 두지 않는다
15012
- autogrow ? "resize-none" : "resize-y",
15013
- AUTOFILL_RESET_CLASS,
15014
- textareaClassName
15015
- ),
15016
- style: {
15017
- minHeight,
15018
- maxHeight,
15019
- // 상한이 있으면 거기 닿았을 때 안에서 스크롤한다. 상한이 없으면 높이가 늘 내용과
15020
- // 같으므로 스크롤바가 나올 일이 없고, 반올림으로 1px 이 넘쳐 깜빡이는 것만 막는다.
15021
- overflowY: autogrow ? maxRows != null ? "auto" : "hidden" : void 0,
15022
- padding: `${p.paddingY} ${p.paddingX}`,
15023
- fontSize: p.fontSize,
15024
- lineHeight: `${p.lineHeight}px`,
15025
- color: disabled ? "var(--cmp-textinput-text-disabled)" : "var(--sys-color-field-text-default)",
15026
- ...textareaStyle
15027
- },
15028
- ...rest
15029
- }
15030
- )
15031
- }
15032
- );
15033
- });
15034
15354
  var PAD2 = {
15035
15355
  sm: { fontSize: 12, lineHeight: 20, stepper: 20, icon: 12 },
15036
15356
  md: { fontSize: 14, lineHeight: 24, stepper: 24, icon: 14 }
@@ -23100,6 +23420,9 @@ exports.CALENDAR_BOARD_DAY_COLORS = CALENDAR_BOARD_DAY_COLORS;
23100
23420
  exports.CALENDAR_BOARD_EVENT_COLORS = CALENDAR_BOARD_EVENT_COLORS;
23101
23421
  exports.CALENDAR_BOARD_EVENT_PRESETS = CALENDAR_BOARD_EVENT_PRESETS;
23102
23422
  exports.CALENDAR_BOARD_MAX_VISIBLE_EVENTS = CALENDAR_BOARD_MAX_VISIBLE_EVENTS;
23423
+ exports.CHAT_MESSAGE_DIRECTIONS = CHAT_MESSAGE_DIRECTIONS;
23424
+ exports.CHAT_MESSAGE_READ_STATUSES = CHAT_MESSAGE_READ_STATUSES;
23425
+ exports.CHAT_MESSAGE_VARIANTS = CHAT_MESSAGE_VARIANTS;
23103
23426
  exports.COLOR_KEYS = COLOR_KEYS;
23104
23427
  exports.FIELD_WIDTH_GRADES = FIELD_WIDTH_GRADES;
23105
23428
  exports.GNB_COLORS = GNB_COLORS;
@@ -23133,6 +23456,8 @@ exports.SCalendar = SCalendar;
23133
23456
  exports.SCalendarBoard = SCalendarBoard;
23134
23457
  exports.SCallout = SCallout;
23135
23458
  exports.SCard = SCard;
23459
+ exports.SChatInput = SChatInput2;
23460
+ exports.SChatMessage = SChatMessage2;
23136
23461
  exports.SCheckbox = SCheckbox;
23137
23462
  exports.SChip = SChip2;
23138
23463
  exports.SChipFilter = SChipFilter;
@@ -23177,6 +23502,7 @@ exports.SListItem = SListItem2;
23177
23502
  exports.SLoadingContainer = SLoadingContainer;
23178
23503
  exports.SLoadingModal = SLoadingModal;
23179
23504
  exports.SLoadingViewport = SLoadingViewport;
23505
+ exports.SLoginCard = SLoginCard2;
23180
23506
  exports.SModal = SModal;
23181
23507
  exports.SModalOutlet = SModalOutlet;
23182
23508
  exports.SNumberInput = SNumberInput;