tp-react-elements-dev 1.12.32 → 1.12.34

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.
@@ -0,0 +1,211 @@
1
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
+ import { Box, Button, IconButton, Modal, Typography } from '@mui/material';
3
+ import { useRef, useState, useEffect } from 'react';
4
+ import { useWatch } from 'react-hook-form';
5
+ import 'dayjs';
6
+ import { renderLabel } from '../../../utils/Constants/FormConstants.esm.js';
7
+ import FormBottomField from '../FormBottomField/FormBottomField.esm.js';
8
+ import VisibilityIcon from '@mui/icons-material/Visibility';
9
+ import CloseIcon from '@mui/icons-material/Close';
10
+ import NavigateBeforeIcon from '@mui/icons-material/NavigateBefore';
11
+ import NavigateNextIcon from '@mui/icons-material/NavigateNext';
12
+ import DeleteIcon from '@mui/icons-material/Delete';
13
+ import UploadFileIcon from '@mui/icons-material/UploadFile';
14
+ import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
15
+ import TableChartIcon from '@mui/icons-material/TableChart';
16
+ import DescriptionIcon from '@mui/icons-material/Description';
17
+
18
+ const MultiFileWithPreview = ({ props, variant, }) => {
19
+ const inputRef = useRef(null);
20
+ const watched = useWatch({ control: props.control, name: props.item.name });
21
+ const [open, setOpen] = useState(false);
22
+ const [selectedFiles, setSelectedFiles] = useState([]);
23
+ const [currentFileIndex, setCurrentFileIndex] = useState(0);
24
+ // Determine if this is single or multiple file mode
25
+ const isSingleFileMode = props.item.maxLength === 1;
26
+ useEffect(() => {
27
+ if (watched === null || watched === undefined) {
28
+ if (inputRef.current)
29
+ inputRef.current.value = '';
30
+ }
31
+ // Handle preloaded files from backend (array of URLs)
32
+ if (Array.isArray(watched) && typeof watched[0] === 'string') {
33
+ setSelectedFiles(watched);
34
+ }
35
+ }, [watched]);
36
+ const isError = !!props.errors?.[props.item.name];
37
+ const handleFileChange = (event) => {
38
+ const files = event.target.files;
39
+ if (!files)
40
+ return;
41
+ const fileArray = Array.from(files);
42
+ const allowedExtensions = {
43
+ excel: ['xls', 'xlsx'],
44
+ pdf: ['pdf'],
45
+ image: ['jpg', 'jpeg', 'png'],
46
+ all: ['pdf', 'jpg', 'jpeg', 'png', 'xls', 'xlsx', 'doc', 'docx', 'zip']};
47
+ const validExtensions = props.item.fileType === 'excel'
48
+ ? allowedExtensions.excel
49
+ : props.item.fileType === 'pdf'
50
+ ? allowedExtensions.pdf
51
+ : props.item.fileType === 'image'
52
+ ? allowedExtensions.image
53
+ : allowedExtensions.all;
54
+ const invalidFile = fileArray.find((file) => {
55
+ const ext = file.name.split('.').pop()?.toLowerCase();
56
+ return !ext || !validExtensions.includes(ext);
57
+ });
58
+ if (invalidFile) {
59
+ props.item?.handleFileError?.(`Please upload ${validExtensions.join(', ')} files only`);
60
+ event.target.value = '';
61
+ props.setValue(props.item.name, null);
62
+ props.setValue(props.item.name + 'File', []);
63
+ return;
64
+ }
65
+ const largeFile = fileArray.find((file) => file.size > 20000000);
66
+ if (largeFile) {
67
+ props.item?.handleFileError?.('File size should be less than 20MB');
68
+ event.target.value = '';
69
+ props.setValue(props.item.name, null);
70
+ props.setValue(props.item.name + 'File', []);
71
+ return;
72
+ }
73
+ // Handle single vs multiple file mode
74
+ let combinedFiles;
75
+ if (isSingleFileMode) {
76
+ // In single file mode, replace the existing file
77
+ combinedFiles = fileArray;
78
+ }
79
+ else {
80
+ // In multiple file mode, combine with existing files
81
+ combinedFiles = [...selectedFiles, ...fileArray];
82
+ // Check if maxLength is exceeded
83
+ if (props.item.maxLength && combinedFiles.length > props.item.maxLength) {
84
+ props.item?.handleFileError?.(`You can only upload up to ${props.item.maxLength} files`);
85
+ event.target.value = '';
86
+ return;
87
+ }
88
+ }
89
+ const combinedFileNames = combinedFiles
90
+ .map((f) => (f instanceof File ? f.name : f.split('/').pop()))
91
+ .join(',');
92
+ props.setValue(props.item.name, combinedFileNames);
93
+ props.setValue(props.item.name + 'File', combinedFiles);
94
+ setSelectedFiles(combinedFiles);
95
+ props.item.onChangeSetFileFn?.(combinedFiles);
96
+ };
97
+ const handlePreviewClick = () => {
98
+ setCurrentFileIndex(0);
99
+ setOpen(true);
100
+ };
101
+ const handleClose = () => setOpen(false);
102
+ const handlePrevious = () => {
103
+ setCurrentFileIndex((prev) => prev > 0 ? prev - 1 : selectedFiles.length - 1);
104
+ };
105
+ const handleNext = () => {
106
+ setCurrentFileIndex((prev) => prev < selectedFiles.length - 1 ? prev + 1 : 0);
107
+ };
108
+ const handleDeleteFile = (indexToDelete) => {
109
+ const updatedFiles = selectedFiles.filter((_, index) => index !== indexToDelete);
110
+ setSelectedFiles(updatedFiles);
111
+ if (inputRef.current) {
112
+ inputRef.current.value = '';
113
+ }
114
+ if (updatedFiles.length === 0) {
115
+ props.setValue(props.item.name, null);
116
+ props.setValue(props.item.name + 'File', []);
117
+ }
118
+ else {
119
+ const fileNames = updatedFiles
120
+ .map((f) => (f instanceof File ? f.name : f.split('/').pop()))
121
+ .join(',');
122
+ props.setValue(props.item.name, fileNames);
123
+ props.setValue(props.item.name + 'File', updatedFiles);
124
+ }
125
+ if (currentFileIndex >= updatedFiles.length) {
126
+ setCurrentFileIndex(Math.max(0, updatedFiles.length - 1));
127
+ }
128
+ props.item.onChangeSetFileFn &&
129
+ props.item.onChangeSetFileFn(updatedFiles);
130
+ if (updatedFiles.length === 0) {
131
+ setOpen(false);
132
+ }
133
+ };
134
+ const isImageFile = (file) => {
135
+ const ext = typeof file === 'string'
136
+ ? file.split('.').pop()?.toLowerCase()
137
+ : file.name.split('.').pop()?.toLowerCase();
138
+ return ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext || '');
139
+ };
140
+ const isPdfFile = (file) => {
141
+ const name = typeof file === 'string' ? file : file.name;
142
+ return name.toLowerCase().endsWith('.pdf');
143
+ };
144
+ const isExcelFile = (file) => {
145
+ const name = typeof file === 'string' ? file : file.name;
146
+ return /\.xlsx?$/.test(name.toLowerCase());
147
+ };
148
+ const isWordFile = (file) => {
149
+ const name = typeof file === 'string' ? file : file.name;
150
+ return /\.(doc|docx)$/.test(name.toLowerCase());
151
+ };
152
+ const getObjectUrl = (file) => typeof file === 'string' ? file : URL.createObjectURL(file);
153
+ return (jsxs(Fragment, { children: [jsxs(Box, { paddingLeft: "4px", sx: { ...props.item.sx, position: 'relative' }, children: [props.item?.label && (jsx(Box, { sx: { fontSize: '10px' }, children: renderLabel(variant, props, true) })), jsxs(Box, { display: "flex", alignItems: "center", gap: 1, children: [jsxs(Button, { variant: "outlined", component: "label", startIcon: jsx(UploadFileIcon, {}), sx: { flex: 1, textTransform: 'none' }, color: isError ? 'error' : 'primary', children: [selectedFiles.length > 0
154
+ ? isSingleFileMode
155
+ ? '1 file selected'
156
+ : `${selectedFiles.length} file(s) selected`
157
+ : isSingleFileMode
158
+ ? 'Upload File'
159
+ : 'Upload Files', jsx("input", { type: "file", hidden: true, multiple: !isSingleFileMode, accept: props.item.fileType === 'excel'
160
+ ? '.xls,.xlsx'
161
+ : props.item.fileType === 'pdf'
162
+ ? '.pdf'
163
+ : props.item.fileType === 'image'
164
+ ? '.jpg,.jpeg,.png'
165
+ : props.item.fileType === 'all'
166
+ ? '.pdf,.jpg,.jpeg,.png,.xls,.xlsx,.doc,.docx'
167
+ : '', ref: inputRef, onChange: handleFileChange })] }), jsx(Box, { sx: { width: 36, display: 'flex', justifyContent: 'center' }, children: props.item.showFileCarousel && selectedFiles.length > 0 ? (jsx(IconButton, { onClick: handlePreviewClick, color: "primary", size: "small", "aria-label": "preview selected files", children: jsx(VisibilityIcon, {}) })) : null })] })] }), jsx(FormBottomField, { ...props }), jsx(Modal, { open: open, onClose: handleClose, children: jsxs(Box, { sx: {
168
+ position: 'absolute',
169
+ top: '50%',
170
+ left: '50%',
171
+ transform: 'translate(-50%, -50%)',
172
+ width: 600,
173
+ bgcolor: 'background.paper',
174
+ borderRadius: 2,
175
+ boxShadow: 24,
176
+ p: 3,
177
+ }, children: [jsxs(Box, { display: "flex", justifyContent: "space-between", alignItems: "center", children: [jsx(Typography, { variant: "h6", children: "File Preview" }), jsxs(Box, { display: "flex", gap: 1, children: [jsx(IconButton, { onClick: () => handleDeleteFile(currentFileIndex), color: "error", title: "Delete current file", children: jsx(DeleteIcon, {}) }), jsx(IconButton, { onClick: handleClose, children: jsx(CloseIcon, {}) })] })] }), jsx(Box, { mt: 2, children: selectedFiles.length > 0 && (jsxs(Fragment, { children: [jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "center", gap: 2, children: [selectedFiles.length > 1 && !isSingleFileMode ? (jsx(IconButton, { onClick: handlePrevious, children: jsx(NavigateBeforeIcon, {}) })) : null, jsx(Box, { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", sx: { p: 2, minHeight: '400px', width: '100%' }, children: (() => {
178
+ const currentFile = selectedFiles[currentFileIndex];
179
+ const fileName = typeof currentFile === 'string'
180
+ ? currentFile.split('/').pop() || currentFile
181
+ : currentFile.name;
182
+ // Images
183
+ if (isImageFile(currentFile)) {
184
+ return (jsx("img", { alt: "Preview", src: getObjectUrl(currentFile), style: {
185
+ maxHeight: '400px',
186
+ maxWidth: '100%',
187
+ objectFit: 'contain',
188
+ borderRadius: 8,
189
+ } }));
190
+ }
191
+ // PDF inline viewer
192
+ if (isPdfFile(currentFile)) {
193
+ const url = getObjectUrl(currentFile);
194
+ return (jsx(Box, { width: "100%", height: 420, children: jsx("object", { data: url, type: "application/pdf", width: "100%", height: "100%", children: jsxs(Box, { display: "flex", alignItems: "center", justifyContent: "center", flexDirection: "column", gap: 1, children: [jsx(PictureAsPdfIcon, { color: "error" }), jsx(Typography, { variant: "body2", children: "Preview not available. " }), jsx(Button, { size: "small", variant: "outlined", href: url, target: "_blank", rel: "noreferrer", children: "Open PDF" })] }) }) }));
195
+ }
196
+ // Excel preview placeholder with actions
197
+ if (isExcelFile(currentFile)) {
198
+ const url = getObjectUrl(currentFile);
199
+ return (jsxs(Box, { textAlign: "center", children: [jsx(TableChartIcon, { color: "primary", sx: { fontSize: 64, mb: 1 } }), jsx(Typography, { variant: "body1", gutterBottom: true, children: fileName }), jsx(Button, { size: "small", variant: "outlined", href: url, target: "_blank", rel: "noreferrer", children: "Open" })] }));
200
+ }
201
+ // Word preview placeholder with actions
202
+ if (isWordFile(currentFile)) {
203
+ const url = getObjectUrl(currentFile);
204
+ return (jsxs(Box, { textAlign: "center", children: [jsx(DescriptionIcon, { color: "primary", sx: { fontSize: 64, mb: 1 } }), jsx(Typography, { variant: "body1", gutterBottom: true, children: fileName }), jsx(Button, { size: "small", variant: "outlined", href: url, target: "_blank", rel: "noreferrer", children: "Open" })] }));
205
+ }
206
+ // Fallback: show name
207
+ return jsx(Typography, { variant: "body1", children: fileName });
208
+ })() }), selectedFiles.length > 1 && !isSingleFileMode ? (jsx(IconButton, { onClick: handleNext, children: jsx(NavigateNextIcon, {}) })) : null] }), selectedFiles.length > 1 && !isSingleFileMode && (jsx(Box, { display: "flex", justifyContent: "center", mt: 2, children: jsxs(Typography, { variant: "body2", color: "text.secondary", children: [currentFileIndex + 1, " of ", selectedFiles.length] }) }))] })) })] }) })] }));
209
+ };
210
+
211
+ export { MultiFileWithPreview as default };
@@ -0,0 +1,3 @@
1
+ export { default as FormRenderFileUpload } from './FormRenderFileUpload';
2
+ export { default as FormRenderMultiFileUpload } from './FormRenderMultiFileUpload';
3
+ export { default as MultiFileWithPreview } from './MultiFileWithPreview';
@@ -0,0 +1,3 @@
1
+ export { default as FormRenderFileUpload } from './FormRenderFileUpload.esm.js';
2
+ export { default as FormRenderMultiFileUpload } from './FormRenderMultiFileUpload.esm.js';
3
+ export { default as MultiFileWithPreview } from './MultiFileWithPreview.esm.js';
@@ -1,19 +1,18 @@
1
+ export { default as SingleSelect } from './Select/SingleSelect.esm.js';
2
+ export { default as SingleSelectNonAutoComplete } from './Select/SingleSelectNonAutoComplete.esm.js';
3
+ export { default as SingleSelectSearchApi } from './Select/SingleSelectSearchApi.esm.js';
4
+ export { default as MultiSelectV1 } from './Select/MultiSelectv1.esm.js';
1
5
  export { default as FormNumberField } from './FormNumberField/FormNumberField.esm.js';
