@farmzone/fz-react-ui 1.0.0 → 1.0.2

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
@@ -943,7 +943,7 @@ var SELECT_BORDER_COLORS = {
943
943
  };
944
944
  function Select2(props) {
945
945
  const {
946
- containerClass,
946
+ containerClassName,
947
947
  options,
948
948
  placeholder = "\uC120\uD0DD\uD574 \uC8FC\uC138\uC694",
949
949
  triggerClassName,
@@ -973,7 +973,7 @@ function Select2(props) {
973
973
  onChange("");
974
974
  onReset?.();
975
975
  };
976
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-0.5", containerClass ?? "w-30"), children: [
976
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-0.5", containerClassName ?? "w-30"), children: [
977
977
  label && /* @__PURE__ */ jsxRuntime.jsx(
978
978
  "label",
979
979
  {
@@ -1056,6 +1056,7 @@ function Radio(props) {
1056
1056
  checked,
1057
1057
  onChange,
1058
1058
  disabled = false,
1059
+ disabledMessage,
1059
1060
  checkColor = DEFAULT_CHECK_COLOR,
1060
1061
  unCheckColor = DEFAULT_UNCHECK_COLOR,
1061
1062
  labelClassName,
@@ -1104,23 +1105,29 @@ function Radio(props) {
1104
1105
  }
1105
1106
  )
1106
1107
  ] });
1108
+ const withTooltip = (node) => {
1109
+ if (!disabled || !disabledMessage) return node;
1110
+ return /* @__PURE__ */ jsxRuntime.jsx(Tooltip2, { content: disabledMessage, position: "bottom", asChild: true, sideOffset: 4, children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "inline-flex", children: node }) });
1111
+ };
1107
1112
  if (onChange !== void 0 && checked !== void 0) {
1108
- return /* @__PURE__ */ jsxRuntime.jsx(
1109
- RadioGroupPrimitive__namespace.Root,
1110
- {
1111
- name,
1112
- value: checked ? value : "",
1113
- onValueChange: (newValue) => {
1114
- if (newValue === value) {
1115
- onChange(value);
1116
- }
1117
- },
1118
- disabled,
1119
- children: control
1120
- }
1113
+ return withTooltip(
1114
+ /* @__PURE__ */ jsxRuntime.jsx(
1115
+ RadioGroupPrimitive__namespace.Root,
1116
+ {
1117
+ name,
1118
+ value: checked ? value : "",
1119
+ onValueChange: (newValue) => {
1120
+ if (newValue === value) {
1121
+ onChange(value);
1122
+ }
1123
+ },
1124
+ disabled,
1125
+ children: control
1126
+ }
1127
+ )
1121
1128
  );
1122
1129
  }
1123
- return control;
1130
+ return withTooltip(control);
1124
1131
  }
