sellmate-design-system-react 9.0.0-beta.101 → 9.0.0-beta.103

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -272,6 +272,126 @@ var fieldWidthValue = (v) => {
272
272
  if (isFieldWidthGrade(v)) return `var(--cmp-field-width-${v})`;
273
273
  return typeof v === "number" ? `${v}px` : v;
274
274
  };
275
+
276
+ // src/lib/locale.ts
277
+ var SLOCALES = ["ko", "en", "ja", "zh"];
278
+ var DEFAULT_LOCALE = "ko";
279
+ var BUNDLES = {
280
+ ko: {
281
+ pagination: {
282
+ firstPage: "\uCCAB \uD398\uC774\uC9C0",
283
+ prevPage: "\uC774\uC804 \uD398\uC774\uC9C0",
284
+ nextPage: "\uB2E4\uC74C \uD398\uC774\uC9C0",
285
+ lastPage: "\uB9C8\uC9C0\uB9C9 \uD398\uC774\uC9C0",
286
+ prevGroup: "\uC774\uC804 \uADF8\uB8F9",
287
+ nextGroup: "\uB2E4\uC74C \uADF8\uB8F9"
288
+ },
289
+ select: {
290
+ placeholder: "\uC120\uD0DD",
291
+ allSelected: "\uC804\uCCB4",
292
+ clear: "\uC120\uD0DD \uC9C0\uC6B0\uAE30",
293
+ deselect: (label) => `${label} \uC120\uD0DD \uD574\uC81C`,
294
+ searchPlaceholder: "\uAC80\uC0C9",
295
+ empty: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
296
+ loading: "\uBD88\uB7EC\uC624\uB294 \uC911..."
297
+ }
298
+ },
299
+ en: {
300
+ pagination: {
301
+ firstPage: "First page",
302
+ prevPage: "Previous page",
303
+ nextPage: "Next page",
304
+ lastPage: "Last page",
305
+ prevGroup: "Previous pages",
306
+ nextGroup: "Next pages"
307
+ },
308
+ select: {
309
+ placeholder: "Select",
310
+ allSelected: "All",
311
+ clear: "Clear selection",
312
+ deselect: (label) => `Deselect ${label}`,
313
+ searchPlaceholder: "Search",
314
+ empty: "No items to display.",
315
+ loading: "Loading..."
316
+ }
317
+ },
318
+ ja: {
319
+ pagination: {
320
+ firstPage: "\u6700\u521D\u306E\u30DA\u30FC\u30B8",
321
+ prevPage: "\u524D\u306E\u30DA\u30FC\u30B8",
322
+ nextPage: "\u6B21\u306E\u30DA\u30FC\u30B8",
323
+ lastPage: "\u6700\u5F8C\u306E\u30DA\u30FC\u30B8",
324
+ prevGroup: "\u524D\u306E\u30B0\u30EB\u30FC\u30D7",
325
+ nextGroup: "\u6B21\u306E\u30B0\u30EB\u30FC\u30D7"
326
+ },
327
+ select: {
328
+ placeholder: "\u9078\u629E",
329
+ allSelected: "\u3059\u3079\u3066",
330
+ clear: "\u9078\u629E\u3092\u30AF\u30EA\u30A2",
331
+ deselect: (label) => `${label} \u306E\u9078\u629E\u3092\u89E3\u9664`,
332
+ searchPlaceholder: "\u691C\u7D22",
333
+ empty: "\u8868\u793A\u3059\u308B\u9805\u76EE\u304C\u3042\u308A\u307E\u305B\u3093\u3002",
334
+ loading: "\u8AAD\u307F\u8FBC\u307F\u4E2D..."
335
+ }
336
+ },
337
+ zh: {
338
+ pagination: {
339
+ firstPage: "\u7B2C\u4E00\u9875",
340
+ prevPage: "\u4E0A\u4E00\u9875",
341
+ nextPage: "\u4E0B\u4E00\u9875",
342
+ lastPage: "\u6700\u540E\u4E00\u9875",
343
+ prevGroup: "\u4E0A\u4E00\u7EC4",
344
+ nextGroup: "\u4E0B\u4E00\u7EC4"
345
+ },
346
+ select: {
347
+ placeholder: "\u8BF7\u9009\u62E9",
348
+ allSelected: "\u5168\u90E8",
349
+ clear: "\u6E05\u9664\u9009\u62E9",
350
+ deselect: (label) => `\u53D6\u6D88\u9009\u62E9 ${label}`,
351
+ searchPlaceholder: "\u641C\u7D22",
352
+ empty: "\u6CA1\u6709\u53EF\u663E\u793A\u7684\u9879\u76EE\u3002",
353
+ loading: "\u52A0\u8F7D\u4E2D..."
354
+ }
355
+ }
356
+ };
357
+ function normalizeLocale(locale) {
358
+ if (!locale) return DEFAULT_LOCALE;
359
+ const lang = locale.toLowerCase().split(/[-_]/)[0];
360
+ return SLOCALES.includes(lang) ? lang : DEFAULT_LOCALE;
361
+ }
362
+ function mergeDefined(base, override) {
363
+ const next = { ...base };
364
+ if (!override) return next;
365
+ for (const key of Object.keys(override)) {
366
+ if (override[key] !== void 0) next[key] = override[key];
367
+ }
368
+ return next;
369
+ }
370
+ function resolveMessages(locale, override) {
371
+ const base = BUNDLES[normalizeLocale(locale)];
372
+ return {
373
+ pagination: mergeDefined(base.pagination, override?.pagination),
374
+ select: mergeDefined(base.select, override?.select)
375
+ };
376
+ }
377
+ function createValue(locale, override) {
378
+ const normalized = normalizeLocale(locale);
379
+ const numberFormat = new Intl.NumberFormat(normalized);
380
+ return {
381
+ locale: normalized,
382
+ messages: resolveMessages(normalized, override),
383
+ formatNumber: (value) => numberFormat.format(value)
384
+ };
385
+ }
386
+ var DEFAULT_VALUE = createValue(void 0);
387
+ var LocaleContext = /* @__PURE__ */ react.createContext(DEFAULT_VALUE);
388
+ function SLocaleProvider({ locale, messages, children }) {
389
+ const value = react.useMemo(() => createValue(locale, messages), [locale, messages]);
390
+ return /* @__PURE__ */ jsxRuntime.jsx(LocaleContext.Provider, { value, children });
391
+ }
392
+ function useLocale() {
393
+ return react.useContext(LocaleContext);
394
+ }
275
395
  var Icon_account = (p) => {
276
396
  const { size = 24, ...rest } = p;
277
397
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -1739,6 +1859,41 @@ var Icon_date = (p) => {
1739
1859
  }
1740
1860
  );
