sellmate-design-system-react 9.0.0-beta.41 → 9.0.0-beta.43

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
@@ -5075,7 +5075,11 @@ var STag = /* @__PURE__ */ forwardRef(function STag2({
5075
5075
  ...rest,
5076
5076
  children: [
5077
5077
  icon && iconLeft && iconNode,
5078
- label && /* @__PURE__ */ jsx("span", { className: "inline-flex min-w-0 items-center justify-center", children: label }),
5078
+ label && // leading-none: 태그는 min-height 로 높이가 정해지고 flex 가 세로 중앙 정렬하므로
5079
+ // 레이블에 line-height 가 필요 없다. 오히려 line-height 를 주면 브라우저가
5080
+ // 폰트 ascent/descent 를 정수 px 로 반올림한 뒤 남는 half-leading 을 내림 처리해
5081
+ // 베이스라인이 최대 1px 위로 밀린다(xs 에서 특히 눈에 띈다).
5082
+ /* @__PURE__ */ jsx("span", { className: "inline-flex min-w-0 items-center justify-center leading-none", children: label }),
5079
5083
  icon && !iconLeft && iconNode
5080
5084
  ]
5081
5085
  }
@@ -7847,6 +7851,8 @@ var SPopup = /* @__PURE__ */ forwardRef(function SPopup2({
7847
7851
  });
7848
7852
  var DEFAULT_LABEL = { tip: "\uD65C\uC6A9 TIP", notion: "\uC0AC\uC6A9\uBC95 \uC548\uB0B4" };
7849
7853
  var ICON = { tip: "helpOutline", notion: "notion" };
7854
+ var VIEWPORT_GUTTER = 24;
7855
+ var CLOSE_INSET = 12;
7850
7856
  function flattenMessages(message, depth = 0) {
7851
7857
  if (Array.isArray(message)) return message.flatMap((m) => flattenMessages(m, depth + 1));
7852
7858
  return message !== "" ? [{ text: message, depth }] : [];
@@ -7857,13 +7863,14 @@ function SGuide({
7857
7863
  message = "",
7858
7864
  url = "",
7859
7865
  popupTitle = "",
7860
- popupWidth,
7866
+ popupWidth = "auto",
7861
7867
  className,
7862
7868
  style
7863
7869
  }) {
7864
7870
  const [open, setOpen] = useState(false);
7865
7871
  const portalContainer = usePortalContainer();
7866
7872
  const isNotion = type === "notion";
7873
+ const isAutoWidth = popupWidth === "auto";
7867
7874
  const defaultLabel = DEFAULT_LABEL[type];
7868
7875
  const iconName = ICON[type];
7869
7876
  const messages = flattenMessages(message);
@@ -7903,30 +7910,45 @@ function SGuide({
7903
7910
  side: "bottom",
7904
7911
  align: "start",
7905
7912
  sideOffset: 4,
7913
+ collisionPadding: VIEWPORT_GUTTER,
7906
7914
  className: cn(
7907
7915
  "relative z-50 rounded-[var(--cmp-guide-contents-radius)] bg-[var(--color-white)]",
7908
- // popupWidth(기본 426px)가 창보다 넓으면 창 폭에 맞춘다 — max-width 가 width 를 이긴다.
7909
- "max-w-[var(--radix-popover-content-available-width)]",
7910
7916
  FLOATING_SLIDE_ANIM
7911
7917
  ),
7912
7918
  style: {
7913
- width: popupWidth != null ? popupWidth : 426,
7919
+ width: isAutoWidth ? "max-content" : popupWidth,
7920
+ // 유일한 폭 상한. auto 든 고정 폭이든 창 좌우 여백을 먹고 들어가지 않는다.
7921
+ // 트리거 위치가 아니라 창 기준이라, 오른쪽 끝 트리거에서도 폭이 깎이지 않는다
7922
+ // (넘치면 Radix 가 왼쪽으로 민다). 100vw 는 세로 스크롤바를 포함하지만
7923
+ // 여백이 그보다 넉넉해 화면 밖으로 나가지 않는다.
7924
+ maxWidth: `calc(100vw - ${VIEWPORT_GUTTER * 2}px)`,
7914
7925
  padding: "var(--cmp-guide-contents-paddingY) var(--cmp-guide-contents-paddingX)",
7915
7926
  boxShadow: "4px 4px 24px 4px rgba(0,0,0,0.1)"
7916
7927
  },
7917
7928
  children: [
7918
- /* @__PURE__ */ jsx("div", { className: "absolute right-[12px] top-[12px]", children: /* @__PURE__ */ jsx(SGhostButton, { icon: "close", size: "sm", ariaLabel: "close", onClick: () => setOpen(false) }) }),
7919
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-[var(--cmp-guide-contents-title-gap)]", children: [
7920
- /* @__PURE__ */ jsx(
7921
- SIcon,
7922
- {
7923
- name: iconName,
7924
- size: 24,
7925
- color: isNotion ? "var(--sys-color-fg-primary)" : "var(--cmp-guide-contents-icon)"
7926
- }
7927
- ),
7928
- /* @__PURE__ */ jsx("h3", { className: "m-0 text-[16px] font-bold leading-[26px] text-[color:var(--cmp-guide-contents-typography-color)]", children: popupTitle || defaultLabel })
7929
- ] }),
7929
+ /* @__PURE__ */ jsx("div", { className: "absolute", style: { top: CLOSE_INSET, right: CLOSE_INSET }, children: /* @__PURE__ */ jsx(SGhostButton, { icon: "close", size: "sm", ariaLabel: "close", onClick: () => setOpen(false) }) }),
7930
+ /* @__PURE__ */ jsxs(
7931
+ "div",
7932
+ {
7933
+ className: "flex items-center gap-[var(--cmp-guide-contents-title-gap)]",
7934
+ style: {
7935
+ // 닫기 버튼은 absolute 라 폭 계산에 안 들어간다. 폭이 내용에 붙는 auto 에서 제목이
7936
+ // 버튼 밑으로 들어가지 않도록, 버튼이 콘텐츠 영역을 침범하는 만큼을 미리 비운다.
7937
+ paddingRight: `max(0px, calc(var(--cmp-ghostButton-sm-size) + ${CLOSE_INSET}px + var(--cmp-guide-contents-title-gap) - var(--cmp-guide-contents-paddingX)))`
7938
+ },
7939
+ children: [
7940
+ /* @__PURE__ */ jsx(
7941
+ SIcon,
7942
+ {
7943
+ name: iconName,
7944
+ size: 24,
7945
+ color: isNotion ? "var(--sys-color-fg-primary)" : "var(--cmp-guide-contents-icon)"
7946
+ }
7947
+ ),
7948
+ /* @__PURE__ */ jsx("h3", { className: "m-0 break-keep text-[16px] font-bold leading-[26px] text-[color:var(--cmp-guide-contents-typography-color)]", children: popupTitle || defaultLabel })
7949
+ ]
7950
+ }
7951
+ ),
7930
7952
  /* @__PURE__ */ jsx("ul", { className: "m-0 mt-[var(--cmp-guide-contents-gap)] flex w-full list-none flex-col gap-[var(--cmp-guide-contents-body-gap)] p-0 text-[12px] leading-[20px] text-[color:var(--cmp-guide-contents-typography-color)]", children: messages.map((m, i) => /* @__PURE__ */ jsx(
7931
7953
  "li",
7932
7954
  {
@@ -7937,7 +7959,7 @@ function SGuide({
7937
7959
  children: /* @__PURE__ */ jsx(
7938
7960
  "p",
7939
7961
  {
7940
- className: "m-0 w-full min-w-0 break-words",
7962
+ className: "m-0 w-full min-w-0 break-keep break-words text-pretty",
7941
7963
  dangerouslySetInnerHTML: { __html: sanitizeInlineHtml(m.text) }
7942
7964
  }
7943
7965
  )
@@ -12736,6 +12758,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
12736
12758
  const dismissedByPointerRef = useRef(false);
12737
12759
  const alignSelectedTopRef = useRef(false);
12738
12760
  const selectedAnchorRef = useRef(-1);
12761
+ const rangeAnchorRef = useRef(null);
12739
12762
  const flat = useMemo(() => flattenOptions(options), [options]);
12740
12763
  const selectable = useMemo(() => selectableOptions(flat), [flat]);
12741
12764
  const hasLeaf = useMemo(() => flat.some((o) => !o.isGroup), [flat]);
@@ -12836,7 +12859,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
12836
12859
  if (leafValues.length === 0) return;
12837
12860
  items.push({ key: `__group_${i}__`, isGroup: true, leafValues });
12838
12861
  } else {
12839
- items.push({ key: o.value, value: o.value });
12862
+ items.push({ key: o.value, value: o.value, index: i });
12840
12863
  }
12841
12864
  });
12842
12865
  return items;
@@ -12861,20 +12884,36 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
12861
12884
  emit3([]);
12862
12885
  if (rules && rules.length > 0) setRuleError(runRules(multi ? [] : "", rules));
12863
12886
  };
12864
- const handleSelect = (nextValue) => {
12887
+ const handleSelect = (nextValue, index, extend = false) => {
12865
12888
  if (multi) {
12866
- const next = selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue];
12867
- emit3(next);
12889
+ const anchor = rangeAnchorRef.current;
12890
+ if (extend && anchor != null && anchor !== index && filtered[anchor]?.isGroup === false) {
12891
+ const [from, to] = anchor < index ? [anchor, index] : [index, anchor];
12892
+ const selectedSet = new Set(selected);
12893
+ const shouldSelect = selectedSet.has(filtered[anchor].value);
12894
+ const rangeValues = filtered.slice(from, to + 1).filter((o) => !o.isGroup && !o.disabled).map((o) => o.value);
12895
+ const inRange = new Set(rangeValues);
12896
+ emit3(
12897
+ shouldSelect ? [...selected, ...rangeValues.filter((v) => !selectedSet.has(v))] : selected.filter((v) => !inRange.has(v))
12898
+ );
12899
+ return;
12900
+ }
12901
+ rangeAnchorRef.current = index;
12902
+ emit3(
12903
+ selected.includes(nextValue) ? selected.filter((v) => v !== nextValue) : [...selected, nextValue]
12904
+ );
12868
12905
  return;
12869
12906
  }
12870
12907
  emit3([nextValue]);
12871
12908
  commitOpen(false, [nextValue]);
12872
12909
  };
12873
12910
  const handleSelectAll = () => {
12911
+ rangeAnchorRef.current = null;
12874
12912
  emit3(allSelected ? [] : allValues);
12875
12913
  };
12876
12914
  const handleToggleGroup = (leafValues) => {
12877
12915
  if (leafValues.length === 0) return;
12916
+ rangeAnchorRef.current = null;
12878
12917
  const selectedSet = new Set(selected);
12879
12918
  const allSelectedInGroup = leafValues.every((v) => selectedSet.has(v));
12880
12919
  if (allSelectedInGroup) {
@@ -12892,6 +12931,9 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
12892
12931
  const anchor = alignSelectedTopRef.current ? selectedAnchorRef.current : -1;
12893
12932
  setFocusedIndex(anchor >= 0 ? anchor : 0);
12894
12933
  }, [open, search, navigableItems.length]);
12934
+ useEffect(() => {
12935
+ rangeAnchorRef.current = null;
12936
+ }, [open, search]);
12895
12937
  useLayoutEffect(() => {
12896
12938
  if (!open || focusedIndex < 0 || !listEl) return;
12897
12939
  const focusedEl = listEl.querySelector('[data-select-focused="true"]');
@@ -12919,7 +12961,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
12919
12961
  return current <= 0 ? navigableItems.length - 1 : current - 1;
12920
12962
  });
12921
12963
  };
12922
- const selectFocusedItem = () => {
12964
+ const selectFocusedItem = (extend = false) => {
12923
12965
  if (focusedIndex < 0 || focusedIndex >= navigableItems.length) return;
12924
12966
  const focused = navigableItems[focusedIndex];
12925
12967
  if (focused.isSelectAll) {
@@ -12930,7 +12972,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
12930
12972
  handleToggleGroup(focused.leafValues ?? []);
12931
12973
  return;
12932
12974
  }
12933
- handleSelect(focused.value);
12975
+ handleSelect(focused.value, focused.index, extend);
12934
12976
  };
12935
12977
  const handleKeyDown = (event) => {
12936
12978
  if (!open) {
@@ -12952,7 +12994,7 @@ var SSelectImpl = /* @__PURE__ */ forwardRef(function SSelect({
12952
12994
  }
12953
12995
  if (event.key === "Enter") {
12954
12996
  event.preventDefault();
12955
- selectFocusedItem();
12997
+ selectFocusedItem(event.shiftKey);
12956
12998
  return;
12957
12999
  }
12958
13000
  if (event.key === "Escape") {
@@ -13297,7 +13339,7 @@ function ItemList({
13297
13339
  style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
13298
13340
  onMouseEnter: () => onHoverValue(o.value),
13299
13341
  onMouseLeave: () => onHoverValue(null),
13300
- onClick: () => onSelect(o.value),
13342
+ onClick: (event) => onSelect(o.value, idx, event.shiftKey),
13301
13343
  children: [
13302
13344
  multi && /* @__PURE__ */ jsx(
13303
13345
  CheckboxBox,
@@ -13477,6 +13519,8 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
13477
13519
  setColumnWidths(visibleCols.map(colWidthFromDef));
13478
13520
  }, [colSignature]);
13479
13521
  const selectedKeys = useMemo(() => new Set(selected.map((r) => r[rowKey])), [selected, rowKey]);
13522
+ const rangeAnchorRef = useRef(null);
13523
+ const shiftPressedRef = useRef(false);
13480
13524
  const isRowSelected = useCallback(
13481
13525
  (row) => selectedKeys.has(row[rowKey]),
13482
13526
  [selectedKeys, rowKey]
@@ -13658,18 +13702,24 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
13658
13702
  let bodyRows = rows;
13659
13703
  let topSpacer = 0;
13660
13704
  let bottomSpacer = 0;
13705
+ let rowOffset = 0;
13661
13706
  if (virtualScroll) {
13662
13707
  bodyRows = rows.slice(vsRange.start, vsRange.end);
13708
+ rowOffset = vsRange.start;
13663
13709
  topSpacer = vsRange.start * effectiveRowHeight;
13664
13710
  bottomSpacer = Math.max(0, (rowCount - vsRange.end) * effectiveRowHeight);
13665
13711
  } else if (pageInfo) {
13666
13712
  bodyRows = rows.slice(pageInfo.startIndex, pageInfo.endIndex);
13713
+ rowOffset = pageInfo.startIndex;
13667
13714
  }
13668
13715
  const isNoData = rowCount === 0 && !loading2;
13669
13716
  const showNoDataSlot = isNoData && emptySlot != null;
13670
13717
  const showPagination = !virtualScroll && (pagination != null || internalPagination);
13671
13718
  const lastPage = internalPagination ? Math.max(1, Math.ceil(rowCount / (innerRowsPerPage || 1))) : pagination?.lastPage ?? Math.max(1, Math.ceil(rowCount / (pagination?.rowsPerPage ?? 10)));
13672
13719
  const displayPage = internalPagination ? currentPage : pagination?.page ?? 1;
13720
+ useEffect(() => {
13721
+ rangeAnchorRef.current = null;
13722
+ }, [displayPage]);
13673
13723
  const changePage = (p) => {
13674
13724
  if (internalPagination) setCurrentPage(p);
13675
13725
  onPageChange?.(p);
@@ -13684,6 +13734,7 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
13684
13734
  };
13685
13735
  const headerCheck = getIsAllChecked(bodyRows);
13686
13736
  const toggleAll = () => {
13737
+ rangeAnchorRef.current = null;
13687
13738
  if (headerCheck === true) {
13688
13739
  const pageKeys = new Set(bodyRows.map((r) => r[rowKey]));
13689
13740
  onSelectedChange?.(selected.filter((r) => !pageKeys.has(r[rowKey])));
@@ -13692,7 +13743,23 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
13692
13743
  onSelectedChange?.([...selected, ...add]);
13693
13744
  }
13694
13745
  };
13695
- const toggleRow = (row) => {
13746
+ const toggleRow = (row, index) => {
13747
+ const anchor = rangeAnchorRef.current;
13748
+ const anchorAlive = anchor != null && anchor.index !== index && rows[anchor.index]?.[rowKey] === anchor.key;
13749
+ if (shiftPressedRef.current && anchor != null && anchorAlive) {
13750
+ const [from, to] = anchor.index < index ? [anchor.index, index] : [index, anchor.index];
13751
+ const shouldSelect = selectedKeys.has(anchor.key);
13752
+ const range = rows.slice(from, to + 1);
13753
+ if (shouldSelect) {
13754
+ const add = range.filter((r) => !selectedKeys.has(r[rowKey]));
13755
+ onSelectedChange?.([...selected, ...add]);
13756
+ } else {
13757
+ const keys = new Set(range.map((r) => r[rowKey]));
13758
+ onSelectedChange?.(selected.filter((r) => !keys.has(r[rowKey])));
13759
+ }
13760
+ return;
13761
+ }
13762
+ rangeAnchorRef.current = { index, key: row[rowKey] };
13696
13763
  if (selectedKeys.has(row[rowKey]))
13697
13764
  onSelectedChange?.(selected.filter((r) => r[rowKey] !== row[rowKey]));
13698
13765
  else onSelectedChange?.([...selected, row]);
@@ -14050,7 +14117,17 @@ var STable = /* @__PURE__ */ forwardRef(function STable2({
14050
14117
  maxWidth: SELECTABLE_COLUMN_WIDTH
14051
14118
  },
14052
14119
  onClick: (e) => e.stopPropagation(),
14053
- children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx(SCheckbox, { value: isSel, onValueChange: () => toggleRow(row) }) })
14120
+ onClickCapture: (e) => {
14121
+ shiftPressedRef.current = e.shiftKey;
14122
+ if (e.shiftKey) window.getSelection()?.removeAllRanges();
14123
+ },
14124
+ children: /* @__PURE__ */ jsx("div", { className: "flex select-none items-center justify-center", children: /* @__PURE__ */ jsx(
14125
+ SCheckbox,
14126
+ {
14127
+ value: isSel,
14128
+ onValueChange: () => toggleRow(row, rowOffset + ri)
14129
+ }
14130
+ ) })
14054
14131
  }
14055
14132
  ),
14056
14133
  visibleCols.map((col, i) => {