sellmate-design-system-react 9.0.0-beta.91 → 9.0.0-beta.92

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
@@ -13394,9 +13394,20 @@ var SystemButton = /* @__PURE__ */ forwardRef(function SystemButton2({
13394
13394
  }
13395
13395
  ),
13396
13396
  label && /* @__PURE__ */ jsx("span", { className: "typo-body-sm-semibold min-w-0 truncate", children: label }),
13397
- count != null && count > 0 && // 개수 오른쪽은 버튼 여백(alertButton.paddingX)만으로는 좁다 — 시안이 4px 을 더 준다.
13397
+ count != null && count > 0 && // 개수는 채운 배지로 단다 — 배경·글자색·치수는 xs deep 태그 토큰이 쥔다(시안).
13398
+ // 배지 색은 고정이다: 흰 면은 파랑, 어두운 면(dark·darker)은 노랑 바탕에 검은 글자.
13399
+ // 배지 오른쪽은 버튼 여백(alertButton.paddingX)만으로는 좁다 — 시안이 4px 을 더 준다.
13398
13400
  // 대응 토큰이 없어 디자인 값을 그대로 쓴다.
13399
- /* @__PURE__ */ jsx("span", { className: "typo-body-md-bold me-[4px] shrink-0", style: { color: accentColor }, children: count.toLocaleString() })
13401
+ /* @__PURE__ */ jsx(
13402
+ STag,
13403
+ {
13404
+ size: "xs",
13405
+ deep: true,
13406
+ color: color === "light" ? "blue" : "yellow",
13407
+ label: count.toLocaleString(),
13408
+ className: "me-[4px] shrink-0"
13409
+ }
13410
+ )
13400
13411
  ]
13401
13412
  }
13402
13413
  );