1741
1861
  };
1862
+ var Icon_dollar = (p) => {
1863
+ const { size = 24, ...rest } = p;
1864
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1865
+ "svg",
1866
+ {
1867
+ width: size,
1868
+ height: size,
1869
+ viewBox: "0 0 24 24",
1870
+ fill: "none",
1871
+ xmlns: "http://www.w3.org/2000/svg",
1872
+ ...rest,
1873
+ children: [
1874
+ /* @__PURE__ */ jsxRuntime.jsx(
1875
+ "path",
1876
+ {
1877
+ d: "M12 5V7.1M12 7.1V12M12 7.1H15.3333M12 7.1H10.3333C9.04467 7.1 8 8.1969 8 9.55C8 10.9031 9.04467 12 10.3333 12H12M12 12V16.9M12 12H13.6667C14.9553 12 16 13.0969 16 14.45C16 15.8031 14.9553 16.9 13.6667 16.9H12M12 16.9V19M12 16.9H8.66667",
1878
+ stroke: "currentColor",
1879
+ strokeWidth: "1.5",
1880
+ strokeLinecap: "round",
1881
+ strokeLinejoin: "round"
1882
+ }
1883
+ ),
1884
+ /* @__PURE__ */ jsxRuntime.jsx(
1885
+ "path",
1886
+ {
1887
+ d: "M22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12Z",
1888
+ stroke: "currentColor",
1889
+ strokeWidth: "1.5",
1890
+ strokeLinecap: "round"
1891
+ }
1892
+ )
1893
+ ]
1894
+ }
1895
+ );
1896
+ };
1742
1897
  var Icon_dot = (p) => {
1743
1898
  const { size = 24, ...rest } = p;
1744
1899
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -1955,6 +2110,54 @@ var Icon_focus = (p) => {
1955
2110
  }
1956
2111
  );
1957
2112
  };