1125
1132
  function RadioGroup(props) {
1126
1133
  const {
@@ -1156,6 +1163,7 @@ function RadioGroup(props) {
1156
1163
  value: option.value,
1157
1164
  label: option.label,
1158
1165
  disabled: disabled || option.disabled,
1166
+ disabledMessage: option.disabled ? option.disabledMessage : void 0,
1159
1167
  checkColor,
1160
1168
  unCheckColor,
1161
1169
  labelClassName,
@@ -1960,6 +1968,28 @@ function parseLocalYmd(ymd) {
1960
1968
  if (dt.getFullYear() !== y || dt.getMonth() !== mo || dt.getDate() !== d) return null;
1961
1969
  return dt;
1962
1970
  }
1971
+ function canSelectToday(options) {
1972
+ const today = options.now ?? /* @__PURE__ */ new Date();
1973
+ const todayYmd = formatDate(today);
1974
+ if (options.maxDate && isCalendarDayAfterMax(today, options.maxDate)) {
1975
+ return false;
1976
+ }
1977
+ if (!options.range) {
1978
+ return true;
1979
+ }
1980
+ if (options.isSelectingEnd) {
1981
+ const start = options.rangeStart?.trim() ?? "";
1982
+ if (start.length === 10 && todayYmd < start) {
1983
+ return false;
1984
+ }
1985
+ return true;
1986
+ }
1987
+ const end = options.rangeEnd?.trim() ?? "";
1988
+ if (end.length === 10 && todayYmd > end) {
1989
+ return false;
1990
+ }
1991
+ return true;
1992
+ }
1963
1993
  function isCalendarDayAfterMax(date, maxYmd) {
1964
1994
  const max = parseLocalYmd(maxYmd);
1965
1995
  if (!max) return false;
@@ -2016,6 +2046,105 @@ function validateAndFormatInput(input) {
2016
2046
  }
2017
2047
  return `${year}-${month}-${day}`;
2018
2048
  }
2049
+
2050
+ // src/components/DatePicker/calendar/config/default.ts
2051
+ var DEFAULT_CALENDAR_YEAR_HALF_SPAN = 25;
2052
+ var CALENDAR_WEEKDAY_LABELS = ["\uC77C", "\uC6D4", "\uD654", "\uC218", "\uBAA9", "\uAE08", "\uD1A0"];
2053
+ var CALENDAR_RANGE_HINT = {
2054
+ start: "\uC2DC\uC791 \uB0A0\uC9DC\uB97C \uC120\uD0DD\uD558\uC138\uC694",
2055
+ end: "\uC885\uB8CC \uB0A0\uC9DC\uB97C \uC120\uD0DD\uD558\uC138\uC694"
2056
+ };
2057
+ var CALENDAR_PORTAL_Z_INDEX = Z_INDEX.CALENDAR_PORTAL;
2058
+ var CALENDAR_SELECT_CLASSNAMES = {
2059
+ containerClass: "w-20 shrink-0",
2060
+ triggerClassName: "h-8 px-2 py-1 text-sm cursor-pointer",
2061
+ contentClassName: "min-w-[5rem]"
2062
+ };
2063
+ var CALENDAR_PANEL_BASE_CLASS = "bg-white border border-gray-300 rounded-lg shadow-lg p-3 min-w-[280px]";
2064
+ var CALENDAR_PANEL_ANCHORED_CLASS = `${CALENDAR_PANEL_BASE_CLASS} absolute z-50`;
2065
+ var YEAR_SELECT_CONTENT_EXTRA_CLASS = "year-select-content custom-view-scrollbar max-h-50 overflow-y-auto";
2066
+ var YEAR_SELECT_OPEN_CONTENT_SELECTOR = ".year-select-content[data-state='open']";
2067
+ var SELECT_ITEM_CHECKED_SELECTOR = "[data-slot='select-item'][data-state='checked']";
2068
+
2069
+ // src/components/DatePicker/calendar/config/calendar.ts
2070
+ function parseYmdDateString(value) {
2071
+ if (value.length !== 10) return null;
2072
+ return parseLocalYmd(value);
2073
+ }
2074
+ function resolveCalendarYearBounds(anchorYear, calendarYearRange) {
2075
+ const fallbackMin = anchorYear - DEFAULT_CALENDAR_YEAR_HALF_SPAN;
2076
+ const fallbackMax = anchorYear + DEFAULT_CALENDAR_YEAR_HALF_SPAN;
2077
+ const rawMin = calendarYearRange?.minYear?.trim();
2078
+ const rawMax = calendarYearRange?.maxYear?.trim();
2079
+ if (rawMin === void 0 || rawMin === "" || rawMax === void 0 || rawMax === "") {
2080
+ return { minYear: fallbackMin, maxYear: fallbackMax };
2081
+ }
2082
+ const minParsed = Number.parseInt(rawMin, 10);
2083
+ const maxParsed = Number.parseInt(rawMax, 10);
2084
+ if (Number.isNaN(minParsed) || Number.isNaN(maxParsed) || minParsed > maxParsed) {
2085
+ return { minYear: fallbackMin, maxYear: fallbackMax };
2086
+ }
2087
+ return { minYear: minParsed, maxYear: maxParsed };
2088
+ }
2089
+ function buildCalendarYearSelectOptions(maxDate, anchorYear = (/* @__PURE__ */ new Date()).getFullYear(), calendarYearRange) {
2090
+ const { minYear, maxYear } = resolveCalendarYearBounds(anchorYear, calendarYearRange);
2091
+ const maxSelectableYear = maxDate ? parseLocalYmd(maxDate)?.getFullYear() ?? null : null;
2092
+ const options = [];
2093
+ for (let y = maxYear; y >= minYear; y--) {
2094
+ options.push({
2095
+ value: String(y),
2096
+ label: `${y}\uB144`,
2097
+ disabled: maxSelectableYear !== null && y > maxSelectableYear
2098
+ });
2099
+ }
2100
+ return options;
2101
+ }
2102
+ function buildCalendarMonthSelectOptions(viewYear, maxDate) {
2103
+ const maxDateParsed = maxDate ? parseLocalYmd(maxDate) : null;
2104
+ const maxYear = maxDateParsed?.getFullYear() ?? null;
2105
+ const maxMonth = maxDateParsed !== null && maxDateParsed !== void 0 ? maxDateParsed.getMonth() + 1 : null;
2106
+ const isMaxYear = maxYear !== null && viewYear === maxYear;
2107
+ const options = [];
2108
+ for (let m = 1; m <= 12; m++) {
2109
+ options.push({
2110
+ value: String(m),
2111
+ label: `${m}\uC6D4`,
2112
+ disabled: isMaxYear && maxMonth !== null && m > maxMonth
2113
+ });
2114
+ }
2115
+ return options;
2116
+ }
2117
+ function getCalendarMonthGrid(year, monthIndex) {
2118
+ const firstDay = new Date(year, monthIndex, 1);
2119
+ const daysInMonth = new Date(year, monthIndex + 1, 0).getDate();
2120
+ const leadingEmpty = firstDay.getDay();
2121
+ const totalCells = leadingEmpty + daysInMonth;
2122
+ const trailingEmpty = totalCells % 7 === 0 ? 0 : 7 - totalCells % 7;
2123
+ return { leadingEmpty, daysInMonth, trailingEmpty };
2124
+ }
2125
+ function disableNextCalendarMonth(year, monthIndex, maxDate) {
2126
+ if (maxDate === void 0) return false;
2127
+ return isCalendarDayAfterMax(new Date(year, monthIndex + 1, 1), maxDate);
2128
+ }
2129
+ function scrollOpenYearSelectCheckedIntoView() {
2130
+ requestAnimationFrame(() => {
2131
+ const root2 = document.querySelector(YEAR_SELECT_OPEN_CONTENT_SELECTOR);
2132
+ if (!(root2 instanceof HTMLElement)) return;
2133
+ const checkedItem = root2.querySelector(SELECT_ITEM_CHECKED_SELECTOR);
2134
+ if (!(checkedItem instanceof HTMLElement)) return;
2135
+ checkedItem.scrollIntoView({ block: "start" });
2136
+ });
2137
+ }
2138
+ function dayCellButtonClass(opts) {
2139
+ const { isToday, isSelected, isInRange, isRangeStart, isRangeEnd, interactiveDisabled } = opts;
2140
+ const parts = ["w-8 h-8 text-sm flex items-center justify-center rounded-2xl transition-colors"];
2141
+ if (isToday) parts.push("bg-gray-100");
2142
+ if (isSelected && !isToday) parts.push("bg-neutral-200 text-gray-700");
2143
+ if (isInRange && !isToday) parts.push("bg-gray-100/90");
2144
+ if ((isRangeStart || isRangeEnd) && !isToday) parts.push("bg-neutral-300 text-gray-700 opacity-100");
2145
+ parts.push(interactiveDisabled ? "opacity-20 cursor-not-allowed" : "hover:bg-blue-100 cursor-pointer");
2146
+ return parts.filter(Boolean).join(" ").trim();
2147
+ }
2019
2148
  function RenderCalendar(props) {
2020
2149
  const {
2021
2150
  currentDate,
@@ -2030,130 +2159,163 @@ function RenderCalendar(props) {
2030
2159
  goToNextMonth,
2031
2160
  onYearChange,
2032
2161
  onMonthChange,
2162
+ onSelectOpenChange,
2033
2163
  editDateSelect = true,
2034
- datePosition = "bottom"
2164
+ datePosition = "bottom",
2165
+ maxDate,
2166
+ calendarYearRange,
2167
+ calendarSelectContainerClass,
2168
+ floating = false,
2169
+ showTodayButton = true
2035
2170
  } = props;
2036
2171
  const year = currentDate.getFullYear();
2037
- const month = currentDate.getMonth();
2038
- const firstDay = new Date(year, month, 1);
2039
- const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
2040
- const yearOptions = [];
2041
- for (let i = currentYear - 25; i <= currentYear + 20; i++) {
2042
- yearOptions.push({ value: i.toString(), label: `${i}\uB144` });
2043
- }
2044
- const monthOptions = [];
2045
- for (let i = 1; i <= 12; i++) {
2046
- monthOptions.push({ value: i.toString(), label: `${i}\uC6D4` });
2047
- }
2048
- const lastDay = new Date(year, month + 1, 0);
2049
- const startDate = new Date(firstDay);
2050
- startDate.setDate(startDate.getDate() - firstDay.getDay());
2051
- const endDate = new Date(lastDay);
2052
- endDate.setDate(endDate.getDate() + (6 - endDate.getDay()));
2053
- const totalDays = Math.ceil((endDate.getTime() - startDate.getTime()) / (1e3 * 60 * 60 * 24)) + 1;
2172
+ const monthIndex = currentDate.getMonth();
2173
+ const anchorYear = (/* @__PURE__ */ new Date()).getFullYear();
2174
+ const disableNextMonth = disableNextCalendarMonth(year, monthIndex, maxDate);
2175
+ const { leadingEmpty, daysInMonth, trailingEmpty } = getCalendarMonthGrid(year, monthIndex);
2176
+ const yearOptions = buildCalendarYearSelectOptions(maxDate, anchorYear, calendarYearRange);
2177
+ const monthOptions = buildCalendarMonthSelectOptions(year, maxDate);
2178
+ const todayFormatted = formatDate(/* @__PURE__ */ new Date());
2179
+ const selectedSingleYmd = range ? null : inputValue;
2180
+ const rangeStartYmd = rangeStart.trim();
2181
+ const rangeEndYmd = rangeEnd.trim();
2054
2182
  const days = [];
2055
- const currentDateFormatted = formatDate(/* @__PURE__ */ new Date());
2056
- const selectedDate = range ? null : inputValue;
2057
- const handleStopPropagation = (e) => e.stopPropagation();
2058
- const handleYearChange = (value) => onYearChange(parseInt(value));
2059
- const handleMonthChange = (value) => onMonthChange(parseInt(value) - 1);
2060
- for (let i = 0; i < totalDays; i++) {
2061
- const date = new Date(startDate);
2062
- date.setDate(startDate.getDate() + i);
2183
+ for (let i = 0; i < leadingEmpty; i++) {
2184
+ days.push(/* @__PURE__ */ jsxRuntime.jsx("div", { className: "size-8", "aria-hidden": true }, `lead-${i}`));
2185
+ }
2186
+ for (let d = 1; d <= daysInMonth; d++) {
2187
+ const date = new Date(year, monthIndex, d);
2063
2188
  const dateFormatted = formatDate(date);
2064
- const isToday = dateFormatted === currentDateFormatted;
2065
- const isSelected = dateFormatted === selectedDate;
2189
+ const isToday = dateFormatted === todayFormatted;
2190
+ const isSelected = dateFormatted === selectedSingleYmd;
2066
2191
  const isInRange = isDateInRange(date, range, rangeStart, rangeEnd);
2067
- const isRangeStart = range && dateFormatted === rangeStart;
2068
- const isRangeEnd = range && dateFormatted === rangeEnd;
2069
- const isCurrentMonth = date.getMonth() === month;
2070
- const rangeStartDate = new Date(rangeStart);
2071
- rangeStartDate.setDate(rangeStartDate.getDate() - 1);
2072
- const isDisabled = range && isSelectingEnd && rangeStart && date < rangeStartDate;
2192
+ const isRangeStartFlag = Boolean(range && dateFormatted === rangeStart);
2193
+ const isRangeEndFlag = Boolean(range && dateFormatted === rangeEnd);
2194
+ const isBeforeRangeStart = range && isSelectingEnd && rangeStartYmd.length === 10 && dateFormatted < rangeStartYmd;
2195
+ const isAfterRangeEnd = range && !isSelectingEnd && rangeEndYmd.length === 10 && dateFormatted > rangeEndYmd;
2196
+ const isGreyedForRange = Boolean(isBeforeRangeStart || isAfterRangeEnd);
2197
+ const isPastMax = maxDate ? isCalendarDayAfterMax(date, maxDate) : false;
2073
2198
  days.push(
2074
2199
  /* @__PURE__ */ jsxRuntime.jsx(
2075
2200
  "button",
2076
2201
  {
2202
+ type: "button",
2077
2203
  onClick: () => handleDateSelect(date),
2078
- disabled: isDisabled || false,
2079
- className: `
2080
- w-8 h-8 text-sm flex items-center justify-center rounded-2xl transition-colors
2081
- ${!isCurrentMonth ? "text-gray-300" : ""}
2082
- ${isToday ? "bg-gray-100 " : ""}
2083
- ${isInRange && !isToday ? "bg-gray-100/90" : ""}
2084
- ${isSelected && !isToday ? "bg-neutral-200 text-gray-700" : ""}
2085
- ${(isRangeStart || isRangeEnd) && !isToday ? "bg-neutral-300 text-gray-700 opacity-100" : ""}
2086
- ${isDisabled ? "opacity-20 cursor-not-allowed" : "hover:bg-blue-100 cursor-pointer"}
2087
- `,
2088
- children: date.getDate()
2204
+ disabled: isGreyedForRange || isPastMax,
2205
+ className: dayCellButtonClass({
2206
+ isToday,
2207
+ isSelected: Boolean(isSelected),
2208
+ isInRange,
2209
+ isRangeStart: isRangeStartFlag,
2210
+ isRangeEnd: isRangeEndFlag,
2211
+ interactiveDisabled: isGreyedForRange || isPastMax
2212
+ }),
2213
+ children: d
2089
2214
  },
2090
- i
2215
+ d
2091
2216
  )
2092
2217
  );
2093
2218
  }
2094
- const calendarPositionClass = range && calendarPosition === "end" ? "right-0" : "left-0";
2095
- const datePositionClass = datePosition === "top" ? "bottom-full mb-1" : "top-full mt-1";
2096
- return /* @__PURE__ */ jsxRuntime.jsxs(
2097
- "div",
2098
- {
2099
- className: `${datePositionClass} ${calendarPositionClass} bg-white border border-gray-300 rounded-lg shadow-lg p-3 z-50 min-w-[280px]`,
2100
- onClick: handleStopPropagation,
2101
- onMouseDown: handleStopPropagation,
2102
- children: [
2103
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-1", children: [
2104
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: goToPreviousMonth, className: "p-1 hover:bg-gray-100 rounded cursor-pointer", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "size-4 text-balck" }) }),
2105
- editDateSelect ? /* @__PURE__ */ jsxRuntime.jsxs(
2106
- "div",
2107
- {
2108
- className: "flex items-center gap-2",
2109
- onClick: handleStopPropagation,
2110
- onMouseDown: handleStopPropagation,
2111
- children: [
2112
- /* @__PURE__ */ jsxRuntime.jsx("div", { onClick: handleStopPropagation, onMouseDown: handleStopPropagation, children: /* @__PURE__ */ jsxRuntime.jsx(
2113
- Select2,
2114
- {
2115
- value: year.toString(),
2116
- options: yearOptions,
2117
- onChange: handleYearChange,
2118
- className: "cursor-pointer"
2119
- }
2120
- ) }),
2121
- /* @__PURE__ */ jsxRuntime.jsx("div", { onClick: handleStopPropagation, onMouseDown: handleStopPropagation, children: /* @__PURE__ */ jsxRuntime.jsx(
2122
- Select2,
2123
- {
2124
- value: (month + 1).toString(),
2125
- options: monthOptions,
2126
- onChange: handleMonthChange,
2127
- className: " cursor-pointer"
2128
- }
2129
- ) })
2130
- ]
2131
- }
2132
- ) : /* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "text-lg font-semibold", children: [
2133
- year,
2134
- "\uB144 ",
2135
- month + 1,
2136
- "\uC6D4"
2137
- ] }),
2138
- /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: goToNextMonth, className: "p-1 hover:bg-gray-100 rounded cursor-pointer", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "size-4" }) })
2139
- ] }),
2140
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 gap-1 mb-1", children: ["\uC77C", "\uC6D4", "\uD654", "\uC218", "\uBAA9", "\uAE08", "\uD1A0"].map((day) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center text-sm font-medium text-gray-500 py-2 ", children: day }, day)) }),
2141
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 gap-2", children: days })
2142
- ]
2143
- }
2144
- );
2145
- }
2146
-
2147
- // src/components/DatePicker/calendar/config/default.ts
2148
- var CALENDAR_PORTAL_Z_INDEX = Z_INDEX.CALENDAR_PORTAL;
2149
-
2150
- // src/components/DatePicker/calendar/config/calendar.ts
2151
- function parseYmdDateString(value) {
2152
- if (value.length !== 10) return null;
2153
- return parseLocalYmd(value);
2219
+ for (let i = 0; i < trailingEmpty; i++) {
2220
+ days.push(/* @__PURE__ */ jsxRuntime.jsx("div", { className: "size-8", "aria-hidden": true }, `trail-${i}`));
2221
+ }
2222
+ const calendarPositionClass = !floating && range && calendarPosition === "end" ? "right-0" : !floating ? "left-0" : "";
2223
+ const datePositionClass = floating ? "" : datePosition === "top" ? "bottom-full mb-1" : "top-full mt-1";
2224
+ const panelClass = floating ? CALENDAR_PANEL_BASE_CLASS : cn(CALENDAR_PANEL_ANCHORED_CLASS, datePositionClass, calendarPositionClass);
2225
+ const handleYearSelectOpenChange = (open) => {
2226
+ onSelectOpenChange?.(open);
2227
+ if (open) scrollOpenYearSelectCheckedIntoView();
2228
+ };
2229
+ const sel = CALENDAR_SELECT_CLASSNAMES;
2230
+ const containerClass = cn(sel.containerClass, calendarSelectContainerClass);
2231
+ const stopBubble = (e) => e.stopPropagation();
2232
+ const isTodaySelectable = canSelectToday({
2233
+ maxDate,
2234
+ range,
2235
+ isSelectingEnd,
2236
+ rangeStart,
2237
+ rangeEnd
2238
+ });
2239
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: panelClass, onClick: stopBubble, onMouseDown: stopBubble, children: [
2240
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-1 flex items-center justify-between", children: [
2241
+ /* @__PURE__ */ jsxRuntime.jsx(
2242
+ "button",
2243
+ {
2244
+ type: "button",
2245
+ onClick: goToPreviousMonth,
2246
+ className: "cursor-pointer rounded p-1 hover:bg-gray-100",
2247
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "size-4" })
2248
+ }
2249
+ ),
2250
+ editDateSelect ? /* @__PURE__ */ jsxRuntime.jsxs(
2251
+ "div",
2252
+ {
2253
+ role: "presentation",
2254
+ className: "flex items-center gap-2",
2255
+ onClick: stopBubble,
2256
+ onMouseDown: stopBubble,
2257
+ children: [
2258
+ /* @__PURE__ */ jsxRuntime.jsx(
2259
+ Select2,
2260
+ {
2261
+ value: year.toString(),
2262
+ options: yearOptions,
2263
+ onChange: (value) => onYearChange(Number.parseInt(value, 10)),
2264
+ containerClassName: containerClass,
2265
+ triggerClassName: sel.triggerClassName,
2266
+ contentClassName: cn(YEAR_SELECT_CONTENT_EXTRA_CLASS, sel.contentClassName),
2267
+ disableScrollButtonEvents: true,
2268
+ onOpenChange: handleYearSelectOpenChange
2269
+ }
2270
+ ),
2271
+ /* @__PURE__ */ jsxRuntime.jsx(
2272
+ Select2,
2273
+ {
2274
+ value: (monthIndex + 1).toString(),
2275
+ options: monthOptions,
2276
+ onChange: (value) => onMonthChange(Number.parseInt(value, 10) - 1),
2277
+ containerClassName: containerClass,
2278
+ triggerClassName: sel.triggerClassName,
2279
+ contentClassName: sel.contentClassName,
2280
+ onOpenChange: (open) => onSelectOpenChange?.(open)
2281
+ }
2282
+ )
2283
+ ]
2284
+ }
2285
+ ) : /* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "text-lg font-semibold", children: [
2286
+ year,
2287
+ "\uB144 ",
2288
+ monthIndex + 1,
2289
+ "\uC6D4"
2290
+ ] }),
2291
+ /* @__PURE__ */ jsxRuntime.jsx(
2292
+ "button",
2293
+ {
2294
+ type: "button",
2295
+ onClick: goToNextMonth,
2296
+ disabled: disableNextMonth,
2297
+ className: cn(
2298
+ "rounded p-1",
2299
+ disableNextMonth ? "cursor-not-allowed opacity-30" : "cursor-pointer hover:bg-gray-100"
2300
+ ),
2301
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "size-4" })
2302
+ }
2303
+ )
2304
+ ] }),
2305
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1 grid grid-cols-7 gap-1", children: CALENDAR_WEEKDAY_LABELS.map((day) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-2 text-center text-sm font-medium text-gray-500", children: day }, day)) }),
2306
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 gap-2", children: days }),
2307
+ range && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 text-sm text-gray-600", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-center", children: isSelectingEnd ? CALENDAR_RANGE_HINT.end : CALENDAR_RANGE_HINT.start }) }),
2308
+ showTodayButton && isTodaySelectable && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "-mx-3 -mb-3 mt-3 flex items-center justify-center border-t border-gray-200 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(
2309
+ "button",
2310
+ {
2311
+ type: "button",
2312
+ onClick: () => handleDateSelect(/* @__PURE__ */ new Date()),
2313
+ className: "cursor-pointer text-sm text-gray-800 hover:text-main",
2314
+ children: "Today"
2315
+ }
2316
+ ) })
2317
+ ] });
2154
2318
  }
