sellmate-design-system-react 9.0.0-beta.77 → 9.0.0-beta.79

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
@@ -15587,6 +15587,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
15587
15587
  name,
15588
15588
  options = [],
15589
15589
  type = "default",
15590
+ valueDisplay = "text",
15590
15591
  showSearch = false,
15591
15592
  showSelectAll = false,
15592
15593
  allSelectedLabel = "\uC804\uCCB4",
@@ -15619,6 +15620,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
15619
15620
  hint,
15620
15621
  errorMessage,
15621
15622
  width,
15623
+ size = "sm",
15622
15624
  className,
15623
15625
  style
15624
15626
  }, ref) {
@@ -15923,6 +15925,51 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
15923
15925
  }
15924
15926
  return labelByValue.get(selected[0]) ?? valueOptions.get(selected[0])?.label ?? selected[0];
15925
15927
  })();
15928
+ const showChips = valueDisplay === "chip" && multi && selected.length > 0 && !allSelected;
15929
+ const disabledValues = useMemo(
15930
+ () => new Set(flat.filter((o) => !o.isGroup && o.disabled).map((o) => o.value)),
15931
+ [flat]
15932
+ );
15933
+ const fullGroupByLeaf = useMemo(() => {
15934
+ const map = /* @__PURE__ */ new Map();
15935
+ if (!showChips || type !== "multi_depth") return map;
15936
+ flat.forEach((o, i) => {
15937
+ if (!o.isGroup) return;
15938
+ const leafValues = collectGroupLeafValues(flat, i);
15939
+ if (leafValues.length === 0 || leafValues.some((v) => !selectedSet.has(v))) return;
15940
+ if (map.has(leafValues[0])) return;
15941
+ const group = { value: o.value, label: o.label, leafValues };
15942
+ for (let j = i + 1; j < flat.length && flat[j].depth > o.depth; j++) {
15943
+ if (!flat[j].isGroup && selectedSet.has(flat[j].value)) map.set(flat[j].value, group);
15944
+ }
15945
+ });
15946
+ return map;
15947
+ }, [flat, selectedSet, showChips, type]);
15948
+ const chips = [];
15949
+ if (showChips) {
15950
+ const placedGroups = /* @__PURE__ */ new Set();
15951
+ for (const v of selected) {
15952
+ const group = fullGroupByLeaf.get(v);
15953
+ if (group != null) {
15954
+ if (placedGroups.has(group.value)) continue;
15955
+ placedGroups.add(group.value);
15956
+ chips.push({
15957
+ // 그룹 value 와 리프 value 가 겹쳐도 key 가 부딪히지 않게 구분한다.
15958
+ key: `__group_${group.value}__`,
15959
+ label: group.label,
15960
+ values: group.leafValues,
15961
+ removable: !effectiveDisabled
15962
+ });
15963
+ continue;
15964
+ }
15965
+ chips.push({
15966
+ key: v,
15967
+ label: labelByValue.get(v) ?? valueOptions.get(v)?.label ?? v,
15968
+ values: [v],
15969
+ removable: !effectiveDisabled && !disabledValues.has(v)
15970
+ });
15971
+ }
15972
+ }
15926
15973
  const isPlaceholder = selected.length === 0;
15927
15974
  const showTriggerSpinner = loading2 && !effectiveDisabled;
15928
15975
  const showClear = clearable && !effectiveDisabled && !isPlaceholder;
@@ -15930,6 +15977,13 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
15930
15977
  emit3([]);
15931
15978
  if (rules && rules.length > 0) setRuleError(runRules(multi ? [] : "", rules));
15932
15979
  };