2113
+ var Icon_fullscreen = (p) => {
2114
+ const { size = 24, ...rest } = p;
2115
+ return /* @__PURE__ */ jsxRuntime.jsx(
2116
+ "svg",
2117
+ {
2118
+ width: size,
2119
+ height: size,
2120
+ viewBox: "0 0 24 24",
2121
+ fill: "none",
2122
+ xmlns: "http://www.w3.org/2000/svg",
2123
+ ...rest,
2124
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2125
+ "path",
2126
+ {
2127
+ d: "M9 3H4C3.44772 3 3 3.44772 3 4V9M15 3H20C20.5523 3 21 3.44772 21 4V9M21 15V20C21 20.5523 20.5523 21 20 21H15M9 21H4C3.44772 21 3 20.5523 3 20V15",
2128
+ stroke: "currentColor",
2129
+ strokeWidth: "1.5",
2130
+ strokeLinecap: "round",
2131
+ strokeLinejoin: "round"
2132
+ }
2133
+ )
2134
+ }
2135
+ );
2136
+ };
2137
+ var Icon_fullscreenExit = (p) => {
2138
+ const { size = 24, ...rest } = p;
2139
+ return /* @__PURE__ */ jsxRuntime.jsx(
2140
+ "svg",
2141
+ {
2142
+ width: size,
2143
+ height: size,
2144
+ viewBox: "0 0 24 24",
2145
+ fill: "none",
2146
+ xmlns: "http://www.w3.org/2000/svg",
2147
+ ...rest,
2148
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2149
+ "path",
2150
+ {
2151
+ d: "M3 9H8C8.55228 9 9 8.55228 9 8V3M15 3V8C15 8.55228 15.4477 9 16 9H21M21 15H16C15.4477 15 15 15.4477 15 16V21M9 21V16C9 15.4477 8.55228 15 8 15H3",
2152
+ stroke: "currentColor",
2153
+ strokeWidth: "1.5",
2154
+ strokeLinecap: "round",
2155
+ strokeLinejoin: "round"
2156
+ }
2157
+ )
2158
+ }
2159
+ );
2160
+ };
1958
2161
  var Icon_global = (p) => {
1959
2162
  const { size = 24, ...rest } = p;
1960
2163
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -3657,6 +3860,7 @@ var ICONS = {
3657
3860
  coupon: Icon_coupon,
3658
3861
  dashboard: Icon_dashboard,
3659
3862
  date: Icon_date,
3863
+ dollar: Icon_dollar,
3660
3864
  dot: Icon_dot,
3661
3865
  download: Icon_download,
3662
3866
  drag: Icon_drag,
@@ -3664,6 +3868,8 @@ var ICONS = {
3664
3868
  etc: Icon_etc,
3665
3869
  event: Icon_event,
3666
3870
  focus: Icon_focus,
3871
+ fullscreen: Icon_fullscreen,
3872
+ fullscreenExit: Icon_fullscreenExit,
3667
3873
  global: Icon_global,
3668
3874
  helpFill: Icon_helpFill,
3669
3875
  helpOutline: Icon_helpOutline,
@@ -10449,7 +10655,17 @@ function TokenIcon2({ name, size }) {
10449
10655
  return /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name, size: "100%", style: { width: size, height: size } });
10450
10656
  }
10451
10657
  var SSystemActionButton = /* @__PURE__ */ react.forwardRef(
10452
- function SSystemActionButton2({ color = "light", type, label, option, disabled = false, className, style, ...rest }, ref) {
10658
+ function SSystemActionButton2({
10659
+ color = "light",
10660
+ type,
10661
+ label,
10662
+ labelSlot,
10663
+ option,
10664
+ disabled = false,
10665
+ className,
10666
+ style,
10667
+ ...rest
10668
+ }, ref) {
10453
10669
  const c = SYSTEM_ACTION_BUTTON_COLOR_CONFIG[color];
10454
10670
  const spec = SYSTEM_ACTION_BUTTON_TYPE_CONFIG[type];
10455
10671
  const listBox = type === "account" ? option ?? {} : void 0;
@@ -10537,7 +10753,19 @@ var SSystemActionButton = /* @__PURE__ */ react.forwardRef(
10537
10753
  icon: "dot",
10538
10754
  label: selected?.label ?? status.value
10539
10755
  }
10540
- ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: label }) }),
10756
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
10757
+ label != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: label }),
10758
+ labelSlot != null && // 라벨↔슬롯 간격에 대응 토큰이 없어 아이콘↔라벨 간격을 같은 호흡으로 쓴다.
10759
+ // 라벨이 없으면 슬롯이 아이콘 바로 옆에서 시작하도록 간격은 라벨이 있을 때만 준다.
10760
+ /* @__PURE__ */ jsxRuntime.jsx(
10761
+ "span",
10762
+ {
10763
+ className: "inline-flex shrink-0 items-center",
10764
+ style: label != null ? { marginLeft: SYSTEM_ACTION_BUTTON_LAYOUT.iconLabelGap } : void 0,
10765
+ children: labelSlot
10766
+ }
10767
+ )
10768
+ ] }) }),
10541
10769
  (actionText != null || spec.actionIcon) && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex shrink-0 items-center", style: { gap: SYSTEM_ACTION_BUTTON_LAYOUT.actionGap }, children: [