@@ -14275,7 +14286,7 @@ var SStepper = /* @__PURE__ */ forwardRef(function SStepper2({
14275
14286
  items.findIndex((item4, index) => getItemValue(item4, index) === value)
14276
14287
  );
14277
14288
  const lastCompletedIndex = items.reduce(
14278
- (lastIndex, item4, index) => item4.completed === true ? index : lastIndex,
14289
+ (lastIndex2, item4, index) => item4.completed === true ? index : lastIndex2,
14279
14290
  -1
14280
14291
  );
14281
14292
  const resolvedClickableIndex = lastCompletedIndex + 1;
@@ -19374,13 +19385,21 @@ function SCalendar({
19374
19385
  }
19375
19386
  );
19376
19387
  }
19388
+
19389
+ // src/lib/mode-listbox.ts
19390
+ var MODE_LIST_HEIGHT = 240;
19391
+ var MODE_ITEM_HEIGHT = 28;
19392
+ var modeListItemClass = (selected, disabled) => cn(
19393
+ "flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
19394
+ disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
19395
+ !disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
19396
+ !disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
19397
+ );
19377
19398
  var MODE_NOUN = {
19378
19399
  date: "\uB0A0\uC9DC",
19379
19400
  month: "\uC6D4",
19380
19401
  year: "\uC5F0\uB3C4"
19381
19402
  };
19382
- var DATEPICKER_MODE_LIST_HEIGHT = 240;
19383
- var DATEPICKER_MODE_ITEM_HEIGHT = 28;
19384
19403
  var YEAR_LIST_EDGE_BUFFER = 24;
19385
19404
  var YEAR_LIST_EXPAND_SIZE = 80;
19386
19405
  var MONTHS = Array.from({ length: 12 }, (_, i) => i + 1);
@@ -19431,7 +19450,7 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
19431
19450
  if (!list) return;
19432
19451
  const index = anchorYearRef.current - rangeStartRef.current;
19433
19452
  syncingScrollRef.current = true;
19434
- list.scrollTop = index * DATEPICKER_MODE_ITEM_HEIGHT;
19453
+ list.scrollTop = index * MODE_ITEM_HEIGHT;
19435
19454
  requestAnimationFrame(() => {
19436
19455
  syncingScrollRef.current = false;
19437
19456
  });
@@ -19440,7 +19459,7 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
19440
19459
  const list = listRef.current;
19441
19460
  if (!list) return;
19442
19461
  if (syncingScrollRef.current) return;
19443
- if (list.scrollTop < DATEPICKER_MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
19462
+ if (list.scrollTop < MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
19444
19463
  const previousHeight = list.scrollHeight;
19445
19464
  setRange((current) => ({
19446
19465
  start: current.start - YEAR_LIST_EXPAND_SIZE,
@@ -19456,7 +19475,7 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
19456
19475
  });
19457
19476
  return;
19458
19477
  }
19459
- if (list.scrollHeight - list.scrollTop - list.clientHeight < DATEPICKER_MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
19478
+ if (list.scrollHeight - list.scrollTop - list.clientHeight < MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
19460
19479
  setRange((current) => ({
19461
19480
  start: current.start,
19462
19481
  end: current.end + YEAR_LIST_EXPAND_SIZE
@@ -19471,8 +19490,8 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
19471
19490
  "aria-label": "year",
19472
19491
  className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
19473
19492
  style: {
19474
- paddingTop: `calc((${DATEPICKER_MODE_LIST_HEIGHT}px - ${DATEPICKER_MODE_ITEM_HEIGHT}px) / 2)`,
19475
- paddingBottom: `calc((${DATEPICKER_MODE_LIST_HEIGHT}px - ${DATEPICKER_MODE_ITEM_HEIGHT}px) / 2)`
19493
+ paddingTop: `calc((${MODE_LIST_HEIGHT}px - ${MODE_ITEM_HEIGHT}px) / 2)`,
19494
+ paddingBottom: `calc((${MODE_LIST_HEIGHT}px - ${MODE_ITEM_HEIGHT}px) / 2)`
19476
19495
  },
19477
19496
  onScroll: handleScroll,
19478
19497
  children: years.map((year) => {
@@ -19485,12 +19504,7 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
19485
19504
  role: "option",
19486
19505
  "aria-selected": selected,
19487
19506
  disabled,
19488
- className: cn(
19489
- "flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
19490
- disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
19491
- !disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
19492
- !disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
19493
- ),
19507
+ className: modeListItemClass(selected, disabled),
19494
19508
  onClick: () => !disabled && onCommit(year),
19495
19509
  children: year
19496
19510
  },
@@ -19546,7 +19560,7 @@ function MonthWheel({ year, value, selectable, onCommit }) {
19546
19560
  if (!list) return;
19547
19561
  list.scrollTop = Math.max(
19548
19562
  0,
19549
- (initialValueRef.current - 1) * DATEPICKER_MODE_ITEM_HEIGHT - (list.clientHeight - DATEPICKER_MODE_ITEM_HEIGHT) / 2
19563
+ (initialValueRef.current - 1) * MODE_ITEM_HEIGHT - (list.clientHeight - MODE_ITEM_HEIGHT) / 2
19550
19564
  );
19551
19565
  }, []);
19552
19566
  return /* @__PURE__ */ jsx("div", { className: "h-full w-full overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsx(
@@ -19566,12 +19580,7 @@ function MonthWheel({ year, value, selectable, onCommit }) {
19566
19580
  role: "option",
19567
19581
  "aria-selected": selected,
19568
19582
  disabled,
19569
- className: cn(
19570
- "flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
19571
- disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
19572
- !disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
19573
- !disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
19574
- ),
19583
+ className: modeListItemClass(selected, disabled),
19575
19584
  onClick: () => !disabled && onCommit(month),
19576
19585
  children: [
19577
19586
  month,
@@ -28658,212 +28667,171 @@ function formatTimeDisplay(value, type) {
28658
28667
  const displayHour = hour % 12 === 0 ? 12 : hour % 12;
28659
28668
  return `${meridiem} ${padTimePart(displayHour)}:${padTimePart(minute)}`;
28660
28669
  }
28661
- var TIME_SELECTOR_WIDTH = 136;
28662
- var MERIDIEM_TIME_SELECTOR_WIDTH = 154;
28663
- function TimeSelector({
28670
+ function scrollSelectedIntoView(list, index, center) {
28671
+ const top = index * MODE_ITEM_HEIGHT;
28672
+ if (center) {
28673
+ list.scrollTop = Math.max(0, top - (list.clientHeight - MODE_ITEM_HEIGHT) / 2);
28674
+ return;
28675
+ }
28676
+ if (top < list.scrollTop) list.scrollTop = top;
28677
+ else if (top + MODE_ITEM_HEIGHT > list.scrollTop + list.clientHeight)
28678
+ list.scrollTop = top + MODE_ITEM_HEIGHT - list.clientHeight;
28679
+ }
28680
+ function lastIndex(arr, pred) {
28681
+ for (let i = arr.length - 1; i >= 0; i--) if (pred(arr[i], i)) return i;
28682
+ return -1;
28683
+ }
28684
+ function ListColumn({ ariaLabel, items }) {
28685
+ const listRef = useRef(null);
28686
+ const mountedRef = useRef(false);
28687
+ const selectedIndex = items.findIndex((item4) => item4.selected);
28688
+ const tabIndexTarget = selectedIndex >= 0 ? selectedIndex : items.findIndex((item4) => !item4.disabled);
28689
+ useLayoutEffect(() => {
28690
+ const list = listRef.current;
28691
+ if (!list || selectedIndex < 0) return;
28692
+ scrollSelectedIntoView(list, selectedIndex, !mountedRef.current);
28693
+ mountedRef.current = true;
28694
+ }, [selectedIndex]);
28695
+ const onKeyDown = (event) => {
28696
+ const buttons = [...listRef.current?.querySelectorAll("button") ?? []];
28697
+ const current = buttons.indexOf(document.activeElement);
28698
+ if (current < 0) return;
28699
+ const enabled = (item4, i) => !item4.disabled && i !== current;
28700
+ let next;
28701
+ if (event.key === "ArrowDown") next = items.findIndex((it, i) => i > current && enabled(it, i));
28702
+ else if (event.key === "ArrowUp")
28703
+ next = lastIndex(items, (it, i) => i < current && enabled(it, i));
28704
+ else if (event.key === "Home") next = items.findIndex((it) => !it.disabled);
28705
+ else if (event.key === "End") next = lastIndex(items, (it) => !it.disabled);
28706
+ else return;
28707
+ event.preventDefault();
28708
+ if (next < 0) return;
28709
+ buttons[next]?.focus({ preventScroll: true });
28710
+ if (listRef.current) scrollSelectedIntoView(listRef.current, next, false);
28711
+ };
28712
+ return /* @__PURE__ */ jsx("div", { className: "h-full w-[var(--cmp-datepicker-mode-yearMonth-column-width)] min-w-0 overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsx(
28713
+ "div",
28714
+ {
28715
+ ref: listRef,
28716
+ role: "listbox",
28717
+ "aria-label": ariaLabel,
28718
+ tabIndex: -1,
28719
+ className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
28720
+ onKeyDown,
28721
+ children: items.map((item4, index) => /* @__PURE__ */ jsx(
28722
+ "button",
28723
+ {
28724
+ type: "button",
28725
+ role: "option",
28726
+ "aria-selected": item4.selected,
28727
+ disabled: item4.disabled,
28728
+ tabIndex: index === tabIndexTarget ? 0 : -1,
28729
+ className: cn(modeListItemClass(item4.selected, item4.disabled), "outline-offset-[-2px]"),
28730
+ onClick: () => !item4.disabled && item4.onSelect(),
28731
+ children: item4.label
28732
+ },
28733
+ item4.key
28734
+ ))
28735
+ }
28736
+ ) });
28737
+ }
28738
+ var HOURS_24 = Array.from({ length: 24 }, (_, i) => i);
28739
+ var HOURS_12 = [12, ...Array.from({ length: 11 }, (_, i) => i + 1)];
28740
+ function TimeListbox({
28664
28741
  value,
28665
28742
  showMeridiem,
28666
28743
  minuteStep,
28667
- onChange
28744
+ isDisabled,
28745
+ onChange,
28746
+ labelPrefix,
28747
+ elevated = true
28668
28748
  }) {
28669
- const parsed = useMemo(() => parseTimeValue(value), [value]);
28670
- const [hour, setHour] = useState(parsed.hour);
28671
- const [minute, setMinute] = useState(parsed.minute);
28672
- const [editingPart, setEditingPart] = useState(null);
28673
- const [inputDraft, setInputDraft] = useState({});
28749
+ const toState = (v) => v ? parseTimeValue(v) : null;
28750
+ const [time, setTime] = useState(() => toState(value));
28674
28751
  useEffect(() => {
28675
- setHour(parsed.hour);
28676
- setMinute(parsed.minute);
28677
- }, [parsed.hour, parsed.minute]);
28678
- const meridiem = hour >= 12 ? "PM" : "AM";
28679
- const displayHour = showMeridiem ? hour % 12 === 0 ? 12 : hour % 12 : hour;
28680
- const commit = (nextHour, nextMinute) => {
28681
- const h = clampTimePart(nextHour, 0, 23);
28682
- const m = clampTimePart(nextMinute, 0, 59);
28683
- setHour(h);
28684
- setMinute(m);
28685
- onChange(formatTimeValue(h, m));
28686
- };
28687
- const setMeridiem = (next) => {
28688
- if (next === meridiem) return;
28689
- commit(next === "AM" ? hour - 12 : hour + 12, minute);
28690
- };
28691
- const changeMinute = (delta) => {
28692
- const next = minute + delta;
28693
- if (next > 59) {
28694
- commit((hour + 1) % 24, next - 60);
28695
- return;
28696
- }
28697
- if (next < 0) {
28698
- commit((hour + 23) % 24, next + 60);
28699
- return;
28700
- }
28701
- commit(hour, next);
28702
- };
28703
- const changeHour = (delta) => commit((hour + delta + 24) % 24, minute);
28704
- const sanitizeInput = (next) => next.replace(/\D/g, "").slice(0, 2);
28705
- const beginEdit = (part, currentValue) => {
28706
- setEditingPart(part);
28707
- setInputDraft((prev) => ({ ...prev, [part]: String(currentValue).padStart(2, "0") }));
28708
- };
28709
- const commitHourDraft = (draft) => {
28710
- const clamped = clampTimePart(Number(draft), showMeridiem ? 1 : 0, showMeridiem ? 12 : 23);
28711
- const nextHour = showMeridiem ? meridiem === "PM" ? clamped === 12 ? 12 : clamped + 12 : clamped === 12 ? 0 : clamped : clamped;
28712
- commit(nextHour, minute);
28713
- return clamped;
28714
- };
28715
- const commitMinuteDraft = (draft) => {
28716
- const clamped = clampTimePart(Number(draft), 0, 59);
28717
- commit(hour, clamped);
28718
- return clamped;
28719
- };
28720
- const endEdit = (part) => {
28721
- const draft = inputDraft[part];
28722
- if (part === "hour" && draft != null && draft !== "") {
28723
- commitHourDraft(draft);
28724
- }
28725
- if (part === "minute" && draft != null && draft !== "") {
28726
- commitMinuteDraft(draft);
28727
- }
28728
- setEditingPart(null);
28729
- setInputDraft({});
28730
- };
28731
- const inputStyle = {
28732
- width: "var(--cmp-timepicker-selector-time-input-width)",
28733
- height: "var(--cmp-textinput-sm-height)",
28734
- borderRadius: "var(--cmp-textinput-sm-radius)",
28735
- border: "var(--cmp-textinput-borderWidth) solid var(--cmp-textinput-border-default)",
28736
- color: "var(--cmp-textinput-text-default)",
28737
- fontSize: 12,
28738
- lineHeight: "20px"
28752
+ setTime(toState(value));
28753
+ }, [value]);
28754
+ const step = Math.max(1, Math.floor(minuteStep));
28755
+ const minutes = Array.from({ length: Math.ceil(60 / step) }, (_, i) => i * step);
28756
+ const blocked = (h, m) => isDisabled?.(h * 60 + m) ?? false;
28757
+ const hourBlocked = (h) => minutes.every((m) => blocked(h, m));
28758
+ const commit = (hours, preferred, part) => {
28759
+ let next = blocked(preferred.hour, preferred.minute) ? null : preferred;
28760
+ for (const h of hours) {
28761
+ if (next) break;
28762
+ const m = minutes.find((mm) => !blocked(h, mm));
28763
+ if (m !== void 0) next = { hour: h, minute: m };
28764
+ }
28765
+ if (!next) return;
28766
+ setTime(next);
28767
+ onChange(formatTimeValue(next.hour, next.minute), part);
28739
28768
  };
28740
- const renderColumn = (type) => {
28741
- const isHour = type === "hour";
28742
- const val = isHour ? displayHour : minute;
28743
- const displayValue2 = editingPart === type ? inputDraft[type] ?? "" : String(val).padStart(2, "0");
28744
- return /* @__PURE__ */ jsxs(
28745
- "div",
28746
- {
28747
- className: "flex flex-col items-center justify-center",
28748
- style: { gap: "var(--cmp-timepicker-selector-time-column-gap)" },
28749
- children: [
28750
- /* @__PURE__ */ jsx(
28751
- SGhostButton,
28752
- {
28753
- icon: "chevronUp",
28754
- size: "xs",
28755
- ariaLabel: `${isHour ? "\uC2DC" : "\uBD84"} \uC99D\uAC00`,
28756
- onClick: () => isHour ? changeHour(1) : changeMinute(minuteStep)
28757
- }
28758
- ),
28759
- /* @__PURE__ */ jsx(
28760
- "input",
28761
- {
28762
- "aria-label": isHour ? "\uC2DC" : "\uBD84",
28763
- inputMode: "numeric",
28764
- value: displayValue2,
28765
- placeholder: "-",
28766
- onFocus: () => beginEdit(type, val),
28767
- onBlur: () => endEdit(type),
28768
- onChange: (event) => {
28769
- const draft = sanitizeInput(event.target.value);
28770
- setInputDraft((prev) => ({ ...prev, [type]: draft }));
28771
- if (draft === "") return;
28772
- const raw = Number(draft);
28773
- if (isHour) {
28774
- if (draft.length < 2) return;
28775
- const clamped2 = commitHourDraft(draft);
28776
- const displayDraft2 = raw === clamped2 ? draft : String(clamped2);
28777
- setInputDraft((prev) => ({ ...prev, [type]: displayDraft2 }));
28778
- return;
28779
- }
28780
- if (draft.length < 2) return;
28781
- const clamped = commitMinuteDraft(draft);
28782
- const displayDraft = raw === clamped ? draft : String(clamped);
28783
- setInputDraft((prev) => ({ ...prev, [type]: displayDraft }));
28784
- },
28785
- className: "box-border bg-white text-center outline-none",
28786
- style: inputStyle
28787
- }
28788
- ),
28789
- /* @__PURE__ */ jsx(
28790
- SGhostButton,
28791
- {
28792
- icon: "chevronDown",
28793
- size: "xs",
28794
- ariaLabel: `${isHour ? "\uC2DC" : "\uBD84"} \uAC10\uC18C`,
28795
- onClick: () => isHour ? changeHour(-1) : changeMinute(-minuteStep)
28796
- }
28797
- )
28798
- ]
28769
+ const hour = time?.hour;
28770
+ const minute = time?.minute ?? 0;
28771
+ const meridiem = hour === void 0 ? void 0 : hour >= 12 ? "PM" : "AM";
28772
+ const prefix = labelPrefix ? `${labelPrefix} ` : "";
28773
+ const meridiemItems = ["AM", "PM"].map((item4) => {
28774
+ const base = item4 === "AM" ? 0 : 12;
28775
+ const range = HOURS_24.slice(base, base + 12);
28776
+ return {
28777
+ key: item4,
28778
+ label: item4 === "AM" ? "\uC624\uC804" : "\uC624\uD6C4",
28779
+ selected: meridiem === item4,
28780
+ disabled: range.every(hourBlocked),
28781
+ onSelect: () => {
28782
+ if (meridiem === item4) return;
28783
+ const h = hour === void 0 ? base : hour % 12 + base;
28784
+ commit(range, { hour: h, minute }, "meridiem");
28799
28785
  }
28800
- );
28801
- };
28786
+ };
28787
+ });
28788
+ const hourItems = showMeridiem ? HOURS_12.map((display) => {
28789
+ const h = display % 12 + (meridiem === "PM" ? 12 : 0);
28790
+ return {
28791
+ key: String(display),
28792
+ label: `${padTimePart(display)}\uC2DC`,
28793
+ selected: hour === h,
28794
+ disabled: hourBlocked(h),
28795
+ onSelect: () => commit([h], { hour: h, minute }, "hour")
28796
+ };
28797
+ }) : HOURS_24.map((h) => ({
28798
+ key: String(h),
28799
+ label: `${padTimePart(h)}\uC2DC`,
28800
+ selected: hour === h,
28801
+ disabled: hourBlocked(h),
28802
+ onSelect: () => commit([h], { hour: h, minute }, "hour")
28803
+ }));
28804
+ const minuteHour = hour ?? (meridiem === "PM" ? 12 : 0);
28805
+ const minuteItems = minutes.map((m) => ({
28806
+ key: String(m),
28807
+ label: `${padTimePart(m)}\uBD84`,
28808
+ selected: time != null && minute === m,
28809
+ disabled: blocked(minuteHour, m),
28810
+ onSelect: () => commit([minuteHour], { hour: minuteHour, minute: m }, "minute")
28811
+ }));
28812
+ const divider = /* @__PURE__ */ jsx(
28813
+ "div",
28814
+ {
28815
+ "aria-hidden": true,
28816
+ className: "w-[var(--border-width-1)] self-stretch bg-[var(--sys-color-divider-default)]"
28817
+ }
28818
+ );
28802
28819
  return /* @__PURE__ */ jsxs(
28803
28820
  "div",
28804
28821
  {
28805
- className: "flex flex-col items-stretch justify-center overflow-hidden",
28806
- style: {
28807
- background: "var(--cmp-timepicker-selector-bg)",
28808
- borderRadius: "var(--cmp-timepicker-selector-radius)",
28809
- boxShadow: "4px 8px 16px -4px rgba(34,34,34,0.1), 0 0 24px -6px rgba(34,34,34,0.12)"
28810
- },
28822
+ className: cn(
28823
+ "inline-flex gap-[var(--cmp-datepicker-mode-yearMonth-gap)] overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]",
28824
+ elevated && "rounded-[var(--cmp-datepicker-mode-radius)] shadow-normal-sm"
28825
+ ),
28826
+ style: { height: MODE_LIST_HEIGHT },
28811
28827
  children: [
28812
- showMeridiem && /* @__PURE__ */ jsx(
28813
- "div",
28814
- {
28815
- className: "flex items-center justify-center",
28816
- style: {
28817
- padding: "var(--cmp-timepicker-selector-section-paddingY) var(--cmp-timepicker-selector-section-paddingX)"
28818
- },
28819
- children: /* @__PURE__ */ jsx("div", { className: "isolate flex", children: ["AM", "PM"].map((item4, index) => {
28820
- const selected = item4 === meridiem;
28821
- return /* @__PURE__ */ jsx(
28822
- "button",
28823
- {
28824
- type: "button",
28825
- onClick: () => setMeridiem(item4),
28826
- className: cn(
28827
- "box-border h-[var(--cmp-textinput-sm-height)] w-[61px] cursor-pointer border border-solid bg-white text-center text-[12px] font-medium leading-[20px]",
28828
- index === 0 ? "rounded-l-[var(--cmp-select-sm-radius)]" : "rounded-r-[var(--cmp-select-sm-radius)]",
28829
- index === 0 ? "-mr-px" : "",
28830
- selected ? "z-[1] border-[var(--cmp-timepicker-border-focus)] text-[var(--cmp-timepicker-border-focus)]" : "border-[var(--cmp-timepicker-border-default)] text-[var(--cmp-field-hint-color)]"
28831
- ),
28832
- children: item4 === "AM" ? "\uC624\uC804" : "\uC624\uD6C4"
28833
- },
28834
- item4
28835
- );
28836
- }) })
28837
- }
28838
- ),
28839
- showMeridiem && /* @__PURE__ */ jsx("div", { className: "h-px w-full bg-[var(--sys-color-divider-default)]" }),
28840
- /* @__PURE__ */ jsxs(
28841
- "div",
28842
- {
28843
- className: "flex items-center justify-center",
28844
- style: {
28845
- gap: "var(--cmp-timepicker-selector-time-gap)",
28846
- padding: "var(--cmp-timepicker-selector-section-paddingY) var(--cmp-timepicker-selector-section-paddingX)"
28847
- },
28848
- children: [
28849
- renderColumn("hour"),
28850
- /* @__PURE__ */ jsx(
28851
- "span",
28852
- {
28853
- className: "text-center",
28854
- style: {
28855
- width: "var(--cmp-timepicker-selector-colon-size)",
28856
- color: "var(--cmp-field-hint-color)",
28857
- fontSize: "var(--cmp-timepicker-selector-colon-size)",
28858
- lineHeight: "var(--cmp-timepicker-selector-colon-size)"
28859
- },
28860
- children: ":"
28861
- }
28862
- ),
28863
- renderColumn("minute")
28864
- ]
28865
- }
28866
- )
28828
+ showMeridiem && /* @__PURE__ */ jsxs(Fragment, { children: [
28829
+ /* @__PURE__ */ jsx(ListColumn, { ariaLabel: `${prefix}\uC624\uC804/\uC624\uD6C4`, items: meridiemItems }),
28830
+ divider
28831
+ ] }),
28832
+ /* @__PURE__ */ jsx(ListColumn, { ariaLabel: `${prefix}\uC2DC`, items: hourItems }),
28833
+ divider,
28834
+ /* @__PURE__ */ jsx(ListColumn, { ariaLabel: `${prefix}\uBD84`, items: minuteItems })
28867
28835
  ]
28868
28836
  }
28869
28837
  );
@@ -28901,11 +28869,9 @@ function STimePicker({
28901
28869
  style
28902
28870
  }) {
28903
28871
  const portalContainer = usePortalContainer();
28904
- const triggerRef = useRef(null);
28905
28872
  const [open, setOpen] = useState(false);
28906
28873
  const triggerId = useFieldControlId();
28907
28874
  const triggerTextId = `${triggerId}-text`;
28908
- const [popoverAlignOffset, setPopoverAlignOffset] = useState(0);
28909
28875
  const [focused, setFocused] = useState(false);
28910
28876
  const [ruleError, setRuleError] = useState("");
28911
28877
  const dims = TIMEPICKER_SIZE_CONFIG[size] ?? TIMEPICKER_SIZE_CONFIG.sm;
@@ -28926,11 +28892,6 @@ function STimePicker({
28926
28892
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
28927
28893
  const commitOpen = (next) => {
28928
28894
  if (disabled) return;
28929
- if (next) {
28930
- const triggerWidth = triggerRef.current?.getBoundingClientRect().width ?? 0;
28931
- const selectorWidth = effectiveShowMeridiem ? MERIDIEM_TIME_SELECTOR_WIDTH : TIME_SELECTOR_WIDTH;
28932
- setPopoverAlignOffset(Math.max(0, (triggerWidth - selectorWidth) / 2));
28933
- }
28934
28895
  setOpen(next);
28935
28896
  onOpenChange?.(next);
28936
28897
  if (!next && rules && rules.length > 0) setRuleError(runRules(value, rules));
@@ -28969,7 +28930,6 @@ function STimePicker({
28969
28930
  /* @__PURE__ */ jsx(RPopover13.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
28970
28931
  "div",
28971
28932
  {
28972
- ref: triggerRef,
28973
28933
  id: triggerId,
28974
28934
  role: "button",
28975
28935
  "aria-disabled": disabled,
@@ -29033,20 +28993,22 @@ function STimePicker({
29033
28993
  RPopover13.Content,
29034
28994
  {
29035
28995
  side: "bottom",
29036
- align: "start",
28996
+ align: "center",
29037
28997
  sideOffset: 4,
29038
- alignOffset: popoverAlignOffset,
29039
28998
  className: cn(
29040
28999
  "z-50 max-w-[var(--radix-popover-content-available-width)]",
29041
29000
  FLOATING_SLIDE_ANIM
29042
29001
  ),
29043
29002
  children: /* @__PURE__ */ jsx(
29044
- TimeSelector,
29003
+ TimeListbox,
29045
29004
  {
29046
29005
  value: normalized,
29047
29006
  showMeridiem: effectiveShowMeridiem,
29048
29007
  minuteStep,
29049
- onChange: commitValue
29008
+ onChange: (time, part) => {
29009
+ commitValue(time);
29010
+ if (part === "minute") commitOpen(false);
29011
+ }
29050
29012
  }
29051
29013
  )
29052
29014
  }
@@ -29076,7 +29038,6 @@ function formatTimeRangeDisplay(value, type) {
29076
29038
  if (value == null || value[0] === "" || value[1] === "") return "";
29077
29039
  return `${formatTimeDisplay(value[0], type)} ~ ${formatTimeDisplay(value[1], type)}`;
29078
29040
  }
29079
- var TIME_RANGE_SELECTOR_WIDTH = 345;
29080
29041
  var MIDDAY_FIELD_MIN_WIDTH = {
29081
29042
  sm: 178,
29082
29043
  md: 216
@@ -29085,6 +29046,10 @@ var MIDDAY_FIELD_MAX_WIDTH = {
29085
29046
  sm: "lg",
29086
29047
  md: "lg"
29087
29048
  };
29049
+ var toMinutes = (time) => {
29050
+ const { hour, minute } = parseTimeValue(time);
29051
+ return hour * 60 + minute;
29052
+ };
29088
29053
  function RangeTimeSelector({
29089
29054
  value,
29090
29055
  showMeridiem,
@@ -29092,233 +29057,32 @@ function RangeTimeSelector({
29092
29057
  rangeOrder,
29093
29058
  onChange
29094
29059
  }) {
29095
- const range = useMemo(() => value ?? ["00:00", "23:59"], [value]);
29096
- const [editingKey, setEditingKey] = useState(null);
29097
- const [inputDraft, setInputDraft] = useState({});
29098
- const autoAdjustedRef = useRef(null);
29099
- const getParts = (side) => parseTimeValue(side === "start" ? range[0] : range[1]);
29100
- const getMeridiem = (side) => getParts(side).hour >= 12 ? "PM" : "AM";
29101
- const getDisplayHour = (side) => {
29102
- const { hour } = getParts(side);
29103
- if (!showMeridiem) return hour;
29104
- const displayHour = hour % 12;
29105
- return displayHour === 0 ? 12 : displayHour;
29106
- };
29107
- const commit = (side, hour, minute, direction = "unknown") => {
29108
- const next = formatTimeValue(hour, minute);
29109
- onChange(
29110
- normalizeNextRange(side === "start" ? [next, range[1]] : [range[0], next], side, direction)
29111
- );
29112
- };
29113
- const getTimeMinutes2 = (time) => {
29114
- const { hour, minute } = parseTimeValue(time);
29115
- return hour * 60 + minute;
29116
- };
29117
- const normalizeNextRange = (nextRange, changedSide, direction) => {
29118
- if (rangeOrder === "allow-cross-day") return nextRange;
29119
- const [start, end] = nextRange;
29120
- const [currentStart, currentEnd] = range;
29121
- if (getTimeMinutes2(start) <= getTimeMinutes2(end)) {
29122
- const shouldKeepSynced = autoAdjustedRef.current?.side === changedSide && currentStart === currentEnd && start !== end && (direction === "unknown" || autoAdjustedRef.current.direction === direction);
29123
- if (shouldKeepSynced) {
29124
- return changedSide === "start" ? [start, start] : [end, end];
29125
- }
29126
- autoAdjustedRef.current = null;
29127
- return nextRange;
29128
- }
29129
- autoAdjustedRef.current = {
29130
- side: changedSide,
29131
- direction: changedSide === "start" ? "increase" : "decrease"
29132
- };
29133
- return changedSide === "start" ? [start, start] : [end, end];
29134
- };
29135
- const changeHour = (side, delta) => {
29136
- const { hour, minute } = getParts(side);
29137
- commit(side, (hour + delta + 24) % 24, minute, delta > 0 ? "increase" : "decrease");
29138
- };
29139
- const changeMinute = (side, delta) => {
29140
- const { hour, minute } = getParts(side);
29141
- const next = minute + delta;
29142
- if (next > 59) {
29143
- commit(side, (hour + 1) % 24, next - 60, delta > 0 ? "increase" : "decrease");
29060
+ const range = value ?? ["00:00", "23:59"];
29061
+ const strict = rangeOrder !== "allow-cross-day";
29062
+ const change = (side, time) => {
29063
+ if (side === "end") {
29064
+ onChange([range[0], time]);
29144
29065
  return;
29145
29066
  }
29146
- if (next < 0) {
29147
- commit(side, (hour + 23) % 24, next + 60, delta > 0 ? "increase" : "decrease");
29148
- return;
29149
- }
29150
- commit(side, hour, next, delta > 0 ? "increase" : "decrease");
29151
- };
29152
- const setMeridiem = (side, next) => {
29153
- const current = getMeridiem(side);
29154
- if (current === next) return;
29155
- const { hour, minute } = getParts(side);
29156
- commit(side, next === "AM" ? hour - 12 : hour + 12, minute);
29157
- };
29158
- const getEditingKey = (side, part) => `${side}-${part}`;
29159
- const sanitizeInput = (next) => next.replace(/\D/g, "").slice(0, 2);
29160
- const beginEdit = (side, part, currentValue) => {
29161
- const key = getEditingKey(side, part);
29162
- setEditingKey(key);
29163
- setInputDraft((prev) => ({ ...prev, [key]: String(currentValue).padStart(2, "0") }));
29164
- };
29165
- const commitHourDraft = (side, draft) => {
29166
- const clamped = clampTimePart(Number(draft), showMeridiem ? 1 : 0, showMeridiem ? 12 : 23);
29167
- const parts = getParts(side);
29168
- const meridiem = getMeridiem(side);
29169
- const nextHour = showMeridiem ? meridiem === "PM" ? clamped === 12 ? 12 : clamped + 12 : clamped === 12 ? 0 : clamped : clamped;
29170
- commit(side, nextHour, parts.minute);
29171
- return clamped;
29172
- };
29173
- const commitMinuteDraft = (side, draft) => {
29174
- const { hour } = getParts(side);
29175
- const clamped = clampTimePart(Number(draft), 0, 59);
29176
- commit(side, hour, clamped);
29177
- return clamped;
29178
- };
29179
- const endEdit = (side, part) => {
29180
- const key = getEditingKey(side, part);
29181
- const draft = inputDraft[key];
29182
- if (part === "hour" && draft != null && draft !== "") {
29183
- commitHourDraft(side, draft);
29184
- }
29185
- if (part === "minute" && draft != null && draft !== "") {
29186
- commitMinuteDraft(side, draft);
29187
- }
29188
- setEditingKey(null);
29189
- setInputDraft({});
29190
- };
29191
- const inputStyle = {
29192
- width: "var(--cmp-timepicker-selector-time-input-width)",
29193
- height: "var(--cmp-textinput-sm-height)",
29194
- borderRadius: "var(--cmp-textinput-sm-radius)",
29195
- border: "var(--cmp-textinput-borderWidth) solid var(--cmp-textinput-border-default)",
29196
- color: "var(--cmp-textinput-text-default)",
29197
- fontSize: 12,
29198
- lineHeight: "20px"
29199
- };
29200
- const renderColumn = (side, kind) => {
29201
- const isHour = kind === "hour";
29202
- const { minute } = getParts(side);
29203
- const val = isHour ? getDisplayHour(side) : minute;
29204
- const currentEditingKey = getEditingKey(side, kind);
29205
- const displayValue2 = editingKey === currentEditingKey ? inputDraft[currentEditingKey] ?? "" : String(val).padStart(2, "0");
29206
- return /* @__PURE__ */ jsxs(
29207
- "div",
29208
- {
29209
- className: "flex flex-col items-center justify-center",
29210
- style: { gap: "var(--cmp-timepicker-selector-time-column-gap)" },
29211
- children: [
29212
- /* @__PURE__ */ jsx(
29213
- SGhostButton,
29214
- {
29215
- icon: "chevronUp",
29216
- size: "xs",
29217
- ariaLabel: `${side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC"} ${isHour ? "\uC2DC" : "\uBD84"} \uC99D\uAC00`,
29218
- onClick: () => isHour ? changeHour(side, 1) : changeMinute(side, minuteStep)
29219
- }
29220
- ),
29221
- /* @__PURE__ */ jsx(
29222
- "input",
29223
- {
29224
- "aria-label": `${side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC"} ${isHour ? "\uC2DC" : "\uBD84"}`,
29225
- inputMode: "numeric",
29226
- value: displayValue2,
29227
- placeholder: "-",
29228
- onFocus: () => beginEdit(side, kind, val),
29229
- onBlur: () => endEdit(side, kind),
29230
- onChange: (event) => {
29231
- const draft = sanitizeInput(event.target.value);
29232
- setInputDraft((prev) => ({ ...prev, [currentEditingKey]: draft }));
29233
- if (draft === "") return;
29234
- const raw = Number(draft);
29235
- if (isHour) {
29236
- if (draft.length < 2) return;
29237
- const clamped2 = commitHourDraft(side, draft);
29238
- const displayDraft2 = raw === clamped2 ? draft : String(clamped2);
29239
- setInputDraft((prev) => ({ ...prev, [currentEditingKey]: displayDraft2 }));
29240
- return;
29241
- }
29242
- if (draft.length < 2) return;
29243
- const clamped = commitMinuteDraft(side, draft);
29244
- const displayDraft = raw === clamped ? draft : String(clamped);
29245
- setInputDraft((prev) => ({ ...prev, [currentEditingKey]: displayDraft }));
29246
- },
29247
- className: "box-border bg-white text-center outline-none",
29248
- style: inputStyle
29249
- }
29250
- ),
29251
- /* @__PURE__ */ jsx(
29252
- SGhostButton,
29253
- {
29254
- icon: "chevronDown",
29255
- size: "xs",
29256
- ariaLabel: `${side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC"} ${isHour ? "\uC2DC" : "\uBD84"} \uAC10\uC18C`,
29257
- onClick: () => isHour ? changeHour(side, -1) : changeMinute(side, -minuteStep)
29258
- }
29259
- )
29260
- ]
29261
- }
29262
- );
29067
+ onChange([time, strict && toMinutes(time) > toMinutes(range[1]) ? time : range[1]]);
29263
29068
  };
29264
29069
  const renderPanel = (side, title) => /* @__PURE__ */ jsxs(
29265
29070
  "div",
29266
29071
  {
29267
- className: "flex flex-col items-center justify-center",
29268
- style: { gap: "var(--cmp-timepicker-selector-range-section-gap)" },
29072
+ className: "flex flex-col items-center",
29073
+ style: { gap: "var(--cmp-timepicker-selector-range-heading-gap)" },
29269
29074
  children: [
29270
- /* @__PURE__ */ jsxs(
29271
- "div",
29272
- {
29273
- className: "flex flex-col items-center",
29274
- style: { gap: "var(--cmp-timepicker-selector-range-heading-gap)" },
29275
- children: [
29276
- /* @__PURE__ */ jsx("div", { className: "whitespace-nowrap text-center text-[12px] font-bold leading-[20px] text-[var(--cmp-timepicker-selector-range-heading-color)]", children: title }),
29277
- showMeridiem && /* @__PURE__ */ jsx("div", { className: "isolate flex", children: ["AM", "PM"].map((item4, index) => {
29278
- const selected = item4 === getMeridiem(side);
29279
- return /* @__PURE__ */ jsx(
29280
- "button",
29281
- {
29282
- type: "button",
29283
- onClick: () => setMeridiem(side, item4),
29284
- className: cn(
29285
- "box-border h-[var(--cmp-textinput-sm-height)] w-[61px] cursor-pointer border border-solid bg-white text-center text-[12px] font-medium leading-[20px]",
29286
- index === 0 ? "rounded-l-[var(--cmp-select-sm-radius)] -mr-px" : "rounded-r-[var(--cmp-select-sm-radius)]",
29287
- selected ? "z-[1] border-[var(--cmp-timepicker-border-focus)] text-[var(--cmp-timepicker-border-focus)]" : "border-[var(--cmp-timepicker-border-default)] text-[var(--cmp-field-hint-color)]"
29288
- ),
29289
- children: item4 === "AM" ? "\uC624\uC804" : "\uC624\uD6C4"
29290
- },
29291
- item4
29292
- );
29293
- }) })
29294
- ]
29295
- }
29296
- ),
29297
- /* @__PURE__ */ jsxs(
29298
- "div",
29075
+ /* @__PURE__ */ jsx("div", { className: "whitespace-nowrap text-center text-[12px] font-bold leading-[20px] text-[var(--cmp-timepicker-selector-range-heading-color)]", children: title }),
29076
+ /* @__PURE__ */ jsx(
29077
+ TimeListbox,
29299
29078
  {
29300
- className: "flex items-center justify-center",
29301
- style: {
29302
- gap: "var(--cmp-timepicker-selector-time-gap)",
29303
- padding: "0 var(--cmp-timepicker-selector-section-paddingX)"
29304
- },
29305
- children: [
29306
- renderColumn(side, "hour"),
29307
- /* @__PURE__ */ jsx(
29308
- "span",
29309
- {
29310
- className: "text-center",
29311
- style: {
29312
- width: "var(--cmp-timepicker-selector-colon-size)",
29313
- color: "var(--cmp-field-hint-color)",
29314
- fontSize: "var(--cmp-timepicker-selector-colon-size)",
29315
- lineHeight: "var(--cmp-timepicker-selector-colon-size)"
29316
- },
29317
- children: ":"
29318
- }
29319
- ),
29320
- renderColumn(side, "minute")
29321
- ]
29079
+ value: side === "start" ? range[0] : range[1],
29080
+ showMeridiem,
29081
+ minuteStep,
29082
+ labelPrefix: side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC",
29083
+ elevated: false,
29084
+ isDisabled: strict && side === "end" ? (minutes) => minutes < toMinutes(range[0]) : void 0,
29085
+ onChange: (time) => change(side, time)
29322
29086
  }
29323
29087
  )
29324
29088
  ]
@@ -29377,11 +29141,9 @@ function STimeRangePicker({
29377
29141
  style
29378
29142
  }) {
29379
29143
  const portalContainer = usePortalContainer();
29380
- const triggerRef = useRef(null);
29381
29144
  const [open, setOpen] = useState(false);
29382
29145
  const triggerId = useFieldControlId();
29383
29146
  const triggerTextId = `${triggerId}-text`;
29384
- const [popoverAlignOffset, setPopoverAlignOffset] = useState(0);
29385
29147
  const [focused, setFocused] = useState(false);
29386
29148
  const [ruleError, setRuleError] = useState("");
29387
29149
  const dims = TIMEPICKER_SIZE_CONFIG[size] ?? TIMEPICKER_SIZE_CONFIG.sm;
@@ -29405,10 +29167,6 @@ function STimeRangePicker({
29405
29167
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
29406
29168
  const commitOpen = (next) => {
29407
29169
  if (disabled) return;
29408
- if (next) {
29409
- const triggerWidth = triggerRef.current?.getBoundingClientRect().width ?? 0;
29410
- setPopoverAlignOffset(Math.max(0, (triggerWidth - TIME_RANGE_SELECTOR_WIDTH) / 2));
29411
- }
29412
29170
  setOpen(next);
29413
29171
  onOpenChange?.(next);
29414
29172
  if (!next && rules && rules.length > 0) setRuleError(runRules(value, rules));
@@ -29447,7 +29205,6 @@ function STimeRangePicker({
29447
29205
  /* @__PURE__ */ jsx(RPopover13.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
29448
29206
  "div",
29449
29207
  {
29450
- ref: triggerRef,
29451
29208
  id: triggerId,
29452
29209
  role: "button",
29453
29210
  "aria-disabled": disabled,
@@ -29511,9 +29268,8 @@ function STimeRangePicker({
29511
29268
  RPopover13.Content,
29512
29269
  {
29513
29270
  side: "bottom",
29514
- align: "start",
29271
+ align: "center",
29515
29272
  sideOffset: 4,
29516
- alignOffset: popoverAlignOffset,
29517
29273
  className: cn(
29518
29274
  "z-50 max-w-[var(--radix-popover-content-available-width)]",
29519
29275
  FLOATING_SLIDE_ANIM