2155
-
2156
- // src/components/DatePicker/calendar/hooks/useCalendarState.ts
2157
2319
  function useCalendarState(props) {
2158
2320
  const { datePosition = "bottom", disabled = false, calendarRef } = props;
2159
2321
  const [isCalendarOpen, setIsCalendarOpen] = React6.useState(false);
@@ -2250,11 +2412,15 @@ function useFloatingCalendarPosition(anchorRef, isOpen2, placement, align = "sta
2250
2412
  setStyle({ ...base, top: rect.bottom + CALENDAR_GAP });
2251
2413
  }
2252
2414
  };
2415
+ const handleScroll = (e) => {
2416
+ if (e.target?.closest?.('[data-slot="select-content"]')) return;
2417
+ update();
2418
+ };
2253
2419
  update();
2254
- window.addEventListener("scroll", update, true);
2420
+ window.addEventListener("scroll", handleScroll, true);
2255
2421
  window.addEventListener("resize", update);
2256
2422
  return () => {
2257
- window.removeEventListener("scroll", update, true);
2423
+ window.removeEventListener("scroll", handleScroll, true);
2258
2424
  window.removeEventListener("resize", update);
2259
2425
  };
2260
2426
  }, [isOpen2, placement, align, anchorRef]);
@@ -2292,6 +2458,7 @@ function DatePickerRange(props) {
2292
2458
  disabled = false,
2293
2459
  maxDate,
2294
2460
  calendarYearRange,
2461
+ calendarSelectContainerClass,
2295
2462
  showTodayButton = true
2296
2463
  } = props;
