@skalfa/skalfa-component 1.0.53 → 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.
- package/dist/input/InputDate.component.d.ts +1 -0
- package/dist/input/InputDate.component.js +47 -29
- package/dist/input/InputDatetime.component.js +2 -2
- package/dist/input/InputTime.component.d.ts +5 -3
- package/dist/input/InputTime.component.js +109 -28
- package/dist/input/Select.component.d.ts +7 -2
- package/dist/input/Select.component.js +91 -38
- package/package.json +1 -1
- package/src/input/InputDate.component.tsx +95 -55
- package/src/input/InputDatetime.component.tsx +2 -0
- package/src/input/InputTime.component.tsx +218 -58
- package/src/input/Select.component.tsx +111 -53
|
@@ -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
|
|
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
|
|
90
|
-
|
|
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 =
|
|
107
|
+
newFilteredOptions = typeof maxShowOption === "number" && maxShowOption > 0
|
|
108
|
+
? dataOptions.slice(0, maxShowOption)
|
|
109
|
+
: dataOptions;
|
|
94
110
|
}
|
|
95
111
|
}
|
|
96
112
|
else {
|
|
97
|
-
newFilteredOptions =
|
|
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:
|
|
158
|
-
|
|
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
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
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",
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
353
|
-
|
|
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" })),
|
|
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
|
@@ -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.
|
|
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
|
|
227
|
-
|
|
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 = () =>
|
|
233
|
-
|
|
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)))) {
|
|
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
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
<
|
|
268
|
-
key={day.
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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.
|
|
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 =
|
|
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
|
|
302
|
-
|
|
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
|
|
343
|
+
<div className="w-full flex gap-2.5 h-[260px] select-none">
|
|
309
344
|
<div
|
|
310
|
-
className="w-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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="
|
|
333
|
-
<div className="flex justify-between items-center mb-
|
|
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-
|
|
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">
|
|
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-
|
|
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-
|
|
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);
|