@page-speed/forms 0.2.3 → 0.3.0
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/README.md +496 -0
- package/dist/inputs.cjs +1279 -68
- package/dist/inputs.cjs.map +1 -1
- package/dist/inputs.d.cts +520 -3
- package/dist/inputs.d.ts +520 -3
- package/dist/inputs.js +1275 -68
- package/dist/inputs.js.map +1 -1
- package/package.json +2 -2
package/dist/inputs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as React7 from 'react';
|
|
2
2
|
|
|
3
3
|
// src/inputs/TextInput.tsx
|
|
4
4
|
function TextInput({
|
|
@@ -23,7 +23,7 @@ function TextInput({
|
|
|
23
23
|
const baseClassName = "text-input";
|
|
24
24
|
const errorClassName = error ? "text-input--error" : "";
|
|
25
25
|
const combinedClassName = `${baseClassName} ${errorClassName} ${className}`.trim();
|
|
26
|
-
return /* @__PURE__ */
|
|
26
|
+
return /* @__PURE__ */ React7.createElement(
|
|
27
27
|
"input",
|
|
28
28
|
{
|
|
29
29
|
type,
|
|
@@ -69,7 +69,7 @@ function TextArea({
|
|
|
69
69
|
const baseClassName = "textarea";
|
|
70
70
|
const errorClassName = error ? "textarea--error" : "";
|
|
71
71
|
const combinedClassName = `${baseClassName} ${errorClassName} ${className}`.trim();
|
|
72
|
-
return /* @__PURE__ */
|
|
72
|
+
return /* @__PURE__ */ React7.createElement(
|
|
73
73
|
"textarea",
|
|
74
74
|
{
|
|
75
75
|
name,
|
|
@@ -106,8 +106,8 @@ function Checkbox({
|
|
|
106
106
|
label,
|
|
107
107
|
...props
|
|
108
108
|
}) {
|
|
109
|
-
const inputRef =
|
|
110
|
-
|
|
109
|
+
const inputRef = React7.useRef(null);
|
|
110
|
+
React7.useEffect(() => {
|
|
111
111
|
if (inputRef.current) {
|
|
112
112
|
inputRef.current.indeterminate = indeterminate;
|
|
113
113
|
}
|
|
@@ -121,7 +121,7 @@ function Checkbox({
|
|
|
121
121
|
const baseClassName = "checkbox";
|
|
122
122
|
const errorClassName = error ? "checkbox--error" : "";
|
|
123
123
|
const combinedClassName = `${baseClassName} ${errorClassName} ${className}`.trim();
|
|
124
|
-
const checkbox = /* @__PURE__ */
|
|
124
|
+
const checkbox = /* @__PURE__ */ React7.createElement(
|
|
125
125
|
"input",
|
|
126
126
|
{
|
|
127
127
|
ref: inputRef,
|
|
@@ -140,7 +140,7 @@ function Checkbox({
|
|
|
140
140
|
}
|
|
141
141
|
);
|
|
142
142
|
if (label) {
|
|
143
|
-
return /* @__PURE__ */
|
|
143
|
+
return /* @__PURE__ */ React7.createElement("label", { className: "checkbox-label" }, checkbox, /* @__PURE__ */ React7.createElement("span", { className: "checkbox-label-text" }, label));
|
|
144
144
|
}
|
|
145
145
|
return checkbox;
|
|
146
146
|
}
|
|
@@ -196,7 +196,7 @@ function CheckboxGroup({
|
|
|
196
196
|
const layoutClassName = `checkbox-group--${layout}`;
|
|
197
197
|
const combinedClassName = `${baseClassName} ${errorClassName} ${layoutClassName} ${className}`.trim();
|
|
198
198
|
const maxReached = Boolean(maxSelections && value.length >= maxSelections);
|
|
199
|
-
return /* @__PURE__ */
|
|
199
|
+
return /* @__PURE__ */ React7.createElement(
|
|
200
200
|
"div",
|
|
201
201
|
{
|
|
202
202
|
className: combinedClassName,
|
|
@@ -209,9 +209,9 @@ function CheckboxGroup({
|
|
|
209
209
|
gridTemplateColumns: `repeat(${gridColumns}, 1fr)`
|
|
210
210
|
} : void 0
|
|
211
211
|
},
|
|
212
|
-
label && /* @__PURE__ */
|
|
213
|
-
description && /* @__PURE__ */
|
|
214
|
-
/* @__PURE__ */
|
|
212
|
+
label && /* @__PURE__ */ React7.createElement("div", { className: "checkbox-group-label" }, label),
|
|
213
|
+
description && /* @__PURE__ */ React7.createElement("div", { className: "checkbox-group-description" }, description),
|
|
214
|
+
/* @__PURE__ */ React7.createElement("div", { className: "checkbox-options" }, showSelectAll && enabledOptions.length > 0 && /* @__PURE__ */ React7.createElement("label", { className: "checkbox-option checkbox-option--select-all" }, /* @__PURE__ */ React7.createElement(
|
|
215
215
|
"input",
|
|
216
216
|
{
|
|
217
217
|
type: "checkbox",
|
|
@@ -227,18 +227,18 @@ function CheckboxGroup({
|
|
|
227
227
|
className: "checkbox-input",
|
|
228
228
|
"aria-label": selectAllLabel
|
|
229
229
|
}
|
|
230
|
-
), /* @__PURE__ */
|
|
230
|
+
), /* @__PURE__ */ React7.createElement("div", { className: "checkbox-content" }, /* @__PURE__ */ React7.createElement("span", { className: "checkbox-label" }, selectAllLabel))), options.map((option) => {
|
|
231
231
|
const isChecked = value.includes(option.value);
|
|
232
232
|
const isDisabled = disabled || option.disabled || maxReached && !isChecked;
|
|
233
233
|
const checkboxId = `${name}-${option.value}`;
|
|
234
|
-
return /* @__PURE__ */
|
|
234
|
+
return /* @__PURE__ */ React7.createElement(
|
|
235
235
|
"label",
|
|
236
236
|
{
|
|
237
237
|
key: option.value,
|
|
238
238
|
className: `checkbox-option ${isDisabled ? "checkbox-option--disabled" : ""}`,
|
|
239
239
|
htmlFor: checkboxId
|
|
240
240
|
},
|
|
241
|
-
/* @__PURE__ */
|
|
241
|
+
/* @__PURE__ */ React7.createElement(
|
|
242
242
|
"input",
|
|
243
243
|
{
|
|
244
244
|
type: "checkbox",
|
|
@@ -254,7 +254,7 @@ function CheckboxGroup({
|
|
|
254
254
|
"aria-describedby": option.description ? `${checkboxId}-description` : props["aria-describedby"]
|
|
255
255
|
}
|
|
256
256
|
),
|
|
257
|
-
/* @__PURE__ */
|
|
257
|
+
/* @__PURE__ */ React7.createElement("div", { className: "checkbox-content" }, renderOption ? renderOption(option) : /* @__PURE__ */ React7.createElement(React7.Fragment, null, /* @__PURE__ */ React7.createElement("span", { className: "checkbox-label" }, option.label), option.description && /* @__PURE__ */ React7.createElement(
|
|
258
258
|
"span",
|
|
259
259
|
{
|
|
260
260
|
className: "checkbox-description",
|
|
@@ -264,7 +264,7 @@ function CheckboxGroup({
|
|
|
264
264
|
)))
|
|
265
265
|
);
|
|
266
266
|
})),
|
|
267
|
-
(minSelections || maxSelections) && /* @__PURE__ */
|
|
267
|
+
(minSelections || maxSelections) && /* @__PURE__ */ React7.createElement("div", { className: "checkbox-group-feedback", "aria-live": "polite" }, minSelections && value.length < minSelections && /* @__PURE__ */ React7.createElement("span", { className: "checkbox-group-feedback-min" }, "Select at least ", minSelections, " option", minSelections !== 1 ? "s" : ""), maxSelections && /* @__PURE__ */ React7.createElement("span", { className: "checkbox-group-feedback-max" }, value.length, "/", maxSelections, " selected"))
|
|
268
268
|
);
|
|
269
269
|
}
|
|
270
270
|
CheckboxGroup.displayName = "CheckboxGroup";
|
|
@@ -317,7 +317,7 @@ function Radio({
|
|
|
317
317
|
const errorClassName = error ? "radio-group--error" : "";
|
|
318
318
|
const layoutClassName = `radio-group--${layout}`;
|
|
319
319
|
const combinedClassName = `${baseClassName} ${errorClassName} ${layoutClassName} ${className}`.trim();
|
|
320
|
-
return /* @__PURE__ */
|
|
320
|
+
return /* @__PURE__ */ React7.createElement(
|
|
321
321
|
"div",
|
|
322
322
|
{
|
|
323
323
|
className: combinedClassName,
|
|
@@ -327,19 +327,19 @@ function Radio({
|
|
|
327
327
|
"aria-required": required || props["aria-required"],
|
|
328
328
|
"aria-label": typeof label === "string" ? label : props["aria-label"]
|
|
329
329
|
},
|
|
330
|
-
label && /* @__PURE__ */
|
|
331
|
-
/* @__PURE__ */
|
|
330
|
+
label && /* @__PURE__ */ React7.createElement("div", { className: "radio-group-label" }, label),
|
|
331
|
+
/* @__PURE__ */ React7.createElement("div", { className: "radio-options" }, options.map((option, index) => {
|
|
332
332
|
const isChecked = value === option.value;
|
|
333
333
|
const isDisabled = disabled || option.disabled;
|
|
334
334
|
const radioId = `${name}-${option.value}`;
|
|
335
|
-
return /* @__PURE__ */
|
|
335
|
+
return /* @__PURE__ */ React7.createElement(
|
|
336
336
|
"label",
|
|
337
337
|
{
|
|
338
338
|
key: option.value,
|
|
339
339
|
className: `radio-option ${isDisabled ? "radio-option--disabled" : ""}`,
|
|
340
340
|
htmlFor: radioId
|
|
341
341
|
},
|
|
342
|
-
/* @__PURE__ */
|
|
342
|
+
/* @__PURE__ */ React7.createElement(
|
|
343
343
|
"input",
|
|
344
344
|
{
|
|
345
345
|
type: "radio",
|
|
@@ -356,7 +356,7 @@ function Radio({
|
|
|
356
356
|
"aria-describedby": option.description ? `${radioId}-description` : props["aria-describedby"]
|
|
357
357
|
}
|
|
358
358
|
),
|
|
359
|
-
/* @__PURE__ */
|
|
359
|
+
/* @__PURE__ */ React7.createElement("div", { className: "radio-content" }, /* @__PURE__ */ React7.createElement("span", { className: "radio-label" }, option.label), option.description && /* @__PURE__ */ React7.createElement(
|
|
360
360
|
"span",
|
|
361
361
|
{
|
|
362
362
|
className: "radio-description",
|
|
@@ -388,19 +388,19 @@ function Select({
|
|
|
388
388
|
renderOption,
|
|
389
389
|
...props
|
|
390
390
|
}) {
|
|
391
|
-
const [isOpen, setIsOpen] =
|
|
392
|
-
const [searchQuery, setSearchQuery] =
|
|
393
|
-
const [focusedIndex, setFocusedIndex] =
|
|
394
|
-
const selectRef =
|
|
395
|
-
const searchInputRef =
|
|
391
|
+
const [isOpen, setIsOpen] = React7.useState(false);
|
|
392
|
+
const [searchQuery, setSearchQuery] = React7.useState("");
|
|
393
|
+
const [focusedIndex, setFocusedIndex] = React7.useState(-1);
|
|
394
|
+
const selectRef = React7.useRef(null);
|
|
395
|
+
const searchInputRef = React7.useRef(null);
|
|
396
396
|
const dropdownId = `${name}-dropdown`;
|
|
397
|
-
const allOptions =
|
|
397
|
+
const allOptions = React7.useMemo(() => {
|
|
398
398
|
if (optionGroups.length > 0) {
|
|
399
399
|
return optionGroups.flatMap((group) => group.options);
|
|
400
400
|
}
|
|
401
401
|
return options;
|
|
402
402
|
}, [options, optionGroups]);
|
|
403
|
-
const filteredOptions =
|
|
403
|
+
const filteredOptions = React7.useMemo(() => {
|
|
404
404
|
if (!searchQuery.trim()) {
|
|
405
405
|
return allOptions;
|
|
406
406
|
}
|
|
@@ -410,7 +410,7 @@ function Select({
|
|
|
410
410
|
return label.toLowerCase().includes(query);
|
|
411
411
|
});
|
|
412
412
|
}, [allOptions, searchQuery]);
|
|
413
|
-
const selectedOption =
|
|
413
|
+
const selectedOption = React7.useMemo(() => {
|
|
414
414
|
return allOptions.find((opt) => opt.value === value);
|
|
415
415
|
}, [allOptions, value]);
|
|
416
416
|
const handleSelect = (optionValue) => {
|
|
@@ -514,7 +514,7 @@ function Select({
|
|
|
514
514
|
const handleBlur = () => {
|
|
515
515
|
onBlur?.();
|
|
516
516
|
};
|
|
517
|
-
|
|
517
|
+
React7.useEffect(() => {
|
|
518
518
|
const handleClickOutside = (event) => {
|
|
519
519
|
if (selectRef.current && !selectRef.current.contains(event.target)) {
|
|
520
520
|
setIsOpen(false);
|
|
@@ -535,7 +535,7 @@ function Select({
|
|
|
535
535
|
const disabledClassName = disabled ? "select--disabled" : "";
|
|
536
536
|
const openClassName = isOpen ? "select--open" : "";
|
|
537
537
|
const combinedClassName = `${baseClassName} ${errorClassName} ${disabledClassName} ${openClassName} ${className}`.trim();
|
|
538
|
-
return /* @__PURE__ */
|
|
538
|
+
return /* @__PURE__ */ React7.createElement(
|
|
539
539
|
"div",
|
|
540
540
|
{
|
|
541
541
|
ref: selectRef,
|
|
@@ -543,7 +543,7 @@ function Select({
|
|
|
543
543
|
onKeyDown: handleKeyDown,
|
|
544
544
|
onBlur: handleBlur
|
|
545
545
|
},
|
|
546
|
-
/* @__PURE__ */
|
|
546
|
+
/* @__PURE__ */ React7.createElement(
|
|
547
547
|
"select",
|
|
548
548
|
{
|
|
549
549
|
name,
|
|
@@ -556,10 +556,10 @@ function Select({
|
|
|
556
556
|
tabIndex: -1,
|
|
557
557
|
style: { display: "none" }
|
|
558
558
|
},
|
|
559
|
-
/* @__PURE__ */
|
|
560
|
-
allOptions.map((option) => /* @__PURE__ */
|
|
559
|
+
/* @__PURE__ */ React7.createElement("option", { value: "" }, "Select..."),
|
|
560
|
+
allOptions.map((option) => /* @__PURE__ */ React7.createElement("option", { key: option.value, value: option.value }, typeof option.label === "string" ? option.label : option.value))
|
|
561
561
|
),
|
|
562
|
-
/* @__PURE__ */
|
|
562
|
+
/* @__PURE__ */ React7.createElement(
|
|
563
563
|
"div",
|
|
564
564
|
{
|
|
565
565
|
className: "select-trigger",
|
|
@@ -573,8 +573,8 @@ function Select({
|
|
|
573
573
|
"aria-disabled": disabled,
|
|
574
574
|
tabIndex: disabled ? -1 : 0
|
|
575
575
|
},
|
|
576
|
-
/* @__PURE__ */
|
|
577
|
-
/* @__PURE__ */
|
|
576
|
+
/* @__PURE__ */ React7.createElement("span", { className: "select-value" }, selectedOption ? renderOption ? renderOption(selectedOption) : selectedOption.label : /* @__PURE__ */ React7.createElement("span", { className: "select-placeholder" }, placeholder)),
|
|
577
|
+
/* @__PURE__ */ React7.createElement("div", { className: "select-icons" }, loading && /* @__PURE__ */ React7.createElement("span", { className: "select-loading" }, "\u23F3"), clearable && value && !disabled && !loading && /* @__PURE__ */ React7.createElement(
|
|
578
578
|
"button",
|
|
579
579
|
{
|
|
580
580
|
type: "button",
|
|
@@ -584,9 +584,9 @@ function Select({
|
|
|
584
584
|
tabIndex: -1
|
|
585
585
|
},
|
|
586
586
|
"\u2715"
|
|
587
|
-
), /* @__PURE__ */
|
|
587
|
+
), /* @__PURE__ */ React7.createElement("span", { className: "select-arrow", "aria-hidden": "true" }, isOpen ? "\u25B2" : "\u25BC"))
|
|
588
588
|
),
|
|
589
|
-
isOpen && /* @__PURE__ */
|
|
589
|
+
isOpen && /* @__PURE__ */ React7.createElement("div", { id: dropdownId, className: "select-dropdown", role: "listbox" }, searchable && /* @__PURE__ */ React7.createElement("div", { className: "select-search" }, /* @__PURE__ */ React7.createElement(
|
|
590
590
|
"input",
|
|
591
591
|
{
|
|
592
592
|
ref: searchInputRef,
|
|
@@ -598,19 +598,19 @@ function Select({
|
|
|
598
598
|
onClick: (e) => e.stopPropagation(),
|
|
599
599
|
"aria-label": "Search options"
|
|
600
600
|
}
|
|
601
|
-
)), /* @__PURE__ */
|
|
601
|
+
)), /* @__PURE__ */ React7.createElement("div", { className: "select-options" }, filteredOptions.length === 0 ? /* @__PURE__ */ React7.createElement("div", { className: "select-no-options" }, "No options found") : optionGroups.length > 0 ? (
|
|
602
602
|
// Render grouped options
|
|
603
603
|
optionGroups.map((group, groupIndex) => {
|
|
604
604
|
const groupOptions = group.options.filter(
|
|
605
605
|
(opt) => filteredOptions.includes(opt)
|
|
606
606
|
);
|
|
607
607
|
if (groupOptions.length === 0) return null;
|
|
608
|
-
return /* @__PURE__ */
|
|
608
|
+
return /* @__PURE__ */ React7.createElement("div", { key: groupIndex, className: "select-optgroup" }, /* @__PURE__ */ React7.createElement("div", { className: "select-optgroup-label" }, group.label), groupOptions.map((option) => {
|
|
609
609
|
const globalIndex = filteredOptions.indexOf(option);
|
|
610
610
|
const isSelected = value === option.value;
|
|
611
611
|
const isFocused = globalIndex === focusedIndex;
|
|
612
612
|
const isDisabled = option.disabled;
|
|
613
|
-
return /* @__PURE__ */
|
|
613
|
+
return /* @__PURE__ */ React7.createElement(
|
|
614
614
|
"div",
|
|
615
615
|
{
|
|
616
616
|
key: option.value,
|
|
@@ -630,7 +630,7 @@ function Select({
|
|
|
630
630
|
const isSelected = value === option.value;
|
|
631
631
|
const isFocused = index === focusedIndex;
|
|
632
632
|
const isDisabled = option.disabled;
|
|
633
|
-
return /* @__PURE__ */
|
|
633
|
+
return /* @__PURE__ */ React7.createElement(
|
|
634
634
|
"div",
|
|
635
635
|
{
|
|
636
636
|
key: option.value,
|
|
@@ -663,13 +663,23 @@ function FileInput({
|
|
|
663
663
|
maxFiles = 1,
|
|
664
664
|
multiple = false,
|
|
665
665
|
showPreview = true,
|
|
666
|
+
showProgress = true,
|
|
667
|
+
uploadProgress = {},
|
|
668
|
+
enableCropping = false,
|
|
669
|
+
cropAspectRatio,
|
|
670
|
+
onCropComplete,
|
|
666
671
|
onValidationError,
|
|
667
672
|
onFileRemove,
|
|
668
673
|
...props
|
|
669
674
|
}) {
|
|
670
|
-
const inputRef =
|
|
671
|
-
const [dragActive, setDragActive] =
|
|
672
|
-
const
|
|
675
|
+
const inputRef = React7.useRef(null);
|
|
676
|
+
const [dragActive, setDragActive] = React7.useState(false);
|
|
677
|
+
const [cropperOpen, setCropperOpen] = React7.useState(false);
|
|
678
|
+
const [imageToCrop, setImageToCrop] = React7.useState(null);
|
|
679
|
+
const [crop, setCrop] = React7.useState({ x: 0, y: 0 });
|
|
680
|
+
const [zoom, setZoom] = React7.useState(1);
|
|
681
|
+
const [croppedAreaPixels, setCroppedAreaPixels] = React7.useState(null);
|
|
682
|
+
const validateFile = React7.useCallback(
|
|
673
683
|
(file) => {
|
|
674
684
|
if (accept) {
|
|
675
685
|
const acceptedTypes = accept.split(",").map((t) => t.trim());
|
|
@@ -704,7 +714,7 @@ function FileInput({
|
|
|
704
714
|
},
|
|
705
715
|
[accept, maxSize]
|
|
706
716
|
);
|
|
707
|
-
const handleFiles =
|
|
717
|
+
const handleFiles = React7.useCallback(
|
|
708
718
|
(fileList) => {
|
|
709
719
|
if (!fileList || fileList.length === 0) return;
|
|
710
720
|
const newFiles = Array.from(fileList);
|
|
@@ -731,14 +741,110 @@ function FileInput({
|
|
|
731
741
|
onValidationError(validationErrors);
|
|
732
742
|
}
|
|
733
743
|
if (validFiles.length > 0 && totalFiles <= maxFiles) {
|
|
734
|
-
const
|
|
735
|
-
|
|
744
|
+
const firstImage = validFiles.find((f) => f.type.startsWith("image/"));
|
|
745
|
+
if (enableCropping && firstImage && !multiple) {
|
|
746
|
+
const previewUrl = URL.createObjectURL(firstImage);
|
|
747
|
+
setImageToCrop({ file: firstImage, url: previewUrl });
|
|
748
|
+
setCropperOpen(true);
|
|
749
|
+
} else {
|
|
750
|
+
const updatedFiles = multiple ? [...value, ...validFiles] : validFiles;
|
|
751
|
+
onChange(updatedFiles.slice(0, maxFiles));
|
|
752
|
+
}
|
|
736
753
|
}
|
|
737
754
|
if (inputRef.current) {
|
|
738
755
|
inputRef.current.value = "";
|
|
739
756
|
}
|
|
740
757
|
},
|
|
741
|
-
[value, onChange, validateFile, maxFiles, multiple, onValidationError]
|
|
758
|
+
[value, onChange, validateFile, maxFiles, multiple, enableCropping, onValidationError]
|
|
759
|
+
);
|
|
760
|
+
const createCroppedImage = React7.useCallback(
|
|
761
|
+
async (imageUrl, cropArea) => {
|
|
762
|
+
return new Promise((resolve, reject) => {
|
|
763
|
+
const image = new Image();
|
|
764
|
+
image.onload = () => {
|
|
765
|
+
const canvas = document.createElement("canvas");
|
|
766
|
+
const ctx = canvas.getContext("2d");
|
|
767
|
+
if (!ctx) {
|
|
768
|
+
reject(new Error("Failed to get canvas context"));
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
canvas.width = cropArea.width;
|
|
772
|
+
canvas.height = cropArea.height;
|
|
773
|
+
ctx.drawImage(
|
|
774
|
+
image,
|
|
775
|
+
cropArea.x,
|
|
776
|
+
cropArea.y,
|
|
777
|
+
cropArea.width,
|
|
778
|
+
cropArea.height,
|
|
779
|
+
0,
|
|
780
|
+
0,
|
|
781
|
+
cropArea.width,
|
|
782
|
+
cropArea.height
|
|
783
|
+
);
|
|
784
|
+
canvas.toBlob((blob) => {
|
|
785
|
+
if (blob) {
|
|
786
|
+
resolve(blob);
|
|
787
|
+
} else {
|
|
788
|
+
reject(new Error("Failed to create blob from canvas"));
|
|
789
|
+
}
|
|
790
|
+
}, "image/jpeg", 0.95);
|
|
791
|
+
};
|
|
792
|
+
image.onerror = () => {
|
|
793
|
+
reject(new Error("Failed to load image"));
|
|
794
|
+
};
|
|
795
|
+
image.src = imageUrl;
|
|
796
|
+
});
|
|
797
|
+
},
|
|
798
|
+
[]
|
|
799
|
+
);
|
|
800
|
+
const handleCropSave = React7.useCallback(async () => {
|
|
801
|
+
if (!imageToCrop || !croppedAreaPixels) return;
|
|
802
|
+
try {
|
|
803
|
+
const croppedBlob = await createCroppedImage(
|
|
804
|
+
imageToCrop.url,
|
|
805
|
+
croppedAreaPixels
|
|
806
|
+
);
|
|
807
|
+
if (onCropComplete) {
|
|
808
|
+
onCropComplete(croppedBlob, imageToCrop.file);
|
|
809
|
+
}
|
|
810
|
+
const croppedFile = new File(
|
|
811
|
+
[croppedBlob],
|
|
812
|
+
imageToCrop.file.name,
|
|
813
|
+
{ type: "image/jpeg" }
|
|
814
|
+
);
|
|
815
|
+
const updatedFiles = multiple ? [...value, croppedFile] : [croppedFile];
|
|
816
|
+
onChange(updatedFiles);
|
|
817
|
+
setCropperOpen(false);
|
|
818
|
+
URL.revokeObjectURL(imageToCrop.url);
|
|
819
|
+
setImageToCrop(null);
|
|
820
|
+
setCrop({ x: 0, y: 0 });
|
|
821
|
+
setZoom(1);
|
|
822
|
+
setCroppedAreaPixels(null);
|
|
823
|
+
} catch (error2) {
|
|
824
|
+
console.error("Failed to crop image:", error2);
|
|
825
|
+
}
|
|
826
|
+
}, [imageToCrop, croppedAreaPixels, createCroppedImage, onCropComplete, value, onChange, multiple]);
|
|
827
|
+
const handleCropCancel = React7.useCallback(() => {
|
|
828
|
+
if (imageToCrop) {
|
|
829
|
+
URL.revokeObjectURL(imageToCrop.url);
|
|
830
|
+
}
|
|
831
|
+
setCropperOpen(false);
|
|
832
|
+
setImageToCrop(null);
|
|
833
|
+
setCrop({ x: 0, y: 0 });
|
|
834
|
+
setZoom(1);
|
|
835
|
+
setCroppedAreaPixels(null);
|
|
836
|
+
}, [imageToCrop]);
|
|
837
|
+
const onCropChange = React7.useCallback((crop2) => {
|
|
838
|
+
setCrop(crop2);
|
|
839
|
+
}, []);
|
|
840
|
+
const onZoomChange = React7.useCallback((zoom2) => {
|
|
841
|
+
setZoom(zoom2);
|
|
842
|
+
}, []);
|
|
843
|
+
const onCropCompleteInternal = React7.useCallback(
|
|
844
|
+
(_, croppedAreaPixels2) => {
|
|
845
|
+
setCroppedAreaPixels(croppedAreaPixels2);
|
|
846
|
+
},
|
|
847
|
+
[]
|
|
742
848
|
);
|
|
743
849
|
const handleChange = (e) => {
|
|
744
850
|
handleFiles(e.target.files);
|
|
@@ -751,6 +857,12 @@ function FileInput({
|
|
|
751
857
|
onFileRemove(fileToRemove, index);
|
|
752
858
|
}
|
|
753
859
|
};
|
|
860
|
+
const handleCrop = (file) => {
|
|
861
|
+
if (!file.type.startsWith("image/")) return;
|
|
862
|
+
const previewUrl = URL.createObjectURL(file);
|
|
863
|
+
setImageToCrop({ file, url: previewUrl });
|
|
864
|
+
setCropperOpen(true);
|
|
865
|
+
};
|
|
754
866
|
const handleDrag = (e) => {
|
|
755
867
|
e.preventDefault();
|
|
756
868
|
e.stopPropagation();
|
|
@@ -789,7 +901,7 @@ function FileInput({
|
|
|
789
901
|
}
|
|
790
902
|
return null;
|
|
791
903
|
};
|
|
792
|
-
|
|
904
|
+
React7.useEffect(() => {
|
|
793
905
|
return () => {
|
|
794
906
|
value.forEach((file) => {
|
|
795
907
|
const previewUrl = getPreviewUrl(file);
|
|
@@ -797,14 +909,17 @@ function FileInput({
|
|
|
797
909
|
URL.revokeObjectURL(previewUrl);
|
|
798
910
|
}
|
|
799
911
|
});
|
|
912
|
+
if (imageToCrop) {
|
|
913
|
+
URL.revokeObjectURL(imageToCrop.url);
|
|
914
|
+
}
|
|
800
915
|
};
|
|
801
|
-
}, [value]);
|
|
916
|
+
}, [value, imageToCrop]);
|
|
802
917
|
const baseClassName = "file-input";
|
|
803
918
|
const errorClassName = error ? "file-input--error" : "";
|
|
804
919
|
const dragClassName = dragActive ? "file-input--drag-active" : "";
|
|
805
920
|
const disabledClassName = disabled ? "file-input--disabled" : "";
|
|
806
921
|
const combinedClassName = `${baseClassName} ${errorClassName} ${dragClassName} ${disabledClassName} ${className}`.trim();
|
|
807
|
-
return /* @__PURE__ */
|
|
922
|
+
return /* @__PURE__ */ React7.createElement("div", { className: combinedClassName }, /* @__PURE__ */ React7.createElement(
|
|
808
923
|
"input",
|
|
809
924
|
{
|
|
810
925
|
ref: inputRef,
|
|
@@ -822,7 +937,7 @@ function FileInput({
|
|
|
822
937
|
"aria-required": required || props["aria-required"],
|
|
823
938
|
style: { display: "none" }
|
|
824
939
|
}
|
|
825
|
-
), /* @__PURE__ */
|
|
940
|
+
), /* @__PURE__ */ React7.createElement(
|
|
826
941
|
"div",
|
|
827
942
|
{
|
|
828
943
|
className: "file-input__dropzone",
|
|
@@ -837,7 +952,7 @@ function FileInput({
|
|
|
837
952
|
"aria-label": placeholder,
|
|
838
953
|
"aria-disabled": disabled
|
|
839
954
|
},
|
|
840
|
-
/* @__PURE__ */
|
|
955
|
+
/* @__PURE__ */ React7.createElement("div", { className: "file-input__dropzone-content" }, /* @__PURE__ */ React7.createElement(
|
|
841
956
|
"svg",
|
|
842
957
|
{
|
|
843
958
|
className: "file-input__icon",
|
|
@@ -851,13 +966,13 @@ function FileInput({
|
|
|
851
966
|
strokeLinejoin: "round",
|
|
852
967
|
"aria-hidden": "true"
|
|
853
968
|
},
|
|
854
|
-
/* @__PURE__ */
|
|
855
|
-
/* @__PURE__ */
|
|
856
|
-
/* @__PURE__ */
|
|
857
|
-
), /* @__PURE__ */
|
|
858
|
-
), value.length > 0 && /* @__PURE__ */
|
|
969
|
+
/* @__PURE__ */ React7.createElement("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
|
|
970
|
+
/* @__PURE__ */ React7.createElement("polyline", { points: "17 8 12 3 7 8" }),
|
|
971
|
+
/* @__PURE__ */ React7.createElement("line", { x1: "12", y1: "3", x2: "12", y2: "15" })
|
|
972
|
+
), /* @__PURE__ */ React7.createElement("p", { className: "file-input__placeholder" }, value.length > 0 ? `${value.length} file(s) selected` : placeholder), accept && /* @__PURE__ */ React7.createElement("p", { className: "file-input__hint" }, "Accepted: ", accept), maxSize && /* @__PURE__ */ React7.createElement("p", { className: "file-input__hint" }, "Max size: ", formatFileSize(maxSize)))
|
|
973
|
+
), value.length > 0 && /* @__PURE__ */ React7.createElement("ul", { className: "file-input__list", role: "list" }, value.map((file, index) => {
|
|
859
974
|
const previewUrl = showPreview ? getPreviewUrl(file) : null;
|
|
860
|
-
return /* @__PURE__ */
|
|
975
|
+
return /* @__PURE__ */ React7.createElement("li", { key: `${file.name}-${index}`, className: "file-input__item" }, previewUrl && /* @__PURE__ */ React7.createElement(
|
|
861
976
|
"img",
|
|
862
977
|
{
|
|
863
978
|
src: previewUrl,
|
|
@@ -866,7 +981,46 @@ function FileInput({
|
|
|
866
981
|
width: "48",
|
|
867
982
|
height: "48"
|
|
868
983
|
}
|
|
869
|
-
), /* @__PURE__ */
|
|
984
|
+
), /* @__PURE__ */ React7.createElement("div", { className: "file-input__details" }, /* @__PURE__ */ React7.createElement("span", { className: "file-input__filename" }, file.name), /* @__PURE__ */ React7.createElement("span", { className: "file-input__filesize" }, formatFileSize(file.size)), showProgress && uploadProgress[file.name] !== void 0 && /* @__PURE__ */ React7.createElement("div", { className: "file-input__progress" }, /* @__PURE__ */ React7.createElement(
|
|
985
|
+
"div",
|
|
986
|
+
{
|
|
987
|
+
className: "file-input__progress-bar",
|
|
988
|
+
style: { width: `${uploadProgress[file.name]}%` },
|
|
989
|
+
role: "progressbar",
|
|
990
|
+
"aria-valuenow": uploadProgress[file.name],
|
|
991
|
+
"aria-valuemin": 0,
|
|
992
|
+
"aria-valuemax": 100,
|
|
993
|
+
"aria-label": `Upload progress: ${uploadProgress[file.name]}%`
|
|
994
|
+
}
|
|
995
|
+
), /* @__PURE__ */ React7.createElement("span", { className: "file-input__progress-text" }, uploadProgress[file.name], "%"))), enableCropping && file.type.startsWith("image/") && /* @__PURE__ */ React7.createElement(
|
|
996
|
+
"button",
|
|
997
|
+
{
|
|
998
|
+
type: "button",
|
|
999
|
+
onClick: (e) => {
|
|
1000
|
+
e.stopPropagation();
|
|
1001
|
+
handleCrop(file);
|
|
1002
|
+
},
|
|
1003
|
+
disabled,
|
|
1004
|
+
className: "file-input__crop",
|
|
1005
|
+
"aria-label": `Crop ${file.name}`
|
|
1006
|
+
},
|
|
1007
|
+
/* @__PURE__ */ React7.createElement(
|
|
1008
|
+
"svg",
|
|
1009
|
+
{
|
|
1010
|
+
width: "20",
|
|
1011
|
+
height: "20",
|
|
1012
|
+
viewBox: "0 0 24 24",
|
|
1013
|
+
fill: "none",
|
|
1014
|
+
stroke: "currentColor",
|
|
1015
|
+
strokeWidth: "2",
|
|
1016
|
+
strokeLinecap: "round",
|
|
1017
|
+
strokeLinejoin: "round",
|
|
1018
|
+
"aria-hidden": "true"
|
|
1019
|
+
},
|
|
1020
|
+
/* @__PURE__ */ React7.createElement("path", { d: "M6.13 1L6 16a2 2 0 0 0 2 2h15" }),
|
|
1021
|
+
/* @__PURE__ */ React7.createElement("path", { d: "M1 6.13L16 6a2 2 0 0 1 2 2v15" })
|
|
1022
|
+
)
|
|
1023
|
+
), /* @__PURE__ */ React7.createElement(
|
|
870
1024
|
"button",
|
|
871
1025
|
{
|
|
872
1026
|
type: "button",
|
|
@@ -878,7 +1032,7 @@ function FileInput({
|
|
|
878
1032
|
className: "file-input__remove",
|
|
879
1033
|
"aria-label": `Remove ${file.name}`
|
|
880
1034
|
},
|
|
881
|
-
/* @__PURE__ */
|
|
1035
|
+
/* @__PURE__ */ React7.createElement(
|
|
882
1036
|
"svg",
|
|
883
1037
|
{
|
|
884
1038
|
width: "20",
|
|
@@ -891,14 +1045,1067 @@ function FileInput({
|
|
|
891
1045
|
strokeLinejoin: "round",
|
|
892
1046
|
"aria-hidden": "true"
|
|
893
1047
|
},
|
|
894
|
-
/* @__PURE__ */
|
|
895
|
-
/* @__PURE__ */
|
|
1048
|
+
/* @__PURE__ */ React7.createElement("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
1049
|
+
/* @__PURE__ */ React7.createElement("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
896
1050
|
)
|
|
897
1051
|
));
|
|
898
|
-
}))
|
|
1052
|
+
})), cropperOpen && imageToCrop && /* @__PURE__ */ React7.createElement("div", { className: "file-input-cropper-modal" }, /* @__PURE__ */ React7.createElement(
|
|
1053
|
+
"div",
|
|
1054
|
+
{
|
|
1055
|
+
className: "file-input-cropper-overlay",
|
|
1056
|
+
onClick: handleCropCancel,
|
|
1057
|
+
"aria-label": "Close cropper"
|
|
1058
|
+
}
|
|
1059
|
+
), /* @__PURE__ */ React7.createElement("div", { className: "file-input-cropper-container" }, /* @__PURE__ */ React7.createElement("div", { className: "file-input-cropper-header" }, /* @__PURE__ */ React7.createElement("h3", { className: "file-input-cropper-title" }, "Crop Image"), /* @__PURE__ */ React7.createElement(
|
|
1060
|
+
"button",
|
|
1061
|
+
{
|
|
1062
|
+
type: "button",
|
|
1063
|
+
className: "file-input-cropper-close",
|
|
1064
|
+
onClick: handleCropCancel,
|
|
1065
|
+
"aria-label": "Close"
|
|
1066
|
+
},
|
|
1067
|
+
"\u2715"
|
|
1068
|
+
)), /* @__PURE__ */ React7.createElement("div", { className: "file-input-cropper-content" }, /* @__PURE__ */ React7.createElement(
|
|
1069
|
+
"div",
|
|
1070
|
+
{
|
|
1071
|
+
className: "file-input-cropper-image-container",
|
|
1072
|
+
onMouseDown: (e) => {
|
|
1073
|
+
e.preventDefault();
|
|
1074
|
+
const startX = e.clientX - crop.x;
|
|
1075
|
+
const startY = e.clientY - crop.y;
|
|
1076
|
+
const handleMouseMove = (moveEvent) => {
|
|
1077
|
+
onCropChange({
|
|
1078
|
+
x: moveEvent.clientX - startX,
|
|
1079
|
+
y: moveEvent.clientY - startY
|
|
1080
|
+
});
|
|
1081
|
+
};
|
|
1082
|
+
const handleMouseUp = () => {
|
|
1083
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
1084
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
1085
|
+
};
|
|
1086
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
1087
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
1088
|
+
}
|
|
1089
|
+
},
|
|
1090
|
+
/* @__PURE__ */ React7.createElement(
|
|
1091
|
+
"img",
|
|
1092
|
+
{
|
|
1093
|
+
src: imageToCrop.url,
|
|
1094
|
+
alt: "Crop preview",
|
|
1095
|
+
className: "file-input-cropper-image",
|
|
1096
|
+
style: {
|
|
1097
|
+
transform: `translate(${crop.x}px, ${crop.y}px) scale(${zoom})`
|
|
1098
|
+
},
|
|
1099
|
+
draggable: false,
|
|
1100
|
+
onLoad: (e) => {
|
|
1101
|
+
const img = e.currentTarget;
|
|
1102
|
+
const containerWidth = 600;
|
|
1103
|
+
const containerHeight = 400;
|
|
1104
|
+
const cropWidth = cropAspectRatio ? Math.min(containerWidth * 0.8, containerHeight * 0.8 * cropAspectRatio) : containerWidth * 0.8;
|
|
1105
|
+
const cropHeight = cropAspectRatio ? cropWidth / cropAspectRatio : containerHeight * 0.8;
|
|
1106
|
+
const scale = zoom;
|
|
1107
|
+
const imgWidth = img.naturalWidth;
|
|
1108
|
+
const imgHeight = img.naturalHeight;
|
|
1109
|
+
const centerX = containerWidth / 2;
|
|
1110
|
+
const centerY = containerHeight / 2;
|
|
1111
|
+
const cropX = (centerX - crop.x - cropWidth / 2) / scale;
|
|
1112
|
+
const cropY = (centerY - crop.y - cropHeight / 2) / scale;
|
|
1113
|
+
onCropCompleteInternal(null, {
|
|
1114
|
+
x: Math.max(0, cropX),
|
|
1115
|
+
y: Math.max(0, cropY),
|
|
1116
|
+
width: Math.min(cropWidth / scale, imgWidth),
|
|
1117
|
+
height: Math.min(cropHeight / scale, imgHeight)
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
),
|
|
1122
|
+
/* @__PURE__ */ React7.createElement(
|
|
1123
|
+
"div",
|
|
1124
|
+
{
|
|
1125
|
+
className: "file-input-cropper-overlay-box",
|
|
1126
|
+
style: {
|
|
1127
|
+
width: cropAspectRatio ? `${Math.min(80, 80 * cropAspectRatio)}%` : "80%",
|
|
1128
|
+
aspectRatio: cropAspectRatio ? String(cropAspectRatio) : void 0
|
|
1129
|
+
}
|
|
1130
|
+
},
|
|
1131
|
+
/* @__PURE__ */ React7.createElement("div", { className: "file-input-cropper-grid" }, /* @__PURE__ */ React7.createElement("div", { className: "file-input-cropper-grid-line" }), /* @__PURE__ */ React7.createElement("div", { className: "file-input-cropper-grid-line" }))
|
|
1132
|
+
)
|
|
1133
|
+
), /* @__PURE__ */ React7.createElement("div", { className: "file-input-cropper-controls" }, /* @__PURE__ */ React7.createElement("label", { htmlFor: "zoom-slider", className: "file-input-cropper-label" }, "Zoom: ", zoom.toFixed(1), "x"), /* @__PURE__ */ React7.createElement(
|
|
1134
|
+
"input",
|
|
1135
|
+
{
|
|
1136
|
+
id: "zoom-slider",
|
|
1137
|
+
type: "range",
|
|
1138
|
+
min: "1",
|
|
1139
|
+
max: "3",
|
|
1140
|
+
step: "0.1",
|
|
1141
|
+
value: zoom,
|
|
1142
|
+
onChange: (e) => onZoomChange(parseFloat(e.target.value)),
|
|
1143
|
+
className: "file-input-cropper-slider",
|
|
1144
|
+
"aria-label": "Zoom level"
|
|
1145
|
+
}
|
|
1146
|
+
))), /* @__PURE__ */ React7.createElement("div", { className: "file-input-cropper-footer" }, /* @__PURE__ */ React7.createElement(
|
|
1147
|
+
"button",
|
|
1148
|
+
{
|
|
1149
|
+
type: "button",
|
|
1150
|
+
className: "file-input-cropper-button file-input-cropper-button--cancel",
|
|
1151
|
+
onClick: handleCropCancel
|
|
1152
|
+
},
|
|
1153
|
+
"Cancel"
|
|
1154
|
+
), /* @__PURE__ */ React7.createElement(
|
|
1155
|
+
"button",
|
|
1156
|
+
{
|
|
1157
|
+
type: "button",
|
|
1158
|
+
className: "file-input-cropper-button file-input-cropper-button--save",
|
|
1159
|
+
onClick: handleCropSave
|
|
1160
|
+
},
|
|
1161
|
+
"Save"
|
|
1162
|
+
)))));
|
|
899
1163
|
}
|
|
900
1164
|
FileInput.displayName = "FileInput";
|
|
1165
|
+
function formatDate(date, format) {
|
|
1166
|
+
if (!date) return "";
|
|
1167
|
+
const d = new Date(date);
|
|
1168
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
1169
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
1170
|
+
const year = d.getFullYear();
|
|
1171
|
+
return format.replace("MM", month).replace("dd", day).replace("yyyy", String(year)).replace("yy", String(year).slice(2));
|
|
1172
|
+
}
|
|
1173
|
+
function parseDate(dateString, format) {
|
|
1174
|
+
if (!dateString) return null;
|
|
1175
|
+
try {
|
|
1176
|
+
if (format === "MM/dd/yyyy" || format === "MM-dd-yyyy") {
|
|
1177
|
+
const parts = dateString.split(/[/-]/);
|
|
1178
|
+
if (parts.length === 3) {
|
|
1179
|
+
const month = parseInt(parts[0], 10) - 1;
|
|
1180
|
+
const day = parseInt(parts[1], 10);
|
|
1181
|
+
const year = parseInt(parts[2], 10);
|
|
1182
|
+
const date2 = new Date(year, month, day);
|
|
1183
|
+
if (!isNaN(date2.getTime())) {
|
|
1184
|
+
return date2;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
const date = new Date(dateString);
|
|
1189
|
+
return isNaN(date.getTime()) ? null : date;
|
|
1190
|
+
} catch {
|
|
1191
|
+
return null;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
function isDateInArray(date, dates) {
|
|
1195
|
+
const dateStr = date.toDateString();
|
|
1196
|
+
return dates.some((d) => d.toDateString() === dateStr);
|
|
1197
|
+
}
|
|
1198
|
+
function DatePicker({
|
|
1199
|
+
name,
|
|
1200
|
+
value,
|
|
1201
|
+
onChange,
|
|
1202
|
+
onBlur,
|
|
1203
|
+
disabled = false,
|
|
1204
|
+
required = false,
|
|
1205
|
+
error = false,
|
|
1206
|
+
className = "",
|
|
1207
|
+
placeholder = "Select date...",
|
|
1208
|
+
format = "MM/dd/yyyy",
|
|
1209
|
+
minDate,
|
|
1210
|
+
maxDate,
|
|
1211
|
+
disabledDates = [],
|
|
1212
|
+
isDateDisabled,
|
|
1213
|
+
clearable = true,
|
|
1214
|
+
showIcon = true,
|
|
1215
|
+
...props
|
|
1216
|
+
}) {
|
|
1217
|
+
const [isOpen, setIsOpen] = React7.useState(false);
|
|
1218
|
+
const [inputValue, setInputValue] = React7.useState("");
|
|
1219
|
+
const [selectedMonth, setSelectedMonth] = React7.useState(value || /* @__PURE__ */ new Date());
|
|
1220
|
+
const containerRef = React7.useRef(null);
|
|
1221
|
+
const inputRef = React7.useRef(null);
|
|
1222
|
+
React7.useEffect(() => {
|
|
1223
|
+
setInputValue(formatDate(value, format));
|
|
1224
|
+
if (value) {
|
|
1225
|
+
setSelectedMonth(value);
|
|
1226
|
+
}
|
|
1227
|
+
}, [value, format]);
|
|
1228
|
+
const handleDateSelect = (date) => {
|
|
1229
|
+
onChange(date);
|
|
1230
|
+
setIsOpen(false);
|
|
1231
|
+
onBlur?.();
|
|
1232
|
+
};
|
|
1233
|
+
const handleInputChange = (e) => {
|
|
1234
|
+
const newValue = e.target.value;
|
|
1235
|
+
setInputValue(newValue);
|
|
1236
|
+
const parsedDate = parseDate(newValue, format);
|
|
1237
|
+
if (parsedDate && !isNaN(parsedDate.getTime())) {
|
|
1238
|
+
onChange(parsedDate);
|
|
1239
|
+
} else if (newValue === "") {
|
|
1240
|
+
onChange(null);
|
|
1241
|
+
}
|
|
1242
|
+
};
|
|
1243
|
+
const handleClear = (e) => {
|
|
1244
|
+
e.stopPropagation();
|
|
1245
|
+
onChange(null);
|
|
1246
|
+
setInputValue("");
|
|
1247
|
+
inputRef.current?.focus();
|
|
1248
|
+
};
|
|
1249
|
+
const handleToggle = () => {
|
|
1250
|
+
if (disabled) return;
|
|
1251
|
+
setIsOpen(!isOpen);
|
|
1252
|
+
};
|
|
1253
|
+
const isDisabled = (date) => {
|
|
1254
|
+
if (minDate && date < minDate) return true;
|
|
1255
|
+
if (maxDate && date > maxDate) return true;
|
|
1256
|
+
if (isDateInArray(date, disabledDates)) return true;
|
|
1257
|
+
if (isDateDisabled && isDateDisabled(date)) return true;
|
|
1258
|
+
return false;
|
|
1259
|
+
};
|
|
1260
|
+
React7.useEffect(() => {
|
|
1261
|
+
const handleClickOutside = (event) => {
|
|
1262
|
+
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
1263
|
+
setIsOpen(false);
|
|
1264
|
+
onBlur?.();
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
if (isOpen) {
|
|
1268
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
1269
|
+
return () => {
|
|
1270
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
}, [isOpen, onBlur]);
|
|
1274
|
+
const renderCalendar = () => {
|
|
1275
|
+
const year = selectedMonth.getFullYear();
|
|
1276
|
+
const month = selectedMonth.getMonth();
|
|
1277
|
+
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
1278
|
+
const firstDayOfMonth = new Date(year, month, 1).getDay();
|
|
1279
|
+
const days = [];
|
|
1280
|
+
for (let i = 0; i < firstDayOfMonth; i++) {
|
|
1281
|
+
days.push(null);
|
|
1282
|
+
}
|
|
1283
|
+
for (let day = 1; day <= daysInMonth; day++) {
|
|
1284
|
+
days.push(new Date(year, month, day));
|
|
1285
|
+
}
|
|
1286
|
+
const monthNames = [
|
|
1287
|
+
"January",
|
|
1288
|
+
"February",
|
|
1289
|
+
"March",
|
|
1290
|
+
"April",
|
|
1291
|
+
"May",
|
|
1292
|
+
"June",
|
|
1293
|
+
"July",
|
|
1294
|
+
"August",
|
|
1295
|
+
"September",
|
|
1296
|
+
"October",
|
|
1297
|
+
"November",
|
|
1298
|
+
"December"
|
|
1299
|
+
];
|
|
1300
|
+
const handlePrevMonth = () => {
|
|
1301
|
+
setSelectedMonth(new Date(year, month - 1, 1));
|
|
1302
|
+
};
|
|
1303
|
+
const handleNextMonth = () => {
|
|
1304
|
+
setSelectedMonth(new Date(year, month + 1, 1));
|
|
1305
|
+
};
|
|
1306
|
+
return /* @__PURE__ */ React7.createElement("div", { className: "datepicker-calendar", role: "grid", "aria-label": "Calendar" }, /* @__PURE__ */ React7.createElement("div", { className: "datepicker-calendar-header" }, /* @__PURE__ */ React7.createElement(
|
|
1307
|
+
"button",
|
|
1308
|
+
{
|
|
1309
|
+
type: "button",
|
|
1310
|
+
className: "datepicker-calendar-nav",
|
|
1311
|
+
onClick: handlePrevMonth,
|
|
1312
|
+
"aria-label": "Previous month"
|
|
1313
|
+
},
|
|
1314
|
+
"\u2190"
|
|
1315
|
+
), /* @__PURE__ */ React7.createElement("div", { className: "datepicker-calendar-month" }, monthNames[month], " ", year), /* @__PURE__ */ React7.createElement(
|
|
1316
|
+
"button",
|
|
1317
|
+
{
|
|
1318
|
+
type: "button",
|
|
1319
|
+
className: "datepicker-calendar-nav",
|
|
1320
|
+
onClick: handleNextMonth,
|
|
1321
|
+
"aria-label": "Next month"
|
|
1322
|
+
},
|
|
1323
|
+
"\u2192"
|
|
1324
|
+
)), /* @__PURE__ */ React7.createElement("div", { className: "datepicker-calendar-weekdays" }, ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map((day) => /* @__PURE__ */ React7.createElement("div", { key: day, className: "datepicker-calendar-weekday" }, day))), /* @__PURE__ */ React7.createElement("div", { className: "datepicker-calendar-days" }, days.map((date, index) => {
|
|
1325
|
+
if (!date) {
|
|
1326
|
+
return /* @__PURE__ */ React7.createElement("div", { key: `empty-${index}`, className: "datepicker-calendar-day datepicker-calendar-day--empty" });
|
|
1327
|
+
}
|
|
1328
|
+
const isSelected = value && date.toDateString() === value.toDateString();
|
|
1329
|
+
const isToday = date.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
|
|
1330
|
+
const disabled2 = isDisabled(date);
|
|
1331
|
+
return /* @__PURE__ */ React7.createElement(
|
|
1332
|
+
"button",
|
|
1333
|
+
{
|
|
1334
|
+
key: date.toISOString(),
|
|
1335
|
+
type: "button",
|
|
1336
|
+
className: `datepicker-calendar-day ${isSelected ? "datepicker-calendar-day--selected" : ""} ${isToday ? "datepicker-calendar-day--today" : ""} ${disabled2 ? "datepicker-calendar-day--disabled" : ""}`,
|
|
1337
|
+
onClick: () => !disabled2 && handleDateSelect(date),
|
|
1338
|
+
disabled: disabled2,
|
|
1339
|
+
"aria-label": formatDate(date, format)
|
|
1340
|
+
},
|
|
1341
|
+
date.getDate()
|
|
1342
|
+
);
|
|
1343
|
+
})));
|
|
1344
|
+
};
|
|
1345
|
+
const baseClassName = "datepicker";
|
|
1346
|
+
const errorClassName = error ? "datepicker--error" : "";
|
|
1347
|
+
const disabledClassName = disabled ? "datepicker--disabled" : "";
|
|
1348
|
+
const openClassName = isOpen ? "datepicker--open" : "";
|
|
1349
|
+
const combinedClassName = `${baseClassName} ${errorClassName} ${disabledClassName} ${openClassName} ${className}`.trim();
|
|
1350
|
+
return /* @__PURE__ */ React7.createElement("div", { ref: containerRef, className: combinedClassName }, /* @__PURE__ */ React7.createElement(
|
|
1351
|
+
"input",
|
|
1352
|
+
{
|
|
1353
|
+
type: "hidden",
|
|
1354
|
+
name,
|
|
1355
|
+
value: value ? value.toISOString() : ""
|
|
1356
|
+
}
|
|
1357
|
+
), /* @__PURE__ */ React7.createElement("div", { className: "datepicker-input-wrapper" }, showIcon && /* @__PURE__ */ React7.createElement("span", { className: "datepicker-icon", "aria-hidden": "true" }, /* @__PURE__ */ React7.createElement(
|
|
1358
|
+
"svg",
|
|
1359
|
+
{
|
|
1360
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1361
|
+
width: "18",
|
|
1362
|
+
height: "18",
|
|
1363
|
+
viewBox: "0 0 24 24",
|
|
1364
|
+
fill: "none",
|
|
1365
|
+
stroke: "currentColor",
|
|
1366
|
+
strokeLinecap: "round",
|
|
1367
|
+
strokeLinejoin: "round",
|
|
1368
|
+
strokeWidth: "2"
|
|
1369
|
+
},
|
|
1370
|
+
/* @__PURE__ */ React7.createElement("path", { d: "M8 2v4m8-4v4m5 8V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8M3 10h18m-5 10l2 2l4-4" })
|
|
1371
|
+
)), /* @__PURE__ */ React7.createElement(
|
|
1372
|
+
"input",
|
|
1373
|
+
{
|
|
1374
|
+
ref: inputRef,
|
|
1375
|
+
type: "text",
|
|
1376
|
+
className: "datepicker-input",
|
|
1377
|
+
value: inputValue,
|
|
1378
|
+
onChange: handleInputChange,
|
|
1379
|
+
onClick: handleToggle,
|
|
1380
|
+
onBlur,
|
|
1381
|
+
disabled,
|
|
1382
|
+
required,
|
|
1383
|
+
placeholder,
|
|
1384
|
+
"aria-invalid": error || props["aria-invalid"] ? "true" : "false",
|
|
1385
|
+
"aria-describedby": props["aria-describedby"],
|
|
1386
|
+
"aria-required": required || props["aria-required"],
|
|
1387
|
+
readOnly: true
|
|
1388
|
+
}
|
|
1389
|
+
), clearable && value && !disabled && /* @__PURE__ */ React7.createElement(
|
|
1390
|
+
"button",
|
|
1391
|
+
{
|
|
1392
|
+
type: "button",
|
|
1393
|
+
className: "datepicker-clear",
|
|
1394
|
+
onClick: handleClear,
|
|
1395
|
+
"aria-label": "Clear date",
|
|
1396
|
+
tabIndex: -1
|
|
1397
|
+
},
|
|
1398
|
+
"\u2715"
|
|
1399
|
+
)), isOpen && !disabled && /* @__PURE__ */ React7.createElement("div", { className: "datepicker-dropdown" }, renderCalendar()));
|
|
1400
|
+
}
|
|
1401
|
+
DatePicker.displayName = "DatePicker";
|
|
1402
|
+
function parseTimeString(timeStr, use24Hour) {
|
|
1403
|
+
if (!timeStr) return null;
|
|
1404
|
+
try {
|
|
1405
|
+
if (use24Hour) {
|
|
1406
|
+
const [hourStr, minuteStr] = timeStr.split(":");
|
|
1407
|
+
const hour24 = parseInt(hourStr, 10);
|
|
1408
|
+
const minute = parseInt(minuteStr, 10);
|
|
1409
|
+
if (isNaN(hour24) || isNaN(minute)) return null;
|
|
1410
|
+
if (hour24 < 0 || hour24 > 23) return null;
|
|
1411
|
+
if (minute < 0 || minute > 59) return null;
|
|
1412
|
+
const period = hour24 >= 12 ? "PM" : "AM";
|
|
1413
|
+
const hour = hour24 === 0 ? 12 : hour24 > 12 ? hour24 - 12 : hour24;
|
|
1414
|
+
return { hour, minute, period };
|
|
1415
|
+
} else {
|
|
1416
|
+
const match = timeStr.match(/^(\d{1,2}):(\d{2})\s*(AM|PM)$/i);
|
|
1417
|
+
if (!match) return null;
|
|
1418
|
+
const hour = parseInt(match[1], 10);
|
|
1419
|
+
const minute = parseInt(match[2], 10);
|
|
1420
|
+
const period = match[3].toUpperCase();
|
|
1421
|
+
if (hour < 1 || hour > 12) return null;
|
|
1422
|
+
if (minute < 0 || minute > 59) return null;
|
|
1423
|
+
return { hour, minute, period };
|
|
1424
|
+
}
|
|
1425
|
+
} catch {
|
|
1426
|
+
return null;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
function formatTimeValue(time, use24Hour) {
|
|
1430
|
+
if (!time) return "";
|
|
1431
|
+
if (use24Hour) {
|
|
1432
|
+
let hour24 = time.hour;
|
|
1433
|
+
if (time.period === "PM" && time.hour !== 12) {
|
|
1434
|
+
hour24 = time.hour + 12;
|
|
1435
|
+
} else if (time.period === "AM" && time.hour === 12) {
|
|
1436
|
+
hour24 = 0;
|
|
1437
|
+
}
|
|
1438
|
+
return `${String(hour24).padStart(2, "0")}:${String(time.minute).padStart(2, "0")}`;
|
|
1439
|
+
} else {
|
|
1440
|
+
return `${time.hour}:${String(time.minute).padStart(2, "0")} ${time.period}`;
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
function TimePicker({
|
|
1444
|
+
name,
|
|
1445
|
+
value,
|
|
1446
|
+
onChange,
|
|
1447
|
+
onBlur,
|
|
1448
|
+
disabled = false,
|
|
1449
|
+
required = false,
|
|
1450
|
+
error = false,
|
|
1451
|
+
className = "",
|
|
1452
|
+
placeholder = "Select time...",
|
|
1453
|
+
use24Hour = false,
|
|
1454
|
+
minuteStep = 1,
|
|
1455
|
+
clearable = true,
|
|
1456
|
+
showIcon = true,
|
|
1457
|
+
...props
|
|
1458
|
+
}) {
|
|
1459
|
+
const [isOpen, setIsOpen] = React7.useState(false);
|
|
1460
|
+
const [timeValue, setTimeValue] = React7.useState(null);
|
|
1461
|
+
const containerRef = React7.useRef(null);
|
|
1462
|
+
const inputRef = React7.useRef(null);
|
|
1463
|
+
React7.useEffect(() => {
|
|
1464
|
+
const parsed = parseTimeString(value, use24Hour);
|
|
1465
|
+
setTimeValue(parsed);
|
|
1466
|
+
}, [value, use24Hour]);
|
|
1467
|
+
const handleHourChange = (hour) => {
|
|
1468
|
+
const newTime = {
|
|
1469
|
+
hour,
|
|
1470
|
+
minute: timeValue?.minute || 0,
|
|
1471
|
+
period: timeValue?.period || "AM"
|
|
1472
|
+
};
|
|
1473
|
+
setTimeValue(newTime);
|
|
1474
|
+
onChange(formatTimeValue(newTime, use24Hour));
|
|
1475
|
+
};
|
|
1476
|
+
const handleMinuteChange = (minute) => {
|
|
1477
|
+
const newTime = {
|
|
1478
|
+
hour: timeValue?.hour || 12,
|
|
1479
|
+
minute,
|
|
1480
|
+
period: timeValue?.period || "AM"
|
|
1481
|
+
};
|
|
1482
|
+
setTimeValue(newTime);
|
|
1483
|
+
onChange(formatTimeValue(newTime, use24Hour));
|
|
1484
|
+
};
|
|
1485
|
+
const handlePeriodChange = (period) => {
|
|
1486
|
+
const newTime = {
|
|
1487
|
+
hour: timeValue?.hour || 12,
|
|
1488
|
+
minute: timeValue?.minute || 0,
|
|
1489
|
+
period
|
|
1490
|
+
};
|
|
1491
|
+
setTimeValue(newTime);
|
|
1492
|
+
onChange(formatTimeValue(newTime, use24Hour));
|
|
1493
|
+
};
|
|
1494
|
+
const handleClear = (e) => {
|
|
1495
|
+
e.stopPropagation();
|
|
1496
|
+
onChange("");
|
|
1497
|
+
setTimeValue(null);
|
|
1498
|
+
inputRef.current?.focus();
|
|
1499
|
+
};
|
|
1500
|
+
const handleToggle = () => {
|
|
1501
|
+
if (disabled) return;
|
|
1502
|
+
setIsOpen(!isOpen);
|
|
1503
|
+
};
|
|
1504
|
+
React7.useEffect(() => {
|
|
1505
|
+
const handleClickOutside = (event) => {
|
|
1506
|
+
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
1507
|
+
setIsOpen(false);
|
|
1508
|
+
onBlur?.();
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
if (isOpen) {
|
|
1512
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
1513
|
+
return () => {
|
|
1514
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
}, [isOpen, onBlur]);
|
|
1518
|
+
const hours = React7.useMemo(() => {
|
|
1519
|
+
if (use24Hour) {
|
|
1520
|
+
return Array.from({ length: 24 }, (_, i) => i);
|
|
1521
|
+
} else {
|
|
1522
|
+
return Array.from({ length: 12 }, (_, i) => i + 1);
|
|
1523
|
+
}
|
|
1524
|
+
}, [use24Hour]);
|
|
1525
|
+
const minutes = React7.useMemo(() => {
|
|
1526
|
+
const mins = [];
|
|
1527
|
+
for (let i = 0; i < 60; i += minuteStep) {
|
|
1528
|
+
mins.push(i);
|
|
1529
|
+
}
|
|
1530
|
+
return mins;
|
|
1531
|
+
}, [minuteStep]);
|
|
1532
|
+
const baseClassName = "timepicker";
|
|
1533
|
+
const errorClassName = error ? "timepicker--error" : "";
|
|
1534
|
+
const disabledClassName = disabled ? "timepicker--disabled" : "";
|
|
1535
|
+
const openClassName = isOpen ? "timepicker--open" : "";
|
|
1536
|
+
const combinedClassName = `${baseClassName} ${errorClassName} ${disabledClassName} ${openClassName} ${className}`.trim();
|
|
1537
|
+
const displayValue = formatTimeValue(timeValue, use24Hour);
|
|
1538
|
+
return /* @__PURE__ */ React7.createElement("div", { ref: containerRef, className: combinedClassName }, /* @__PURE__ */ React7.createElement(
|
|
1539
|
+
"input",
|
|
1540
|
+
{
|
|
1541
|
+
type: "hidden",
|
|
1542
|
+
name,
|
|
1543
|
+
value
|
|
1544
|
+
}
|
|
1545
|
+
), /* @__PURE__ */ React7.createElement("div", { className: "timepicker-input-wrapper" }, showIcon && /* @__PURE__ */ React7.createElement("span", { className: "timepicker-icon", "aria-hidden": "true" }, /* @__PURE__ */ React7.createElement(
|
|
1546
|
+
"svg",
|
|
1547
|
+
{
|
|
1548
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1549
|
+
width: "18",
|
|
1550
|
+
height: "18",
|
|
1551
|
+
viewBox: "0 0 24 24",
|
|
1552
|
+
fill: "none",
|
|
1553
|
+
stroke: "currentColor",
|
|
1554
|
+
strokeLinecap: "round",
|
|
1555
|
+
strokeLinejoin: "round",
|
|
1556
|
+
strokeWidth: "2"
|
|
1557
|
+
},
|
|
1558
|
+
/* @__PURE__ */ React7.createElement("circle", { cx: "12", cy: "12", r: "10" }),
|
|
1559
|
+
/* @__PURE__ */ React7.createElement("path", { d: "M12 6v6l4 2" })
|
|
1560
|
+
)), /* @__PURE__ */ React7.createElement(
|
|
1561
|
+
"input",
|
|
1562
|
+
{
|
|
1563
|
+
ref: inputRef,
|
|
1564
|
+
type: "text",
|
|
1565
|
+
className: "timepicker-input",
|
|
1566
|
+
value: displayValue,
|
|
1567
|
+
onClick: handleToggle,
|
|
1568
|
+
onBlur,
|
|
1569
|
+
disabled,
|
|
1570
|
+
required,
|
|
1571
|
+
placeholder,
|
|
1572
|
+
"aria-invalid": error || props["aria-invalid"] ? "true" : "false",
|
|
1573
|
+
"aria-describedby": props["aria-describedby"],
|
|
1574
|
+
"aria-required": required || props["aria-required"],
|
|
1575
|
+
readOnly: true
|
|
1576
|
+
}
|
|
1577
|
+
), clearable && value && !disabled && /* @__PURE__ */ React7.createElement(
|
|
1578
|
+
"button",
|
|
1579
|
+
{
|
|
1580
|
+
type: "button",
|
|
1581
|
+
className: "timepicker-clear",
|
|
1582
|
+
onClick: handleClear,
|
|
1583
|
+
"aria-label": "Clear time",
|
|
1584
|
+
tabIndex: -1
|
|
1585
|
+
},
|
|
1586
|
+
"\u2715"
|
|
1587
|
+
)), isOpen && !disabled && /* @__PURE__ */ React7.createElement("div", { className: "timepicker-dropdown" }, /* @__PURE__ */ React7.createElement("div", { className: "timepicker-selectors" }, /* @__PURE__ */ React7.createElement("div", { className: "timepicker-column" }, /* @__PURE__ */ React7.createElement("div", { className: "timepicker-column-label" }, use24Hour ? "Hour" : "Hour"), /* @__PURE__ */ React7.createElement("div", { className: "timepicker-column-options" }, hours.map((hour) => {
|
|
1588
|
+
const displayHour = use24Hour ? hour : hour;
|
|
1589
|
+
const isSelected = use24Hour ? timeValue?.hour === (hour === 0 ? 12 : hour > 12 ? hour - 12 : hour) && timeValue?.period === (hour >= 12 ? "PM" : "AM") : timeValue?.hour === hour;
|
|
1590
|
+
return /* @__PURE__ */ React7.createElement(
|
|
1591
|
+
"button",
|
|
1592
|
+
{
|
|
1593
|
+
key: hour,
|
|
1594
|
+
type: "button",
|
|
1595
|
+
className: `timepicker-option ${isSelected ? "timepicker-option--selected" : ""}`,
|
|
1596
|
+
onClick: () => {
|
|
1597
|
+
if (use24Hour) {
|
|
1598
|
+
const hour12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
|
|
1599
|
+
const period = hour >= 12 ? "PM" : "AM";
|
|
1600
|
+
const newTime = {
|
|
1601
|
+
hour: hour12,
|
|
1602
|
+
minute: timeValue?.minute || 0,
|
|
1603
|
+
period
|
|
1604
|
+
};
|
|
1605
|
+
setTimeValue(newTime);
|
|
1606
|
+
onChange(formatTimeValue(newTime, use24Hour));
|
|
1607
|
+
} else {
|
|
1608
|
+
handleHourChange(hour);
|
|
1609
|
+
}
|
|
1610
|
+
},
|
|
1611
|
+
"aria-label": `${String(displayHour).padStart(2, "0")} hours`
|
|
1612
|
+
},
|
|
1613
|
+
String(displayHour).padStart(2, "0")
|
|
1614
|
+
);
|
|
1615
|
+
}))), /* @__PURE__ */ React7.createElement("div", { className: "timepicker-column" }, /* @__PURE__ */ React7.createElement("div", { className: "timepicker-column-label" }, "Minute"), /* @__PURE__ */ React7.createElement("div", { className: "timepicker-column-options" }, minutes.map((minute) => {
|
|
1616
|
+
const isSelected = timeValue?.minute === minute;
|
|
1617
|
+
return /* @__PURE__ */ React7.createElement(
|
|
1618
|
+
"button",
|
|
1619
|
+
{
|
|
1620
|
+
key: minute,
|
|
1621
|
+
type: "button",
|
|
1622
|
+
className: `timepicker-option ${isSelected ? "timepicker-option--selected" : ""}`,
|
|
1623
|
+
onClick: () => handleMinuteChange(minute),
|
|
1624
|
+
"aria-label": `${String(minute).padStart(2, "0")} minutes`
|
|
1625
|
+
},
|
|
1626
|
+
String(minute).padStart(2, "0")
|
|
1627
|
+
);
|
|
1628
|
+
}))), !use24Hour && /* @__PURE__ */ React7.createElement("div", { className: "timepicker-column timepicker-column--period" }, /* @__PURE__ */ React7.createElement("div", { className: "timepicker-column-label" }, "Period"), /* @__PURE__ */ React7.createElement("div", { className: "timepicker-column-options" }, /* @__PURE__ */ React7.createElement(
|
|
1629
|
+
"button",
|
|
1630
|
+
{
|
|
1631
|
+
type: "button",
|
|
1632
|
+
className: `timepicker-option ${timeValue?.period === "AM" ? "timepicker-option--selected" : ""}`,
|
|
1633
|
+
onClick: () => handlePeriodChange("AM")
|
|
1634
|
+
},
|
|
1635
|
+
"AM"
|
|
1636
|
+
), /* @__PURE__ */ React7.createElement(
|
|
1637
|
+
"button",
|
|
1638
|
+
{
|
|
1639
|
+
type: "button",
|
|
1640
|
+
className: `timepicker-option ${timeValue?.period === "PM" ? "timepicker-option--selected" : ""}`,
|
|
1641
|
+
onClick: () => handlePeriodChange("PM")
|
|
1642
|
+
},
|
|
1643
|
+
"PM"
|
|
1644
|
+
))))));
|
|
1645
|
+
}
|
|
1646
|
+
TimePicker.displayName = "TimePicker";
|
|
1647
|
+
function formatDate2(date, format) {
|
|
1648
|
+
if (!date) return "";
|
|
1649
|
+
const d = new Date(date);
|
|
1650
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
1651
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
1652
|
+
const year = d.getFullYear();
|
|
1653
|
+
return format.replace("MM", month).replace("dd", day).replace("yyyy", String(year)).replace("yy", String(year).slice(2));
|
|
1654
|
+
}
|
|
1655
|
+
function isDateInArray2(date, dates) {
|
|
1656
|
+
const dateStr = date.toDateString();
|
|
1657
|
+
return dates.some((d) => d.toDateString() === dateStr);
|
|
1658
|
+
}
|
|
1659
|
+
function isDateInRange(date, start, end) {
|
|
1660
|
+
if (!start || !end) return false;
|
|
1661
|
+
const time = date.getTime();
|
|
1662
|
+
return time >= start.getTime() && time <= end.getTime();
|
|
1663
|
+
}
|
|
1664
|
+
function DateRangePicker({
|
|
1665
|
+
name,
|
|
1666
|
+
value = { start: null, end: null },
|
|
1667
|
+
onChange,
|
|
1668
|
+
onBlur,
|
|
1669
|
+
disabled = false,
|
|
1670
|
+
required = false,
|
|
1671
|
+
error = false,
|
|
1672
|
+
className = "",
|
|
1673
|
+
placeholder = "Select date range...",
|
|
1674
|
+
format = "MM/dd/yyyy",
|
|
1675
|
+
minDate,
|
|
1676
|
+
maxDate,
|
|
1677
|
+
disabledDates = [],
|
|
1678
|
+
isDateDisabled,
|
|
1679
|
+
clearable = true,
|
|
1680
|
+
showIcon = true,
|
|
1681
|
+
separator = " - ",
|
|
1682
|
+
...props
|
|
1683
|
+
}) {
|
|
1684
|
+
const [isOpen, setIsOpen] = React7.useState(false);
|
|
1685
|
+
const [selectedMonth, setSelectedMonth] = React7.useState(value.start || /* @__PURE__ */ new Date());
|
|
1686
|
+
const [rangeStart, setRangeStart] = React7.useState(value.start);
|
|
1687
|
+
const [rangeEnd, setRangeEnd] = React7.useState(value.end);
|
|
1688
|
+
const [hoverDate, setHoverDate] = React7.useState(null);
|
|
1689
|
+
const containerRef = React7.useRef(null);
|
|
1690
|
+
React7.useEffect(() => {
|
|
1691
|
+
setRangeStart(value.start);
|
|
1692
|
+
setRangeEnd(value.end);
|
|
1693
|
+
if (value.start) {
|
|
1694
|
+
setSelectedMonth(value.start);
|
|
1695
|
+
}
|
|
1696
|
+
}, [value]);
|
|
1697
|
+
const handleDateSelect = (date) => {
|
|
1698
|
+
if (!rangeStart || rangeStart && rangeEnd) {
|
|
1699
|
+
setRangeStart(date);
|
|
1700
|
+
setRangeEnd(null);
|
|
1701
|
+
onChange({ start: date, end: null });
|
|
1702
|
+
} else {
|
|
1703
|
+
if (date < rangeStart) {
|
|
1704
|
+
setRangeStart(date);
|
|
1705
|
+
setRangeEnd(rangeStart);
|
|
1706
|
+
onChange({ start: date, end: rangeStart });
|
|
1707
|
+
setIsOpen(false);
|
|
1708
|
+
} else {
|
|
1709
|
+
setRangeEnd(date);
|
|
1710
|
+
onChange({ start: rangeStart, end: date });
|
|
1711
|
+
setIsOpen(false);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
onBlur?.();
|
|
1715
|
+
};
|
|
1716
|
+
const handleClear = (e) => {
|
|
1717
|
+
e.stopPropagation();
|
|
1718
|
+
onChange({ start: null, end: null });
|
|
1719
|
+
setRangeStart(null);
|
|
1720
|
+
setRangeEnd(null);
|
|
1721
|
+
};
|
|
1722
|
+
const handleToggle = () => {
|
|
1723
|
+
if (disabled) return;
|
|
1724
|
+
setIsOpen(!isOpen);
|
|
1725
|
+
};
|
|
1726
|
+
const isDisabled = (date) => {
|
|
1727
|
+
if (minDate && date < minDate) return true;
|
|
1728
|
+
if (maxDate && date > maxDate) return true;
|
|
1729
|
+
if (isDateInArray2(date, disabledDates)) return true;
|
|
1730
|
+
if (isDateDisabled && isDateDisabled(date)) return true;
|
|
1731
|
+
return false;
|
|
1732
|
+
};
|
|
1733
|
+
React7.useEffect(() => {
|
|
1734
|
+
const handleClickOutside = (event) => {
|
|
1735
|
+
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
1736
|
+
setIsOpen(false);
|
|
1737
|
+
onBlur?.();
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
if (isOpen) {
|
|
1741
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
1742
|
+
return () => {
|
|
1743
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
}, [isOpen, onBlur]);
|
|
1747
|
+
const renderCalendar = () => {
|
|
1748
|
+
const year = selectedMonth.getFullYear();
|
|
1749
|
+
const month = selectedMonth.getMonth();
|
|
1750
|
+
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
1751
|
+
const firstDayOfMonth = new Date(year, month, 1).getDay();
|
|
1752
|
+
const days = [];
|
|
1753
|
+
for (let i = 0; i < firstDayOfMonth; i++) {
|
|
1754
|
+
days.push(null);
|
|
1755
|
+
}
|
|
1756
|
+
for (let day = 1; day <= daysInMonth; day++) {
|
|
1757
|
+
days.push(new Date(year, month, day));
|
|
1758
|
+
}
|
|
1759
|
+
const monthNames = [
|
|
1760
|
+
"January",
|
|
1761
|
+
"February",
|
|
1762
|
+
"March",
|
|
1763
|
+
"April",
|
|
1764
|
+
"May",
|
|
1765
|
+
"June",
|
|
1766
|
+
"July",
|
|
1767
|
+
"August",
|
|
1768
|
+
"September",
|
|
1769
|
+
"October",
|
|
1770
|
+
"November",
|
|
1771
|
+
"December"
|
|
1772
|
+
];
|
|
1773
|
+
const handlePrevMonth = () => {
|
|
1774
|
+
setSelectedMonth(new Date(year, month - 1, 1));
|
|
1775
|
+
};
|
|
1776
|
+
const handleNextMonth = () => {
|
|
1777
|
+
setSelectedMonth(new Date(year, month + 1, 1));
|
|
1778
|
+
};
|
|
1779
|
+
return /* @__PURE__ */ React7.createElement("div", { className: "daterangepicker-calendar", role: "grid", "aria-label": "Calendar" }, /* @__PURE__ */ React7.createElement("div", { className: "daterangepicker-calendar-header" }, /* @__PURE__ */ React7.createElement(
|
|
1780
|
+
"button",
|
|
1781
|
+
{
|
|
1782
|
+
type: "button",
|
|
1783
|
+
className: "daterangepicker-calendar-nav",
|
|
1784
|
+
onClick: handlePrevMonth,
|
|
1785
|
+
"aria-label": "Previous month"
|
|
1786
|
+
},
|
|
1787
|
+
"\u2190"
|
|
1788
|
+
), /* @__PURE__ */ React7.createElement("div", { className: "daterangepicker-calendar-month" }, monthNames[month], " ", year), /* @__PURE__ */ React7.createElement(
|
|
1789
|
+
"button",
|
|
1790
|
+
{
|
|
1791
|
+
type: "button",
|
|
1792
|
+
className: "daterangepicker-calendar-nav",
|
|
1793
|
+
onClick: handleNextMonth,
|
|
1794
|
+
"aria-label": "Next month"
|
|
1795
|
+
},
|
|
1796
|
+
"\u2192"
|
|
1797
|
+
)), /* @__PURE__ */ React7.createElement("div", { className: "daterangepicker-calendar-weekdays" }, ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map((day) => /* @__PURE__ */ React7.createElement("div", { key: day, className: "daterangepicker-calendar-weekday" }, day))), /* @__PURE__ */ React7.createElement("div", { className: "daterangepicker-calendar-days" }, days.map((date, index) => {
|
|
1798
|
+
if (!date) {
|
|
1799
|
+
return /* @__PURE__ */ React7.createElement("div", { key: `empty-${index}`, className: "daterangepicker-calendar-day daterangepicker-calendar-day--empty" });
|
|
1800
|
+
}
|
|
1801
|
+
const isStart = rangeStart && date.toDateString() === rangeStart.toDateString();
|
|
1802
|
+
const isEnd = rangeEnd && date.toDateString() === rangeEnd.toDateString();
|
|
1803
|
+
const isInRange = rangeStart && rangeEnd && isDateInRange(date, rangeStart, rangeEnd);
|
|
1804
|
+
const isInHoverRange = rangeStart && !rangeEnd && hoverDate && (date >= rangeStart && date <= hoverDate || date <= rangeStart && date >= hoverDate);
|
|
1805
|
+
const isToday = date.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
|
|
1806
|
+
const disabled2 = isDisabled(date);
|
|
1807
|
+
return /* @__PURE__ */ React7.createElement(
|
|
1808
|
+
"button",
|
|
1809
|
+
{
|
|
1810
|
+
key: date.toISOString(),
|
|
1811
|
+
type: "button",
|
|
1812
|
+
className: `daterangepicker-calendar-day ${isStart || isEnd ? "daterangepicker-calendar-day--selected" : ""} ${isInRange || isInHoverRange ? "daterangepicker-calendar-day--in-range" : ""} ${isToday ? "daterangepicker-calendar-day--today" : ""} ${disabled2 ? "daterangepicker-calendar-day--disabled" : ""}`,
|
|
1813
|
+
onClick: () => !disabled2 && handleDateSelect(date),
|
|
1814
|
+
onMouseEnter: () => setHoverDate(date),
|
|
1815
|
+
onMouseLeave: () => setHoverDate(null),
|
|
1816
|
+
disabled: disabled2,
|
|
1817
|
+
"aria-label": formatDate2(date, format)
|
|
1818
|
+
},
|
|
1819
|
+
date.getDate()
|
|
1820
|
+
);
|
|
1821
|
+
})));
|
|
1822
|
+
};
|
|
1823
|
+
const baseClassName = "daterangepicker";
|
|
1824
|
+
const errorClassName = error ? "daterangepicker--error" : "";
|
|
1825
|
+
const disabledClassName = disabled ? "daterangepicker--disabled" : "";
|
|
1826
|
+
const openClassName = isOpen ? "daterangepicker--open" : "";
|
|
1827
|
+
const combinedClassName = `${baseClassName} ${errorClassName} ${disabledClassName} ${openClassName} ${className}`.trim();
|
|
1828
|
+
const displayValue = rangeStart && rangeEnd ? `${formatDate2(rangeStart, format)}${separator}${formatDate2(rangeEnd, format)}` : rangeStart ? formatDate2(rangeStart, format) : "";
|
|
1829
|
+
return /* @__PURE__ */ React7.createElement("div", { ref: containerRef, className: combinedClassName }, /* @__PURE__ */ React7.createElement(
|
|
1830
|
+
"input",
|
|
1831
|
+
{
|
|
1832
|
+
type: "hidden",
|
|
1833
|
+
name: `${name}[start]`,
|
|
1834
|
+
value: rangeStart ? rangeStart.toISOString() : ""
|
|
1835
|
+
}
|
|
1836
|
+
), /* @__PURE__ */ React7.createElement(
|
|
1837
|
+
"input",
|
|
1838
|
+
{
|
|
1839
|
+
type: "hidden",
|
|
1840
|
+
name: `${name}[end]`,
|
|
1841
|
+
value: rangeEnd ? rangeEnd.toISOString() : ""
|
|
1842
|
+
}
|
|
1843
|
+
), /* @__PURE__ */ React7.createElement("div", { className: "daterangepicker-input-wrapper" }, showIcon && /* @__PURE__ */ React7.createElement("span", { className: "daterangepicker-icon", "aria-hidden": "true" }, /* @__PURE__ */ React7.createElement(
|
|
1844
|
+
"svg",
|
|
1845
|
+
{
|
|
1846
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1847
|
+
width: "18",
|
|
1848
|
+
height: "18",
|
|
1849
|
+
viewBox: "0 0 24 24",
|
|
1850
|
+
fill: "none",
|
|
1851
|
+
stroke: "currentColor",
|
|
1852
|
+
strokeLinecap: "round",
|
|
1853
|
+
strokeLinejoin: "round",
|
|
1854
|
+
strokeWidth: "2"
|
|
1855
|
+
},
|
|
1856
|
+
/* @__PURE__ */ React7.createElement("path", { d: "M8 2v4m8-4v4m5 8V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8M3 10h18m-5 10l2 2l4-4" })
|
|
1857
|
+
)), /* @__PURE__ */ React7.createElement(
|
|
1858
|
+
"input",
|
|
1859
|
+
{
|
|
1860
|
+
type: "text",
|
|
1861
|
+
className: "daterangepicker-input",
|
|
1862
|
+
value: displayValue,
|
|
1863
|
+
onClick: handleToggle,
|
|
1864
|
+
onBlur,
|
|
1865
|
+
disabled,
|
|
1866
|
+
required,
|
|
1867
|
+
placeholder,
|
|
1868
|
+
"aria-invalid": error || props["aria-invalid"] ? "true" : "false",
|
|
1869
|
+
"aria-describedby": props["aria-describedby"],
|
|
1870
|
+
"aria-required": required || props["aria-required"],
|
|
1871
|
+
readOnly: true
|
|
1872
|
+
}
|
|
1873
|
+
), clearable && (rangeStart || rangeEnd) && !disabled && /* @__PURE__ */ React7.createElement(
|
|
1874
|
+
"button",
|
|
1875
|
+
{
|
|
1876
|
+
type: "button",
|
|
1877
|
+
className: "daterangepicker-clear",
|
|
1878
|
+
onClick: handleClear,
|
|
1879
|
+
"aria-label": "Clear date range",
|
|
1880
|
+
tabIndex: -1
|
|
1881
|
+
},
|
|
1882
|
+
"\u2715"
|
|
1883
|
+
)), isOpen && !disabled && /* @__PURE__ */ React7.createElement("div", { className: "daterangepicker-dropdown" }, renderCalendar(), rangeStart && !rangeEnd && /* @__PURE__ */ React7.createElement("div", { className: "daterangepicker-hint" }, "Select end date")));
|
|
1884
|
+
}
|
|
1885
|
+
DateRangePicker.displayName = "DateRangePicker";
|
|
1886
|
+
function htmlToMarkdown(html) {
|
|
1887
|
+
let markdown = html;
|
|
1888
|
+
markdown = markdown.replace(/<strong>(.*?)<\/strong>/g, "**$1**");
|
|
1889
|
+
markdown = markdown.replace(/<b>(.*?)<\/b>/g, "**$1**");
|
|
1890
|
+
markdown = markdown.replace(/<em>(.*?)<\/em>/g, "*$1*");
|
|
1891
|
+
markdown = markdown.replace(/<i>(.*?)<\/i>/g, "*$1*");
|
|
1892
|
+
markdown = markdown.replace(/<u>(.*?)<\/u>/g, "<u>$1</u>");
|
|
1893
|
+
markdown = markdown.replace(/<h1>(.*?)<\/h1>/g, "# $1\n");
|
|
1894
|
+
markdown = markdown.replace(/<h2>(.*?)<\/h2>/g, "## $1\n");
|
|
1895
|
+
markdown = markdown.replace(/<h3>(.*?)<\/h3>/g, "### $1\n");
|
|
1896
|
+
markdown = markdown.replace(/<a href="(.*?)">(.*?)<\/a>/g, "[$2]($1)");
|
|
1897
|
+
markdown = markdown.replace(/<ul>(.*?)<\/ul>/gs, (_match, content) => {
|
|
1898
|
+
return content.replace(/<li>(.*?)<\/li>/g, "- $1\n");
|
|
1899
|
+
});
|
|
1900
|
+
markdown = markdown.replace(/<ol>(.*?)<\/ol>/gs, (_match, content) => {
|
|
1901
|
+
let counter = 1;
|
|
1902
|
+
return content.replace(/<li>(.*?)<\/li>/g, () => {
|
|
1903
|
+
return `${counter++}. $1
|
|
1904
|
+
`;
|
|
1905
|
+
});
|
|
1906
|
+
});
|
|
1907
|
+
markdown = markdown.replace(/<p>(.*?)<\/p>/g, "$1\n\n");
|
|
1908
|
+
markdown = markdown.replace(/<br\s*\/?>/g, "\n");
|
|
1909
|
+
markdown = markdown.replace(/\n{3,}/g, "\n\n").trim();
|
|
1910
|
+
return markdown;
|
|
1911
|
+
}
|
|
1912
|
+
function markdownToHtml(markdown) {
|
|
1913
|
+
let html = markdown;
|
|
1914
|
+
html = html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1915
|
+
html = html.replace(/^### (.*?)$/gm, "<h3>$1</h3>");
|
|
1916
|
+
html = html.replace(/^## (.*?)$/gm, "<h2>$1</h2>");
|
|
1917
|
+
html = html.replace(/^# (.*?)$/gm, "<h1>$1</h1>");
|
|
1918
|
+
html = html.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>");
|
|
1919
|
+
html = html.replace(/\*(.*?)\*/g, "<em>$1</em>");
|
|
1920
|
+
html = html.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>');
|
|
1921
|
+
html = html.replace(/^- (.*?)$/gm, "<li>$1</li>");
|
|
1922
|
+
html = html.replace(/(<li>.*?<\/li>\n?)+/gs, "<ul>$&</ul>");
|
|
1923
|
+
html = html.replace(/^\d+\. (.*?)$/gm, "<li>$1</li>");
|
|
1924
|
+
html = html.replace(/^(?!<[h|ul|ol|li])(.+)$/gm, "<p>$1</p>");
|
|
1925
|
+
html = html.replace(/\n/g, "<br>");
|
|
1926
|
+
return html;
|
|
1927
|
+
}
|
|
1928
|
+
function RichTextEditor({
|
|
1929
|
+
name,
|
|
1930
|
+
value = "",
|
|
1931
|
+
onChange,
|
|
1932
|
+
onBlur,
|
|
1933
|
+
disabled = false,
|
|
1934
|
+
required = false,
|
|
1935
|
+
error = false,
|
|
1936
|
+
className = "",
|
|
1937
|
+
mode = "wysiwyg",
|
|
1938
|
+
allowModeSwitch = false,
|
|
1939
|
+
placeholder = "Start typing...",
|
|
1940
|
+
minHeight = "200px",
|
|
1941
|
+
maxHeight,
|
|
1942
|
+
showToolbar = true,
|
|
1943
|
+
toolbarButtons = ["bold", "italic", "underline", "heading", "bulletList", "orderedList", "link"],
|
|
1944
|
+
...props
|
|
1945
|
+
}) {
|
|
1946
|
+
const [currentMode, setCurrentMode] = React7.useState(mode);
|
|
1947
|
+
const [content, setContent] = React7.useState(value);
|
|
1948
|
+
const editorRef = React7.useRef(null);
|
|
1949
|
+
const textareaRef = React7.useRef(null);
|
|
1950
|
+
React7.useEffect(() => {
|
|
1951
|
+
setContent(value);
|
|
1952
|
+
if (currentMode === "wysiwyg" && editorRef.current) {
|
|
1953
|
+
editorRef.current.innerHTML = value;
|
|
1954
|
+
}
|
|
1955
|
+
}, [value, currentMode]);
|
|
1956
|
+
const handleWysiwygChange = () => {
|
|
1957
|
+
if (editorRef.current) {
|
|
1958
|
+
const newContent = editorRef.current.innerHTML;
|
|
1959
|
+
setContent(newContent);
|
|
1960
|
+
onChange(newContent);
|
|
1961
|
+
}
|
|
1962
|
+
};
|
|
1963
|
+
const handleMarkdownChange = (e) => {
|
|
1964
|
+
const newContent = e.target.value;
|
|
1965
|
+
setContent(newContent);
|
|
1966
|
+
onChange(newContent);
|
|
1967
|
+
};
|
|
1968
|
+
const execCommand = (command, value2) => {
|
|
1969
|
+
document.execCommand(command, false, value2);
|
|
1970
|
+
editorRef.current?.focus();
|
|
1971
|
+
handleWysiwygChange();
|
|
1972
|
+
};
|
|
1973
|
+
const handleModeToggle = () => {
|
|
1974
|
+
const newMode = currentMode === "wysiwyg" ? "markdown" : "wysiwyg";
|
|
1975
|
+
if (newMode === "markdown") {
|
|
1976
|
+
const markdown = htmlToMarkdown(content);
|
|
1977
|
+
setContent(markdown);
|
|
1978
|
+
onChange(markdown);
|
|
1979
|
+
} else {
|
|
1980
|
+
const html = markdownToHtml(content);
|
|
1981
|
+
setContent(html);
|
|
1982
|
+
onChange(html);
|
|
1983
|
+
if (editorRef.current) {
|
|
1984
|
+
editorRef.current.innerHTML = html;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
setCurrentMode(newMode);
|
|
1988
|
+
};
|
|
1989
|
+
const toolbarConfig = {
|
|
1990
|
+
bold: {
|
|
1991
|
+
command: "bold",
|
|
1992
|
+
icon: "B",
|
|
1993
|
+
title: "Bold",
|
|
1994
|
+
action: () => execCommand("bold")
|
|
1995
|
+
},
|
|
1996
|
+
italic: {
|
|
1997
|
+
command: "italic",
|
|
1998
|
+
icon: "I",
|
|
1999
|
+
title: "Italic",
|
|
2000
|
+
action: () => execCommand("italic")
|
|
2001
|
+
},
|
|
2002
|
+
underline: {
|
|
2003
|
+
command: "underline",
|
|
2004
|
+
icon: "U",
|
|
2005
|
+
title: "Underline",
|
|
2006
|
+
action: () => execCommand("underline")
|
|
2007
|
+
},
|
|
2008
|
+
heading: {
|
|
2009
|
+
command: "heading",
|
|
2010
|
+
icon: "H",
|
|
2011
|
+
title: "Heading",
|
|
2012
|
+
action: () => execCommand("formatBlock", "<h2>")
|
|
2013
|
+
},
|
|
2014
|
+
bulletList: {
|
|
2015
|
+
command: "bulletList",
|
|
2016
|
+
icon: "\u2022",
|
|
2017
|
+
title: "Bullet List",
|
|
2018
|
+
action: () => execCommand("insertUnorderedList")
|
|
2019
|
+
},
|
|
2020
|
+
orderedList: {
|
|
2021
|
+
command: "orderedList",
|
|
2022
|
+
icon: "1.",
|
|
2023
|
+
title: "Numbered List",
|
|
2024
|
+
action: () => execCommand("insertOrderedList")
|
|
2025
|
+
},
|
|
2026
|
+
link: {
|
|
2027
|
+
command: "link",
|
|
2028
|
+
icon: "\u{1F517}",
|
|
2029
|
+
title: "Insert Link",
|
|
2030
|
+
action: () => {
|
|
2031
|
+
const url = window.prompt("Enter URL:");
|
|
2032
|
+
if (url) {
|
|
2033
|
+
execCommand("createLink", url);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
const baseClassName = "richtexteditor";
|
|
2039
|
+
const errorClassName = error ? "richtexteditor--error" : "";
|
|
2040
|
+
const disabledClassName = disabled ? "richtexteditor--disabled" : "";
|
|
2041
|
+
const modeClassName = `richtexteditor--${currentMode}`;
|
|
2042
|
+
const combinedClassName = `${baseClassName} ${errorClassName} ${disabledClassName} ${modeClassName} ${className}`.trim();
|
|
2043
|
+
const editorStyle = {
|
|
2044
|
+
minHeight,
|
|
2045
|
+
maxHeight,
|
|
2046
|
+
overflowY: maxHeight ? "auto" : void 0
|
|
2047
|
+
};
|
|
2048
|
+
return /* @__PURE__ */ React7.createElement("div", { className: combinedClassName }, /* @__PURE__ */ React7.createElement("input", { type: "hidden", name, value: content }), showToolbar && /* @__PURE__ */ React7.createElement("div", { className: "richtexteditor-toolbar" }, /* @__PURE__ */ React7.createElement("div", { className: "richtexteditor-toolbar-buttons" }, toolbarButtons.map((buttonName) => {
|
|
2049
|
+
const button = toolbarConfig[buttonName];
|
|
2050
|
+
if (!button) return null;
|
|
2051
|
+
return /* @__PURE__ */ React7.createElement(
|
|
2052
|
+
"button",
|
|
2053
|
+
{
|
|
2054
|
+
key: buttonName,
|
|
2055
|
+
type: "button",
|
|
2056
|
+
className: "richtexteditor-toolbar-button",
|
|
2057
|
+
onClick: () => editorRef.current && button.action(editorRef.current),
|
|
2058
|
+
title: button.title,
|
|
2059
|
+
disabled: disabled || currentMode === "markdown",
|
|
2060
|
+
"aria-label": button.title
|
|
2061
|
+
},
|
|
2062
|
+
button.icon
|
|
2063
|
+
);
|
|
2064
|
+
})), allowModeSwitch && /* @__PURE__ */ React7.createElement(
|
|
2065
|
+
"button",
|
|
2066
|
+
{
|
|
2067
|
+
type: "button",
|
|
2068
|
+
className: "richtexteditor-mode-toggle",
|
|
2069
|
+
onClick: handleModeToggle,
|
|
2070
|
+
disabled,
|
|
2071
|
+
title: `Switch to ${currentMode === "wysiwyg" ? "Markdown" : "WYSIWYG"}`,
|
|
2072
|
+
"aria-label": `Switch to ${currentMode === "wysiwyg" ? "Markdown" : "WYSIWYG"}`
|
|
2073
|
+
},
|
|
2074
|
+
currentMode === "wysiwyg" ? "MD" : "WYSIWYG"
|
|
2075
|
+
)), /* @__PURE__ */ React7.createElement("div", { className: "richtexteditor-editor", style: editorStyle }, currentMode === "wysiwyg" ? /* @__PURE__ */ React7.createElement(
|
|
2076
|
+
"div",
|
|
2077
|
+
{
|
|
2078
|
+
ref: editorRef,
|
|
2079
|
+
className: "richtexteditor-content",
|
|
2080
|
+
role: "textbox",
|
|
2081
|
+
contentEditable: !disabled,
|
|
2082
|
+
onInput: handleWysiwygChange,
|
|
2083
|
+
onBlur,
|
|
2084
|
+
"data-placeholder": placeholder,
|
|
2085
|
+
"aria-invalid": error || props["aria-invalid"] ? "true" : "false",
|
|
2086
|
+
"aria-describedby": props["aria-describedby"],
|
|
2087
|
+
"aria-required": required || props["aria-required"],
|
|
2088
|
+
suppressContentEditableWarning: true
|
|
2089
|
+
}
|
|
2090
|
+
) : /* @__PURE__ */ React7.createElement(
|
|
2091
|
+
"textarea",
|
|
2092
|
+
{
|
|
2093
|
+
ref: textareaRef,
|
|
2094
|
+
className: "richtexteditor-markdown",
|
|
2095
|
+
value: content,
|
|
2096
|
+
onChange: handleMarkdownChange,
|
|
2097
|
+
onBlur,
|
|
2098
|
+
disabled,
|
|
2099
|
+
required,
|
|
2100
|
+
placeholder,
|
|
2101
|
+
"aria-invalid": error || props["aria-invalid"] ? "true" : "false",
|
|
2102
|
+
"aria-describedby": props["aria-describedby"],
|
|
2103
|
+
"aria-required": required || props["aria-required"]
|
|
2104
|
+
}
|
|
2105
|
+
)));
|
|
2106
|
+
}
|
|
2107
|
+
RichTextEditor.displayName = "RichTextEditor";
|
|
901
2108
|
|
|
902
|
-
export { Checkbox, CheckboxGroup, FileInput, Radio, Select, TextArea, TextInput };
|
|
2109
|
+
export { Checkbox, CheckboxGroup, DatePicker, DateRangePicker, FileInput, Radio, RichTextEditor, Select, TextArea, TextInput, TimePicker };
|
|
903
2110
|
//# sourceMappingURL=inputs.js.map
|
|
904
2111
|
//# sourceMappingURL=inputs.js.map
|