15980
+ const handleRemoveChip = (targets) => {
15981
+ const remove = new Set(targets);
15982
+ const next = selected.filter((v) => !remove.has(v));
15983
+ emit3(next);
15984
+ if (rules && rules.length > 0) setRuleError(runRules(next, rules));
15985
+ triggerRef.current?.focus();
15986
+ };
15933
15987
  const handleSelect = (nextValue, index, extend = false) => {
15934
15988
  if (multi) {
15935
15989
  const anchor = rangeAnchorRef.current;
@@ -16085,6 +16139,8 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
16085
16139
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
16086
16140
  disabled: effectiveDisabled,
16087
16141
  width,
16142
+ size,
16143
+ multiline: showChips,
16088
16144
  className,
16089
16145
  style,
16090
16146
  children: [
@@ -16102,55 +16158,59 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
16102
16158
  "aria-busy": showTriggerSpinner || void 0,
16103
16159
  onClick: () => requestOpen(!open),
16104
16160
  onKeyDown: handleKeyDown,
16105
- className: "flex h-full w-full cursor-pointer items-center justify-between gap-[8px] bg-transparent px-[12px] text-left text-[12px] leading-[20px] outline-none disabled:cursor-not-allowed",
16161
+ className: cn(
16162
+ "flex h-full w-full cursor-pointer items-center justify-between bg-transparent text-left outline-none disabled:cursor-not-allowed",
16163
+ size === "md" ? "typo-body-md-default gap-(--cmp-select-md-gap) px-(--cmp-select-md-paddingX)" : "typo-body-sm-default gap-(--cmp-select-sm-gap) px-(--cmp-select-sm-paddingX)"
16164
+ ),
16106
16165
  style: {
16166
+ // 칩 한 줄이 기본 높이 안에서 세로 가운데에 오는 여백. 줄이 늘면 같은 여백을 유지한 채 트리거가 커진다.
16167
+ // sm 칩은 높이가 라벨 줄높이(typo-body-xs-medium)라 남는 높이가 4px 로 떨어진다.
16168
+ paddingBlock: showChips ? size === "md" ? "calc((var(--cmp-select-md-height) - var(--cmp-select-md-borderWidth) * 2 - var(--cmp-chip-height)) / 2)" : "var(--spacing-sd-4)" : void 0,
16107
16169
  color: effectiveDisabled ? "var(--cmp-select-text-disabled)" : isPlaceholder ? "var(--cmp-select-icon-default)" : "var(--cmp-select-text-default)"
16108
16170
  },
16109
16171
  children: [
16110
- isHtmlLabel(triggerText) ? /* @__PURE__ */ jsx(
16111
- "span",
16112
- {
16113
- className: "truncate",
16114
- dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(triggerText) }
16115
- }
16116
- ) : /* @__PURE__ */ jsx("span", { className: "truncate", children: triggerText }),
16117
- /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-[8px]", children: [
16118
- showClear && /* @__PURE__ */ jsxs(
16172
+ showChips ? (
16173
+ // md 는 SChip 과 같은 모양(높이 --cmp-chip-height)이다. SChip 을 그대로 넣지 않는 것은
16174
+ // SChip 의 × 가 <button> 이라 트리거 button 안에 중첩할 수 없어서다 — 모양만 같은 토큰으로 맞춘다.
16175
+ // sm 은 28px 트리거에 들어가도록 낮춘 칩 — 높이를 주지 않고 라벨 줄높이(typo-body-xs-medium)가 정한다.
16176
+ // 두 크기 모두 패딩·라벨↔× 간격은 chip 토큰 그대로.
16177
+ /* @__PURE__ */ jsx(
16119
16178
  "span",
16120
16179
  {
16121
- role: "button",
16122
- tabIndex: 0,
16123
- "aria-label": "\uC120\uD0DD \uC9C0\uC6B0\uAE30",
16124
- className: "group/select-clear relative inline-flex shrink-0 cursor-pointer items-center justify-center",
16125
- style: {
16126
- width: "var(--cmp-ghostButton-xxs-size)",
16127
- height: "var(--cmp-ghostButton-xxs-size)",
16128
- borderRadius: "var(--cmp-ghostButton-radius-sm)"
16129
- },
16130
- onClick: (event) => {
16131
- event.preventDefault();
16132
- event.stopPropagation();
16133
- handleClear();
16134
- },
16135
- onKeyDown: (event) => {
16136
- if (event.key !== "Enter" && event.key !== " ") return;
16137
- event.preventDefault();
16138
- event.stopPropagation();
16139
- handleClear();
16140
- },
16141
- children: [
16142
- /* @__PURE__ */ jsx(
16143
- "span",
16144
- {
16145
- "aria-hidden": true,
16146
- className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover/select-clear:opacity-5",
16147
- style: { background: "var(--cmp-ghostButton-default-hover-bg)" }
16148
- }
16149
- ),
16150
- /* @__PURE__ */ jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
16151
- ]
16180
+ className: cn(
16181
+ "flex min-w-0 flex-1 flex-wrap items-center",
16182
+ size === "md" ? "gap-(--cmp-chipInput-chip-gap)" : "gap-sd-6"
16183
+ ),
16184
+ children: chips.map((chip) => /* @__PURE__ */ jsxs(
16185
+ "span",
16186
+ {
16187
+ className: cn(
16188
+ "box-border inline-flex min-w-0 max-w-full items-center gap-(--cmp-chip-gap) overflow-hidden whitespace-nowrap px-(--cmp-chip-paddingX)",
16189
+ size === "md" ? "h-(--cmp-chip-height) text-[12px] font-medium leading-[20px]" : "typo-body-xs-medium"
16190
+ ),
16191
+ style: {
16192
+ borderRadius: "var(--cmp-chip-radius)",
16193
+ background: chip.removable ? "var(--cmp-chip-bg-default)" : "var(--cmp-chip-bg-disabled)",
16194
+ color: chip.removable ? "var(--cmp-chip-content-default)" : "var(--cmp-chip-content-disabled)"
16195
+ },
16196
+ children: [
16197
+ /* @__PURE__ */ jsx(TriggerLabel, { label: chip.label }),
16198
+ chip.removable && /* @__PURE__ */ jsx(
16199
+ TriggerRemoveButton,
16200
+ {
16201
+ ariaLabel: `${isHtmlLabel(chip.label) ? extractText(chip.label) : chip.label} \uC120\uD0DD \uD574\uC81C`,
16202
+ onRemove: () => handleRemoveChip(chip.values)
16203
+ }
16204
+ )
16205
+ ]
16206
+ },
16207
+ chip.key
16208
+ ))
16152
16209
  }
16153
- ),
16210
+ )
16211
+ ) : /* @__PURE__ */ jsx(TriggerLabel, { label: triggerText }),
16212
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-[8px]", children: [
16213
+ showClear && /* @__PURE__ */ jsx(TriggerRemoveButton, { ariaLabel: "\uC120\uD0DD \uC9C0\uC6B0\uAE30", onRemove: handleClear }),
16154
16214
  showTriggerSpinner ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "inline-flex shrink-0", "data-select-trigger-spinner": true, children: /* @__PURE__ */ jsx(SCircleProgress, { indeterminate: true, type: "neutral", size: 12 }) }) : /* @__PURE__ */ jsx(
16155
16215
  SIcon,
16156
16216
  {
@@ -16482,6 +16542,47 @@ function ItemList({
16482
16542
  bottomSpacer > 0 && /* @__PURE__ */ jsx("div", { "aria-hidden": true, style: { height: bottomSpacer } })
16483
16543
  ] });
16484
16544
  }
16545
+ function TriggerLabel({ label }) {
16546
+ return isHtmlLabel(label) ? /* @__PURE__ */ jsx("span", { className: "truncate", dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(label) } }) : /* @__PURE__ */ jsx("span", { className: "truncate", children: label });
16547
+ }
16548
+ function TriggerRemoveButton({ ariaLabel, onRemove }) {
16549
+ return /* @__PURE__ */ jsxs(
16550
+ "span",
16551
+ {
16552
+ role: "button",
16553
+ tabIndex: 0,
16554
+ "aria-label": ariaLabel,
16555
+ className: "group/select-remove relative inline-flex shrink-0 cursor-pointer items-center justify-center",
16556
+ style: {
16557
+ width: "var(--cmp-ghostButton-xxs-size)",
16558
+ height: "var(--cmp-ghostButton-xxs-size)",
16559
+ borderRadius: "var(--cmp-ghostButton-radius-sm)"
16560
+ },
16561
+ onClick: (event) => {
16562
+ event.preventDefault();
16563
+ event.stopPropagation();
16564
+ onRemove();
16565
+ },
16566
+ onKeyDown: (event) => {
16567
+ if (event.key !== "Enter" && event.key !== " ") return;
16568
+ event.preventDefault();
16569
+ event.stopPropagation();
16570
+ onRemove();
16571
+ },
16572
+ children: [
16573
+ /* @__PURE__ */ jsx(
16574
+ "span",
16575
+ {
16576
+ "aria-hidden": true,
16577
+ className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover/select-remove:opacity-5",
16578
+ style: { background: "var(--cmp-ghostButton-default-hover-bg)" }
16579
+ }
16580
+ ),
16581
+ /* @__PURE__ */ jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
16582
+ ]
16583
+ }
16584
+ );
16585
+ }
16485
16586
  function CheckboxBox({
16486
16587
  state: state2,
16487
16588
  disabled = false,
@@ -22545,71 +22646,76 @@ function SRadioButton({
22545
22646
  }) {
22546
22647
  const s = SIZE_CONFIG2[size];
22547
22648
  const [hoveredValue, setHoveredValue] = useState(null);
22548
- return /* @__PURE__ */ jsx(
22549
- "div",
22550
- {
22551
- role: "radiogroup",
22552
- "aria-disabled": disabled,
22553
- className: cn("inline-flex", className),
22554
- style,
22555
- children: options.map((o, i) => {
22556
- const selected = o.value === value;
22557
- const od = disabled || o.disabled;
22558
- const first = i === 0;
22559
- const last = i === options.length - 1;
22560
- const tooltip = showTooltip && o.tooltip ? o.tooltip : void 0;
22561
- const button = /* @__PURE__ */ jsx(
22562
- "button",
22563
- {
22564
- type: "button",
22565
- role: "radio",
22566
- name,
22567
- "aria-checked": selected,
22568
- disabled: od,
22569
- onClick: () => !od && !selected && onValueChange?.(o.value),
22570
- onMouseEnter: tooltip ? () => setHoveredValue(o.value) : void 0,
22571
- onMouseLeave: tooltip ? () => setHoveredValue(null) : void 0,
22572
- onFocus: tooltip ? () => setHoveredValue(o.value) : void 0,
22573
- onBlur: tooltip ? () => setHoveredValue(null) : void 0,
22574
- className: cn(
22575
- "relative box-border grow inline-flex items-center justify-center whitespace-nowrap border border-solid font-medium transition-colors",
22576
- first ? "" : "-ml-px",
22577
- selected ? "z-10" : "",
22578
- !od && !selected && "hover:!bg-[var(--cmp-radioButton-bg-hover)] hover:!text-[color:var(--cmp-radioButton-content-hover)] hover:!border-[color:var(--cmp-radioButton-bg-hover)] hover:z-10",
22579
- od ? "cursor-not-allowed" : "cursor-pointer"
22580
- ),
22581
- style: {
22582
- height: `var(--cmp-radioButton-${size}-height)`,
22583
- padding: `0 var(--cmp-radioButton-${size}-paddingX)`,
22584
- fontSize: `${s.fontSize}px`,
22585
- lineHeight: `${s.lineHeight}px`,
22586
- borderRadius: [
22587
- first ? s.radius : "0",
22588
- last ? s.radius : "0",
22589
- last ? s.radius : "0",
22590
- first ? s.radius : "0"
22591
- ].join(" "),
22592
- background: od ? "var(--cmp-radioButton-bg-disabled)" : "var(--cmp-radioButton-bg-default)",
22593
- borderColor: od ? "var(--cmp-radioButton-border-disabled)" : selected ? "var(--cmp-radioButton-border-select)" : "var(--cmp-radioButton-border-default)",
22594
- color: od ? "var(--cmp-radioButton-content-disabled)" : selected ? "var(--cmp-radioButton-content-select)" : "var(--cmp-radioButton-content-default)"
22649
+ return (
22650
+ // isolate: 선택·hover 버튼의 z-10 은 겹친 테두리(-ml-px) 위로 올리기 위한 값이라 그룹 안에서
22651
+ // 끝나야 한다. 빼면 상위 컨텍스트까지 올라가 모달 안 floating 밴드(z-2)를 이겨, 위에서 연
22652
+ // SSelect listbox 위로 선택된 버튼만 뚫고 그려진다.
22653
+ /* @__PURE__ */ jsx(
22654
+ "div",
22655
+ {
22656
+ role: "radiogroup",
22657
+ "aria-disabled": disabled,
22658
+ className: cn("inline-flex isolate", className),
22659
+ style,
22660
+ children: options.map((o, i) => {
22661
+ const selected = o.value === value;
22662
+ const od = disabled || o.disabled;
22663
+ const first = i === 0;
22664
+ const last = i === options.length - 1;
22665
+ const tooltip = showTooltip && o.tooltip ? o.tooltip : void 0;
22666
+ const button = /* @__PURE__ */ jsx(
22667
+ "button",
22668
+ {
22669
+ type: "button",
22670
+ role: "radio",
22671
+ name,
22672
+ "aria-checked": selected,
22673
+ disabled: od,
22674
+ onClick: () => !od && !selected && onValueChange?.(o.value),
22675
+ onMouseEnter: tooltip ? () => setHoveredValue(o.value) : void 0,
22676
+ onMouseLeave: tooltip ? () => setHoveredValue(null) : void 0,
22677
+ onFocus: tooltip ? () => setHoveredValue(o.value) : void 0,
22678
+ onBlur: tooltip ? () => setHoveredValue(null) : void 0,
22679
+ className: cn(
22680
+ "relative box-border grow inline-flex items-center justify-center whitespace-nowrap border border-solid font-medium transition-colors",
22681
+ first ? "" : "-ml-px",
22682
+ selected ? "z-10" : "",
22683
+ !od && !selected && "hover:!bg-[var(--cmp-radioButton-bg-hover)] hover:!text-[color:var(--cmp-radioButton-content-hover)] hover:!border-[color:var(--cmp-radioButton-bg-hover)] hover:z-10",
22684
+ od ? "cursor-not-allowed" : "cursor-pointer"
22685
+ ),
22686
+ style: {
22687
+ height: `var(--cmp-radioButton-${size}-height)`,
22688
+ padding: `0 var(--cmp-radioButton-${size}-paddingX)`,
22689
+ fontSize: `${s.fontSize}px`,
22690
+ lineHeight: `${s.lineHeight}px`,
22691
+ borderRadius: [
22692
+ first ? s.radius : "0",
22693
+ last ? s.radius : "0",
22694
+ last ? s.radius : "0",
22695
+ first ? s.radius : "0"
22696
+ ].join(" "),
22697
+ background: od ? "var(--cmp-radioButton-bg-disabled)" : "var(--cmp-radioButton-bg-default)",
22698
+ borderColor: od ? "var(--cmp-radioButton-border-disabled)" : selected ? "var(--cmp-radioButton-border-select)" : "var(--cmp-radioButton-border-default)",
22699
+ color: od ? "var(--cmp-radioButton-content-disabled)" : selected ? "var(--cmp-radioButton-content-select)" : "var(--cmp-radioButton-content-default)"
22700
+ },
22701
+ children: o.label
22595
22702
  },
22596
- children: o.label
22597
- },
22598
- o.value
22599
- );
22600
- return tooltip ? /* @__PURE__ */ jsx(
22601
- STooltip2,
22602
- {
22603
- trigger: "none",
22604
- open: hoveredValue === o.value,
22605
- tooltipType: "accent",
22606
- message: [tooltip],
22607
- children: button
22608
- },
22609
- o.value
22610
- ) : button;
22611
- })
22612
- }
22703
+ o.value
22704
+ );
22705
+ return tooltip ? /* @__PURE__ */ jsx(
22706
+ STooltip2,
22707
+ {
22708
+ trigger: "none",
22709
+ open: hoveredValue === o.value,
22710
+ tooltipType: "accent",
22711
+ message: [tooltip],
22712
+ children: button
22713
+ },
22714
+ o.value
22715
+ ) : button;
22716
+ })
22717
+ }
22718
+ )
22613
22719
  );
22614
22720
  }
22615
22721
  function SRadioCard({