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