@skalfa/skalfa-component 1.0.52 → 1.0.54

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.
@@ -8,7 +8,7 @@ const react_1 = require("react");
8
8
  const _utils_1 = require("@utils");
9
9
  const OutsideClick_component_1 = require("../wrap/OutsideClick.component");
10
10
  ;
11
- function SelectComponent({ name, label, placeholder, tip, leftIcon, rightIcon, value, invalid, disabled, validations, multiple, autoFocus, clearable, options = [], searchable, serverOptionControl, idbOptionControl, serverSearchable, includedOptions = [], exceptOptions = [], tempOptions, newOption, maxShowOption = 10, creatable, creatableLabel, register, unregister, onChange, onFocus, onBlur, className = "", }) {
11
+ function SelectComponent({ name, label, placeholder, tip, leftIcon, rightIcon, value, invalid, disabled, validations, multiple, autoFocus, clearable, options = [], searchable, serverOptionControl, idbOptionControl, serverSearchable, includedOptions = [], exceptOptions = [], tempOptions, newOption, maxShowOption, creatable, creatableLabel, customOption, selectableOption, register, unregister, onChange, onFocus, onBlur, className = "", }) {
12
12
  const [inputShowValue, setInputShowValue] = (0, react_1.useState)("");
13
13
  const [keydown, setKeydown] = (0, react_1.useState)(false);
14
14
  const [useTemp, setUseTemp] = (0, react_1.useState)(true);
@@ -86,15 +86,33 @@ function SelectComponent({ name, label, placeholder, tip, leftIcon, rightIcon, v
86
86
  if (dataOptions?.length || creatableKey) {
87
87
  let newFilteredOptions = [];
88
88
  if (searchable && !serverSearchable && dataOptions?.length) {
89
- if (e.target.value) {
90
- newFilteredOptions = dataOptions.filter((Option) => Option.label?.toLowerCase().indexOf(e.target.value.toLowerCase()) > -1).slice(0, maxShowOption);
89
+ if (e?.target?.value) {
90
+ const val = e.target.value.toLowerCase();
91
+ const matched = dataOptions.filter((opt) => {
92
+ if (opt.searchable?.some((s) => s.toLowerCase().indexOf(val) > -1))
93
+ return true;
94
+ if (typeof opt.label === "string" && opt.label.toLowerCase().indexOf(val) > -1)
95
+ return true;
96
+ if (typeof opt.code === "string" && opt.code.toLowerCase().indexOf(val) > -1)
97
+ return true;
98
+ if (typeof opt.name === "string" && opt.name.toLowerCase().indexOf(val) > -1)
99
+ return true;
100
+ return false;
101
+ });
102
+ newFilteredOptions = typeof maxShowOption === "number" && maxShowOption > 0
103
+ ? matched.slice(0, maxShowOption)
104
+ : matched;
91
105
  }
92
106
  else {
93
- newFilteredOptions = dataOptions.slice(0, maxShowOption);
107
+ newFilteredOptions = typeof maxShowOption === "number" && maxShowOption > 0
108
+ ? dataOptions.slice(0, maxShowOption)
109
+ : dataOptions;
94
110
  }
95
111
  }
96
112
  else {
97
- newFilteredOptions = dataOptions || [];
113
+ newFilteredOptions = typeof maxShowOption === "number" && maxShowOption > 0
114
+ ? (dataOptions || []).slice(0, maxShowOption)
115
+ : (dataOptions || []);
98
116
  }
99
117
  setActiveOption(-1);
100
118
  setFilteredOptions(newFilteredOptions);
@@ -152,30 +170,57 @@ function SelectComponent({ name, label, placeholder, tip, leftIcon, rightIcon, v
152
170
  };
153
171
  const fetchOptions = async () => {
154
172
  setLoadingOption(true);
173
+ const selectableOpt = selectableOption || serverOptionControl?.selectableOption || serverOptionControl?.params?.selectableOption;
155
174
  const serverControl = {
156
175
  ...serverOptionControl,
157
- params: serverSearchable ? { search: keywordSearch, ...(serverOptionControl?.params || {}) } : (serverOptionControl?.params || {}),
158
- headers: { "X-Option": 1 }
176
+ params: {
177
+ ...(serverOptionControl?.params || {}),
178
+ ...(selectableOpt ? { selectableOption: selectableOpt } : {}),
179
+ ...(serverSearchable ? { search: keywordSearch } : {}),
180
+ },
181
+ headers: { "X-Option": 1, ...(serverOptionControl?.headers || {}) }
159
182
  };
160
- const getCacheOptions = await _utils_1.cavity.get(serverOptionControl?.cacheName || `option_${serverOptionControl?.path}`);
161
- const cacheOptions = (getCacheOptions?.data || []);
162
- if (cacheOptions?.length) {
163
- setDataOptions([...cacheOptions, ...includedOptions].filter((op) => !exceptOptions?.includes(op.value)));
164
- setLoadingOption(false);
183
+ const cacheKey = typeof serverOptionControl?.cacheName === "string" && serverOptionControl.cacheName.trim() !== ""
184
+ ? serverOptionControl.cacheName
185
+ : serverOptionControl?.cacheName === true
186
+ ? `option_${serverOptionControl?.path || serverOptionControl?.url}`
187
+ : null;
188
+ if (cacheKey) {
189
+ const getCacheOptions = await _utils_1.cavity.get(cacheKey);
190
+ const cacheData = getCacheOptions?.data;
191
+ const cacheOptions = (Array.isArray(cacheData?.data) ? cacheData.data : Array.isArray(cacheData) ? cacheData : []);
192
+ if (cacheOptions?.length) {
193
+ const cached = [...cacheOptions, ...includedOptions].filter((op) => !exceptOptions?.includes(op.value));
194
+ setDataOptions(cached);
195
+ setFilteredOptions(typeof maxShowOption === "number" && maxShowOption > 0 ? cached.slice(0, maxShowOption) : cached);
196
+ setLoadingOption(false);
197
+ return;
198
+ }
165
199
  }
166
- else {
167
- const mutateOptions = await (0, _utils_1.api)(serverControl || {});
168
- setDataOptions([...(mutateOptions?.data?.data || []), ...(includedOptions || [])].filter((op) => !exceptOptions?.includes(op.value)));
169
- setShowOption(true);
170
- if (serverOptionControl?.cacheName != false) {
171
- _utils_1.cavity.set({
172
- key: serverOptionControl?.cacheName || `option_${serverOptionControl?.path}`,
173
- data: mutateOptions?.data,
174
- expired: 5,
175
- });
200
+ const mutateOptions = await (0, _utils_1.api)(serverControl || {});
201
+ const fetchedData = mutateOptions?.data?.data || mutateOptions?.data || [];
202
+ const optionsData = Array.isArray(fetchedData) ? fetchedData.map((item) => {
203
+ if (item && typeof item === "object") {
204
+ return {
205
+ ...item,
206
+ name: item.name ?? item.label,
207
+ label: item.label ?? item.name ?? item.code ?? item.value,
208
+ };
176
209
  }
177
- setLoadingOption(false);
210
+ return item;
211
+ }) : [];
212
+ const finalOptions = [...optionsData, ...(includedOptions || [])].filter((op) => !exceptOptions?.includes(op.value));
213
+ setDataOptions(finalOptions);
214
+ setFilteredOptions(typeof maxShowOption === "number" && maxShowOption > 0 ? finalOptions.slice(0, maxShowOption) : finalOptions);
215
+ setShowOption(true);
216
+ if (cacheKey) {
217
+ _utils_1.cavity.set({
218
+ key: cacheKey,
219
+ data: mutateOptions?.data,
220
+ expired: 5,
221
+ });
178
222
  }
223
+ setLoadingOption(false);
179
224
  };
180
225
  const fetchIdbOptions = async () => {
181
226
  setLoadingOption(true);
@@ -195,6 +240,7 @@ function SelectComponent({ name, label, placeholder, tip, leftIcon, rightIcon, v
195
240
  };
196
241
  });
197
242
  setDataOptions(rows);
243
+ setFilteredOptions(typeof maxShowOption === "number" && maxShowOption > 0 ? rows.slice(0, maxShowOption) : rows);
198
244
  setLoadingOption(false);
199
245
  }
200
246
  };
@@ -225,7 +271,7 @@ function SelectComponent({ name, label, placeholder, tip, leftIcon, rightIcon, v
225
271
  isFocusingCreatable.current = false;
226
272
  setShowOption(false);
227
273
  inputHandler.setFocus(false);
228
- }, children: (0, jsx_runtime_1.jsxs)("div", { className: "input-container", children: [(0, jsx_runtime_1.jsxs)("label", { htmlFor: randomId, className: (0, _utils_1.cn)("input-label", disabled && "input-label-disabled", inputHandler.focus && "input-label-focus", invalidMessage && "input-label-error", (0, _utils_1.pcn)(className, "label"), disabled && (0, _utils_1.pcn)(className, "label", "disabled"), inputHandler.focus && (0, _utils_1.pcn)(className, "label", "focus"), invalidMessage && (0, _utils_1.pcn)(className, "label", "error")), children: [label, validations && _utils_1.validation.hasRules(validations, "required") && (0, jsx_runtime_1.jsx)("span", { className: "text-danger ml-1", children: "*" })] }), tip && ((0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-tip", disabled && "input-tip-disabled", (0, _utils_1.pcn)(className, "tip"), disabled && (0, _utils_1.pcn)(className, "tip", "disabled")), children: tip })), (0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [isCreatingNew && typeof inputHandler.value === "object" && inputHandler.value !== null ? (Object.entries(inputHandler.value).map(([subK, subV]) => ((0, jsx_runtime_1.jsx)("input", { type: "hidden", name: `${name.endsWith("_id") ? name.slice(0, -3) : name}[${subK}]`, value: String(subV ?? "") }, subK)))) : ((0, jsx_runtime_1.jsx)("input", { type: "hidden", value: !multiple ? (typeof inputHandler.value === "object" ? JSON.stringify(inputHandler.value) : String(inputHandler.value ?? "")) : Array().concat(inputHandler.value).map((val) => String(val)), name: name })), isCreatingNew && ((0, jsx_runtime_1.jsx)("div", { className: "absolute left-1.5 top-1/2 -translate-y-1/2 bg-stroke text-sm px-3.5 py-1.5 rounded-lg select-none pointer-events-none z-10 font-normal", children: "Baru" })), (0, jsx_runtime_1.jsx)("input", { type: "text", readOnly: !searchable, id: randomId, placeholder: !inputHandler.value || (Array.isArray(inputHandler.value) && !inputHandler.value.length) ? placeholder : "", disabled: disabled, className: (0, _utils_1.cn)("input cursor-pointer", leftIcon && "input-with-left-icon", rightIcon && "input-with-right-icon", isCreatingNew && "pl-[4.8rem]", invalidMessage && "input-error", (0, _utils_1.pcn)(className, "input"), invalidMessage && (0, _utils_1.pcn)(className, "input", "error")), value: (useTemp && tempOptions ? tempOptions.at(0)?.label : serverSearchable ? keyword : (isCreatingNew ? customText : inputShowValue)), onChange: (e) => {
274
+ }, children: (0, jsx_runtime_1.jsxs)("div", { className: "input-container", children: [(0, jsx_runtime_1.jsxs)("label", { htmlFor: randomId, className: (0, _utils_1.cn)("input-label", disabled && "input-label-disabled", inputHandler.focus && "input-label-focus", invalidMessage && "input-label-error", (0, _utils_1.pcn)(className, "label"), disabled && (0, _utils_1.pcn)(className, "label", "disabled"), inputHandler.focus && (0, _utils_1.pcn)(className, "label", "focus"), invalidMessage && (0, _utils_1.pcn)(className, "label", "error")), children: [label, validations && _utils_1.validation.hasRules(validations, "required") && (0, jsx_runtime_1.jsx)("span", { className: "text-danger ml-1", children: "*" })] }), tip && ((0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-tip", disabled && "input-tip-disabled", (0, _utils_1.pcn)(className, "tip"), disabled && (0, _utils_1.pcn)(className, "tip", "disabled")), children: tip })), (0, jsx_runtime_1.jsxs)("div", { className: "relative", children: [isCreatingNew && typeof inputHandler.value === "object" && inputHandler.value !== null ? (Object.entries(inputHandler.value).map(([subK, subV]) => ((0, jsx_runtime_1.jsx)("input", { type: "hidden", name: `${name.endsWith("_id") ? name.slice(0, -3) : name}[${subK}]`, value: String(subV ?? "") }, subK)))) : ((0, jsx_runtime_1.jsx)("input", { type: "hidden", value: !multiple ? (typeof inputHandler.value === "object" ? JSON.stringify(inputHandler.value) : String(inputHandler.value ?? "")) : Array().concat(inputHandler.value).map((val) => String(val)), name: name })), isCreatingNew && ((0, jsx_runtime_1.jsx)("div", { className: "absolute left-1.5 top-1/2 -translate-y-1/2 bg-stroke text-sm px-3.5 py-1.5 rounded-lg select-none pointer-events-none z-10 font-normal", children: "Baru" })), (0, jsx_runtime_1.jsx)("input", { type: "text", readOnly: !searchable, id: randomId, placeholder: !inputHandler.value || (Array.isArray(inputHandler.value) && !inputHandler.value.length) ? placeholder : "", disabled: disabled, className: (0, _utils_1.cn)("input cursor-pointer pr-10", clearable && "pr-18", leftIcon && "input-with-left-icon", rightIcon && "input-with-right-icon", isCreatingNew && "pl-[4.8rem]", invalidMessage && "input-error", (0, _utils_1.pcn)(className, "input"), invalidMessage && (0, _utils_1.pcn)(className, "input", "error")), value: (useTemp && tempOptions ? tempOptions.at(0)?.label : serverSearchable ? keyword : (isCreatingNew ? customText : inputShowValue)), onChange: (e) => {
229
275
  setUseTemp(false);
230
276
  searchable && setInputShowValue(e.target.value);
231
277
  serverSearchable && setKeyword(e.target.value);
@@ -286,16 +332,16 @@ function SelectComponent({ name, label, placeholder, tip, leftIcon, rightIcon, v
286
332
  }, onKeyDown: (e) => {
287
333
  dataOptions?.length && onKeyDownOption(e);
288
334
  }, autoComplete: "off", autoFocus: autoFocus }), (multiple && !searchable || (searchable && !inputHandler.focus)) && ((0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)("select-multiple-values", leftIcon ? "select-multiple-values-left-icon" : "select-multiple-values-no-icon"), style: { maxWidth: `calc(100% - ${leftIcon ? "5.2rem" : "3.2rem"})` }, children: (0, jsx_runtime_1.jsx)("div", { className: "input-values-container", children: multiple && typeof inputHandler.value != "string" && Array().concat(inputHandler.value)?.map((item, key) => {
289
- return ((0, jsx_runtime_1.jsxs)("div", { className: "input-values-item", children: [(0, jsx_runtime_1.jsx)("span", { children: dataOptions?.find((option) => option.value == item)?.label }), (0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { icon: "solid/times", className: "input-values-delete", onClick: () => {
290
- const values = Array().concat(inputHandler.value);
291
- const index = values.findIndex((val) => val == item);
292
- inputHandler.setValue(values.filter((_, val) => val != index));
293
- if (!values.filter((_, val) => val != index)?.length) {
294
- setInputShowValue("");
295
- serverSearchable && setKeyword("");
296
- onChange?.("");
297
- }
298
- } })] }, key));
335
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "input-values-item", children: [(0, jsx_runtime_1.jsx)("span", { children: dataOptions?.find((option) => option.value == item)?.label }), (0, jsx_runtime_1.jsx)("div", { className: "input-values-delete", children: (0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { icon: "solid/times", onClick: () => {
336
+ const values = Array().concat(inputHandler.value);
337
+ const index = values.findIndex((val) => val == item);
338
+ inputHandler.setValue(values.filter((_, val) => val != index));
339
+ if (!values.filter((_, val) => val != index)?.length) {
340
+ setInputShowValue("");
341
+ serverSearchable && setKeyword("");
342
+ onChange?.("");
343
+ }
344
+ } }) })] }, key));
299
345
  }) }) })), leftIcon && ((0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { className: (0, _utils_1.cn)("input-icon", "input-icon-left", disabled && "input-icon-disabled", inputHandler.focus && "input-icon-focus", (0, _utils_1.pcn)(className, "icon"), disabled && (0, _utils_1.pcn)(className, "icon", "disabled"), inputHandler.focus && (0, _utils_1.pcn)(className, "icon", "focus")), icon: leftIcon })), !multiple && clearable && inputHandler.value && ((0, jsx_runtime_1.jsx)("div", { className: (0, _utils_1.cn)("input-icon", "input-icon-clear", disabled && "input-icon-disabled", (0, _utils_1.pcn)(className, "icon"), disabled && (0, _utils_1.pcn)(className, "icon", "disabled")), onClick: () => {
300
346
  setIsCreatingNew(false);
301
347
  setCustomText("");
@@ -339,7 +385,7 @@ function SelectComponent({ name, label, placeholder, tip, leftIcon, rightIcon, v
339
385
  }, children: (0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { icon: "solid/check", className: "w-4 h-4" }) })] })), (newOption ? [newOption, ...filteredOptions] : filteredOptions).map((option, key) => {
340
386
  const selected = !isCreatingNew && (!!((typeof inputHandler.value == "string" || typeof inputHandler.value == "number") && inputHandler.value == option.value) ||
341
387
  (Array.isArray(inputHandler.value) && Array().concat(inputHandler.value).find((val) => val == option.value)));
342
- return ((0, jsx_runtime_1.jsxs)("li", { className: (0, _utils_1.cn)("input-suggest", (key == activeOption || selected) && "input-suggest-active", (0, _utils_1.pcn)(className, "suggest-item"), (key == activeOption || selected) && (0, _utils_1.pcn)(className, "suggest-item", "active")), onMouseDown: () => {
388
+ return ((0, jsx_runtime_1.jsxs)("li", { className: (0, _utils_1.cn)("input-suggest flex items-center", (key == activeOption || selected) && "input-suggest-active", (0, _utils_1.pcn)(className, "suggest-item"), (key == activeOption || selected) && (0, _utils_1.pcn)(className, "suggest-item", "active")), onMouseDown: () => {
343
389
  setKeydown(true);
344
390
  setTimeout(() => inputHandler.setFocus(true), 110);
345
391
  }, onMouseUp: () => {
@@ -349,8 +395,11 @@ function SelectComponent({ name, label, placeholder, tip, leftIcon, rightIcon, v
349
395
  setShowOption(false);
350
396
  setIsCreatingNew(false);
351
397
  if (!multiple) {
352
- setInputShowValue(option.label);
353
- serverSearchable && setKeyword(option.label);
398
+ const displayLabel = typeof option.label === "string" || typeof option.label === "number"
399
+ ? option.label
400
+ : (option.name || option.code || option.value);
401
+ setInputShowValue(displayLabel);
402
+ serverSearchable && setKeyword(typeof displayLabel === "string" ? displayLabel : "");
354
403
  inputHandler.setValue(option.value);
355
404
  onChange?.(option.value, option);
356
405
  }
@@ -370,6 +419,10 @@ function SelectComponent({ name, label, placeholder, tip, leftIcon, rightIcon, v
370
419
  }
371
420
  }
372
421
  setTimeout(() => inputHandler.setFocus(false), 120);
373
- }, children: [selected && ((0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { icon: "solid/check", className: "select-suggest-check" })), option.label] }, key));
422
+ }, children: [selected && ((0, jsx_runtime_1.jsx)(skalfa_icon_1.Icon, { icon: "solid/check", className: "select-suggest-check" })), customOption
423
+ ? customOption(option)
424
+ : (option.customOption
425
+ ? (typeof option.customOption === "function" ? option.customOption(option) : option.customOption)
426
+ : (option.customLabel || option.label))] }, key));
374
427
  })] }) })), invalidMessage && ((0, jsx_runtime_1.jsx)("small", { className: (0, _utils_1.cn)("input-error-message", (0, _utils_1.pcn)(className, "error")), children: invalidMessage }))] }) }));
375
428
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skalfa/skalfa-component",
3
- "version": "1.0.52",
3
+ "version": "1.0.54",
4
4
  "description": "Reusable UI components for Skalfa App.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -111,7 +111,7 @@ export function InputDateComponent({
111
111
  value={inputHandler.value}
112
112
  onChange={(e) => {
113
113
  inputHandler.setValue(e.target.value);
114
- inputHandler.setValue(false);
114
+ inputHandler.setIdle(false);
115
115
  onChange?.(e.target.value);
116
116
  }}
117
117
  onFocus={(e) => {
@@ -158,9 +158,12 @@ export function InputDateComponent({
158
158
  {!isSm && inputHandler.focus && (
159
159
  <div className="input-date-picker-popover">
160
160
  <InputDatePickerComponent
161
+ value={inputHandler.value}
161
162
  onChange={(e) => {
162
163
  inputHandler.setValue(e);
164
+ inputHandler.setIdle(false);
163
165
  onChange?.(e);
166
+ inputHandler.setFocus(false);
164
167
  }}
165
168
  />
166
169
  </div>
@@ -191,8 +194,10 @@ export function InputDateComponent({
191
194
  >
192
195
  <div className="p-4">
193
196
  <InputDatePickerComponent
197
+ value={inputHandler.value}
194
198
  onChange={(e) => {
195
199
  inputHandler.setValue(e);
200
+ inputHandler.setIdle(false);
196
201
  onChange?.(e);
197
202
  }}
198
203
  />
@@ -206,6 +211,7 @@ export function InputDateComponent({
206
211
 
207
212
 
208
213
  export interface InputDatePickerProps {
214
+ value ?: string;
209
215
  onChange ?: (date: string) => void;
210
216
  minDate ?: string;
211
217
  maxDate ?: string;
@@ -215,6 +221,7 @@ export interface InputDatePickerProps {
215
221
 
216
222
 
217
223
  export const InputDatePickerComponent: React.FC<InputDatePickerProps> = ({
224
+ value,
218
225
  onChange,
219
226
  minDate,
220
227
  maxDate,
@@ -223,35 +230,51 @@ export const InputDatePickerComponent: React.FC<InputDatePickerProps> = ({
223
230
  const activeYearRef = useRef<HTMLDivElement | null>(null);
224
231
  const containerYearRef = useRef<HTMLDivElement | null>(null);
225
232
 
226
- const [currentDate, setCurrentDate] = useState(moment());
227
- const [selectedDate, setSelectedDate] = useState(moment());
233
+ const initialDate = useMemo(() => {
234
+ return value && moment(value).isValid() ? moment(value) : moment();
235
+ }, [value]);
236
+
237
+ const [currentDate, setCurrentDate] = useState(initialDate);
238
+ const [selectedDate, setSelectedDate] = useState(initialDate);
239
+
240
+ useEffect(() => {
241
+ if (value && moment(value).isValid()) {
242
+ const m = moment(value);
243
+ setCurrentDate(m);
244
+ setSelectedDate(m);
245
+ }
246
+ }, [value]);
228
247
 
229
248
  const startDate = moment(currentDate).startOf("month").startOf("week");
230
249
  const endDate = moment(currentDate).endOf("month").endOf("week");
231
250
 
232
- const handlePrevMonth = () => setCurrentDate(moment(currentDate).subtract(1, "month"));
233
- const handleNextMonth = () => setCurrentDate(moment(currentDate).add(1, "month"));
251
+ const handlePrevMonth = (e?: React.MouseEvent) => {
252
+ e?.preventDefault();
253
+ e?.stopPropagation();
254
+ setCurrentDate(moment(currentDate).subtract(1, "month"));
255
+ };
256
+ const handleNextMonth = (e?: React.MouseEvent) => {
257
+ e?.preventDefault();
258
+ e?.stopPropagation();
259
+ setCurrentDate(moment(currentDate).add(1, "month"));
260
+ };
234
261
 
235
262
  const handleDateClick = (date: moment.Moment) => {
236
- if ((minDate && date.isBefore(moment(minDate))) || (maxDate && date.isAfter(moment(maxDate)))) { return; }
263
+ if ((minDate && date.isBefore(moment(minDate), "day")) || (maxDate && date.isAfter(moment(maxDate), "day"))) {
264
+ return;
265
+ }
237
266
 
238
267
  setSelectedDate(date);
239
268
  onChange?.(date.format("YYYY-MM-DD"));
240
269
  };
241
270
 
242
271
  const renderDays = () => {
243
- const days = [];
244
- const startDay = moment(startDate);
245
-
246
- for (let i = 0; i < 7; i++) {
247
- days.push(
248
- <div key={i} className="text-center font-bold">
249
- {startDay.add(i, "days").format("dd")}
250
- </div>
251
- );
252
- }
253
-
254
- return days;
272
+ const dayNames = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
273
+ return dayNames.map((d, i) => (
274
+ <div key={i} className="text-center text-[11px] font-semibold text-light-foreground">
275
+ {d}
276
+ </div>
277
+ ));
255
278
  };
256
279
 
257
280
  const renderCells = () => {
@@ -262,23 +285,35 @@ export const InputDatePickerComponent: React.FC<InputDatePickerProps> = ({
262
285
  while (day.isBefore(endDate) || day.isSame(endDate, "day")) {
263
286
  for (let i = 0; i < 7; i++) {
264
287
  const cloneDay = moment(day);
288
+ const isCurrentMonth = day.isSame(currentDate, "month");
289
+ const isSelected = day.isSame(selectedDate, "day");
290
+ const isToday = day.isSame(moment(), "day");
291
+ const isDisabled =
292
+ (minDate && day.isBefore(moment(minDate), "day")) ||
293
+ (maxDate && day.isAfter(moment(maxDate), "day"));
265
294
 
266
295
  days.push(
267
- <div
268
- key={day.toString()}
269
- className={`w-8 aspect-square flex items-center justify-center text-center rounded-lg transition-all
270
- ${day.isSame(currentDate, "month") ? "text-foreground" : "text-light-foreground"}
271
- ${day.isSame(selectedDate, "day") ? "bg-primary text-background" : "hover:bg-light-primary"}
272
- ${day.isSame(moment(), "day") ? "border !border-primary" : "hover:bg-light-primary"}
273
- ${(minDate && day.isBefore(moment(minDate))) || (maxDate && day.isAfter(moment(maxDate))) ? "opacity-10 cursor-not-allowed" : "cursor-pointer"}`}
274
- onClick={() => handleDateClick(cloneDay)}
275
- >{day.format("D")}</div>
296
+ <button
297
+ key={day.format("YYYY-MM-DD")}
298
+ type="button"
299
+ className={cn(
300
+ "w-8 h-8 text-xs flex items-center justify-center text-center rounded-lg transition-all border-none select-none",
301
+ isCurrentMonth ? "text-foreground font-medium" : "text-light-foreground/40",
302
+ isSelected ? "bg-primary text-white font-bold hover:bg-primary" : (
303
+ isToday ? "border border-primary font-semibold hover:bg-light-primary" : "hover:bg-light-primary"
304
+ ),
305
+ isDisabled ? "opacity-20 cursor-not-allowed pointer-events-none" : "cursor-pointer"
306
+ )}
307
+ onClick={() => !isDisabled && handleDateClick(cloneDay)}
308
+ >
309
+ {day.format("D")}
310
+ </button>
276
311
  );
277
312
 
278
313
  day.add(1, "day");
279
314
  }
280
315
 
281
- rows.push(<div key={day.toString()} className="grid grid-cols-7 gap-1">{days}</div>);
316
+ rows.push(<div key={day.format("YYYY-MM-DD-row")} className="grid grid-cols-7 gap-1">{days}</div>);
282
317
 
283
318
  days = [];
284
319
  }
@@ -289,7 +324,7 @@ export const InputDatePickerComponent: React.FC<InputDatePickerProps> = ({
289
324
  const years = useMemo(() => {
290
325
  const dumpYears = [];
291
326
 
292
- for (let i = 1945; i <= moment().year(); i++) {
327
+ for (let i = 1940; i <= moment().year() + 10; i++) {
293
328
  dumpYears.push(i);
294
329
  }
295
330
 
@@ -298,55 +333,60 @@ export const InputDatePickerComponent: React.FC<InputDatePickerProps> = ({
298
333
 
299
334
  useEffect(() => {
300
335
  if (activeYearRef.current && containerYearRef.current) {
301
- containerYearRef.current.scrollTo({
302
- top: activeYearRef.current.offsetTop - containerYearRef.current.offsetTop,
303
- });
336
+ const container = containerYearRef.current;
337
+ const activeEl = activeYearRef.current;
338
+ container.scrollTop = activeEl.offsetTop - container.offsetTop - (container.clientHeight / 2) + (activeEl.clientHeight / 2);
304
339
  }
305
- }, []);
340
+ }, [currentDate.year()]);
306
341
 
307
342
  return (
308
- <div className="w-full flex gap-2 max-h-[260]">
343
+ <div className="w-full flex gap-2.5 h-[260px] select-none">
309
344
  <div
310
- className="w-1/5 overflow-y-auto input-scroll pr-1"
345
+ className="w-[68px] shrink-0 h-full overflow-y-auto input-scroll pr-1 border-r border-stroke"
311
346
  ref={containerYearRef}
312
347
  >
313
- <div className="flex flex-col">
348
+ <div className="flex flex-col gap-0.5">
314
349
  {years?.map((item) => {
315
350
  const isActive = currentDate?.year() === item;
316
351
 
317
352
  return (
318
- <>
319
- <div
320
- key={item}
321
- ref={isActive ? activeYearRef : null}
322
- className={`py-1 px-2 font-semibold rounded-[6px] cursor-pointer ${isActive && "bg-light-primary"}`}
323
- onClick={() => setCurrentDate(moment().set("year", item))}
324
- >
325
- {item}
326
- </div>
327
- </>
353
+ <div
354
+ key={item}
355
+ ref={isActive ? activeYearRef : null}
356
+ className={cn(
357
+ "py-1 px-2 font-semibold rounded-md cursor-pointer transition-colors text-center select-none",
358
+ isActive ? "bg-primary text-white" : "hover:bg-light-primary text-foreground"
359
+ )}
360
+ onClick={() => setCurrentDate(moment(currentDate).set("year", item))}
361
+ >
362
+ {item}
363
+ </div>
328
364
  );
329
365
  })}
330
366
  </div>
331
367
  </div>
332
- <div className="w-4/5">
333
- <div className="flex justify-between items-center mb-2">
368
+ <div className="flex-1 flex flex-col justify-between pl-0.5">
369
+ <div className="flex justify-between items-center mb-1.5">
334
370
  <button
371
+ type="button"
335
372
  onClick={handlePrevMonth}
336
- className="w-8 text-sm aspect-square rounded-full cursor-pointer"
373
+ className="w-7 h-7 flex items-center justify-center text-xs rounded-full hover:bg-light-primary transition-colors cursor-pointer border-none bg-transparent"
337
374
  >
338
- <Icon icon="solid/chevron-left" />
375
+ <Icon icon="solid/chevron-left" className="w-3.5 h-3.5" />
339
376
  </button>
340
- <h2 className="font-semibold">{currentDate.format("MMMM")}</h2>
377
+ <h2 className="font-semibold">
378
+ {currentDate.format("MMMM YYYY")}
379
+ </h2>
341
380
  <button
381
+ type="button"
342
382
  onClick={handleNextMonth}
343
- className="w-8 text-sm aspect-square rounded-full cursor-pointer"
383
+ className="w-7 h-7 flex items-center justify-center text-xs rounded-full hover:bg-light-primary transition-colors cursor-pointer border-none bg-transparent"
344
384
  >
345
- <Icon icon="solid/chevron-right" />
385
+ <Icon icon="solid/chevron-right" className="w-3.5 h-3.5" />
346
386
  </button>
347
387
  </div>
348
- <div className="grid grid-cols-7 gap-1 mb-2">{renderDays()}</div>
349
- <div>{renderCells()}</div>
388
+ <div className="grid grid-cols-7 gap-1 mb-1">{renderDays()}</div>
389
+ <div className="flex flex-col gap-1">{renderCells()}</div>
350
390
  </div>
351
391
 
352
392
  {rightElement && <div>{rightElement}</div>}
@@ -188,6 +188,7 @@ export function InputDatetimeComponent({
188
188
  />
189
189
  {pickerType === "date" ? (
190
190
  <InputDatePickerComponent
191
+ value={dateValue}
191
192
  onChange={(e) => {
192
193
  setDateValue(e);
193
194
  handleChange(e, timeValue);
@@ -246,6 +247,7 @@ export function InputDatetimeComponent({
246
247
  />
247
248
  {pickerType === "date" ? (
248
249
  <InputDatePickerComponent
250
+ value={dateValue}
249
251
  onChange={(e) => {
250
252
  setDateValue(e);
251
253
  handleChange(e, timeValue);