2297
2464
  const [inputs, setInputs] = React6.useState({ start: startValue, end: endValue });
@@ -2428,6 +2595,7 @@ function DatePickerRange(props) {
2428
2595
  datePosition: autoDatePosition,
2429
2596
  maxDate,
2430
2597
  calendarYearRange,
2598
+ calendarSelectContainerClass,
2431
2599
  showTodayButton
2432
2600
  }
2433
2601
  )
@@ -2507,6 +2675,7 @@ function DatePickerSingle(props) {
2507
2675
  disabled = false,
2508
2676
  maxDate,
2509
2677
  calendarYearRange,
2678
+ calendarSelectContainerClass,
2510
2679
  onBlur,
2511
2680
  showTodayButton = true
2512
2681
  } = props;
@@ -2599,6 +2768,7 @@ function DatePickerSingle(props) {
2599
2768
  datePosition: autoDatePosition,
2600
2769
  maxDate,
2601
2770
  calendarYearRange,
2771
+ calendarSelectContainerClass,
2602
2772
  showTodayButton
2603
2773
  }
2604
2774
  )
@@ -2657,6 +2827,7 @@ function DatePicker(props) {
2657
2827
  disabled,
2658
2828
  maxDate,
2659
2829
  calendarYearRange,
2830
+ calendarSelectContainerClass,
2660
2831
  showTodayButton
2661
2832
  } = props;
2662
2833
  if (isRange) {
@@ -2674,6 +2845,7 @@ function DatePicker(props) {
2674
2845
  disabled,
2675
2846
  maxDate,
2676
2847
  calendarYearRange,
2848
+ calendarSelectContainerClass,
2677
2849
  showTodayButton
2678
2850
  }
2679
2851
  );
@@ -2693,6 +2865,7 @@ function DatePicker(props) {
2693
2865
  disabled,
2694
2866
  maxDate,
2695
2867
  calendarYearRange,
2868
+ calendarSelectContainerClass,
2696
2869
  showTodayButton,
2697
2870
  onBlur
2698
2871
  }
@@ -3558,7 +3731,7 @@ function TableFooter(props) {
3558
3731
  const { paginationInfo, renderLeft, renderRight } = props;
3559
3732
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between py-4 min-h-16", children: [
3560
3733
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-start flex-1 min-w-0", children: renderLeft }),
3561
- paginationInfo && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 px-5", children: /* @__PURE__ */ jsxRuntime.jsx(Pagination, { ...paginationInfo }) }),
3734
+ paginationInfo && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-5", children: /* @__PURE__ */ jsxRuntime.jsx(Pagination, { ...paginationInfo }) }),
3562
3735
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end flex-1 min-w-0", children: renderRight })
3563
3736
  ] }) });
3564
3737
  }