10542
10770
  actionText != null && spec.actionText && /* @__PURE__ */ jsxRuntime.jsx(
10543
10771
  "span",
@@ -14087,8 +14315,10 @@ var PAGE_PADDINGS = ["default", "none"];
14087
14315
  var PAGE_CONTENT_WIDTHS = ["full", "fixed", "fixedCenter"];
14088
14316
  var PAGE_HEADER_VARIANTS = ["plain", "bar"];
14089
14317
  var SPageHeader = /* @__PURE__ */ withDisplayName(
14090
- react.forwardRef(function SPageHeader2({ title, subText, variant, onBack, slot, scrolled, className, style, ...rest }, ref) {
14318
+ react.forwardRef(function SPageHeader2({ title, subText, titleSlot, variant, onBack, slot, scrolled, className, style, ...rest }, ref) {
14091
14319
  const hasBack = onBack != null;
14320
+ const hasTitleSlot = titleSlot != null && titleSlot !== false;
14321
+ const titleEl = /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-heading-lg min-w-0 truncate text-(--cmp-pageHeader-title-text)", children: title });
14092
14322
  const bar = variant === "bar";
14093
14323
  const column = react.useContext(SPageColumnContext);
14094
14324
  const isScrolled = scrolled ?? column?.scrolled ?? false;
@@ -14138,7 +14368,13 @@ var SPageHeader = /* @__PURE__ */ withDisplayName(
14138
14368
  !hasBack && "pl-(--cmp-pageHeader-title-paddingLeft)"
14139
14369
  ),
14140
14370
  children: [
14141
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-heading-lg min-w-0 truncate text-(--cmp-pageHeader-title-text)", children: title }),
14371
+ hasTitleSlot ? (
14372
+ // max-w-full: plain 의 열은 items-start 라 래퍼가 내용 폭을 따라가 긴 제목이 잘리지 않고 태그를 밀어낸다
14373
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex max-w-full min-w-0 items-center gap-(--cmp-pageHeader-gap)", children: [
14374
+ titleEl,
14375
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 items-center", children: titleSlot })
14376
+ ] })
14377
+ ) : titleEl,
14142
14378
  subText && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-md-default min-w-0 truncate text-(--cmp-pageHeader-subtitle-text)", children: subText })
14143
14379
  ]
14144
14380
  }
@@ -14307,6 +14543,7 @@ function SPagination({
14307
14543
  className,
14308
14544
  style
14309
14545
  }) {
14546
+ const t = useLocale().messages.pagination;
14310
14547
  const start = Math.floor((currentPage - 1) / PER_PAGE) * PER_PAGE + 1;
14311
14548
  const end = Math.min(start + PER_PAGE - 1, lastPage);
14312
14549
  const pageNumbers = Array.from({ length: Math.max(end - start + 1, 0) }, (_, i) => start + i);
@@ -14333,18 +14570,18 @@ function SPagination({
14333
14570
  }
14334
14571
  );
14335
14572
  const prevButtons = simple ? currentPage > 1 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14336
- navBtn("chevronLeftEnd", "\uCCAB \uD398\uC774\uC9C0", () => go(1)),
14337
- navBtn("chevronLeft", "\uC774\uC804 \uD398\uC774\uC9C0", () => go(currentPage - 1))
14573
+ navBtn("chevronLeftEnd", t.firstPage, () => go(1)),
14574
+ navBtn("chevronLeft", t.prevPage, () => go(currentPage - 1))
14338
14575
  ] }) : !isFirstGroup && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14339
- navBtn("chevronLeftEnd", "\uCCAB \uD398\uC774\uC9C0", () => go(1)),
14340
- navBtn("chevronLeft", "\uC774\uC804 \uADF8\uB8F9", () => groupMove("backward"))
14576
+ navBtn("chevronLeftEnd", t.firstPage, () => go(1)),
14577
+ navBtn("chevronLeft", t.prevGroup, () => groupMove("backward"))
14341
14578
  ] });
14342
14579
  const nextButtons = simple ? currentPage < lastPage && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14343
- navBtn("chevronRight", "\uB2E4\uC74C \uD398\uC774\uC9C0", () => go(currentPage + 1)),
14344
- navBtn("chevronRightEnd", "\uB9C8\uC9C0\uB9C9 \uD398\uC774\uC9C0", () => go(lastPage))
14580
+ navBtn("chevronRight", t.nextPage, () => go(currentPage + 1)),
14581
+ navBtn("chevronRightEnd", t.lastPage, () => go(lastPage))
14345
14582
  ] }) : !isLastGroup && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14346