2
6
  export { default as FormNumberFieldDecimal } from './FormNumberField/FormNumberFieldDecimal.esm.js';
3
7
  export { default as FormTextAreaField } from './FormTextAreaField/FormTextAreaField.esm.js';
4
8
  export { default as FormTextField } from './FormTextField/FormTextField.esm.js';
5
9
  export { default as PasswordField } from './PasswordField/PasswordField.esm.js';
6
10
  export { default as FormCheckBox } from './FormCheckBox/FormCheckBox.esm.js';
7
- export { default as SingleSelect } from './Select/SingleSelect.esm.js';
8
- export { default as SingleSelectNonAutoComplete } from './Select/SingleSelectNonAutoComplete.esm.js';
9
- export { default as SingleSelectSearchApi } from './Select/SingleSelectSearchApi.esm.js';
10
- export { default as MultiSelectV1 } from './Select/MultiSelectv1.esm.js';
11
11
  export { default as FormCheckBoxGroup } from './FormCheckBoxGroup/FormCheckBoxGroup.esm.js';
12
12
  export { default as FormRadioGroup } from './FormRadioGroup/FormRadioGroup.esm.js';
13
13
  export { default as DatepickerWrapperV2 } from './DatePicker/DatepickerWrapperV2.esm.js';
14
14
  export { default as MonthPicker } from './DatePicker/MonthPicker.esm.js';