@@ -3714,7 +3887,7 @@ function useTableFixed(columns, isHeader = false) {
3714
3887
  window.addEventListener("resize", onResize);
3715
3888
  const firstKey = columns[0]?.key;
3716
3889
  const cell = firstKey ? document.getElementById(`${firstKey}${isHeader ? "-header" : "-body"}`) : null;
3717
- const tableEl = cell?.closest("table") || document.querySelector("table.table-layout");
3890
+ const tableEl = cell?.closest("table") || document.querySelector("table.table-fixed");
3718
3891
  if (tableEl && "ResizeObserver" in window) {
3719
3892
  resizeObserverRef.current = new ResizeObserver(handleResize);
3720
3893
  resizeObserverRef.current.observe(tableEl);
@@ -3801,19 +3974,14 @@ function TableHeader(props) {
3801
3974
  const columns = React6.useMemo(() => {
3802
3975
  return customColumns.map((column) => {
3803
3976
  const { key } = column;
3804
- const getWidth = () => {
3805
- if (key === "__checkbox__") {
3806
- return "40px";
3807
- }
3808
- return column.width ?? "calc(100%/4)";
3809
- };
3810
- const getAlign = () => {
3811
- if (key === "__checkbox__") {
3812
- return "center";
3813
- }
3814
- return column.align ?? "left";
3977
+ const isCheckbox = key === "__checkbox__";
3978
+ return {
3979
+ ...column,
3980
+ width: isCheckbox ? "40px" : column.width,
3981
+ minWidth: isCheckbox ? "40px" : column.minWidth ?? column.width,
3982
+ maxWidth: isCheckbox ? "40px" : column.maxWidth,
3983
+ align: isCheckbox ? "center" : column.align ?? "left"
3815
3984
  };
3816
- return { ...column, width: getWidth(), align: getAlign() };
3817
3985
  });
3818
3986
  }, [customColumns]);
3819
3987
  const { getColumnFixedStyle } = useTableFixed(columns, true);
@@ -4286,11 +4454,11 @@ function SkeletonTd(props) {
4286
4454
  return /* @__PURE__ */ jsxRuntime.jsx(
4287
4455
  "td",
4288
4456
  {
4289
- className: "px-2 py-[6.8px] whitespace-nowrap text-sm border-r border-b border-gray-200/70 last:border-r-0",
4457
+ className: `${col.key === "__checkbox__" ? "p-0" : "px-2 py-[6.8px]"} whitespace-nowrap text-sm border-r border-b border-gray-200/70 last:border-r-0`,
4290
4458
  style: {
4291
4459
  width: col.width,
4292
4460
  minWidth: col.minWidth || col.width,
4293
- maxWidth: col.maxWidth || col.width
4461
+ maxWidth: col.key === "__checkbox__" ? col.maxWidth || col.width : void 0
4294
4462
  },
4295
4463
  children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center min-h-4", children: showSquare ? /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "size-4 rounded-xs" }) : /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "h-1.5 rounded-sm", style: { width: "calc(100% - 20px)" } }) })
4296
4464
  }
@@ -4371,7 +4539,7 @@ function getAlignStyle(align) {
4371
4539
  return align === "left" ? "flex-start" : align === "center" ? "center" : "flex-end";
4372
4540
  }
4373
4541
  function applyColDefaults(col) {
4374
- const width = col.key === "__checkbox__" ? "40px" : col.width ?? col.minWidth ?? "100px";
4542
+ const width = col.key === "__checkbox__" ? "40px" : col.width ?? col.minWidth;
4375
4543
  const align = col.key === "__checkbox__" ? "center" : col.align ?? "left";
4376
4544
  return { ...col, width, align };
4377
4545
  }
@@ -4820,17 +4988,18 @@ function Table(props) {
4820
4988
  const { width, minWidth, maxWidth, align } = col;
4821
4989
  const fixedStyle = isPinned ? getColumnFixedStyleRef.current(col) : void 0;
4822
4990
  const cellContent = col.render ? col.render(record[col.key], record, rowIdx) : String(record[col.key] ?? "");
4991
+ const isCheckbox = col.key === "__checkbox__";
4823
4992
  return /* @__PURE__ */ jsxRuntime.jsx(
4824
4993
  "td",
4825
4994
  {
4826
4995
  id: `${col.key}-body`,
4827
4996
  "data-row-cell": "true",
4828
4997
  "data-col-key": col.key,
4829
- className: "px-2 py-[6.8px] whitespace-nowrap text-sm text-gray-900 border-r border-b border-gray-200 last:border-r-0 bg-inherit group-hover:bg-gray-100 transition-all",
4998
+ className: `${isCheckbox ? "p-0" : "px-2 py-[6.8px]"} whitespace-nowrap text-sm text-gray-900 border-r border-b border-gray-200 last:border-r-0 bg-inherit group-hover:bg-gray-100 transition-all`,
4830
4999
  style: {
4831
5000
  width,
4832
5001
  minWidth: minWidth || width,
4833
- maxWidth: maxWidth || width,
5002
+ maxWidth: isCheckbox ? maxWidth || width : void 0,
4834
5003
  ...fixedStyle
4835
5004
  },
4836
5005
  onClick: (event) => {
@@ -4926,7 +5095,7 @@ function Table(props) {
4926
5095
  ref: scrollRef,
4927
5096
  className: "relative w-full overflow-y-auto overflow-x-auto custom-view-scrollbar bg-white",
4928
5097
  style: { height: `${tableHeight}px` },
4929
- children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full table-layout", children: [
5098
+ children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full table-fixed", children: [
4930
5099
  /* @__PURE__ */ jsxRuntime.jsx(
4931
5100
  TableHeader,
4932
5101
  {
@@ -6026,6 +6195,7 @@ var FORM_CONTROL_CLASS = "h-8 w-full rounded border border-gray-300 bg-white px-
6026
6195
  function formControlClass(hasError, extra) {
6027
6196
  return cn2(FORM_CONTROL_CLASS, FORM_CONTROL_FOCUS_CLASS, hasError && FORM_FIELD_ERROR_BORDER_CLASS, extra);
6028
6197
  }
6198
+ var FIELD_READONLY_TEXT_CLASS = "text-sm text-gray-800 leading-tight";
6029
6199
  function fieldControlWrapClass(className) {
6030
6200
  return className ? cn2("min-w-0 shrink-0", className) : "min-w-0 w-full";
6031
6201
  }
@@ -6372,6 +6542,7 @@ function AddressField(props) {
6372
6542
  const {
6373
6543
  placeholder = "\uC8FC\uC18C\uB97C \uAC80\uC0C9\uD558\uC138\uC694",
6374
6544
  disabled = false,
6545
+ readOnly = false,
6375
6546
  className = "",
6376
6547
  addressDetailKey,
6377
6548
  addressClassNames
@@ -6386,6 +6557,9 @@ function AddressField(props) {
6386
6557
  searchButtonRef.current?.click();
6387
6558
  }
6388
6559
  }, [isFocused]);
6560
+ if (readOnly) {
6561
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: FIELD_READONLY_TEXT_CLASS, children: String(value ?? "") || "\u2014" }) });
6562
+ }
6389
6563
  const handleComplete = (data) => {
6390
6564
  isAddressSelectedRef.current = true;
6391
6565
  const nextAddress = data.roadAddress || data.address;
@@ -6504,12 +6678,12 @@ function CascadingSelectField(props) {
6504
6678
  }
6505
6679
  function CascadingSelectFieldInner(props) {
6506
6680
  const {
6507
- field: { onChange, value },
6681
+ field: { onChange, onBlur, value },
6508
6682
  config,
6509
6683
  hasError = false,
6510
6684
  cascadingSelect
6511
6685
  } = props;
6512
- const { disabled = false, className = "", cascadingSelectClassNames } = config;
6686
+ const { disabled = false, readOnly = false, className = "", cascadingSelectClassNames } = config;
6513
6687
  const {
6514
6688
  primaryOptions,
6515
6689
  getSecondaryOptions,
@@ -6523,10 +6697,17 @@ function CascadingSelectFieldInner(props) {
6523
6697
  [formValue, getSecondaryOptions, separator]
6524
6698
  );
6525
6699
  React6.useLayoutEffect(() => {
6700
+ if (readOnly) return;
6526
6701
  if (normalizedFormValue !== formValue) {
6527
6702
  onChange(normalizedFormValue);
6528
6703
  }
6529
- }, [formValue, normalizedFormValue, onChange]);
6704
+ }, [formValue, normalizedFormValue, onChange, readOnly]);
6705
+ if (readOnly) {
6706
+ const primaryLabel = primaryOptions.find((o) => String(o.value) === primary)?.label ?? primary;
6707
+ const secondaryLabel = secondary ? secondaryOptions.find((o) => String(o.value) === secondary)?.label ?? secondary : "";
6708
+ const displayText = secondaryLabel ? `${primaryLabel} ${secondaryLabel}` : primaryLabel || "\u2014";
6709
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: FIELD_READONLY_TEXT_CLASS, children: displayText }) });
6710
+ }
6530
6711
  const updateValue = (nextPrimary, nextSecondary) => {
6531
6712
  onChange(createCascadingSelectValue(nextPrimary, nextSecondary, separator));
6532
6713
  };
@@ -6547,11 +6728,12 @@ function CascadingSelectFieldInner(props) {
6547
6728
  disabled,
6548
6729
  hasError,
6549
6730
  options: toSelectOptions(primaryOptions),
6550
- containerClass: firstSelectClass.containerClassName,
6731
+ containerClassName: firstSelectClass.containerClassName,
6551
6732
  triggerClassName: firstSelectClass.triggerClassName,
6552
6733
  canReset: true,
6553
6734
  onChange: (nextPrimary) => {
6554
6735
  updateValue(nextPrimary, "");
6736
+ if (!nextPrimary) onBlur();
6555
6737
  }
6556
6738
  }
6557
6739
  ),
@@ -6563,11 +6745,12 @@ function CascadingSelectFieldInner(props) {
6563
6745
  disabled: disabled || !primary || secondaryOptions.length === 0,
6564
6746
  hasError,
6565
6747
  options: toSelectOptions(secondaryOptions),
6566
- containerClass: secondSelectClass.containerClassName,
6748
+ containerClassName: secondSelectClass.containerClassName,
6567
6749
  triggerClassName: secondSelectClass.triggerClassName,
6568
6750
  canReset: true,
6569
6751
  onChange: (nextSecondary) => {
6570
6752
  updateValue(primary, nextSecondary);
6753
+ onBlur();
6571
6754
  }
6572
6755
  }
6573
6756
  )
@@ -6578,7 +6761,11 @@ function CheckboxField(props) {
6578
6761
  field: { onChange, value },
6579
6762
  config
6580
6763
  } = props;
6581
- const { disabled = false, checkboxLabel, className = "" } = config;
6764
+ const { disabled = false, readOnly = false, checkboxLabel, className = "" } = config;
6765
+ if (readOnly) {
6766
+ const text = value ? checkboxLabel ?? "\uC120\uD0DD\uB428" : "\u2014";
6767
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: FIELD_READONLY_TEXT_CLASS, children: text }) });
6768
+ }
6582
6769
  return /* @__PURE__ */ jsxRuntime.jsx(
6583
6770
  Checkbox,
6584
6771
  {
@@ -6599,11 +6786,16 @@ function DateField(props) {
6599
6786
  const {
6600
6787
  placeholder = "YYYY-MM-DD",
6601
6788
  disabled = false,
6789
+ readOnly = false,
6602
6790
  className = "w-40",
6603
6791
  datePosition = "bottom",
6604
- disableFutureDates = false
6792
+ disableFutureDates = false,
6793
+ calendarSelectContainerClass = "w-20"
6605
6794
  } = config;
6606
6795
  const maxDate = disableFutureDates ? formatDate(/* @__PURE__ */ new Date()) : void 0;
6796
+ if (readOnly) {
6797
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn2("flex items-center", fieldControlWrapClass(className)), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: FIELD_READONLY_TEXT_CLASS, children: String(value ?? "") || "\u2014" }) });
6798
+ }
6607
6799
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn2("flex items-center", fieldControlWrapClass(className)), children: /* @__PURE__ */ jsxRuntime.jsx(
6608
6800
  DatePicker,
6609
6801
  {
@@ -6615,7 +6807,8 @@ function DateField(props) {
6615
6807
  className: "w-full",
6616
6808
  validation: hasError,
6617
6809
  datePosition,
6618
- maxDate
6810
+ maxDate,
6811
+ calendarSelectContainerClass
6619
6812
  }
6620
6813
  ) });