- navBtn("chevronRight", "\uB2E4\uC74C \uADF8\uB8F9", () => groupMove("forward")),
14347
- navBtn("chevronRightEnd", "\uB9C8\uC9C0\uB9C9 \uD398\uC774\uC9C0", () => go(lastPage))
14583
+ navBtn("chevronRight", t.nextGroup, () => groupMove("forward")),
14584
+ navBtn("chevronRightEnd", t.lastPage, () => go(lastPage))
14348
14585
  ] });
14349
14586
  return /* @__PURE__ */ jsxRuntime.jsxs(
14350
14587
  "div",
@@ -14381,6 +14618,224 @@ function SPagination({
14381
14618
  );
14382
14619
  }
14383
14620
 
14621
+ // src/components/SSectionNav/section-nav-scroll.ts
14622
+ var ACTIVATE_RATIO = 0.25;
14623
+ function pickActiveIndex(tops, m) {
14624
+ if (tops.length === 0) return -1;
14625
+ const atBottom = m.scrollTop > 0 && m.scrollTop + m.clientHeight >= m.scrollHeight - 1;
14626
+ const last = tops.length - 1;
14627
+ if (atBottom && tops[last] < m.clientHeight) return last;
14628
+ const line = m.clientHeight * ACTIVATE_RATIO;
14629
+ let active = 0;
14630
+ tops.forEach((top, i) => {
14631
+ if (top <= line) active = i;
14632
+ });
14633
+ return active;
14634
+ }
14635
+ function findSection(id, scope) {
14636
+ if (!scope) return document.getElementById(id);
14637
+ return scope.querySelector(`[id="${id.replace(/["\\]/g, "\\$&")}"]`);
14638
+ }
14639
+ var isScrollable = (el) => {
14640
+ const { overflowY } = window.getComputedStyle(el);
14641
+ return overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay";
14642
+ };
14643
+ function findScrollParent(el) {
14644
+ for (let p = el.parentElement; p; p = p.parentElement) {
14645
+ if (p === document.body || p === document.documentElement) break;
14646
+ if (isScrollable(p)) return p;
14647
+ }
14648
+ return document.scrollingElement ?? document.documentElement;
14649
+ }
14650
+ var isRootScroller = (el) => el === document.scrollingElement || el === document.documentElement;
14651
+ var viewportTop = (container) => isRootScroller(container) ? 0 : container.getBoundingClientRect().top + container.clientTop;
14652
+ var readMetrics = (container) => ({
14653
+ // 문서 전체는 가로 스크롤바를 뺀 높이(documentElement.clientHeight)여야 scrollHeight 와 같은 기준이다
14654
+ clientHeight: isRootScroller(container) ? document.documentElement.clientHeight : container.clientHeight,
14655
+ scrollTop: container.scrollTop,
14656
+ scrollHeight: container.scrollHeight
14657
+ });
14658
+ var sectionTop = (section, container) => section.getBoundingClientRect().top - viewportTop(container);
14659
+ function scrollToSection(section, container, behavior = "smooth") {
14660
+ const margin = parseFloat(window.getComputedStyle(section).scrollMarginTop) || 0;
14661
+ const top = container.scrollTop + sectionTop(section, container) - margin;
14662
+ const reduce = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
14663
+ if (typeof container.scrollTo === "function") {
14664
+ container.scrollTo({ top, behavior: behavior === "smooth" && !reduce ? "smooth" : "instant" });
14665
+ } else {
14666
+ container.scrollTop = top;
14667
+ }
14668
+ }
14669
+ var SETTLE_MS = 150;
14670
+ var SSectionNav = /* @__PURE__ */ react.forwardRef(function SSectionNav2({
14671
+ items,
14672
+ value,
14673
+ defaultValue,
14674
+ onValueChange,
14675
+ scrollContainer,
14676
+ "aria-label": ariaLabel,
14677
+ className,
14678
+ style
14679
+ }, ref) {
14680
+ const controlled = value !== void 0;
14681
+ const [inner, setInner] = react.useState(defaultValue);
14682
+ const active = controlled ? value : inner;
14683
+ const activeRef = react.useRef(active);
14684
+ const emittedRef = react.useRef(void 0);
14685
+ activeRef.current = active;
14686
+ react.useEffect(() => {
14687
+ emittedRef.current = void 0;
14688
+ }, [active]);
14689
+ const onChangeRef = react.useRef(onValueChange);
14690
+ onChangeRef.current = onValueChange;
14691
+ const select = react.useCallback(
14692
+ (next, notify = true) => {
14693
+ if (next === activeRef.current) return;
14694
+ if (!controlled) setInner(next);
14695
+ if (notify && next !== emittedRef.current) {
14696
+ emittedRef.current = next;
14697
+ onChangeRef.current?.(next);
14698
+ }
14699
+ },
14700
+ [controlled]
14701
+ );
14702
+ const lockedRef = react.useRef(false);
14703
+ const unlockTimer = react.useRef(void 0);
14704
+ const holdLock = react.useCallback(() => {
14705
+ lockedRef.current = true;
14706
+ clearTimeout(unlockTimer.current);
14707
+ unlockTimer.current = setTimeout(() => {
14708
+ lockedRef.current = false;
14709
+ }, SETTLE_MS);
14710
+ }, []);
14711
+ const initialTarget = react.useRef(controlled ? void 0 : defaultValue);
14712
+ const initialPending = react.useRef(initialTarget.current !== void 0);
14713
+ const valuesKey = items.map((i) => i.value).join("\0");
14714
+ react.useEffect(() => {
14715
+ const scope = scrollContainer?.current ?? null;
14716
+ const findPresent = () => items.map((item4) => ({ item: item4, el: findSection(item4.value, scope) })).filter((s) => s.el !== null);
14717
+ let detach;
14718
+ let waiter;
14719
+ const attach = () => {
14720
+ const [firstPresent] = findPresent();
14721
+ if (!firstPresent) return false;
14722
+ const container = scope ?? findScrollParent(firstPresent.el);
14723
+ const root = isRootScroller(container);
14724
+ const scroller = root ? window : container;
14725
+ const update = (notify) => {
14726
+ const present = findPresent();
14727
+ const index = pickActiveIndex(
14728
+ present.map((s) => sectionTop(s.el, container)),
14729
+ readMetrics(container)
14730
+ );
14731
+ if (index >= 0) select(present[index].item.value, notify);
14732
+ };
14733
+ const refresh = () => {
14734
+ if (!lockedRef.current) update(true);
14735
+ };
14736
+ const onScroll = () => {
14737
+ if (lockedRef.current) {
14738
+ holdLock();
14739
+ return;
14740
+ }
14741
+ initialPending.current = false;
14742
+ update(true);
14743
+ };
14744
+ const applyInitial = () => {
14745
+ const target = initialTarget.current;
14746
+ if (!initialPending.current || target === void 0) return;
14747
+ const section = findSection(target, scope);
14748
+ if (!section) return;
14749
+ initialPending.current = false;
14750
+ holdLock();
14751
+ scrollToSection(section, container, "instant");
14752
+ };
14753
+ scroller.addEventListener("scroll", onScroll, { passive: true });
14754
+ const sizes = /* @__PURE__ */ new WeakMap();
14755
+ const resize = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver((entries4) => {
14756
+ const changed = entries4.filter((e) => {
14757
+ const size = `${e.contentRect.width}x${e.contentRect.height}`;
14758
+ const before = sizes.get(e.target);
14759
+ sizes.set(e.target, size);
14760
+ return before !== void 0 && before !== size;
14761
+ });
14762
+ if (changed.length > 0) refresh();
14763
+ });
14764
+ const observeSizes = () => {
14765
+ resize?.observe(root ? document.documentElement : container);
14766
+ findPresent().forEach((s) => resize?.observe(s.el));
14767
+ };
14768
+ observeSizes();
14769
+ let presentKey = findPresent().map((s) => s.item.value).join("\0");
14770
+ const mutation = typeof MutationObserver === "undefined" ? void 0 : new MutationObserver(() => {
14771
+ const key = findPresent().map((s) => s.item.value).join("\0");
14772
+ if (key === presentKey) return;
14773
+ presentKey = key;
14774
+ observeSizes();
14775
+ applyInitial();
14776
+ refresh();
14777
+ });
14778
+ mutation?.observe(root ? document.body : container, { childList: true, subtree: true });
14779
+ const current = activeRef.current;
14780
+ if (current === void 0 || !items.some((i) => i.value === current)) update(false);
14781
+ applyInitial();
14782
+ detach = () => {
14783
+ scroller.removeEventListener("scroll", onScroll);
14784
+ resize?.disconnect();
14785
+ mutation?.disconnect();
14786
+ };
14787
+ return true;
14788
+ };
14789
+ if (!attach() && typeof MutationObserver !== "undefined") {
14790
+ waiter = new MutationObserver(() => {
14791
+ if (attach()) waiter?.disconnect();
14792
+ });
14793
+ waiter.observe(scope ?? document.body, { childList: true, subtree: true });
14794
+ }
14795
+ return () => {
14796
+ waiter?.disconnect();
14797
+ detach?.();
14798
+ };
14799
+ }, [valuesKey, scrollContainer, select, holdLock]);
14800
+ const go = (next) => {
14801
+ const scope = scrollContainer?.current ?? null;
14802
+ const section = findSection(next, scope);
14803
+ if (!section) {
14804
+ throw new Error(`SSectionNav: id="${next}" \uC778 \uC139\uC158 \uC694\uC18C\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4`);
14805
+ }
14806
+ holdLock();
14807
+ select(next);
14808
+ scrollToSection(section, scope ?? findScrollParent(section));
14809
+ };
14810
+ return /* @__PURE__ */ jsxRuntime.jsx("nav", { ref, "aria-label": ariaLabel, className, style, children: /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "m-0 flex list-none flex-col gap-(--cmp-sideNav-gap) p-0", children: items.map((item4) => {
14811
+ const selected = item4.value === active;
14812
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
14813
+ "button",
14814
+ {
14815
+ type: "button",
14816
+ "aria-current": selected ? "location" : void 0,
14817
+ onClick: () => go(item4.value),
14818
+ className: cn(
14819
+ "box-border flex h-(--cmp-sideNav-height) w-full cursor-pointer items-center rounded-(--cmp-sideNav-radius) border-0 px-(--cmp-sideNav-paddingX) text-left transition-colors duration-200",
14820
+ selected ? "typo-body-md-semibold bg-(--cmp-sideNav-bg-selected) text-(--cmp-sideNav-fg-selected)" : "typo-body-md-default bg-transparent text-(--cmp-sideNav-fg-default) hover:font-semibold hover:text-(--cmp-sideNav-fg-hover)"
14821
+ ),
14822
+ children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "grid min-w-0 flex-1", children: [
14823
+ /* @__PURE__ */ jsxRuntime.jsx(
14824
+ "span",
14825
+ {
14826
+ "aria-hidden": true,
14827
+ className: "invisible truncate font-semibold",
14828
+ style: { gridArea: "1 / 1" },
14829
+ children: item4.label
14830
+ }
14831
+ ),
14832
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", style: { gridArea: "1 / 1" }, children: item4.label })
14833
+ ] })
14834
+ }
14835
+ ) }, item4.value);
14836
+ }) }) });
14837
+ });
14838
+
14384
14839
  // src/components/SStepper/stepper.config.ts
