sellmate-design-system-react 9.0.0-beta.2 → 9.0.0-beta.4

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
@@ -5148,6 +5148,10 @@ var BODY_PADDING_CLASS = {
5148
5148
  wide: "p-(--sys-space-panel-padding-wide)",
5149
5149
  none: ""
5150
5150
  };
5151
+ var BODY_BACKGROUND_CLASS = {
5152
+ frame: "",
5153
+ neutral: "bg-(--sys-color-bg-neutralLight)"
5154
+ };
5151
5155
  var topBorderColorClass = {
5152
5156
  default: "bg-(--cmp-sectionHeaderCard-topBorder-color-default)",
5153
5157
  accent: "bg-(--cmp-sectionHeaderCard-topBorder-color-accent)"
@@ -5157,8 +5161,8 @@ var titleSizeClass = {
5157
5161
  sm: "typo-heading-sm"
5158
5162
  };
5159
5163
  var SSectionHeaderCardBodyImpl = /* @__PURE__ */ react.forwardRef(
5160
- function SSectionHeaderCardBody({ padding = "default", className, children, ...rest }, ref) {
5161
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn(BODY_PADDING_CLASS[padding], className), ...rest, children });
5164
+ function SSectionHeaderCardBody({ padding = "default", background = "frame", children }, ref) {
5165
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn(BODY_PADDING_CLASS[padding], BODY_BACKGROUND_CLASS[background]), children });
5162
5166
  }
5163
5167
  );