15
15
  export { default as Monthpickerrender } from './DatePicker/Monthpickerrender.esm.js';
16
- export { default as YearPickerField } from './YearPickerField/YearPickerField.esm.js';
17
16
  export { default as TimePicker } from './TimePicker/TimePicker.esm.js';
18
17
  export { default as FormRenderFileUpload } from './FileUpload/FormRenderFileUpload.esm.js';
19
18
  export { default as FormRenderMultiFileUpload } from './FileUpload/FormRenderMultiFileUpload.esm.js';
@@ -22,3 +21,4 @@ export { default as RichTextEditorWrapper } from './RichTextEditor/RichTextEdito
22
21
  export { default as FormBottomField } from './FormBottomField/FormBottomField.esm.js';
23
22
  export { default as FormErrorField } from './FormErrorField/FormErrorField.esm.js';
24
23
  export { default as HelperText } from './HelperText/HelperText.esm.js';
24
+ export { default as YearPickerField } from './YearPickerField/YearPickerField.esm.js';
@@ -1,79 +1,79 @@
1
1
  import { styled } from '@mui/material/styles';
2
2
  import { Button, Typography, Grid, Dialog, DialogTitle } from '@mui/material';
3
3
 
4
- styled(Button) `
5
- border: 0;
6
- color: #fff;
7
- position: absolute;
8
- text-align: center;
9
- padding: 5px 12px;
10
- font-size: 13px;
11
- height: 30px;
12
- right: -38px;
13
- text-transform: none;
14
- z-index: 2;
15
- border-radius: 10px 0px 0px 10px;
16
- &:hover {
17
- right: 0px;
18
- }
4
+ styled(Button) `
5
+ border: 0;
6
+ color: #fff;
7
+ position: absolute;
8
+ text-align: center;
9
+ padding: 5px 12px;
10
+ font-size: 13px;
11
+ height: 30px;
12
+ right: -38px;
13
+ text-transform: none;
14
+ z-index: 2;
15
+ border-radius: 10px 0px 0px 10px;
16
+ &:hover {
17
+ right: 0px;
18
+ }
19
19
  `;
20
- const SubmitButton = styled(Button) `
21
- text-transform: none;
20
+ const SubmitButton = styled(Button) `
21
+ text-transform: none;
22
22
  `;