14385
14840
  var STEPPER_SIZES = ["sm", "lg"];
14386
14841
  var V2 = (path) => `var(--cmp-stepper-${path})`;
@@ -16218,8 +16673,8 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
16218
16673
  valueDisplay = "text",
16219
16674
  showSearch = false,
16220
16675
  showSelectAll = false,
16221
- allSelectedLabel = "\uC804\uCCB4",
16222
- placeholder = "\uC120\uD0DD",
16676
+ allSelectedLabel: allSelectedLabelProp,
16677
+ placeholder: placeholderProp,
16223
16678
  disabled = false,
16224
16679
  clearable = false,
16225
16680
  error = false,
@@ -16260,6 +16715,9 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
16260
16715
  const partialOptions = pagingEnabled || serverSearch;
16261
16716
  const portalContainer = usePortalContainer();
16262
16717
  const insideModal = useInsideModal();
16718
+ const t = useLocale().messages.select;
16719
+ const allSelectedLabel = allSelectedLabelProp ?? t.allSelected;
16720
+ const placeholder = placeholderProp ?? t.placeholder;
16263
16721
  const [search, setSearch] = react.useState("");
16264
16722
  const [ruleError, setRuleError] = react.useState("");
16265
16723
  const [open, setOpen] = react.useState(false);