5164
5168
  var SSectionHeaderCardHeaderImpl = /* @__PURE__ */ react.forwardRef(
@@ -5171,8 +5175,7 @@ var SSectionHeaderCardHeaderImpl = /* @__PURE__ */ react.forwardRef(
5171
5175
  subtitle,
5172
5176
  slot,
5173
5177
  thickness = false,
5174
- className,
5175
- ...rest
5178
+ className
5176
5179
  }, ref) {
5177
5180
  const hasHelp = helpText != null && helpText.length > 0;
5178
5181
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -5193,7 +5196,6 @@ var SSectionHeaderCardHeaderImpl = /* @__PURE__ */ react.forwardRef(
5193
5196
  "box-border flex min-h-(--cmp-sectionHeaderCard-header-minHeight) w-full shrink-0 items-center gap-(--cmp-sectionHeaderCard-header-gap) overflow-hidden border-b border-(--cmp-sectionHeaderCard-header-border) bg-(--cmp-sectionHeaderCard-header-bg) px-(--cmp-sectionHeaderCard-header-paddingX) py-(--cmp-sectionHeaderCard-header-paddingY)",
5194
5197
  className
5195
5198
  ),
5196
- ...rest,
5197
5199
  children: [
5198
5200
  marker && /* @__PURE__ */ jsxRuntime.jsx(SBadge, { color: "darkblue", className: "shrink-0" }),
5199
5201
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 shrink-0 items-center gap-[8px]", children: [
@@ -5237,8 +5239,24 @@ var SSectionHeaderCardHeaderImpl = /* @__PURE__ */ react.forwardRef(
5237
5239
  }
5238
5240
  );
5239
5241
  var SSectionHeaderCardRootImpl = /* @__PURE__ */ react.forwardRef(
5240
- function SSectionHeaderCard({ className, children, ...rest }, ref) {
5241
- return /* @__PURE__ */ jsxRuntime.jsx(
5242
+ function SSectionHeaderCard({
5243
+ title,
5244
+ titleSize,
5245
+ marker,
5246
+ required,
5247
+ helpText,
5248
+ subtitle,
5249
+ slot,
5250
+ thickness,
5251
+ headerClassName,
5252
+ padding,
5253
+ background,
5254
+ className,
5255
+ children,
5256
+ ...rest
5257
+ }, ref) {
5258
+ const hasHeader = title != null;
5259
+ return /* @__PURE__ */ jsxRuntime.jsxs(
5242
5260
  "div",
5243
5261
  {
5244
5262
  ref,
@@ -5247,24 +5265,28 @@ var SSectionHeaderCardRootImpl = /* @__PURE__ */ react.forwardRef(
5247
5265
  className
5248
5266
  ),
5249
5267
  ...rest,
5250
- children
5268
+ children: [
5269
+ hasHeader && /* @__PURE__ */ jsxRuntime.jsx(
5270
+ SSectionHeaderCardHeaderImpl,
5271
+ {
5272
+ title,
5273
+ titleSize,
5274
+ marker,
5275
+ required,
5276
+ helpText,
5277
+ subtitle,
5278
+ slot,
5279
+ thickness,
5280
+ className: headerClassName
5281
+ }
5282
+ ),
5283
+ /* @__PURE__ */ jsxRuntime.jsx(SSectionHeaderCardBodyImpl, { padding, background, children })
5284
+ ]
5251
5285
  }
5252
5286
  );
5253
5287
  }
5254
5288
  );
5255
- var SSectionHeaderCardRoot = /* @__PURE__ */ withDisplayName(SSectionHeaderCardRootImpl, "SSectionHeaderCard");
5256
- var SSectionHeaderCardHeader2 = /* @__PURE__ */ withDisplayName(
5257
- SSectionHeaderCardHeaderImpl,
5258
- "SSectionHeaderCard.Header"
5259
- );
5260
- var SSectionHeaderCardBody2 = /* @__PURE__ */ withDisplayName(
5261
- SSectionHeaderCardBodyImpl,
5262
- "SSectionHeaderCard.Body"
5263
- );
5264
- var SSectionHeaderCard2 = /* @__PURE__ */ Object.assign(SSectionHeaderCardRoot, {
5265
- Header: SSectionHeaderCardHeader2,
5266
- Body: SSectionHeaderCardBody2
5267
- });
5289
+ var SSectionHeaderCard2 = /* @__PURE__ */ withDisplayName(SSectionHeaderCardRootImpl, "SSectionHeaderCard");
5268
5290
  var SDivider = /* @__PURE__ */ react.forwardRef(function SDivider2({ vertical = false, className, style, ...rest }, ref) {
5269
5291
  return /* @__PURE__ */ jsxRuntime.jsx(
5270
5292
  "hr",
@@ -6895,7 +6917,7 @@ function SModalContainer({
6895
6917
  "div",
6896
6918
  {
6897
6919
  className: cn(
6898
- "relative flex max-h-[calc(100dvh-48px)] max-w-[calc(100dvw-48px)] flex-col overflow-hidden rounded-[var(--cmp-modal-radius)] bg-[var(--cmp-modal-bg)] shadow-normal-md",
6920
+ "relative flex max-h-[85dvh] max-w-[calc(100dvw-48px)] flex-col overflow-hidden rounded-[var(--cmp-modal-radius)] bg-[var(--cmp-modal-bg)] shadow-normal-md",
6899
6921
  shaking && "animate-modal-shake",
6900
6922
  className
6901
6923
  ),
@@ -7152,7 +7174,7 @@ function SActionModal({
7152
7174
  showClose: true,
7153
7175
  width,
7154
7176
  height,
7155
- className: "w-fit min-w-[min(480px,calc(100dvw-48px))] min-h-[min(320px,calc(100dvh-48px))]",
7177
+ className: "w-fit min-w-[min(480px,calc(100dvw-48px))] min-h-[min(320px,85dvh)]",
7156
7178
  children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-auto flex-col", children: [
7157
7179
  /* @__PURE__ */ jsxRuntime.jsx(SOverlayHeader, { title: modalTitle, description, reserveCloseSpace: true }),
7158
7180
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-auto px-[var(--cmp-modal-action-body-paddingX)] pb-[var(--cmp-modal-action-body-paddingBottom)]", children }),
@@ -8874,6 +8896,7 @@ var BACKGROUND_CLASS = {
8874
8896
  var SPage = /* @__PURE__ */ react.forwardRef(function SPage2({
8875
8897
  background = "frame",
8876
8898
  scrollEndSpacing = true,
8899
+ contentHeight = "auto",
8877
8900
  header,
8878
8901
  className,
8879
8902
  style,
@@ -8888,13 +8911,14 @@ var SPage = /* @__PURE__ */ react.forwardRef(function SPage2({
8888
8911
  onScroll?.(e);
8889
8912
  if (header != null && !header.fix) setScrolled(e.currentTarget.scrollTop > 0);
8890
8913
  };
8914
+ const fill = contentHeight === "fill";
8891
8915
  const main = /* @__PURE__ */ jsxRuntime.jsx(
8892
8916
  "main",
8893
8917
  {
8894
8918
  ref,
8895
8919
  className: cn(
8896
8920
  "box-border min-h-0 min-w-0 flex-1 overflow-auto p-[var(--cmp-pageBody-padding-default)]",
8897
- scrollEndSpacing && "pb-[var(--cmp-pageBody-padding-scrollEnd)]",
8921
+ !fill && scrollEndSpacing && "pb-[var(--cmp-pageBody-padding-scrollEnd)]",
8898
8922
  SCROLLBAR_CLASS,
8899
8923
  BACKGROUND_CLASS[background],
8900
8924
  className
@@ -8902,7 +8926,7 @@ var SPage = /* @__PURE__ */ react.forwardRef(function SPage2({
8902
8926
  style: header == null ? { ...gridStyle, ...style } : style,
8903
8927
  onScroll: handleScroll,
8904
8928
  ...rest,
8905
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { minWidth: MIN_CONTENT_WIDTH }, children })
8929
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(fill && "h-full"), style: { minWidth: MIN_CONTENT_WIDTH }, children })
8906
8930
  }
8907
8931
  );
8908
8932
  if (header == null) return main;
@@ -8926,6 +8950,7 @@ var SPage = /* @__PURE__ */ react.forwardRef(function SPage2({
8926
8950
 
8927
8951
  // src/components/SPage/page.config.ts
8928
8952
  var PAGE_BACKGROUNDS = ["frame", "neutral", "screen"];
8953
+ var PAGE_CONTENT_HEIGHTS = ["auto", "fill"];
8929
8954
 
8930
8955
  // src/components/STabs/tabs.config.ts
8931
8956
  var TAB_MAIN_SIZE = {
@@ -9743,6 +9768,7 @@ var SField = /* @__PURE__ */ react.forwardRef(function SField2({
9743
9768
  readOnly = false,
9744
9769
  width,
9745
9770
  minWidth,
9771
+ maxWidth,
9746
9772
  multiline = false,
9747
9773
  borderless = false,
9748
9774
  children,
@@ -9778,7 +9804,10 @@ var SField = /* @__PURE__ */ react.forwardRef(function SField2({
9778
9804
  const resolvedSuccess = status === "pass";
9779
9805
  const showError = resolvedError && errorMessage !== "";
9780
9806
  const interactive = !disabled && !readOnly;
9781
- const hug = cssLen(width) !== void 0;
9807
+ const isPercentWidth = typeof width === "string" && width.trim().endsWith("%");
9808
+ const hug = cssLen(width) !== void 0 && !isPercentWidth;
9809
+ const addonWidth = effectiveAddonLabel !== "" ? cssLen(labelWidth) : void 0;
9810
+ const resolvedMaxWidth = cssLen(maxWidth) === void 0 || effectiveAddonLabel === "" ? cssLen(maxWidth) : addonWidth ? `calc(${cssLen(maxWidth)} + ${addonWidth})` : void 0;
9782
9811
  const borderColor = borderless ? "transparent" : disabled ? "var(--color-grey-45)" : interactive && focused ? "var(--sys-color-field-border-focus)" : resolvedSuccess ? "var(--sys-color-field-border-success)" : resolvedError ? "var(--sys-color-field-border-danger)" : "var(--sys-color-field-border-default)";
9783
9812
  const bgColor = borderless ? "transparent" : disabled ? "var(--color-grey-30)" : readOnly ? "var(--sys-color-field-bg-readOnly)" : "var(--sys-color-field-bg-default)";
9784
9813
  const textColor = disabled ? "var(--color-grey-65)" : readOnly ? "var(--sys-color-field-text-readOnly)" : "var(--sys-color-field-text-default)";
@@ -9834,8 +9863,12 @@ var SField = /* @__PURE__ */ react.forwardRef(function SField2({
9834
9863
  /* @__PURE__ */ jsxRuntime.jsxs(
9835
9864
  "div",
9836
9865
  {
9837
- className: cn("flex min-w-0 flex-col", !hug && "flex-1"),
9838
- style: { width: cssLen(width), minWidth: cssLen(minWidth) },
9866
+ className: cn("flex min-w-0 flex-col", !hug && !isPercentWidth && "flex-1"),
9867
+ style: {
9868
+ width: cssLen(width),
9869
+ minWidth: cssLen(minWidth),
9870
+ maxWidth: resolvedMaxWidth
9871
+ },
9839
9872
  children: [
9840
9873
  /* @__PURE__ */ jsxRuntime.jsxs(
9841
9874
  "div",
@@ -10590,6 +10623,7 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
10590
10623
  height,
10591
10624
  stickyColumn,
10592
10625
  radius = "default",
10626
+ bordered = true,
10593
10627
  noDataLabel = "\uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
10594
10628
  noDataSlot,
10595
10629
  isLoading = false,
@@ -10848,7 +10882,7 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
10848
10882
  };
10849
10883
  const radiusClass = radius === "full" ? "rounded-none" : radius === "useTop" ? "rounded-b-[var(--cmp-table-radius)] rounded-t-none" : "rounded-[var(--cmp-table-radius)]";
10850
10884
  const cellPadY = dense ? "var(--cmp-table-body-paddingY-dense)" : "var(--cmp-table-body-paddingY-default)";
10851
- const cellPadX = "var(--cmp-table-body-paddingX-wide)";
10885
+ const cellPadXOf = (col) => col.contentType === "control" ? "var(--cmp-table-body-paddingX-default)" : "var(--cmp-table-body-paddingX-wide)";
10852
10886
  const rowH = dense ? "var(--cmp-table-body-dense-height)" : "var(--cmp-table-body-default-height)";
10853
10887
  const totalColSpan = visibleCols.length + (selectable ? 1 : 0);
10854
10888
  const stickyInfo = (i) => {
@@ -10902,7 +10936,8 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
10902
10936
  // 만으로는 스태킹 컨텍스트가 생기지 않아, 모달 안에 들어가면 30·31이 모달 Content
10903
10937
  // 레벨까지 올라가 같은 컨텍스트의 floating portal 밴드(z-2)를 덮는다.
10904
10938
  // 자손은 이미 overflow-hidden으로 클리핑되므로 테이블 내부 순서는 달라지지 않는다.
10905
- "bg-white relative isolate flex flex-col overflow-hidden border border-solid border-[color:var(--cmp-table-border-color)]",
10939
+ "bg-white relative isolate flex flex-col overflow-hidden",
10940
+ bordered && "border border-solid border-[color:var(--cmp-table-border-color)]",
10906
10941
  // height가 없으면 루트가 받은 높이를 끝까지 채운다. 호스트가 className으로
10907
10942
  // `flex-1 min-h-0` 등을 줘 루트 높이가 확정된 경우, 행이 적어도 테두리 박스가
10908
10943
  // 남은 영역을 그대로 차지한다. basis는 auto로 둬 높이가 확정되지 않은
@@ -11072,9 +11107,10 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11072
11107
  stickyShadowClass(i),
11073
11108
  typeof col.tdClass === "function" ? col.tdClass(row) : col.tdClass
11074
11109
  );
11110
+ const tdPadX = cellPadXOf(col);
11075
11111
  const tdStyle = {
11076
- paddingLeft: cellPadX,
11077
- paddingRight: cellPadX,
11112
+ paddingLeft: tdPadX,
11113
+ paddingRight: tdPadX,
11078
11114
  paddingTop: cellPadY,
11079
11115
  paddingBottom: cellPadY,
11080
11116
  textAlign: col.align ?? "left",
@@ -11127,19 +11163,29 @@ var STable = /* @__PURE__ */ react.forwardRef(function STable2({
11127
11163
  // SPagination은 전체 폭 기준 중앙, rows-per-page SSelect는 우측에 absolute 배치해 중앙 정렬을 유지한다.
11128
11164
  // 루트 높이가 확정된 경우(테이블이 남은 영역을 채우는 배치) 페이지네이션 바가
11129
11165
  // 눌리지 않도록 shrink-0으로 48px를 보장한다.
11130
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative -mt-px flex h-[48px] shrink-0 items-center justify-center rounded-[var(--cmp-table-radius)] border border-solid border-[color:var(--cmp-table-border-color)] bg-[var(--color-grey-05)]", children: [
11131
- /* @__PURE__ */ jsxRuntime.jsx(SPagination, { currentPage: displayPage, lastPage, onPageChange: changePage }),
11132
- useRowsPerPageSelect && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-[10px] top-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsxRuntime.jsx(
11133
- SSelect2,
11134
- {
11135
- options: rowsPerPageOption,
11136
- value: useInternalPagination ? innerRowsPerPage : pagination?.rowsPerPage,
11137
- emitValue: true,
11138
- onValueChange: (v) => changeRowsPerPage(Number(v)),
11139
- width: 128
11140
- }
11141
- ) })
11142
- ] })
11166
+ /* @__PURE__ */ jsxRuntime.jsxs(
11167
+ "div",
11168
+ {
11169
+ className: cn(
11170
+ "relative flex h-[48px] shrink-0 items-center justify-center rounded-[var(--cmp-table-radius)] bg-[var(--color-grey-05)]",
11171
+ // 테두리를 끄면 겹칠 대상이 없어진다 — -mt-px 를 남기면 1px 어긋남만 생긴다.
11172
+ bordered && "-mt-px border border-solid border-[color:var(--cmp-table-border-color)]"
11173
+ ),
11174
+ children: [
11175
+ /* @__PURE__ */ jsxRuntime.jsx(SPagination, { currentPage: displayPage, lastPage, onPageChange: changePage }),
11176
+ useRowsPerPageSelect && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-[10px] top-1/2 -translate-y-1/2", children: /* @__PURE__ */ jsxRuntime.jsx(
11177
+ SSelect2,
11178
+ {
11179
+ options: rowsPerPageOption,
11180
+ value: useInternalPagination ? innerRowsPerPage : pagination?.rowsPerPage,
11181
+ emitValue: true,
11182
+ onValueChange: (v) => changeRowsPerPage(Number(v)),
11183
+ width: 128
11184
+ }
11185
+ ) })
11186
+ ]
11187
+ }
11188
+ )
11143
11189
  ]
11144
11190
  }
11145
11191
  );
@@ -12156,12 +12202,275 @@ function SCalendar({
12156
12202
  }
12157
12203
  );
12158
12204
  }
12205
+ var DATEPICKER_MODE_LIST_HEIGHT = 240;
12206
+ var DATEPICKER_MODE_ITEM_HEIGHT = 28;
12207
+ var YEAR_LIST_EDGE_BUFFER = 24;
12208
+ var YEAR_LIST_EXPAND_SIZE = 80;
12209
+ var MONTHS = Array.from({ length: 12 }, (_, i) => i + 1);
12210
+ var getTodayParts = () => {
12211
+ const today = /* @__PURE__ */ new Date();
12212
+ return {
12213
+ year: today.getFullYear(),
12214
+ month: today.getMonth() + 1
12215
+ };
12216
+ };
12217
+ var parseYear = (value) => {
12218
+ const year = Number(value?.slice(0, 4));
12219
+ return Number.isFinite(year) && year > 0 ? year : void 0;
12220
+ };
12221
+ var parseMonth = (value) => {
12222
+ const month = Number(value?.slice(5, 7));
12223
+ return Number.isFinite(month) && month >= 1 && month <= 12 ? month : void 0;
12224
+ };
12225
+ var formatMonthValue = (year, month) => `${year}-${String(month).padStart(2, "0")}`;
12226
+ var isYearDisabled = (year, selectable) => {
12227
+ if (!selectable) return false;
12228
+ const [start, end] = selectable;
12229
+ if (start !== "" && year < Number(start.slice(0, 4))) return true;
12230
+ if (end !== "" && year > Number(end.slice(0, 4))) return true;
12231
+ return false;
12232
+ };
12233
+ var isMonthDisabled = (year, month, selectable) => {
12234
+ if (!selectable) return false;
12235
+ const value = formatMonthValue(year, month);
12236
+ const [start, end] = selectable;
12237
+ if (start !== "" && value < start.slice(0, 7)) return true;
12238
+ if (end !== "" && value > end.slice(0, 7)) return true;
12239
+ return false;
12240
+ };
12241
+ function YearWheel({ value, anchorYear, selectable, onCommit }) {
12242
+ const listRef = react.useRef(null);
12243
+ const syncingScrollRef = react.useRef(false);
12244
+ const anchorYearRef = react.useRef(anchorYear);
12245
+ const [range, setRange] = react.useState(() => ({
12246
+ start: anchorYear - YEAR_LIST_EXPAND_SIZE,
12247
+ end: anchorYear + YEAR_LIST_EXPAND_SIZE
12248
+ }));
12249
+ const rangeStartRef = react.useRef(range.start);
12250
+ rangeStartRef.current = range.start;
12251
+ const years = Array.from({ length: range.end - range.start + 1 }, (_, i) => range.start + i);
12252
+ react.useLayoutEffect(() => {
12253
+ const list = listRef.current;
12254
+ if (!list) return;
12255
+ const index = anchorYearRef.current - rangeStartRef.current;
12256
+ syncingScrollRef.current = true;
12257
+ list.scrollTop = index * DATEPICKER_MODE_ITEM_HEIGHT;
12258
+ requestAnimationFrame(() => {
12259
+ syncingScrollRef.current = false;
12260
+ });
12261
+ }, []);
12262
+ const handleScroll = () => {
12263
+ const list = listRef.current;
12264
+ if (!list) return;
12265
+ if (syncingScrollRef.current) return;
12266
+ if (list.scrollTop < DATEPICKER_MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
12267
+ const previousHeight = list.scrollHeight;
12268
+ setRange((current) => ({
12269
+ start: current.start - YEAR_LIST_EXPAND_SIZE,
12270
+ end: current.end
12271
+ }));
12272
+ requestAnimationFrame(() => {
12273
+ if (!listRef.current) return;
12274
+ syncingScrollRef.current = true;
12275
+ listRef.current.scrollTop += listRef.current.scrollHeight - previousHeight;
12276
+ requestAnimationFrame(() => {
12277
+ syncingScrollRef.current = false;
12278
+ });
12279
+ });
12280
+ return;
12281
+ }
12282
+ if (list.scrollHeight - list.scrollTop - list.clientHeight < DATEPICKER_MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
12283
+ setRange((current) => ({
12284
+ start: current.start,
12285
+ end: current.end + YEAR_LIST_EXPAND_SIZE
12286
+ }));
12287
+ }
12288
+ };
12289
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsxRuntime.jsx(
12290
+ "div",
12291
+ {
12292
+ ref: listRef,
12293
+ role: "listbox",
12294
+ "aria-label": "year",
12295
+ className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
12296
+ style: {
12297
+ paddingTop: `calc((${DATEPICKER_MODE_LIST_HEIGHT}px - ${DATEPICKER_MODE_ITEM_HEIGHT}px) / 2)`,
12298
+ paddingBottom: `calc((${DATEPICKER_MODE_LIST_HEIGHT}px - ${DATEPICKER_MODE_ITEM_HEIGHT}px) / 2)`
12299
+ },
12300
+ onScroll: handleScroll,
12301
+ children: years.map((year) => {
12302
+ const selected = year === value;
12303
+ const disabled = isYearDisabled(year, selectable);
12304
+ return /* @__PURE__ */ jsxRuntime.jsx(
12305
+ "button",
12306
+ {
12307
+ type: "button",
12308
+ role: "option",
12309
+ "aria-selected": selected,
12310
+ disabled,
12311
+ className: cn(
12312
+ "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]",
12313
+ disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
12314
+ !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)]",
12315
+ !disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
12316
+ ),
12317
+ onClick: () => !disabled && onCommit(year),
12318
+ children: year
12319
+ },
12320
+ year
12321
+ );
12322
+ })
12323
+ }
12324
+ ) });
12325
+ }
12326
+ function SDatePickerYearListbox({
12327
+ value,
12328
+ anchorYear,
12329
+ selectable,
12330
+ onValueChange,
12331
+ className,
12332
+ style
12333
+ }) {
12334
+ const today = getTodayParts();
12335
+ const resolvedAnchorYear = anchorYear ?? today.year;
12336
+ const [year, setYear] = react.useState(() => parseYear(value) ?? resolvedAnchorYear);
12337
+ react.useEffect(() => {
12338
+ setYear(parseYear(value) ?? resolvedAnchorYear);
12339
+ }, [value, resolvedAnchorYear]);
12340
+ const commitYear = (nextYear) => {
12341
+ setYear(nextYear);
12342
+ onValueChange?.(String(nextYear));
12343
+ };
12344
+ return /* @__PURE__ */ jsxRuntime.jsx(
12345
+ "div",
12346
+ {
12347
+ className: cn(
12348
+ "inline-flex h-[240px] w-[var(--cmp-datepicker-mode-year-width)] overflow-hidden rounded-[var(--cmp-datepicker-mode-radius)] bg-[var(--cmp-datepicker-mode-bg)] shadow-normal-sm",
12349
+ className
12350
+ ),
12351
+ style,
12352
+ children: /* @__PURE__ */ jsxRuntime.jsx(
12353
+ YearWheel,
12354
+ {
12355
+ value: year,
12356
+ anchorYear: resolvedAnchorYear,
12357
+ selectable,
12358
+ onCommit: commitYear
12359
+ }
12360
+ )
12361
+ }
12362
+ );
12363
+ }
12364
+ function MonthWheel({ year, value, selectable, onCommit }) {
12365
+ const listRef = react.useRef(null);
12366
+ const initialValueRef = react.useRef(value);
12367
+ react.useLayoutEffect(() => {
12368
+ const list = listRef.current;
12369
+ if (!list) return;
12370
+ list.scrollTop = Math.max(
12371
+ 0,
12372
+ (initialValueRef.current - 1) * DATEPICKER_MODE_ITEM_HEIGHT - (list.clientHeight - DATEPICKER_MODE_ITEM_HEIGHT) / 2
12373
+ );
12374
+ }, []);
12375
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsxRuntime.jsx(
12376
+ "div",
12377
+ {
12378
+ ref: listRef,
12379
+ role: "listbox",
12380
+ "aria-label": "month",
12381
+ className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
12382
+ children: MONTHS.map((month) => {
12383
+ const selected = month === value;
12384
+ const disabled = isMonthDisabled(year, month, selectable);
12385
+ return /* @__PURE__ */ jsxRuntime.jsxs(
12386
+ "button",
12387
+ {
12388
+ type: "button",
12389
+ role: "option",
12390
+ "aria-selected": selected,
12391
+ disabled,
12392
+ className: cn(
12393
+ "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]",
12394
+ disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
12395
+ !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)]",
12396
+ !disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
12397
+ ),
12398
+ onClick: () => !disabled && onCommit(month),
12399
+ children: [
12400
+ month,
12401
+ "\uC6D4"
12402
+ ]
12403
+ },
12404
+ month
12405
+ );
12406
+ })
12407
+ }
12408
+ ) });
12409
+ }
12410
+ function SDatePickerMonthListbox({
12411
+ value,
12412
+ anchorYear,
12413
+ selectable,
12414
+ onValueChange,
12415
+ onMonthSelect,
12416
+ className,
12417
+ style
12418
+ }) {
12419
+ const today = getTodayParts();
12420
+ const resolvedAnchorYear = anchorYear ?? today.year;
12421
+ const [year, setYear] = react.useState(() => parseYear(value) ?? resolvedAnchorYear);
12422
+ const [month, setMonth] = react.useState(() => parseMonth(value) ?? today.month);
12423
+ react.useEffect(() => {
12424
+ setYear(parseYear(value) ?? resolvedAnchorYear);
12425
+ setMonth(parseMonth(value) ?? today.month);
12426
+ }, [value, resolvedAnchorYear, today.month]);
12427
+ const commitYear = (nextYear) => {
12428
+ setYear(nextYear);
12429
+ onValueChange?.(formatMonthValue(nextYear, month));
12430
+ };
12431
+ const commitMonth = (nextMonth) => {
12432
+ setMonth(nextMonth);
12433
+ const nextValue = formatMonthValue(year, nextMonth);
12434
+ onValueChange?.(nextValue);
12435
+ onMonthSelect?.(nextValue);
12436
+ };
12437
+ return /* @__PURE__ */ jsxRuntime.jsxs(
12438
+ "div",
12439
+ {
12440
+ className: cn(
12441
+ "inline-flex h-[240px] gap-[var(--cmp-datepicker-mode-yearMonth-gap)] overflow-hidden rounded-[var(--cmp-datepicker-mode-radius)] bg-[var(--cmp-datepicker-mode-bg)] shadow-normal-sm",
12442
+ className
12443
+ ),
12444
+ style,
12445
+ children: [
12446
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 w-[var(--cmp-datepicker-mode-yearMonth-column-width)]", children: /* @__PURE__ */ jsxRuntime.jsx(
12447
+ YearWheel,
12448
+ {
12449
+ value: year,
12450
+ anchorYear: resolvedAnchorYear,
12451
+ selectable,
12452
+ onCommit: commitYear
12453
+ }
12454
+ ) }),
12455
+ /* @__PURE__ */ jsxRuntime.jsx(
12456
+ "div",
12457
+ {
12458
+ "aria-hidden": true,
12459
+ className: "w-[var(--border-width-1)] self-stretch bg-[var(--sys-color-divider-default)]"
12460
+ }
12461
+ ),
12462
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 w-[var(--cmp-datepicker-mode-yearMonth-column-width)]", children: /* @__PURE__ */ jsxRuntime.jsx(MonthWheel, { year, value: month, selectable, onCommit: commitMonth }) })
12463
+ ]
12464
+ }
12465
+ );
12466
+ }
12159
12467
  function SDatePicker({
12160
12468
  value,
12161
12469
  onValueChange,
12162
12470
  onViewChange,
12471
+ mode = "date",
12163
12472
  size = "sm",
12164
- placeholder = "YYYY-MM-DD",
12473
+ placeholder,
12165
12474
  selectable,
12166
12475
  disabled = false,
12167
12476
  width,
@@ -12187,6 +12496,7 @@ function SDatePicker({
12187
12496
  const [focused, setFocused] = react.useState(false);
12188
12497
  const [ruleError, setRuleError] = react.useState("");
12189
12498
  const hasValue = value != null && value !== "";
12499
+ const resolvedPlaceholder = placeholder ?? (mode === "year" ? "YYYY" : mode === "month" ? "YYYY-MM" : "YYYY-MM-DD");
12190
12500
  useFormField({
12191
12501
  name,
12192
12502
  validate: () => {
@@ -12198,12 +12508,39 @@ function SDatePicker({
12198
12508
  });
12199
12509
  const resolvedError = error || ruleError !== "";
12200
12510
  const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
12201
- const dims = size === "md" ? { padX: 16, gap: 12, icon: 20, minW: 104, fontSize: 14, lineHeight: 24, radius: 6 } : { padX: 12, gap: 8, icon: 16, minW: 80, fontSize: 12, lineHeight: 20, radius: 4 };
12511
+ const dims = size === "md" ? {
12512
+ padX: 16,
12513
+ gap: 12,
12514
+ icon: 20,
12515
+ minW: 104,
12516
+ fieldMinW: 168,
12517
+ // 상한 등급 lg — 값 길이가 YYYY-MM-DD 로 정해져 있어, width="100%" 를 받아도
12518
+ // 행 전체로 늘어나지 않게 막는다 (SKeyValueTable 이 모든 컨트롤에 100% 를 넘긴다)
12519
+ fieldMaxW: 240,
12520
+ fontSize: 14,
12521
+ lineHeight: 24,
12522
+ radius: 6
12523
+ } : {
12524
+ padX: 12,
12525
+ gap: 8,
12526
+ icon: 16,
12527
+ minW: 80,
12528
+ fieldMinW: 128,
12529
+ // 상한 등급 md
12530
+ fieldMaxW: 160,
12531
+ fontSize: 12,
12532
+ lineHeight: 20,
12533
+ radius: 4
12534
+ };
12202
12535
  const select = (date) => {
12203
12536
  onValueChange?.(date);
12204
12537
  if (rules && rules.length > 0) setRuleError(runRules(date, rules));
12205
12538
  setOpen(false);
12206
12539
  };
12540
+ const selectListboxValue = (nextValue) => {
12541
+ onValueChange?.(nextValue);
12542
+ if (rules && rules.length > 0) setRuleError(runRules(nextValue, rules));
12543
+ };
12207
12544
  return /* @__PURE__ */ jsxRuntime.jsx(
12208
12545
  SField,
12209
12546
  {
@@ -12222,6 +12559,8 @@ function SDatePicker({
12222
12559
  focused: focused || open,
12223
12560
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
12224
12561
  width,
12562
+ minWidth: dims.fieldMinW,
12563
+ maxWidth: dims.fieldMaxW,
12225
12564
  disabled,
12226
12565
  className,
12227
12566
  style,
@@ -12256,7 +12595,7 @@ function SDatePicker({
12256
12595
  lineHeight: `${dims.lineHeight}px`,
12257
12596
  color: disabled ? "var(--cmp-datepicker-text-disabled)" : hasValue ? "var(--cmp-datepicker-text-default)" : "var(--cmp-field-hint-color)"
12258
12597
  },
12259
- children: hasValue ? value : placeholder
12598
+ children: hasValue ? value : resolvedPlaceholder
12260
12599
  }
12261
12600
  )
12262
12601
  ]
@@ -12266,13 +12605,13 @@ function SDatePicker({
12266
12605
  RPopover9__namespace.Content,
12267
12606
  {
12268
12607
  side: "bottom",
12269
- align: "start",
12608
+ align: mode === "date" ? "start" : "center",
12270
12609
  sideOffset: 4,
12271
12610
  className: cn(
12272
12611
  "z-50 max-w-[var(--radix-popover-content-available-width)]",
12273
12612
  FLOATING_SLIDE_ANIM
12274
12613
  ),
12275
- children: /* @__PURE__ */ jsxRuntime.jsx(
12614
+ children: mode === "date" ? /* @__PURE__ */ jsxRuntime.jsx(
12276
12615
  SCalendar,
12277
12616
  {
12278
12617
  value: value ?? void 0,
@@ -12281,6 +12620,24 @@ function SDatePicker({
12281
12620
  onValueChange: select,
12282
12621
  onViewChange
12283
12622
  }
12623
+ ) : mode === "year" ? /* @__PURE__ */ jsxRuntime.jsx(
12624
+ SDatePickerYearListbox,
12625
+ {
12626
+ value,
12627
+ selectable,
12628
+ onValueChange: (nextValue) => {
12629
+ selectListboxValue(nextValue);
12630
+ setOpen(false);
12631
+ }
12632
+ }
12633
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
12634
+ SDatePickerMonthListbox,
12635
+ {
12636
+ value,
12637
+ selectable,
12638
+ onValueChange: selectListboxValue,
12639
+ onMonthSelect: () => setOpen(false)
12640
+ }
12284
12641
  )
12285
12642
  }
12286
12643
  ) })
@@ -12662,6 +13019,9 @@ function SDateRangePicker({
12662
13019
  icon: 20,
12663
13020
  minW: 104,
12664
13021
  fieldMinW: 260,
13022
+ // 상한 등급 xl — 값이 `YYYY-MM-DD ~ YYYY-MM-DD` 라 단일 날짜의 두 배가 넘는다.
13023
+ // 최소너비(260)가 이미 lg(240)를 넘으므로 상한도 한 등급 위여야 한다
13024
+ fieldMaxW: 480,
12665
13025
  fontSize: 14,
12666
13026
  lineHeight: 24,
12667
13027
  radius: 6
@@ -12671,6 +13031,8 @@ function SDateRangePicker({
12671
13031
  icon: 16,
12672
13032
  minW: 80,
12673
13033
  fieldMinW: 216,
13034
+ // 상한 등급 lg — 최소너비 216 이 md(160)를 넘어 md 로는 담기지 않는다
13035
+ fieldMaxW: 240,
12674
13036
  fontSize: 12,
12675
13037
  lineHeight: 20,
12676
13038
  radius: 4
@@ -12701,6 +13063,7 @@ function SDateRangePicker({
12701
13063
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
12702
13064
  width,
12703
13065
  minWidth: dims.fieldMinW,
13066
+ maxWidth: dims.fieldMaxW,
12704
13067
  disabled,
12705
13068
  className,
12706
13069
  style,
@@ -13165,6 +13528,7 @@ function SKeyValueTable({
13165
13528
  values = {},
13166
13529
  search = false,
13167
13530
  radius = "default",
13531
+ bordered = true,
13168
13532
  onChange,
13169
13533
  onSearch,
13170
13534
  className,
@@ -13229,7 +13593,8 @@ function SKeyValueTable({
13229
13593
  });
13230
13594
  const radiusClass = radius === "full" ? "rounded-none" : radius === "useTop" ? "rounded-b-[8px] rounded-t-none" : "rounded-[8px]";
13231
13595
  const frameClass = cn(
13232
- "overflow-hidden border border-solid border-[color:var(--cmp-table-border-color)]",
13596
+ "overflow-hidden",
13597
+ bordered && "border border-solid border-[color:var(--cmp-table-border-color)]",
13233
13598
  radiusClass
13234
13599
  );
13235
13600
  const table = /* @__PURE__ */ jsxRuntime.jsxs(
@@ -13814,7 +14179,16 @@ var CHIPFILTER = {
13814
14179
  itemTextDefault: "var(--cmp-chipFilter-item-text-default)",
13815
14180
  itemTextSelected: "var(--cmp-chipFilter-item-text-selected)"
13816
14181
  };
14182
+ var DEFAULT_PERIOD_PRESETS = [
14183
+ { value: "day", label: "\uC77C\uBCC4" },
14184
+ { value: "month", label: "\uC6D4\uBCC4" },
14185
+ { value: "quarter", label: "\uBD84\uAE30\uBCC4" },
14186
+ { value: "half", label: "\uBC18\uAE30\uBCC4" },
14187
+ { value: "year", label: "\uC5F0\uB3C4\uBCC4" },
14188
+ { value: "custom", label: "\uC0AC\uC6A9\uC790 \uC9C0\uC815", custom: true }
14189
+ ];
13817
14190
  var isKeywordValue = (value) => typeof value === "object" && value != null && !Array.isArray(value) && "keywords" in value;
14191
+ var isPeriodValue = (value) => typeof value === "object" && value != null && !Array.isArray(value) && "unit" in value;
13818
14192
  var keywordEntries = (value) => {
13819
14193
  if (isKeywordValue(value)) return value.keywords;
13820
14194
  if (Array.isArray(value)) {
@@ -13838,12 +14212,33 @@ var todayIso = () => {
13838
14212
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
13839
14213
  };
13840
14214
  var isInlineDateField = (field) => field.type === "date" && field.radioButton === true && (field.presets?.length ?? 0) > 0;
14215
+ var getFieldPresets = (field) => {
14216
+ if (field.type === "period") return field.presets ?? DEFAULT_PERIOD_PRESETS;
14217
+ return field.presets ?? [];
14218
+ };
14219
+ var getDefaultPeriodInputValue = (unit) => {
14220
+ const today = todayIso();
14221
+ if (unit === "day") return today;
14222
+ if (unit === "month") return today.slice(0, 7);
14223
+ if (unit === "quarter") return 1;
14224
+ if (unit === "half") return "first";
14225
+ if (unit === "year") return today.slice(0, 4);
14226
+ return [today, today];
14227
+ };
14228
+ var createPeriodValue = (unit) => ({
14229
+ unit,
14230
+ value: getDefaultPeriodInputValue(unit)
14231
+ });
13841
14232
  var resolveFieldDefault = (field) => {
13842
14233
  if (field.defaultValue !== void 0) return field.defaultValue;
13843
14234
  if (isInlineDateField(field)) return todayIso();
13844
14235
  if (!field.required) return void 0;
13845
14236
  if (field.type === "single") return field.options?.[0]?.value;
13846
14237
  if (field.type === "date") return todayIso();
14238
+ if (field.type === "period") {
14239
+ const unit = getFieldPresets(field).find((preset) => !preset.custom)?.value;
14240
+ return unit ? createPeriodValue(unit) : void 0;
14241
+ }
13847
14242
  return void 0;
13848
14243
  };
13849
14244
  var valuesEqual = (a, b) => {
@@ -13867,6 +14262,8 @@ var isAtDefaultValue = (field, value) => {
13867
14262
  return def !== void 0 && valuesEqual(value, def);
13868
14263
  };
13869
14264
  var isInlineField = (field) => isInlineDateField(field) || field.type === "custom";
14265
+ var isFixedField = (field) => field.fix === true || field.required === true;
14266
+ var isAddableField = (field) => !isFixedField(field);
13870
14267
  var isGroupedFields = (fields) => fields.length > 0 && "fields" in fields[0];
13871
14268
  var isGroupSatisfied = (group, values) => {
13872
14269
  if (!group.rule) return true;
@@ -13893,9 +14290,10 @@ var defaultGroupTooltipMessage = (rule, fields) => {
13893
14290
  };
13894
14291
  var matchPresetId = (presets, value) => {
13895
14292
  if (isEmptyValue(value)) return null;
14293
+ if (isPeriodValue(value)) return value.unit;
13896
14294
  for (const preset of presets) {
13897
- if (preset.custom || !preset.resolve) continue;
13898
- const resolved = preset.resolve();
14295
+ if (preset.custom) continue;
14296
+ const resolved = preset.resolve ? preset.resolve() : preset.value;
13899
14297
  if (Array.isArray(resolved) && Array.isArray(value)) {
13900
14298
  if (resolved[0] === value[0] && resolved[1] === value[1]) return preset.value;
13901
14299
  } else if (!Array.isArray(resolved) && !Array.isArray(value) && resolved === value) {
@@ -13911,18 +14309,36 @@ var formatValueList = (labels) => {
13911
14309
  };
13912
14310
  var displayValue = (field, value) => {
13913
14311
  if (isEmptyValue(value)) return "";
13914
- if (field.type === "multi")
13915
- return formatValueList(toArrayValue(value).map((v) => optionLabel(field, v)));
14312
+ if (field.type === "multi") {
14313
+ const values = toArrayValue(value);
14314
+ const selectableValues = (field.options ?? []).filter((option) => !option.disabled).map((option) => option.value);
14315
+ if (selectableValues.length > 0 && selectableValues.every((selectableValue) => values.includes(selectableValue))) {
14316
+ return "\uC804\uCCB4";
14317
+ }
14318
+ return formatValueList(values.map((v) => optionLabel(field, v)));
14319
+ }
13916
14320
  if (field.type === "single") return optionLabel(field, value);
13917
14321
  if (field.type === "date")
13918
14322
  return Array.isArray(value) ? `${value[0]} ~ ${value[1]}` : String(value);
14323
+ if (field.type === "period") {
14324
+ if (!isPeriodValue(value)) {
14325
+ const preset2 = getFieldPresets(field).find((p) => p.value === value);
14326
+ return preset2?.label ?? String(value);
14327
+ }
14328
+ const preset = getFieldPresets(field).find((p) => p.value === value.unit);
14329
+ const label = preset?.label ?? value.unit;
14330
+ if (value.unit === "quarter") return `${label}(${value.value ?? 1}\uBD84\uAE30)`;
14331
+ if (value.unit === "half") return `${label}(${value.value === "second" ? "\uD558\uBC18\uAE30" : "\uC0C1\uBC18\uAE30"})`;
14332
+ if (Array.isArray(value.value)) return `${value.value[0]} ~ ${value.value[1]}`;
14333
+ return value.value != null && value.value !== "" ? String(value.value) : label;
14334
+ }
13919
14335
  if (field.type === "keyword") return formatValueList(keywordEntries(value));
13920
14336
  return String(value);
13921
14337
  };
13922
14338
  var chipDisplayValue = (field, value) => {
13923
14339
  const text = displayValue(field, value);
13924
14340
  if (field.type !== "date" || isEmptyValue(value)) return text;
13925
- const presets = field.presets ?? [];
14341
+ const presets = getFieldPresets(field);
13926
14342
  if (presets.length === 0) return text;
13927
14343
  const preset = presets.find((p) => p.value === matchPresetId(presets, value));
13928
14344
  return preset && !preset.custom ? `${preset.label}(${text})` : text;
@@ -13939,7 +14355,6 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
13939
14355
  onAddFilter,
13940
14356
  label = "\uAC80\uC0C9 \uD544\uD130",
13941
14357
  showLabel = false,
13942
- showAddButton = true,
13943
14358
  showReset = true,
13944
14359
  disabled = false,
13945
14360
  className,
@@ -13954,7 +14369,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
13954
14369
  const values = normalizeValues(value);
13955
14370
  const portalContainer = usePortalContainer();
13956
14371
  const [internalActiveKeys, setInternalActiveKeys] = react.useState(
13957
- () => fields.filter((field) => field.defaultActive !== false).map((field) => field.key)
14372
+ () => fields.filter((field) => isFixedField(field) || field.defaultActive !== false).map((field) => field.key)
13958
14373
  );
13959
14374
  const [openKey, setOpenKey] = react.useState(null);
13960
14375
  const [addOpen, setAddOpen] = react.useState(false);
@@ -13996,7 +14411,9 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
13996
14411
  setInternalActiveKeys((current) => {
13997
14412
  const known = new Set(fields.map((field) => field.key));
13998
14413
  const kept = current.filter((key) => known.has(key));
13999
- const additions = fields.filter((field) => field.defaultActive !== false && !kept.includes(field.key)).map((field) => field.key);
14414
+ const additions = fields.filter(
14415
+ (field) => (isFixedField(field) || field.defaultActive !== false) && !kept.includes(field.key)
14416
+ ).map((field) => field.key);
14000
14417
  return [...kept, ...additions];
14001
14418
  });
14002
14419
  }, [activeKeys, fields]);
@@ -14017,25 +14434,31 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
14017
14434
  });
14018
14435
  if (changed) onValueChange?.({ ...values, ...patch });
14019
14436
  }, [fields, values, onValueChange]);
14020
- const resolvedActiveKeys = activeKeys ?? internalActiveKeys;
14437
+ const fixedKeys = fields.filter(isFixedField).map((field) => field.key);
14438
+ const resolvedActiveKeys = Array.from(
14439
+ /* @__PURE__ */ new Set([...activeKeys ?? internalActiveKeys, ...fixedKeys])
14440
+ );
14021
14441
  const activeFields = fields.filter((field) => resolvedActiveKeys.includes(field.key));
14022
- const inactiveFields = fields.filter((field) => !resolvedActiveKeys.includes(field.key));
14442
+ const inactiveFields = fields.filter(
14443
+ (field) => isAddableField(field) && !resolvedActiveKeys.includes(field.key)
14444
+ );
14023
14445
  const groupIndexByKey = /* @__PURE__ */ new Map();
14024
14446
  groups.forEach((group, groupIndex) => {
14025
14447
  group.fields.forEach((field) => groupIndexByKey.set(field.key, groupIndex));
14026
14448
  });
14027
14449
  const hasValue = fields.some((field) => !isEmptyValue(values[field.key]));
14028
- const hasRightActions = showAddButton || showReset;
14450
+ const hasAddButton = fields.some(isAddableField);
14451
+ const hasRightActions = hasAddButton || showReset;
14029
14452
  const currentOpenField = fields.find((field) => field.key === openKey);
14030
14453
  const editorPopoverStyle = react.useMemo(() => {
14031
14454
  if (currentOpenField?.type === "keyword") {
14032
14455
  return { width: CHIPFILTER_LISTBOX_WIDTH };
14033
14456
  }
14034
- if (currentOpenField?.type === "date" && (currentOpenField.presets?.length ?? 0) > 0) {
14457
+ if ((currentOpenField?.type === "date" || currentOpenField?.type === "period") && getFieldPresets(currentOpenField).length > 0) {
14035
14458
  return { width: CHIPFILTER_DATE_PRESET_LISTBOX_WIDTH };
14036
14459
  }
14037
14460
  return { width: CHIPFILTER_LISTBOX_WIDTH };
14038
- }, [currentOpenField?.type, currentOpenField?.presets?.length]);
14461
+ }, [currentOpenField]);
14039
14462
  const setActive = (next) => {
14040
14463
  if (!activeKeys) setInternalActiveKeys(next);
14041
14464
  onActiveKeysChange?.(next);
@@ -14054,6 +14477,12 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
14054
14477
  const def = field ? resolveFieldDefault(field) : void 0;
14055
14478
  runSearch(emitValue(key, def !== void 0 ? def : null));
14056
14479
  };
14480
+ const removeField = (key) => {
14481
+ const nextValues = emitValue(key, null);
14482
+ setActive(resolvedActiveKeys.filter((activeKey) => activeKey !== key));
14483
+ if (openKey === key) setOpenKey(null);
14484
+ runSearch(nextValues);
14485
+ };
14057
14486
  const reset = react.useCallback(() => {
14058
14487
  const next = fields.reduce((acc, field) => {
14059
14488
  const def = resolveFieldDefault(field);
@@ -14144,7 +14573,15 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
14144
14573
  flushRun();
14145
14574
  runGroupIndex = groupIndex;
14146
14575
  }
14147
- const showClear = !isAtDefaultValue(field, values[field.key]);
14576
+ const addable = isAddableField(field);
14577
+ const showClear = addable || !isAtDefaultValue(field, values[field.key]);
14578
+ const handleClear = () => {
14579
+ if (addable) {
14580
+ removeField(field.key);
14581
+ return;
14582
+ }
14583
+ clearValue(field.key);
14584
+ };
14148
14585
  const node = isInlineDateField(field) ? /* @__PURE__ */ jsxRuntime.jsx(
14149
14586
  InlineDatePresetFilter,
14150
14587
  {
@@ -14154,15 +14591,27 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
14154
14591
  warning,
14155
14592
  showClear,
14156
14593
  onValueChange: (next) => emitValueAndSearch(field.key, next),
14157
- onClear: () => clearValue(field.key)
14594
+ onClear: handleClear
14158
14595
  },
14159
14596
  field.key
14160
- ) : field.type === "custom" ? /* @__PURE__ */ jsxRuntime.jsx(react.Fragment, { children: field.render?.({
14161
- value: values[field.key],
14162
- disabled: disabled || field.disabled,
14163
- warning,
14164
- onValueChange: (next) => emitValueAndSearch(field.key, next)
14165
- }) }, field.key) : /* @__PURE__ */ jsxRuntime.jsx(
14597
+ ) : field.type === "custom" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex shrink-0 items-center gap-[4px]", children: [
14598
+ field.render?.({
14599
+ value: values[field.key],
14600
+ disabled: disabled || field.disabled,
14601
+ warning,
14602
+ onValueChange: (next) => emitValueAndSearch(field.key, next)
14603
+ }),
14604
+ addable && !disabled && !field.disabled && showClear && /* @__PURE__ */ jsxRuntime.jsx(
14605
+ "button",
14606
+ {
14607
+ type: "button",
14608
+ "aria-label": `${field.label} \uD544\uD130 \uC81C\uAC70`,
14609
+ className: "inline-flex size-[16px] shrink-0 items-center justify-center rounded-[var(--cmp-ghostButton-radius-sm)] border-none bg-transparent p-0",
14610
+ onClick: handleClear,
14611
+ children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "close", size: 12, color: "var(--cmp-ghostButton-default-content)" })
14612
+ }
14613
+ )
14614
+ ] }, field.key) : /* @__PURE__ */ jsxRuntime.jsx(
14166
14615
  FilterChip,
14167
14616
  {
14168
14617
  field,
@@ -14172,9 +14621,10 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
14172
14621
  disabled: disabled || field.disabled,
14173
14622
  warning,
14174
14623
  showClear,
14624
+ showClearWhenEmpty: addable,
14175
14625
  refCallback: (node2) => triggerRefs.current.set(field.key, node2),
14176
14626
  onOpen: () => setOpenKey(openKey === field.key ? null : field.key),
14177
- onClear: () => clearValue(field.key)
14627
+ onClear: handleClear
14178
14628
  },
14179
14629
  field.key
14180
14630
  );
@@ -14195,7 +14645,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
14195
14645
  })(),
14196
14646
  hasRightActions && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14197
14647
  /* @__PURE__ */ jsxRuntime.jsx(Divider, { inset: true }),
14198
- showAddButton && /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open: addOpen, onOpenChange: setAddOpen, children: [
14648
+ hasAddButton && /* @__PURE__ */ jsxRuntime.jsxs(RPopover9__namespace.Root, { open: addOpen, onOpenChange: setAddOpen, children: [
14199
14649
  /* @__PURE__ */ jsxRuntime.jsx(RPopover9__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
14200
14650
  "button",
14201
14651
  {
@@ -14242,7 +14692,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
14242
14692
  "inline-flex shrink-0 cursor-pointer items-center gap-[4px] border-none bg-transparent p-0 text-[12px] font-medium leading-[20px] disabled:cursor-not-allowed",
14243
14693
  // 부모 flex gap(6px)은 칩 사이 간격 기준이라, 필터 추가 버튼과의 12px 간격을 맞추려면
14244
14694
  // 부족한 만큼(12px - gap)을 추가로 띄운다. 필터 추가가 없을 땐 divider와 이미 12px이라 불필요.
14245
- showAddButton && "ml-[calc(12px-var(--cmp-chipFilter-chip-group))]"
14695
+ hasAddButton && "ml-[calc(12px-var(--cmp-chipFilter-chip-group))]"
14246
14696
  ),
14247
14697
  style: {
14248
14698
  color: disabled || !hasValue ? "var(--cmp-textLink-content-disabled)" : "var(--cmp-textLink-content-default)"
@@ -14326,6 +14776,7 @@ function FilterChip({
14326
14776
  disabled,
14327
14777
  warning,
14328
14778
  showClear,
14779
+ showClearWhenEmpty,
14329
14780
  refCallback,
14330
14781
  onOpen,
14331
14782
  onClear
@@ -14395,7 +14846,7 @@ function FilterChip({
14395
14846
  }
14396
14847
  )
14397
14848
  ] }),
14398
- applied && !disabled && showClear && /* @__PURE__ */ jsxRuntime.jsx(
14849
+ !disabled && showClear && (applied || showClearWhenEmpty) && /* @__PURE__ */ jsxRuntime.jsx(
14399
14850
  "span",
14400
14851
  {
14401
14852
  role: "button",
@@ -14427,7 +14878,7 @@ function EditorPanel({
14427
14878
  onValueChange,
14428
14879
  onClose
14429
14880
  }) {
14430
- const wrapsInPaddedTop = field.type === "date";
14881
+ const wrapsInPaddedTop = field.type === "date" || field.type === "period";
14431
14882
  const isMulti = field.type === "multi";
14432
14883
  const selectableValues = isMulti ? (field.options ?? []).filter((option) => !option.disabled).map((option) => option.value) : [];
14433
14884
  const selectedValues = isMulti ? toArrayValue(value) : [];
@@ -14506,6 +14957,17 @@ function FilterEditor({
14506
14957
  disabled,
14507
14958
  onValueChange
14508
14959
  }) {
14960
+ if (field.type === "period") {
14961
+ return /* @__PURE__ */ jsxRuntime.jsx(
14962
+ PeriodPresetEditor,
14963
+ {
14964
+ field,
14965
+ value,
14966
+ disabled,
14967
+ onValueChange
14968
+ }
14969
+ );
14970
+ }
14509
14971
  if (field.type === "date") {
14510
14972
  return /* @__PURE__ */ jsxRuntime.jsx(
14511
14973
  DatePresetEditor,
@@ -14545,7 +15007,7 @@ function DatePresetEditor({
14545
15007
  disabled,
14546
15008
  onValueChange
14547
15009
  }) {
14548
- const presets = field.presets ?? [];
15010
+ const presets = getFieldPresets(field);
14549
15011
  const [selectedId, setSelectedId] = react.useState(() => matchPresetId(presets, value));
14550
15012
  const syncedInitialValueRef = react.useRef(!isEmptyValue(value));
14551
15013
  react.useEffect(() => {
@@ -14556,9 +15018,9 @@ function DatePresetEditor({
14556
15018
  const selectedPreset = presets.find((preset) => preset.value === selectedId);
14557
15019
  const selectPreset = (preset) => {
14558
15020
  setSelectedId(preset.value);
14559
- if (!preset.custom) onValueChange(preset.resolve ? preset.resolve() : null);
15021
+ if (!preset.custom) onValueChange(preset.resolve ? preset.resolve() : preset.value);
14560
15022
  };
14561
- if (presets.length === 0) {
15023
+ if (field.type === "date" && presets.length === 0) {
14562
15024
  return /* @__PURE__ */ jsxRuntime.jsx(
14563
15025
  SDatePicker,
14564
15026
  {
@@ -14594,6 +15056,130 @@ function DatePresetEditor({
14594
15056
  )
14595
15057
  ] });
14596
15058
  }
15059
+ function PeriodPresetEditor({
15060
+ field,
15061
+ value,
15062
+ disabled,
15063
+ onValueChange
15064
+ }) {
15065
+ const presets = getFieldPresets(field);
15066
+ const firstUnit = presets.find((preset) => !preset.custom)?.value ?? "day";
15067
+ const [selectedId, setSelectedId] = react.useState(() => matchPresetId(presets, value) ?? firstUnit);
15068
+ const periodValue = isPeriodValue(value) ? value : createPeriodValue(selectedId ?? firstUnit);
15069
+ const selectedUnit = selectedId ?? periodValue.unit;
15070
+ react.useEffect(() => {
15071
+ if (isEmptyValue(value)) return;
15072
+ setSelectedId(matchPresetId(presets, value) ?? firstUnit);
15073
+ }, [firstUnit, presets, value]);
15074
+ const selectPreset = (preset) => {
15075
+ const nextUnit = preset.value;
15076
+ setSelectedId(nextUnit);
15077
+ onValueChange(createPeriodValue(nextUnit));
15078
+ };
15079
+ const updateInputValue = (nextValue) => {
15080
+ onValueChange({ unit: selectedUnit, value: nextValue });
15081
+ };
15082
+ const renderInput = () => {
15083
+ if (selectedUnit === "day") {
15084
+ return /* @__PURE__ */ jsxRuntime.jsx(
15085
+ SDatePicker,
15086
+ {
15087
+ value: typeof periodValue.value === "string" ? periodValue.value : null,
15088
+ onValueChange: updateInputValue,
15089
+ selectable: field.selectable,
15090
+ disabled,
15091
+ width: "100%"
15092
+ }
15093
+ );
15094
+ }
15095
+ if (selectedUnit === "month") {
15096
+ return /* @__PURE__ */ jsxRuntime.jsx(
15097
+ SDatePicker,
15098
+ {
15099
+ mode: "month",
15100
+ value: typeof periodValue.value === "string" ? periodValue.value : null,
15101
+ onValueChange: updateInputValue,
15102
+ selectable: field.selectable,
15103
+ disabled,
15104
+ width: "100%"
15105
+ }
15106
+ );
15107
+ }
15108
+ if (selectedUnit === "quarter") {
15109
+ return /* @__PURE__ */ jsxRuntime.jsx(
15110
+ SSelect2,
15111
+ {
15112
+ value: typeof periodValue.value === "number" ? periodValue.value : 1,
15113
+ onValueChange: updateInputValue,
15114
+ emitValue: true,
15115
+ disabled,
15116
+ width: "100%",
15117
+ options: [
15118
+ { value: 1, label: "1\uBD84\uAE30" },
15119
+ { value: 2, label: "2\uBD84\uAE30" },
15120
+ { value: 3, label: "3\uBD84\uAE30" },
15121
+ { value: 4, label: "4\uBD84\uAE30" }
15122
+ ]
15123
+ }
15124
+ );
15125
+ }
15126
+ if (selectedUnit === "half") {
15127
+ return /* @__PURE__ */ jsxRuntime.jsx(
15128
+ SSelect2,
15129
+ {
15130
+ value: typeof periodValue.value === "string" ? periodValue.value : "first",
15131
+ onValueChange: updateInputValue,
15132
+ emitValue: true,
15133
+ disabled,
15134
+ width: "100%",
15135
+ options: [
15136
+ { value: "first", label: "\uC0C1\uBC18\uAE30" },
15137
+ { value: "second", label: "\uD558\uBC18\uAE30" }
15138
+ ]
15139
+ }
15140
+ );
15141
+ }
15142
+ if (selectedUnit === "year") {
15143
+ return /* @__PURE__ */ jsxRuntime.jsx(
15144
+ SDatePicker,
15145
+ {
15146
+ mode: "year",
15147
+ value: typeof periodValue.value === "string" ? periodValue.value : null,
15148
+ onValueChange: updateInputValue,
15149
+ selectable: field.selectable,
15150
+ disabled,
15151
+ width: "100%"
15152
+ }
15153
+ );
15154
+ }
15155
+ return /* @__PURE__ */ jsxRuntime.jsx(
15156
+ SDateRangePicker,
15157
+ {
15158
+ value: Array.isArray(periodValue.value) && periodValue.value.length === 2 ? periodValue.value : null,
15159
+ onValueChange: updateInputValue,
15160
+ selectable: field.selectable,
15161
+ maxRange: field.maxRange,
15162
+ disabled,
15163
+ width: "100%"
15164
+ }
15165
+ );
15166
+ };
15167
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-[var(--cmp-chipFilter-listbox-top-gap)]", children: [
15168
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-[8px]", children: presets.map((preset) => /* @__PURE__ */ jsxRuntime.jsx(
15169
+ SRadio,
15170
+ {
15171
+ val: preset.value,
15172
+ value: selectedId,
15173
+ label: preset.label,
15174
+ disabled,
15175
+ onValueChange: () => selectPreset(preset)
15176
+ },
15177
+ preset.value
15178
+ )) }),
15179
+ /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": true, className: "h-px w-full", style: { background: CHIPFILTER.listboxBorder } }),
15180
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full [&>*]:w-full", children: renderInput() })
15181
+ ] });
15182
+ }
14597
15183
  function InlineDatePresetFilter({
14598
15184
  field,
14599
15185
  value,
@@ -15598,7 +16184,9 @@ var TIMEPICKER_SIZE_CONFIG = {
15598
16184
  fontSize: 12,
15599
16185
  lineHeight: 20,
15600
16186
  minWidth: 80,
15601
- fieldMinWidth: 128
16187
+ fieldMinWidth: 128,
16188
+ // 상한 등급 md
16189
+ fieldMaxWidth: 160
15602
16190
  },
15603
16191
  md: {
15604
16192
  height: V3("md-height"),
@@ -15610,7 +16198,9 @@ var TIMEPICKER_SIZE_CONFIG = {
15610
16198
  fontSize: 14,
15611
16199
  lineHeight: 24,
15612
16200
  minWidth: 96,
15613
- fieldMinWidth: 160
16201
+ fieldMinWidth: 160,
16202
+ // 상한 등급 lg
16203
+ fieldMaxWidth: 240
15614
16204
  }
15615
16205
  };
15616
16206
  function clampTimePart(value, min, max) {
@@ -15934,6 +16524,7 @@ function STimePicker({
15934
16524
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
15935
16525
  width,
15936
16526
  minWidth: dims.fieldMinWidth,
16527
+ maxWidth: dims.fieldMaxWidth,
15937
16528
  disabled,
15938
16529
  className,
15939
16530
  style,
@@ -16050,6 +16641,10 @@ var MIDDAY_FIELD_MIN_WIDTH = {
16050
16641
  sm: 178,
16051
16642
  md: 216
16052
16643
  };
16644
+ var MIDDAY_FIELD_MAX_WIDTH = {
16645
+ sm: 240,
16646
+ md: 240
16647
+ };
16053
16648
  function RangeTimeSelector({
16054
16649
  value,
16055
16650
  useMeridiem,
@@ -16396,6 +16991,7 @@ function STimeRangePicker({
16396
16991
  errorMessage: ruleError !== "" ? ruleError : errorMessage,
16397
16992
  width,
16398
16993
  minWidth: effectiveUseMeridiem ? MIDDAY_FIELD_MIN_WIDTH[size] : dims.fieldMinWidth,
16994
+ maxWidth: effectiveUseMeridiem ? MIDDAY_FIELD_MAX_WIDTH[size] : dims.fieldMaxWidth,
16399
16995
  disabled,
16400
16996
  className,
16401
16997
  style,
@@ -16504,6 +17100,7 @@ exports.GNB_RAIL_WIDTH = GNB_RAIL_WIDTH;
16504
17100
  exports.ICONS = ICONS;
16505
17101
  exports.IMAGE_FITS = IMAGE_FITS;
16506
17102
  exports.PAGE_BACKGROUNDS = PAGE_BACKGROUNDS;
17103
+ exports.PAGE_CONTENT_HEIGHTS = PAGE_CONTENT_HEIGHTS;
16507
17104
  exports.RangeCalendar = RangeCalendar;
16508
17105
  exports.SActionModal = SActionModal;
16509
17106
  exports.SBadge = SBadge;
@@ -16520,6 +17117,8 @@ exports.SChipInput = SChipInput;
16520
17117
  exports.SCircleProgress = SCircleProgress;
16521
17118
  exports.SConfirmModal = SConfirmModal;
16522
17119
  exports.SDatePicker = SDatePicker;
17120
+ exports.SDatePickerMonthListbox = SDatePickerMonthListbox;
17121
+ exports.SDatePickerYearListbox = SDatePickerYearListbox;
16523
17122
  exports.SDateRangePicker = SDateRangePicker;
16524
17123
  exports.SDivider = SDivider;
16525
17124
  exports.SDraggableGroup = SDraggableGroup;