23
- const CancelButton = styled(Button) `
24
- text-transform: none;
25
- color: #000;
26
- background: #ececee;
27
- &:hover {
28
- background: #0009;
29
- color: #fff;
30
- }
23
+ const CancelButton = styled(Button) `
24
+ text-transform: none;
25
+ color: #000;
26
+ background: #ececee;
27
+ &:hover {
28
+ background: #0009;
29
+ color: #fff;
30
+ }
31
31
  `;
32
- const SaveAsDraftButton = styled(Button) `
33
- text-transform: none;
34
- font-size: 12px;
35
- background: orange;
36
- &:hover {
37
- background: orange;
38
- }
32
+ const SaveAsDraftButton = styled(Button) `
33
+ text-transform: none;
34
+ font-size: 12px;
35
+ background: orange;
36
+ &:hover {
37
+ background: orange;
38
+ }
39
39
  `;
40
40
  styled(Typography) ``;
41
41
  styled(Grid, {
42
42
  shouldForwardProp: (prop) => prop !== 'isActive' && prop !== 'noOfColumn',
43
- }) `
44
- flex-direction: column;
45
- margin-bottom: 8px;
46
- min-width: 10%;
47
- padding-left: 0px;
48
- z-index: 2;
49
- padding-right: 0px;
50
- border: 1px solid #0003;
51
- font-size: 12px;
52
- padding: 6px;
53
- text-align: center;
54
- &:hover {
55
- cursor: pointer;
56
- }
43
+ }) `
44
+ flex-direction: column;
45
+ margin-bottom: 8px;
46
+ min-width: 10%;
47
+ padding-left: 0px;
48
+ z-index: 2;
49
+ padding-right: 0px;
50
+ border: 1px solid #0003;
51
+ font-size: 12px;
52
+ padding: 6px;
53
+ text-align: center;
54
+ &:hover {
55
+ cursor: pointer;
56
+ }
57
57
  `;
58
- const DialogContainer = styled(Dialog) `
59
- position: fixed;
60
- top: -16px;
61
- left: 0;
62
- right: 0;
63
- bottom: auto;
64
- & .css-tlc64q-MuiPaper-root-MuiDialog-paper,
65
- .css-mbdu2s {
66
- max-width: 900px;
67
- }
58
+ const DialogContainer = styled(Dialog) `
59
+ position: fixed;
60
+ top: -16px;
61
+ left: 0;
62
+ right: 0;
63
+ bottom: auto;
64
+ & .css-tlc64q-MuiPaper-root-MuiDialog-paper,
65
+ .css-mbdu2s {
66
+ max-width: 900px;
67
+ }
68
68
  `;