@@ -17001,7 +17459,9 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
17001
17459
  chip.removable && /* @__PURE__ */ jsxRuntime.jsx(
17002
17460
  TriggerRemoveButton,
17003
17461
  {
17004
- ariaLabel: `${isHtmlLabel(chip.label) ? extractText(chip.label) : chip.label} \uC120\uD0DD \uD574\uC81C`,
17462
+ ariaLabel: t.deselect(
17463
+ isHtmlLabel(chip.label) ? extractText(chip.label) : chip.label
17464
+ ),
17005
17465
  onRemove: () => handleRemoveChip(chip.values)
17006
17466
  }
17007
17467
  )
@@ -17013,7 +17473,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
17013
17473
  )
17014
17474
  ) : /* @__PURE__ */ jsxRuntime.jsx(TriggerLabel, { label: triggerText }),
17015
17475
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex shrink-0 items-center gap-[8px]", children: [
17016
- showClear && /* @__PURE__ */ jsxRuntime.jsx(TriggerRemoveButton, { ariaLabel: "\uC120\uD0DD \uC9C0\uC6B0\uAE30", onRemove: handleClear }),
17476
+ showClear && /* @__PURE__ */ jsxRuntime.jsx(TriggerRemoveButton, { ariaLabel: t.clear, onRemove: handleClear }),
17017
17477
  showTriggerSpinner ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "inline-flex shrink-0", "data-select-trigger-spinner": true, children: /* @__PURE__ */ jsxRuntime.jsx(SCircleProgress, { indeterminate: true, type: "neutral", size: 12 }) }) : /* @__PURE__ */ jsxRuntime.jsx(
17018
17478
  SIcon,
17019
17479
  {
@@ -17093,7 +17553,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
17093
17553
  value: search,
17094
17554
  onValueChange: handleSearchChange,
17095
17555
  onKeyDown: handleKeyDown,
17096
- placeholder: "\uAC80\uC0C9",
17556
+ placeholder: t.searchPlaceholder,
17097
17557
  clearable: true,
17098
17558
  "aria-controls": listboxId,
17099
17559
  "aria-activedescendant": focusedIndex >= 0 ? activeOptionId : void 0
@@ -17137,7 +17597,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
17137
17597
  }
17138
17598
  ) : loading2 ? null : (
17139
17599
  // 첫 페이지를 받는 중에는 "없다"고 단정하지 않는다 — 아래 로딩 표시가 대신 나온다.
17140
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-[12px] py-[12px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listBoxItem-content-disabled)]", children: "\uD45C\uC2DC\uD560 \uD56D\uBAA9\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
17600
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-[12px] py-[12px] text-left text-[12px] leading-[20px] text-[color:var(--cmp-listBoxItem-content-disabled)]", children: t.empty })
17141
17601
  ),