6621
6814
  }
@@ -6631,11 +6824,17 @@ function InputField(props) {
6631
6824
  readOnly = false,
6632
6825
  maxLength,
6633
6826
  className = "",
6634
- type = "input"
6827
+ type = "input",
6828
+ numbersOnly = false
6635
6829
  } = config;
6636
6830
  const inputType = type === "email" ? "email" : type === "password" ? "password" : "text";
6831
+ if (readOnly) {
6832
+ const text = type === "password" ? value ? "**********" : "" : String(value ?? "") || "";
6833
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: FIELD_READONLY_TEXT_CLASS, children: text }) });
6834
+ }
6637
6835
  const handleChange = (e) => {
6638
- onChange(e.target.value);
6836
+ const value2 = numbersOnly ? e.target.value.replace(/\D/g, "") : e.target.value;
6837
+ onChange(value2);
6639
6838
  };
6640
6839
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx(
6641
6840
  Input2,
@@ -6666,7 +6865,7 @@ function InputWithButtonField(props) {
6666
6865
  placeholder = "\uC870\uD68C \uBC84\uD2BC\uC73C\uB85C \uC120\uD0DD",
6667
6866
  disabled = false,
6668
6867
  className = "",
6669
- readOnly = true,
6868
+ readOnly = false,
6670
6869
  inputWithButton,
6671
6870
  onClickInputWithButton
6672
6871
  } = config;
@@ -6675,13 +6874,16 @@ function InputWithButtonField(props) {
6675
6874
  if (disabled) return;
6676
6875
  onClickInputWithButton?.();
6677
6876
  };
6877
+ if (readOnly) {
6878
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: FIELD_READONLY_TEXT_CLASS, children: String(value ?? "") || "\u2014" }) });
6879
+ }
6678
6880
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full min-w-0 items-center gap-2", children: [
6679
6881
  /* @__PURE__ */ jsxRuntime.jsx(
6680
6882
  Input2,
6681
6883
  {
6682
6884
  placeholder,
6683
6885
  value: String(value ?? ""),
6684
- readOnly,
6886
+ readOnly: true,
6685
6887
  disabled,
6686
6888
  bare: true,
6687
6889
  className: formControlClass(hasError),
@@ -6721,6 +6923,9 @@ function NumberField(props) {
6721
6923
  }
6722
6924
  };
6723
6925
  const displayValue = value != null && value !== "" ? isMoney ? formatNumberWithCommas(value) : String(value) : "";
6926
+ if (readOnly) {
6927
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: FIELD_READONLY_TEXT_CLASS, children: displayValue || "\u2014" }) });
6928
+ }
6724
6929
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx(
6725
6930
  Input2,
6726
6931
  {
@@ -6750,6 +6955,9 @@ function PhoneField(props) {
6750
6955
  const handleChange = (e) => {
6751
6956
  onChange(formatPhoneNumber(e.target.value));
6752
6957
  };
6958
+ if (readOnly) {
6959
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: FIELD_READONLY_TEXT_CLASS, children: String(value ?? "") || "\u2014" }) });
6960
+ }
6753
6961
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx(
6754
6962
  Input2,
6755
6963
  {
@@ -6774,7 +6982,11 @@ function RadioField(props) {
6774
6982
  field: { onChange, value, name },
6775
6983
  config
6776
6984
  } = props;
6777
- const { disabled = false, options = [], className = "" } = config;
6985
+ const { disabled = false, readOnly = false, options = [], className = "" } = config;
6986
+ if (readOnly) {
6987
+ const label = options.find((o) => String(o.value) === String(value ?? ""))?.label ?? String(value ?? "");
6988
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: FIELD_READONLY_TEXT_CLASS, children: label || "\u2014" }) });
6989
+ }
6778
6990
  return /* @__PURE__ */ jsxRuntime.jsx(
6779
6991
  RadioGroup,
6780
6992
  {
@@ -6786,7 +6998,9 @@ function RadioField(props) {
6786
6998
  containerClassName: cn2("justify-start", fieldControlWrapClass(className)),
6787
6999
  options: options.map((option) => ({
6788
7000
  label: option.label,
6789
- value: String(option.value)
7001
+ value: String(option.value),
7002
+ disabled: option.disabled,
7003
+ disabledMessage: option.disabledMessage
6790
7004
  }))
6791
7005
  }
6792
7006
  );
@@ -6806,11 +7020,16 @@ function SelectField(props) {
6806
7020
  const {
6807
7021
  placeholder = "\uC120\uD0DD\uD558\uC138\uC694",
6808
7022
  disabled = false,
7023
+ readOnly = false,
6809
7024
  options = [],
6810
7025
  className = "",
6811
7026
  canReset = false
6812
7027
  } = config;
6813
7028
  const selectOptions = toSelectOptions2(options);
7029
+ if (readOnly) {
7030
+ const label = selectOptions.find((o) => o.value === String(value ?? ""))?.label ?? String(value ?? "");
7031
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: FIELD_READONLY_TEXT_CLASS, children: label || "\u2014" }) });
7032
+ }
6814
7033
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx(
6815
7034
  Select2,
6816
7035
  {
@@ -6819,7 +7038,7 @@ function SelectField(props) {
6819
7038
  disabled,
6820
7039
  hasError,
6821
7040
  options: selectOptions,
6822
- containerClass: "flex w-full min-w-0 max-w-full items-center",
7041
+ containerClassName: "flex w-full min-w-0 max-w-full items-center",
6823
7042
  className: "w-full min-w-0",
6824
7043
  triggerClassName: cn2(
6825
7044
  "h-8 w-full min-w-0 max-w-full rounded border bg-white px-2 py-1 text-sm shadow-none",
@@ -6857,16 +7076,6 @@ function combineSelectWithInputValue(prefix, input) {
6857
7076
  return `${prefix}${input}`;
6858
7077
  }
6859
7078
  var DEFAULT_SELECT_TRIGGER = "h-8 rounded border bg-white px-2 text-sm shadow-none min-w-[5rem]";
6860
- function resolveSelectWithInputState(formValue, defaultPrefix, options) {
6861
- const parsed = parseSelectWithInputValue(formValue, defaultPrefix);
6862
- const validPrefixes = new Set(options.map((opt) => String(opt.value)));
6863
- const prefix = validPrefixes.has(parsed.prefix) ? parsed.prefix : defaultPrefix;
6864
- return {
6865
- prefix,
6866
- inputPart: parsed.input,
6867
- normalizedFormValue: combineSelectWithInputValue(prefix, parsed.input)
6868
- };
6869
- }
6870
7079
  function SelectWithInputField(props) {
6871
7080
  const {
6872
7081
  field: { onChange, onBlur, value },
@@ -6875,37 +7084,58 @@ function SelectWithInputField(props) {
6875
7084
  } = props;
6876
7085
  const {
6877
7086
  disabled = false,
7087
+ readOnly = false,
6878
7088
  options = [],
6879
7089
  maxLength,
6880
7090
  className = "",
6881
7091
  selectWithInput,
6882
7092
  selectWithInputClassNames
6883
7093
  } = config;
6884
- const inputMode = selectWithInput?.inputMode ?? "number";
7094
+ const validationCtx = useSubmitFormValidation();
7095
+ const numbersOnly = selectWithInput?.numbersOnly ?? selectWithInput?.inputMode !== "text";
6885
7096
  const defaultPrefix = String(options[0]?.value ?? "");
6886
7097
  const numberInputRef = React6.useRef(null);
6887
7098
  const formValue = String(value ?? "");
6888
- const { prefix, inputPart, normalizedFormValue } = React6.useMemo(
6889
- () => resolveSelectWithInputState(formValue, defaultPrefix, options),
6890
- [defaultPrefix, formValue, options]
6891
- );
7099
+ const { prefix, inputPart, normalizedFormValue } = React6.useMemo(() => {
7100
+ const parsed = parseSelectWithInputValue(formValue, defaultPrefix);
7101
+ const validPrefixes = new Set(options.map((opt) => String(opt.value)));
7102
+ const resolvedPrefix = validPrefixes.has(parsed.prefix) ? parsed.prefix : defaultPrefix;
7103
+ return {
7104
+ prefix: resolvedPrefix,
7105
+ inputPart: parsed.input,
7106
+ normalizedFormValue: combineSelectWithInputValue(resolvedPrefix, parsed.input)
7107
+ };
7108
+ }, [defaultPrefix, formValue, options]);
7109
+ const [selectedPrefix, setSelectedPrefix] = React6.useState(prefix);
7110
+ const [prevFormValue, setPrevFormValue] = React6.useState(formValue);
7111
+ if (prevFormValue !== formValue) {
7112
+ setPrevFormValue(formValue);
7113
+ setSelectedPrefix(formValue ? prefix : defaultPrefix);
7114
+ }
6892
7115
  React6.useLayoutEffect(() => {
6893
- if (normalizedFormValue !== formValue) {
7116
+ if (readOnly) return;
7117
+ if (formValue && normalizedFormValue !== formValue) {
6894
7118
  onChange(normalizedFormValue);
6895
7119
  }
6896
- }, [formValue, normalizedFormValue, onChange]);
7120
+ }, [formValue, normalizedFormValue, onChange, readOnly]);
7121
+ if (readOnly) {
7122
+ const prefixLabel = options.find((o) => String(o.value) === prefix)?.label ?? prefix;
7123
+ const displayText = inputPart ? `${prefixLabel} ${inputPart}` : prefix ? prefixLabel : "\u2014";
7124
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: FIELD_READONLY_TEXT_CLASS, children: displayText || "\u2014" }) });
7125
+ }
6897
7126
  const updateCombined = (nextPrefix, nextInput) => {
6898
7127
  onChange(combineSelectWithInputValue(nextPrefix, nextInput));
6899
7128
  };
6900
7129
  const normalizeInput = (raw) => {
6901
- if (inputMode === "number") {
6902
- return raw.replace(/\D+/g, "");
6903
- }
7130
+ if (numbersOnly) return raw.replace(/\D+/g, "");
6904
7131
  return raw;
6905
7132
  };
6906
7133
  const handleInputChange = (raw) => {
6907
7134
  const normalized = normalizeInput(raw.normalize("NFKC"));
6908
- updateCombined(prefix, normalized);
7135
+ if (normalized) {
7136
+ validationCtx?.markFieldHadValue(String(config.name));
7137
+ }
7138
+ updateCombined(selectedPrefix, normalized);
6909
7139
  };
6910
7140
  const selectClass = resolveSelectClassNameProp(selectWithInputClassNames?.selectClassName, {
6911
7141
  containerClassName: "shrink-0",
@@ -6915,15 +7145,17 @@ function SelectWithInputField(props) {
6915
7145
  /* @__PURE__ */ jsxRuntime.jsx(
6916
7146
  Select2,
6917
7147
  {
6918
- value: prefix,
7148
+ value: selectedPrefix,
6919
7149
  placeholder: "\uC120\uD0DD",
6920
7150
  disabled,
6921
7151
  hasError,
6922
7152
  options: toSelectOptions(options),
6923
- containerClass: selectClass.containerClassName,
7153
+ containerClassName: selectClass.containerClassName,
6924
7154
  triggerClassName: selectClass.triggerClassName,
6925
7155
  onChange: (nextPrefix) => {
7156
+ setSelectedPrefix(nextPrefix);
6926
7157
  updateCombined(nextPrefix, inputPart);
7158
+ onBlur();
6927
7159
  }
6928
7160
  }
6929
7161
  ),
@@ -6941,7 +7173,7 @@ function SelectWithInputField(props) {
6941
7173
  },
6942
7174
  onBlur: (e) => {
6943
7175
  const normalized = normalizeInput(e.target.value.normalize("NFKC"));
6944
- handleInputChange(normalized);
7176
+ updateCombined(selectedPrefix, normalized);
6945
7177
  onBlur();
6946
7178
  }
6947
7179
  }
@@ -6953,7 +7185,10 @@ function SwitchField(props) {
6953
7185
  field: { onChange, value },
6954
7186
  config
6955
7187
  } = props;
6956
- const { disabled = false, className = "" } = config;
7188
+ const { disabled = false, readOnly = false, className = "" } = config;
7189
+ if (readOnly) {
7190
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: FIELD_READONLY_TEXT_CLASS, children: value ? "\uCF1C\uC9D0" : "\uAEBC\uC9D0" }) });
7191
+ }
6957
7192
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx(BaseSwitch, { checked: Boolean(value), onCheckedChange: onChange, disabled }) });
6958
7193
  }