69
- const DialogTitleWrapper = styled(DialogTitle) `
70
- padding: 8px 16px;
71
- display: flex;
72
- align-items: center;
73
- justify-content: space-between;
74
- font-size: 14px;
75
- cursor: move;
76
- border-bottom: 1px solid rgb(229 231 235 / 1);
69
+ const DialogTitleWrapper = styled(DialogTitle) `
70
+ padding: 8px 16px;
71
+ display: flex;
72
+ align-items: center;
73
+ justify-content: space-between;
74
+ font-size: 14px;
75
+ cursor: move;
76
+ border-bottom: 1px solid rgb(229 231 235 / 1);
77
77
  `;
78
78
 
79
79
  export { CancelButton, DialogContainer, DialogTitleWrapper, SaveAsDraftButton, SubmitButton };
@@ -10,7 +10,7 @@ export interface TextFieldInputProps {
10
10
  export interface FormSectionPropsItem {
11
11
  name: string;
12
12
  label: string;
13
- inputType: 'text' | 'password' | 'number' | 'select' | 'autocomplete-select' | 'autocomplete-multi-select' | 'datepicker' | 'multiselect' | 'select-v2' | 'basic-select' | 'decimal' | 'alpha-numerical' | 'yearpicker' | 'dateRangePicker' | 'monthpicker' | 'multiselect' | 'file' | 'multifile' | 'textarea' | 'phoneNumber' | 'pincode' | 'email' | 'toggleSwitch' | 'rich-text-editor' | 'multiEmail' | 'timepicker' | 'checkbox-group' | 'radio-group' | 'checkbox' | 'custom' | '';
13
+ inputType: 'text' | 'password' | 'number' | 'select' | 'autocomplete-select' | 'autocomplete-multi-select' | 'datepicker' | 'multiselect' | 'select-v2' | 'basic-select' | 'decimal' | 'alpha-numerical' | 'yearpicker' | 'dateRangePicker' | 'monthpicker' | 'multiselect' | 'file' | 'multifile' | 'multifile-with-preview' | 'textarea' | 'phoneNumber' | 'pincode' | 'email' | 'toggleSwitch' | 'rich-text-editor' | 'multiEmail' | 'timepicker' | 'checkbox-group' | 'radio-group' | 'checkbox' | 'custom' | '';
14
14
  options?: OptionsProps[];
15
15
  required?: boolean;
16
16
  errorMessage?: string;
@@ -18,7 +18,8 @@ export interface FormSectionPropsItem {
18
18
  disable?: boolean;
19
19
  onChangeFn?: (e: string | number | undefined | null | boolean) => void;
20
20
  onBlurFn?: (e: string | number | undefined | null | boolean) => void;
21
- onChangeSetFileFn?: (e: File) => void;
21
+ onChangeSetFileFn?: (e: File | File[]) => void;
22
+ showFileCarousel?: boolean;
22
23
  maxLength?: number;
23
24
  autoFocus?: boolean;
24
25
  maxValue?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tp-react-elements-dev",
3
- "version": "1.12.32",
3
+ "version": "1.12.34",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "React form components library built with React Hook Form and Yup",