17142
17602
  loading2 && /* @__PURE__ */ jsxRuntime.jsxs(
17143
17603
  "div",
@@ -17146,7 +17606,7 @@ var SSelectImpl = /* @__PURE__ */ react.forwardRef(function SSelect({
17146
17606
  className: "flex items-center justify-center gap-[8px] px-[12px] py-[12px] text-[12px] leading-[20px] text-[color:var(--cmp-listBoxItem-content-disabled)]",
17147
17607
  children: [
17148
17608
  /* @__PURE__ */ jsxRuntime.jsx(SCircleProgress, { indeterminate: true, type: "neutral", size: 16 }),
17149
- "\uBD88\uB7EC\uC624\uB294 \uC911..."
17609
+ t.loading
17150
17610
  ]
17151
17611
  }
17152
17612
  )
@@ -30380,6 +30840,7 @@ exports.SKELETON_TYPES = SKELETON_TYPES;
30380
30840
  exports.SKELETON_TYPE_CONFIG = SKELETON_TYPE_CONFIG;
30381
30841
  exports.SKeyValueTable = SKeyValueTable;
30382
30842
  exports.SLAYOUT_MIN_WIDTH = SLAYOUT_MIN_WIDTH;
30843
+ exports.SLOCALES = SLOCALES;
30383
30844
  exports.SLauncherListBox = SLauncherListBox;
30384
30845
  exports.SLayout = SLayout;
30385
30846
  exports.SLayoutContext = SLayoutContext;
@@ -30389,6 +30850,7 @@ exports.SList = SList2;
30389
30850
  exports.SListItem = SListItem2;
30390
30851
  exports.SLoadingContainer = SLoadingContainer;
30391
30852
  exports.SLoadingModal = SLoadingModal;
30853
+ exports.SLocaleProvider = SLocaleProvider;
30392
30854
  exports.SLoginCard = SLoginCard2;
30393
30855
  exports.SLogo = SLogo;
30394
30856
  exports.SMeatballButton = SMeatballButton2;
@@ -30411,6 +30873,7 @@ exports.SSPLITTER_UNITS = SSPLITTER_UNITS;
30411
30873
  exports.SScrollArea = SScrollArea;
30412
30874
  exports.SSearchInput = SSearchInput;
30413
30875
  exports.SSectionHeaderCard = SSectionHeaderCard2;
30876
+ exports.SSectionNav = SSectionNav;
30414
30877
  exports.SSelect = SSelect2;
30415
30878
  exports.SSkeleton = SSkeleton;
30416
30879
  exports.SSlider = SSlider;
@@ -30457,7 +30920,9 @@ exports.isColorKey = isColorKey;
30457
30920
  exports.isFieldWidthGrade = isFieldWidthGrade;
30458
30921
  exports.loading = loading;
30459
30922
  exports.normalizeExtension = normalizeExtension;
30923
+ exports.normalizeLocale = normalizeLocale;
30460
30924
  exports.resolveColor = resolveColor;
30925
+ exports.useLocale = useLocale;
30461
30926
  exports.useSLayout = useSLayout;
30462
30927
  //# sourceMappingURL=index.cjs.map
30463
30928
  //# sourceMappingURL=index.cjs.map