6959
7194
  function TextareaField(props) {
@@ -6966,6 +7201,9 @@ function TextareaField(props) {
6966
7201
  const handleChange = (e) => {
6967
7202
  onChange(e.target.value);
6968
7203
  };
7204
+ if (readOnly) {
7205
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn2(FIELD_READONLY_TEXT_CLASS, "whitespace-pre-wrap"), children: String(value ?? "") || "\u2014" }) });
7206
+ }
6969
7207
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: fieldControlWrapClass(className), children: /* @__PURE__ */ jsxRuntime.jsx(
6970
7208
  Textarea,
6971
7209
  {
@@ -7052,7 +7290,7 @@ function useBlurValidation(name, options) {
7052
7290
  (value, fieldOnChange) => {
7053
7291
  markHadValueIfFilled(value);
7054
7292
  fieldOnChange(value);
7055
- if (options.revalidateOnChange || shouldValidateEmptyOnChange(value)) {
7293
+ if (options.revalidateOnChange || options.validateEmptyOnChange && shouldValidateEmptyOnChange(value)) {
7056
7294
  void trigger(name);
7057
7295
  }
7058
7296
  void triggerRelatedFields();
@@ -7060,6 +7298,7 @@ function useBlurValidation(name, options) {
7060
7298
  [
7061
7299
  markHadValueIfFilled,
7062
7300
  options.revalidateOnChange,
7301
+ options.validateEmptyOnChange,
7063
7302
  shouldValidateEmptyOnChange,
7064
7303
  trigger,
7065
7304
  name,
@@ -7080,9 +7319,7 @@ var REVALIDATE_ON_CHANGE_TYPES = /* @__PURE__ */ new Set([
7080
7319
  "radio",
7081
7320
  "switch",
7082
7321
  "select",
7083
- "date",
7084
7322
  "cascading-select",
7085
- "select-with-input",
7086
7323
  "input-with-button",
7087
7324
  "address"
7088
7325
  ]);
@@ -7103,14 +7340,15 @@ function FormFieldRow(props) {
7103
7340
  const { control } = reactHookForm.useFormContext();
7104
7341
  const { isSubmitted } = reactHookForm.useFormState({ control });
7105
7342
  const { name, label, type = "input", required = false, renderCustomField, contentAlign = "left" } = config;
7343
+ const revalidateOnChange = REVALIDATE_ON_CHANGE_TYPES.has(type);
7106
7344
  const { handleBlur, handleChange } = useBlurValidation(name, {
7107
7345
  required,
7108
- revalidateOnChange: REVALIDATE_ON_CHANGE_TYPES.has(type),
7346
+ revalidateOnChange,
7347
+ validateEmptyOnChange: revalidateOnChange,
7109
7348
  revalidateFields: config.revalidateFields
7110
7349
  });
7111
7350
  const showError = Boolean(fieldState.error?.message) && (isSubmitted || fieldState.isTouched);
7112
7351
  const isTextarea = type === "textarea";
7113
- const revalidateOnChange = REVALIDATE_ON_CHANGE_TYPES.has(type);
7114
7352
  const wrappedField = wrapFieldHandlers(field, {
7115
7353
  onBlur: () => handleBlur(() => field.onBlur()),
7116
7354
  onChange: (value) => {
@@ -7211,9 +7449,11 @@ function rowPropsToFieldConfig(props) {
7211
7449
  options,
7212
7450
  className,
7213
7451
  allowDecimal,
7452
+ numbersOnly,
7214
7453
  canReset,
7215
7454
  datePosition,
7216
7455
  disableFutureDates,
7456
+ calendarSelectContainerClass,
7217
7457
  revalidateFields,
7218
7458
  renderCustomField,
7219
7459
  colspan,
@@ -7240,9 +7480,11 @@ function rowPropsToFieldConfig(props) {
7240
7480
  options,
7241
7481
  className,
7242
7482
  allowDecimal,
7483
+ numbersOnly,
7243
7484
  canReset,
7244
7485
  datePosition,
7245
7486
  disableFutureDates,
7487
+ calendarSelectContainerClass,
7246
7488
  revalidateFields,
7247
7489
  renderCustomField,
7248
7490
  colspan,
@@ -7402,14 +7644,7 @@ function PageFilter(props) {
7402
7644
  const rowGap2 = index !== 0 ? option.label ? gap : 5 : 0;
7403
7645
  switch (option.type) {
7404
7646
  case "input":
7405
- return /* @__PURE__ */ jsxRuntime.jsx(LabeledFilterOption, { label: option.label, gap: rowGap2, children: /* @__PURE__ */ jsxRuntime.jsx(
7406
- Input2,
7407
- {
7408
- placeholder: option.placeholder,
7409
- value: currentValue,
7410
- onChange: handleInputChange(optionKey)
7411
- }
7412
- ) }, String(optionKey));
7647
+ return /* @__PURE__ */ jsxRuntime.jsx(LabeledFilterOption, { label: option.label, gap: rowGap2, children: /* @__PURE__ */ jsxRuntime.jsx(Input2, { placeholder: option.placeholder, value: currentValue, onChange: handleInputChange(optionKey) }) }, String(optionKey));
7413
7648
  case "select":
7414
7649
  return /* @__PURE__ */ jsxRuntime.jsx(LabeledFilterOption, { label: option.label, gap: rowGap2, children: /* @__PURE__ */ jsxRuntime.jsx(
7415
7650
  Select2,
@@ -7417,7 +7652,8 @@ function PageFilter(props) {
7417
7652
  value: currentValue,
7418
7653
  onChange: handleSelectChange(optionKey),
7419
7654
  options: option.options,
7420
- placeholder: "\uC120\uD0DD\uD574\uC8FC\uC138\uC694"
7655
+ placeholder: "\uC120\uD0DD",
7656
+ containerClassName: option.containerClassName ?? "w-30"
7421
7657
  }
7422
7658
  ) }, String(optionKey));
7423
7659
  case "radio":