@campxdev/react-blueprint 1.7.10 → 1.7.11
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/package.json +1 -1
- package/src/App.tsx +15 -71
- package/src/components/Assets/Icons/IconComponents/ThreeDotsMenuIcon.tsx +45 -0
- package/src/components/Assets/Icons/Icons.tsx +2 -0
- package/src/components/DataDisplay/Card/styles.tsx +1 -1
- package/src/components/Input/DateTimePicker/DateTimePicker.tsx +95 -0
- package/src/components/Input/FileUpload/FileUpload.tsx +215 -0
- package/src/components/Input/FormControlWrapper/FormControlWrapper.tsx +12 -0
- package/src/components/Input/export.ts +2 -0
- package/src/components/Layout/AppHeader/AppHeader.tsx +14 -2
- package/src/components/Layout/AppHeader/styles/styles.tsx +10 -2
- package/src/components/Layout/PageContent/PageContent.tsx +19 -2
- package/src/components/Layout/StepperTitle/StepperTitle.tsx +39 -0
- package/src/components/Layout/export.ts +1 -0
- package/src/components/Navigation/PreviewFiles/PreviewFiles.tsx +10 -3
- package/src/stories/Input/DateTimePicker.stories.tsx +124 -0
- package/src/themes/commonTheme.ts +7 -0
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Button } from '@mui/material';
|
|
2
|
+
import { useForm } from 'react-hook-form';
|
|
3
|
+
|
|
3
4
|
import './App.css';
|
|
4
|
-
import {
|
|
5
|
+
import { FileUpload, FormControlWrapper } from './components/export';
|
|
5
6
|
|
|
6
7
|
interface RowType {
|
|
7
8
|
lastName: string;
|
|
@@ -10,82 +11,25 @@ interface RowType {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
function App() {
|
|
14
|
+
const { control, watch, handleSubmit } = useForm({});
|
|
15
|
+
|
|
16
|
+
const onSubmit = (formData: any) => {
|
|
17
|
+
console.log(formData);
|
|
18
|
+
};
|
|
19
|
+
|
|
13
20
|
return (
|
|
14
21
|
<div
|
|
15
22
|
style={{
|
|
16
23
|
display: 'flex',
|
|
17
24
|
justifyContent: 'flex-start',
|
|
18
25
|
paddingLeft: '16px',
|
|
26
|
+
flexDirection: 'column',
|
|
19
27
|
}}
|
|
20
28
|
>
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
userName: 'ss',
|
|
26
|
-
action: 'create',
|
|
27
|
-
message: 's',
|
|
28
|
-
timestamp: new Date().toISOString(),
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
userName: 'ss',
|
|
32
|
-
action: 'update',
|
|
33
|
-
message: 's',
|
|
34
|
-
timestamp: new Date().toISOString(),
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
userName: 'ss',
|
|
38
|
-
action: 'update',
|
|
39
|
-
message: 's',
|
|
40
|
-
timestamp: new Date().toISOString(),
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
userName: 'ss',
|
|
44
|
-
action: 'update',
|
|
45
|
-
message: 's',
|
|
46
|
-
timestamp: new Date().toISOString(),
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
userName: 'ss',
|
|
50
|
-
action: 'update',
|
|
51
|
-
message: 's',
|
|
52
|
-
timestamp: new Date().toISOString(),
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
userName: 'ss',
|
|
56
|
-
action: 'delete',
|
|
57
|
-
message: 's',
|
|
58
|
-
timestamp: new Date().toISOString(),
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
userName: 'ss',
|
|
62
|
-
action: 'update',
|
|
63
|
-
message: 's',
|
|
64
|
-
timestamp: new Date().toISOString(),
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
userName: 'ss',
|
|
68
|
-
action: 'update',
|
|
69
|
-
message: 's',
|
|
70
|
-
timestamp: new Date().toISOString(),
|
|
71
|
-
},
|
|
72
|
-
]}
|
|
73
|
-
isFetchingNextPage={false}
|
|
74
|
-
fetchNextPage={function (): void {
|
|
75
|
-
// throw new Error('Function not implemented.');
|
|
76
|
-
}}
|
|
77
|
-
hasNextPage={undefined}
|
|
78
|
-
fromDate={null}
|
|
79
|
-
toDate={null}
|
|
80
|
-
setFromDate={function (date: Date | null): void {
|
|
81
|
-
// throw new Error('Function not implemented.');
|
|
82
|
-
}}
|
|
83
|
-
setToDate={function (date: Date | null): void {
|
|
84
|
-
// throw new Error('Function not implemented.');
|
|
85
|
-
}}
|
|
86
|
-
isLoading={false}
|
|
87
|
-
/>
|
|
88
|
-
</LocalizationProvider>
|
|
29
|
+
<FormControlWrapper control={control}>
|
|
30
|
+
<FileUpload label={'Files'} name="daaku" files={watch().daaku} />
|
|
31
|
+
</FormControlWrapper>
|
|
32
|
+
<Button onClick={handleSubmit(onSubmit)}>Submit</Button>
|
|
89
33
|
</div>
|
|
90
34
|
);
|
|
91
35
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useTheme } from '@mui/material';
|
|
2
|
+
|
|
3
|
+
export const ThreeDotsMenuIcon = ({
|
|
4
|
+
size = 16,
|
|
5
|
+
color,
|
|
6
|
+
backgroundColor,
|
|
7
|
+
}: {
|
|
8
|
+
size?: number;
|
|
9
|
+
color?: string;
|
|
10
|
+
backgroundColor?: string;
|
|
11
|
+
}) => {
|
|
12
|
+
const theme = useTheme();
|
|
13
|
+
const fill = color ?? theme.palette.primary.main;
|
|
14
|
+
const background = backgroundColor || theme.palette.secondary.main;
|
|
15
|
+
return (
|
|
16
|
+
<svg
|
|
17
|
+
width={size}
|
|
18
|
+
height={size}
|
|
19
|
+
viewBox="0 0 24 24"
|
|
20
|
+
fill="none"
|
|
21
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
22
|
+
>
|
|
23
|
+
<g clip-path="url(#clip0_462_9729)">
|
|
24
|
+
<rect width="24" height="24" rx="4" fill={background} />
|
|
25
|
+
<path
|
|
26
|
+
d="M16 11.9999C16 12.8836 16.7163 13.5999 17.6 13.5999C18.4836 13.5999 19.2 12.8836 19.2 11.9999C19.2 11.1162 18.4836 10.3999 17.6 10.3999C16.7163 10.3999 16 11.1162 16 11.9999Z"
|
|
27
|
+
fill={fill}
|
|
28
|
+
/>
|
|
29
|
+
<path
|
|
30
|
+
d="M10.4 11.9999C10.4 12.8836 11.1163 13.5999 12 13.5999C12.8836 13.5999 13.6 12.8836 13.6 11.9999C13.6 11.1162 12.8836 10.3999 12 10.3999C11.1163 10.3999 10.4 11.1162 10.4 11.9999Z"
|
|
31
|
+
fill={fill}
|
|
32
|
+
/>
|
|
33
|
+
<path
|
|
34
|
+
d="M4.8 11.9999C4.8 12.8836 5.51634 13.5999 6.4 13.5999C7.28366 13.5999 8 12.8836 8 11.9999C8 11.1162 7.28366 10.3999 6.4 10.3999C5.51634 10.3999 4.8 11.1162 4.8 11.9999Z"
|
|
35
|
+
fill={fill}
|
|
36
|
+
/>
|
|
37
|
+
</g>
|
|
38
|
+
<defs>
|
|
39
|
+
<clipPath id="clip0_462_9729">
|
|
40
|
+
<rect width="24" height="24" rx="4" fill="white" />
|
|
41
|
+
</clipPath>
|
|
42
|
+
</defs>
|
|
43
|
+
</svg>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
@@ -110,6 +110,7 @@ import { StudentsIcon } from './IconComponents/StudentsIcon';
|
|
|
110
110
|
import { SuccessFilledIcon } from './IconComponents/SuccessFilledIcon';
|
|
111
111
|
import { TasksIcon } from './IconComponents/TasksIcon';
|
|
112
112
|
import { TextLocalIcon } from './IconComponents/TextLocalIcon';
|
|
113
|
+
import { ThreeDotsMenuIcon } from './IconComponents/ThreeDotsMenuIcon';
|
|
113
114
|
import { TicketSystemIcon } from './IconComponents/TicketingSystemIcon';
|
|
114
115
|
import { TicketsIcon } from './IconComponents/TicketsIcon';
|
|
115
116
|
import { TimeTableIcon } from './IconComponents/TimeTableIcon';
|
|
@@ -133,6 +134,7 @@ export const Icons = {
|
|
|
133
134
|
RedoIcon,
|
|
134
135
|
WhatsappIcon,
|
|
135
136
|
AssignmentIcon,
|
|
137
|
+
ThreeDotsMenuIcon,
|
|
136
138
|
ReportIssueIcon,
|
|
137
139
|
SmsIcon,
|
|
138
140
|
EmailIcon,
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { StackProps } from '@mui/material';
|
|
2
|
+
import {
|
|
3
|
+
DateTimePicker as MuiDateTimePicker,
|
|
4
|
+
DateTimePickerProps as MuiDateTimePickerProps,
|
|
5
|
+
PickersShortcutsItem,
|
|
6
|
+
PickerValidDate,
|
|
7
|
+
} from '@mui/x-date-pickers';
|
|
8
|
+
import { format as DateFnsFormat } from 'date-fns';
|
|
9
|
+
|
|
10
|
+
import { Icons } from '../../export';
|
|
11
|
+
import { LabelWrapper } from '../LabelWrapper/LabelWrapper';
|
|
12
|
+
|
|
13
|
+
type DateTimePickerProps<
|
|
14
|
+
TDate extends PickerValidDate,
|
|
15
|
+
TEnableAccessibleFieldDOMStructure extends boolean = false,
|
|
16
|
+
> = {
|
|
17
|
+
format?:
|
|
18
|
+
| 'yyyy'
|
|
19
|
+
| 'MMMM yyyy'
|
|
20
|
+
| 'MMM yyyy'
|
|
21
|
+
| 'dd MMMM yyyy'
|
|
22
|
+
| 'dd/MM/yyyy'
|
|
23
|
+
| 'MM/dd/yyyy'
|
|
24
|
+
| 'yyyy-MM-dd'
|
|
25
|
+
| 'yyyy-MM-dd hh:mm a'
|
|
26
|
+
| 'yyyy-MM-dd HH:mm'
|
|
27
|
+
| 'dd MMM yyyy HH:mm'
|
|
28
|
+
| 'dd MMM yyyy hh:mm a'
|
|
29
|
+
| 'dd/MM/yyyy hh:mm a'
|
|
30
|
+
| 'dd/MM/yyyy HH:mm'
|
|
31
|
+
| 'hh:mm a'
|
|
32
|
+
| 'HH:mm'
|
|
33
|
+
| 'hh:mm:ss a'
|
|
34
|
+
| 'HH:mm:ss'
|
|
35
|
+
| 'MMM dd, yyyy hh:mm a'
|
|
36
|
+
| 'MMMM dd, yyyy HH:mm'
|
|
37
|
+
| 'EEE, MMM dd yyyy'
|
|
38
|
+
| 'EEEE, MMMM dd, yyyy';
|
|
39
|
+
views?: ('year' | 'month' | 'day' | 'hours' | 'minutes' | 'seconds')[];
|
|
40
|
+
helperText?: string;
|
|
41
|
+
placeholder?: string;
|
|
42
|
+
shortcutsItems?: PickersShortcutsItem<any>[];
|
|
43
|
+
required?: boolean;
|
|
44
|
+
containerProps?: StackProps;
|
|
45
|
+
openPickerIcon?: React.ElementType;
|
|
46
|
+
} & MuiDateTimePickerProps<TDate, TEnableAccessibleFieldDOMStructure>;
|
|
47
|
+
|
|
48
|
+
export const DateTimePicker = <
|
|
49
|
+
TDate extends PickerValidDate,
|
|
50
|
+
TEnableAccessibleFieldDOMStructure extends boolean = false,
|
|
51
|
+
>({
|
|
52
|
+
label,
|
|
53
|
+
name,
|
|
54
|
+
required = false,
|
|
55
|
+
format = 'dd MMM yyyy hh:mm a',
|
|
56
|
+
views = ['year', 'month', 'day', 'hours', 'minutes', 'seconds'],
|
|
57
|
+
helperText,
|
|
58
|
+
placeholder = '',
|
|
59
|
+
shortcutsItems = [],
|
|
60
|
+
openPickerIcon: Icon = Icons.NoteIcon,
|
|
61
|
+
containerProps,
|
|
62
|
+
value,
|
|
63
|
+
...rest
|
|
64
|
+
}: DateTimePickerProps<TDate, TEnableAccessibleFieldDOMStructure>) => {
|
|
65
|
+
return (
|
|
66
|
+
<LabelWrapper
|
|
67
|
+
label={label}
|
|
68
|
+
required={required}
|
|
69
|
+
name={name}
|
|
70
|
+
containerProps={containerProps}
|
|
71
|
+
>
|
|
72
|
+
<MuiDateTimePicker
|
|
73
|
+
format={format}
|
|
74
|
+
views={views}
|
|
75
|
+
defaultValue={value}
|
|
76
|
+
slotProps={{
|
|
77
|
+
textField: {
|
|
78
|
+
helperText,
|
|
79
|
+
placeholder,
|
|
80
|
+
},
|
|
81
|
+
shortcuts: {
|
|
82
|
+
items: shortcutsItems,
|
|
83
|
+
},
|
|
84
|
+
}}
|
|
85
|
+
slots={{
|
|
86
|
+
openPickerIcon: Icon,
|
|
87
|
+
}}
|
|
88
|
+
dayOfWeekFormatter={(day: any) => {
|
|
89
|
+
return `${DateFnsFormat(day, 'EEEE').slice(0, 2)}.`;
|
|
90
|
+
}}
|
|
91
|
+
{...rest}
|
|
92
|
+
/>
|
|
93
|
+
</LabelWrapper>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Box,
|
|
3
|
+
FormLabel,
|
|
4
|
+
Stack,
|
|
5
|
+
styled,
|
|
6
|
+
SxProps,
|
|
7
|
+
useTheme,
|
|
8
|
+
} from '@mui/material';
|
|
9
|
+
import { IconButtons, Icons, PreviewFiles, Typography } from '../../export';
|
|
10
|
+
|
|
11
|
+
export type FileUploadProps = {
|
|
12
|
+
label: string;
|
|
13
|
+
name: string;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
files?: File[];
|
|
16
|
+
onChange?: (files: File[]) => void;
|
|
17
|
+
handleRemove?: (index: number) => void;
|
|
18
|
+
accept?: 'image/*' | '.pdf' | '.docx' | '.txt' | '*';
|
|
19
|
+
onInvalidFile?: (file: File) => void;
|
|
20
|
+
inputText?: string;
|
|
21
|
+
multiple?: boolean;
|
|
22
|
+
hideDeleteButton?: boolean;
|
|
23
|
+
errorText?: string;
|
|
24
|
+
sx?: SxProps;
|
|
25
|
+
showImage?: boolean;
|
|
26
|
+
previewSx?: SxProps;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const FileUpload = ({
|
|
30
|
+
label,
|
|
31
|
+
name = 'file-input',
|
|
32
|
+
disabled = false,
|
|
33
|
+
onChange,
|
|
34
|
+
handleRemove,
|
|
35
|
+
files = [],
|
|
36
|
+
accept = '*',
|
|
37
|
+
inputText,
|
|
38
|
+
onInvalidFile,
|
|
39
|
+
hideDeleteButton = false,
|
|
40
|
+
multiple = true,
|
|
41
|
+
errorText,
|
|
42
|
+
showImage,
|
|
43
|
+
previewSx,
|
|
44
|
+
sx,
|
|
45
|
+
}: FileUploadProps) => {
|
|
46
|
+
const theme = useTheme();
|
|
47
|
+
|
|
48
|
+
if (!onChange) {
|
|
49
|
+
return <Typography>onChange Missing</Typography>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
53
|
+
if (event.target.files) {
|
|
54
|
+
const newFiles = Array.from(event.target.files);
|
|
55
|
+
const validFiles: File[] = [];
|
|
56
|
+
const invalidFiles: File[] = [];
|
|
57
|
+
|
|
58
|
+
newFiles.forEach((file) => {
|
|
59
|
+
if (
|
|
60
|
+
accept === '*' ||
|
|
61
|
+
file.type.match(accept) ||
|
|
62
|
+
file.name.match(accept)
|
|
63
|
+
) {
|
|
64
|
+
validFiles.push(file);
|
|
65
|
+
} else {
|
|
66
|
+
invalidFiles.push(file);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
invalidFiles.forEach((file) => onInvalidFile?.(file));
|
|
71
|
+
|
|
72
|
+
if (validFiles.length > 0) {
|
|
73
|
+
if (multiple) {
|
|
74
|
+
onChange([...files, ...validFiles]);
|
|
75
|
+
} else {
|
|
76
|
+
onChange([validFiles[0]]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Stack sx={sx}>
|
|
84
|
+
<Typography variant="label1">{label}</Typography>
|
|
85
|
+
<input
|
|
86
|
+
accept={accept}
|
|
87
|
+
style={{ display: 'none' }}
|
|
88
|
+
id={name}
|
|
89
|
+
name={name}
|
|
90
|
+
type="file"
|
|
91
|
+
multiple={multiple}
|
|
92
|
+
disabled={disabled}
|
|
93
|
+
onChange={handleFileChange}
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
<FormLabel htmlFor={name}>
|
|
97
|
+
<StyledFileSelectorContainer>
|
|
98
|
+
<Stack display={'flex'} alignItems={'center'}>
|
|
99
|
+
<Icons.ExportIcon size={20} />
|
|
100
|
+
<Typography
|
|
101
|
+
color={
|
|
102
|
+
disabled
|
|
103
|
+
? theme.palette.secondary.dark
|
|
104
|
+
: theme.palette.primary.main
|
|
105
|
+
}
|
|
106
|
+
variant="label2"
|
|
107
|
+
>
|
|
108
|
+
{inputText ?? 'Upload Files'}
|
|
109
|
+
</Typography>
|
|
110
|
+
</Stack>
|
|
111
|
+
</StyledFileSelectorContainer>
|
|
112
|
+
{errorText && (
|
|
113
|
+
<Typography
|
|
114
|
+
sx={{
|
|
115
|
+
display: 'flex',
|
|
116
|
+
alignItems: 'flex-end',
|
|
117
|
+
justifyContent: 'flex-end',
|
|
118
|
+
color: theme.palette.highlight.highlightRed,
|
|
119
|
+
}}
|
|
120
|
+
variant="caption"
|
|
121
|
+
>
|
|
122
|
+
{errorText}
|
|
123
|
+
</Typography>
|
|
124
|
+
)}
|
|
125
|
+
</FormLabel>
|
|
126
|
+
|
|
127
|
+
<Box display="flex" gap="12px" flexWrap="wrap">
|
|
128
|
+
{showImage ? (
|
|
129
|
+
files.map((file, index) => (
|
|
130
|
+
<StyledSelectedFileContainer key={index}>
|
|
131
|
+
<Box
|
|
132
|
+
sx={{
|
|
133
|
+
height: '120px',
|
|
134
|
+
display: 'flex',
|
|
135
|
+
overflow: 'hidden',
|
|
136
|
+
borderRadius: '8px',
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
<img
|
|
140
|
+
src={URL.createObjectURL(file)}
|
|
141
|
+
style={{
|
|
142
|
+
height: '100%',
|
|
143
|
+
width: '100%',
|
|
144
|
+
objectFit: 'cover',
|
|
145
|
+
}}
|
|
146
|
+
alt={`Preview ${index}`}
|
|
147
|
+
/>
|
|
148
|
+
</Box>
|
|
149
|
+
|
|
150
|
+
{!disabled && !hideDeleteButton && (
|
|
151
|
+
<IconButtons.DeleteButton
|
|
152
|
+
onClick={() => {
|
|
153
|
+
const updatedFiles = [...files];
|
|
154
|
+
updatedFiles.splice(index, 1);
|
|
155
|
+
onChange(updatedFiles);
|
|
156
|
+
}}
|
|
157
|
+
size="small"
|
|
158
|
+
/>
|
|
159
|
+
)}
|
|
160
|
+
</StyledSelectedFileContainer>
|
|
161
|
+
))
|
|
162
|
+
) : (
|
|
163
|
+
<Box
|
|
164
|
+
sx={{
|
|
165
|
+
display: 'flex',
|
|
166
|
+
justifyContent: 'center',
|
|
167
|
+
alignItems: 'center',
|
|
168
|
+
padding: '8px',
|
|
169
|
+
overflow: 'hidden',
|
|
170
|
+
backgroundColor: theme.palette.background.default,
|
|
171
|
+
borderRadius: '8px',
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
174
|
+
<PreviewFiles
|
|
175
|
+
sx={previewSx}
|
|
176
|
+
files={files}
|
|
177
|
+
onChange={(newFiles) => onChange?.(newFiles)}
|
|
178
|
+
/>
|
|
179
|
+
</Box>
|
|
180
|
+
)}
|
|
181
|
+
</Box>
|
|
182
|
+
</Stack>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const StyledSelectedFileContainer = styled(Box)(({ theme }) => ({
|
|
187
|
+
borderRadius: '10px',
|
|
188
|
+
position: 'relative',
|
|
189
|
+
flex: '1 1 auto',
|
|
190
|
+
textAlign: 'center',
|
|
191
|
+
marginTop: '5px',
|
|
192
|
+
minWidth: '400px',
|
|
193
|
+
'& > button': {
|
|
194
|
+
position: 'absolute',
|
|
195
|
+
right: '-4%',
|
|
196
|
+
top: '-10%',
|
|
197
|
+
},
|
|
198
|
+
}));
|
|
199
|
+
|
|
200
|
+
const StyledFileSelectorContainer = styled(Box)(({ theme }) => ({
|
|
201
|
+
border: '1.5px dotted',
|
|
202
|
+
height: '120px',
|
|
203
|
+
display: 'flex',
|
|
204
|
+
width: '100%',
|
|
205
|
+
borderRadius: '5px',
|
|
206
|
+
borderColor: theme.palette.primary.main,
|
|
207
|
+
justifyContent: 'center',
|
|
208
|
+
alignItems: 'center',
|
|
209
|
+
'&:hover': {
|
|
210
|
+
borderColor: theme.palette.primary.light,
|
|
211
|
+
backgroundColor: theme.palette.secondary.light,
|
|
212
|
+
transition:
|
|
213
|
+
'border-color 0.2s ease-in-out, background-color 0.2s ease-in-out',
|
|
214
|
+
},
|
|
215
|
+
}));
|
|
@@ -2,6 +2,7 @@ import { Stack, SxProps } from '@mui/material';
|
|
|
2
2
|
import { TextFieldProps } from '@mui/material/TextField';
|
|
3
3
|
import React, { ReactElement, ReactNode, isValidElement } from 'react';
|
|
4
4
|
import { Control, Controller, FieldValues, Path } from 'react-hook-form';
|
|
5
|
+
import { FileUpload, FileUploadProps } from '../export';
|
|
5
6
|
import FormActions, { FormActionsProps } from '../FormActions/FormActions';
|
|
6
7
|
|
|
7
8
|
interface FormControlWrapperProps<T extends FieldValues = FieldValues> {
|
|
@@ -21,6 +22,8 @@ type ControlledElementProps<T extends FieldValues = FieldValues> = {
|
|
|
21
22
|
error?: boolean;
|
|
22
23
|
helperText?: ReactNode;
|
|
23
24
|
children?: ReactNode;
|
|
25
|
+
files?: File[];
|
|
26
|
+
onChange?: (files: File[]) => void;
|
|
24
27
|
[key: string]: any;
|
|
25
28
|
} & TextFieldProps;
|
|
26
29
|
|
|
@@ -47,10 +50,19 @@ export function FormControlWrapper<T extends FieldValues = FieldValues>({
|
|
|
47
50
|
name={name}
|
|
48
51
|
control={control}
|
|
49
52
|
render={({ field, fieldState: { error } }) => {
|
|
53
|
+
if (element.type === FileUpload) {
|
|
54
|
+
return React.cloneElement(element, {
|
|
55
|
+
...(restProps as Partial<FileUploadProps>),
|
|
56
|
+
files: field.value || [],
|
|
57
|
+
...field,
|
|
58
|
+
errorText: error ? error.message : undefined,
|
|
59
|
+
} as FileUploadProps);
|
|
60
|
+
}
|
|
50
61
|
const additionalProps =
|
|
51
62
|
element.type === 'input' && restProps.type === 'checkbox'
|
|
52
63
|
? { checked: field.value }
|
|
53
64
|
: { value: field.value };
|
|
65
|
+
console.log(error);
|
|
54
66
|
return React.cloneElement(element, {
|
|
55
67
|
...restProps,
|
|
56
68
|
...field,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export * from './Button/Button';
|
|
2
2
|
export * from './DatePicker/DatePicker';
|
|
3
|
+
export * from './DateTimePicker/DateTimePicker';
|
|
4
|
+
export * from './FileUpload/FileUpload';
|
|
3
5
|
export * from './FormActions/FormActions';
|
|
4
6
|
export * from './FormControlWrapper/FormControlWrapper';
|
|
5
7
|
export * from './FormWrapper/FormWrapper';
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
IconButton,
|
|
3
|
+
Stack,
|
|
4
|
+
SxProps,
|
|
5
|
+
useMediaQuery,
|
|
6
|
+
useTheme,
|
|
7
|
+
} from '@mui/material';
|
|
2
8
|
import { ReactNode } from 'react';
|
|
3
9
|
import { ReportIssueIcon } from '../../Assets/Icons/IconComponents/ReportIssueIcon';
|
|
4
10
|
import { Typography } from '../../DataDisplay/Typography/Typography';
|
|
@@ -18,6 +24,7 @@ export interface AppHeaderProps {
|
|
|
18
24
|
profileSx?: any;
|
|
19
25
|
onLogoutClick?: any;
|
|
20
26
|
showActiveDevices?: boolean;
|
|
27
|
+
headerSx?: SxProps;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
export const AppHeader = ({
|
|
@@ -31,6 +38,7 @@ export const AppHeader = ({
|
|
|
31
38
|
collapsed,
|
|
32
39
|
institutionsData,
|
|
33
40
|
showActiveDevices = true,
|
|
41
|
+
headerSx,
|
|
34
42
|
}: AppHeaderProps) => {
|
|
35
43
|
const theme = useTheme();
|
|
36
44
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('md'));
|
|
@@ -39,7 +47,11 @@ export const AppHeader = ({
|
|
|
39
47
|
window.open('https://helpdesk.campx.in/helpdesk/my-tickets', '_blank');
|
|
40
48
|
};
|
|
41
49
|
return (
|
|
42
|
-
<StyledHeader
|
|
50
|
+
<StyledHeader
|
|
51
|
+
collapsed={collapsed}
|
|
52
|
+
className="appHeader"
|
|
53
|
+
headerSx={headerSx}
|
|
54
|
+
>
|
|
43
55
|
<Typography variant={isSmallScreen ? 'subtitle3' : 'subtitle2'}>
|
|
44
56
|
{clientName}
|
|
45
57
|
</Typography>
|
|
@@ -2,7 +2,14 @@ import { Box, Divider, styled } from '@mui/material';
|
|
|
2
2
|
import { Link } from 'react-router-dom';
|
|
3
3
|
|
|
4
4
|
export const StyledHeader = styled('header')(
|
|
5
|
-
({
|
|
5
|
+
({
|
|
6
|
+
theme,
|
|
7
|
+
headerSx,
|
|
8
|
+
}: {
|
|
9
|
+
theme?: any;
|
|
10
|
+
collapsed: boolean;
|
|
11
|
+
headerSx?: any;
|
|
12
|
+
}) => ({
|
|
6
13
|
display: 'flex',
|
|
7
14
|
flexDirection: 'row',
|
|
8
15
|
alignItems: 'center',
|
|
@@ -17,10 +24,11 @@ export const StyledHeader = styled('header')(
|
|
|
17
24
|
[theme.breakpoints.down('md')]: {
|
|
18
25
|
height: '50px',
|
|
19
26
|
minHeight: '50px',
|
|
20
|
-
width: 'calc(100% - 84px)',
|
|
27
|
+
width: headerSx?.width?.sm ? headerSx?.width?.sm : 'calc(100% - 84px)',
|
|
21
28
|
marginTop: '12px',
|
|
22
29
|
padding: '0 12px',
|
|
23
30
|
},
|
|
31
|
+
...headerSx,
|
|
24
32
|
}),
|
|
25
33
|
);
|
|
26
34
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Box, BoxProps, styled } from '@mui/material';
|
|
2
2
|
|
|
3
|
+
export type PageContentProps = {
|
|
4
|
+
variant?: 'stepper' | 'standard';
|
|
5
|
+
} & BoxProps;
|
|
6
|
+
|
|
3
7
|
const PageContentContainer = styled(Box)(({ theme }) => ({
|
|
4
8
|
display: 'flex',
|
|
5
9
|
flexDirection: 'column',
|
|
@@ -11,8 +15,21 @@ const PageContentContainer = styled(Box)(({ theme }) => ({
|
|
|
11
15
|
borderRadius: '8px',
|
|
12
16
|
}));
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
const PageStepperContainer = styled(Box)(({ theme }) => ({
|
|
19
|
+
display: 'flex',
|
|
20
|
+
alignItems: 'center',
|
|
21
|
+
flexDirection: 'column',
|
|
22
|
+
justifyContent: 'flex-start',
|
|
23
|
+
gap: '10px',
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
export const PageContent = ({
|
|
27
|
+
variant = 'standard',
|
|
28
|
+
...props
|
|
29
|
+
}: PageContentProps) => {
|
|
30
|
+
return variant == 'stepper' ? (
|
|
31
|
+
<PageStepperContainer {...props}>{props.children}</PageStepperContainer>
|
|
32
|
+
) : (
|
|
16
33
|
<PageContentContainer gap="12px" {...props}>
|
|
17
34
|
{props.children}
|
|
18
35
|
</PageContentContainer>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Box, BoxProps, styled } from '@mui/material';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { Typography } from '../../export';
|
|
4
|
+
|
|
5
|
+
export type StepperTitleProps = {
|
|
6
|
+
title?: string;
|
|
7
|
+
isShowTitle?: boolean;
|
|
8
|
+
content: ReactNode;
|
|
9
|
+
contentProps?: BoxProps;
|
|
10
|
+
titleProps?: BoxProps;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const StepperTitleContainer = styled(Box)(({ theme }) => ({
|
|
14
|
+
borderRadius: '8px',
|
|
15
|
+
minHeight: '100px',
|
|
16
|
+
minWidth: '60%',
|
|
17
|
+
backgroundColor: theme.palette.surface.paperBackground,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
const TitleContainer = styled(Box)(({ theme }) => ({
|
|
21
|
+
backgroundColor: theme.palette.surface.grey,
|
|
22
|
+
height: '45px',
|
|
23
|
+
display: 'flex',
|
|
24
|
+
alignItems: 'center',
|
|
25
|
+
paddingLeft: '16px',
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
export const StepperTitle = (props: StepperTitleProps) => {
|
|
29
|
+
return (
|
|
30
|
+
<StepperTitleContainer {...props.contentProps}>
|
|
31
|
+
{props.isShowTitle && (
|
|
32
|
+
<TitleContainer {...props.titleProps}>
|
|
33
|
+
<Typography variant="subtitle3">{props.title}</Typography>
|
|
34
|
+
</TitleContainer>
|
|
35
|
+
)}
|
|
36
|
+
{props.content}
|
|
37
|
+
</StepperTitleContainer>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
@@ -2,4 +2,5 @@ export * from './AppHeader/AppHeader';
|
|
|
2
2
|
export * from './PageContent/PageContent';
|
|
3
3
|
export * from './PageHeader/components/DensitySelector/DensitySelector';
|
|
4
4
|
export * from './PageHeader/PageHeader';
|
|
5
|
+
export * from './StepperTitle/StepperTitle';
|
|
5
6
|
export * from './TabsLayout/TabsLayout';
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
IconButton,
|
|
5
5
|
Stack,
|
|
6
6
|
styled,
|
|
7
|
+
SxProps,
|
|
7
8
|
Typography,
|
|
8
9
|
useTheme,
|
|
9
10
|
} from '@mui/material';
|
|
@@ -15,6 +16,7 @@ export type PreviewFilesProps = {
|
|
|
15
16
|
label?: string;
|
|
16
17
|
onChange?: (newFiles: File[], deletedFile: File) => void;
|
|
17
18
|
showDownload?: boolean;
|
|
19
|
+
sx?: SxProps;
|
|
18
20
|
} & Omit<BoxProps, 'onChange'>;
|
|
19
21
|
|
|
20
22
|
export const PreviewFiles = ({
|
|
@@ -22,6 +24,7 @@ export const PreviewFiles = ({
|
|
|
22
24
|
label,
|
|
23
25
|
onChange,
|
|
24
26
|
showDownload,
|
|
27
|
+
sx,
|
|
25
28
|
...props
|
|
26
29
|
}: PreviewFilesProps) => {
|
|
27
30
|
const theme = useTheme(),
|
|
@@ -86,7 +89,7 @@ export const PreviewFiles = ({
|
|
|
86
89
|
if (resolvedFiles.length === 0) return <></>;
|
|
87
90
|
|
|
88
91
|
return (
|
|
89
|
-
<Box {...props}>
|
|
92
|
+
<Box sx={sx} {...props}>
|
|
90
93
|
{label && (
|
|
91
94
|
<Typography
|
|
92
95
|
marginBottom={'8px'}
|
|
@@ -103,12 +106,16 @@ export const PreviewFiles = ({
|
|
|
103
106
|
direction={'row'}
|
|
104
107
|
sx={{ display: 'flex', alignItems: 'center', gap: '12px' }}
|
|
105
108
|
>
|
|
106
|
-
{
|
|
109
|
+
<Box width={'30px'} height={'30px'}>
|
|
110
|
+
{getFileIcon(file)}
|
|
111
|
+
</Box>
|
|
112
|
+
|
|
107
113
|
<Stack direction={'column'} marginLeft={'8px'}>
|
|
108
114
|
<Typography
|
|
109
115
|
fontSize={'14px'}
|
|
110
116
|
color={theme.palette.primary.main}
|
|
111
117
|
variant="label2"
|
|
118
|
+
sx={{ wordBreak: 'break-all' }}
|
|
112
119
|
>
|
|
113
120
|
{file.name}
|
|
114
121
|
</Typography>
|
|
@@ -149,7 +156,7 @@ export const PreviewFiles = ({
|
|
|
149
156
|
|
|
150
157
|
const PreviewContainer = styled(Box)(({ theme }) => ({
|
|
151
158
|
width: '100%',
|
|
152
|
-
|
|
159
|
+
minHeight: '60px',
|
|
153
160
|
cursor: 'pointer',
|
|
154
161
|
backgroundColor: theme.palette.secondary.light,
|
|
155
162
|
padding: '16px 8px',
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { LocalizationProvider } from '@mui/x-date-pickers';
|
|
2
|
+
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFnsV3';
|
|
3
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
4
|
+
import { add } from 'date-fns';
|
|
5
|
+
import { DateTimePicker, Icons } from '../../components/export';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof DateTimePicker> = {
|
|
8
|
+
title: 'Input/DateTimePicker',
|
|
9
|
+
component: DateTimePicker,
|
|
10
|
+
decorators: [
|
|
11
|
+
(Story) => (
|
|
12
|
+
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
|
13
|
+
<Story />
|
|
14
|
+
</LocalizationProvider>
|
|
15
|
+
),
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
|
|
21
|
+
type Story = StoryObj<typeof DateTimePicker>;
|
|
22
|
+
|
|
23
|
+
const shortcutsItems = [
|
|
24
|
+
{ label: 'Now', getValue: () => new Date() },
|
|
25
|
+
{ label: '1 Hour Later', getValue: () => add(new Date(), { hours: 1 }) },
|
|
26
|
+
{ label: '1 Hour Ago', getValue: () => add(new Date(), { hours: -1 }) },
|
|
27
|
+
{
|
|
28
|
+
label: 'New Year',
|
|
29
|
+
getValue: () => new Date(new Date().getFullYear(), 0, 1, 0, 0),
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
export const Primary: Story = {
|
|
34
|
+
args: {
|
|
35
|
+
label: 'Date Time Picker',
|
|
36
|
+
name: 'date-time-picker',
|
|
37
|
+
placeholder: 'Select Date and Time',
|
|
38
|
+
required: true,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const WithShortcuts: Story = {
|
|
43
|
+
args: {
|
|
44
|
+
...Primary.args,
|
|
45
|
+
label: 'With Shortcuts',
|
|
46
|
+
shortcutsItems,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const CustomFormat: Story = {
|
|
51
|
+
args: {
|
|
52
|
+
...Primary.args,
|
|
53
|
+
label: 'Custom Format',
|
|
54
|
+
format: 'MMM dd, yyyy hh:mm a',
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const ViewsYearMonth: Story = {
|
|
59
|
+
args: {
|
|
60
|
+
...Primary.args,
|
|
61
|
+
label: 'Year and Month Views',
|
|
62
|
+
views: ['year', 'month'],
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const WithHelperText: Story = {
|
|
67
|
+
args: {
|
|
68
|
+
...Primary.args,
|
|
69
|
+
label: 'With Helper Text',
|
|
70
|
+
helperText: 'Please select a valid date and time',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const MinMaxDates: Story = {
|
|
75
|
+
args: {
|
|
76
|
+
...Primary.args,
|
|
77
|
+
label: 'With Min and Max Dates',
|
|
78
|
+
minDate: new Date('2024-01-01'),
|
|
79
|
+
maxDate: new Date('2024-12-31'),
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const CustomIcon: Story = {
|
|
84
|
+
args: {
|
|
85
|
+
...Primary.args,
|
|
86
|
+
label: 'With Custom Icon',
|
|
87
|
+
openPickerIcon: Icons.CareerIcon,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const ReadOnly: Story = {
|
|
92
|
+
args: {
|
|
93
|
+
...Primary.args,
|
|
94
|
+
label: 'Read Only',
|
|
95
|
+
required: false,
|
|
96
|
+
disabled: true,
|
|
97
|
+
value: new Date('2024-09-15 14:30'),
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const DisableFutureDates: Story = {
|
|
102
|
+
args: {
|
|
103
|
+
...Primary.args,
|
|
104
|
+
label: 'Disable Future Dates',
|
|
105
|
+
disableFuture: true,
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const DisablePastDates: Story = {
|
|
110
|
+
args: {
|
|
111
|
+
...Primary.args,
|
|
112
|
+
label: 'Disable Past Dates',
|
|
113
|
+
disablePast: true,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const DisableSpecificDates: Story = {
|
|
118
|
+
args: {
|
|
119
|
+
...Primary.args,
|
|
120
|
+
label: 'Disable Weekends',
|
|
121
|
+
shouldDisableDate: (date: Date) =>
|
|
122
|
+
date.getDay() === 0 || date.getDay() === 6,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
@@ -334,6 +334,10 @@ export const getCommonTheme = (mode: Theme) => {
|
|
|
334
334
|
fontSize: '14px ',
|
|
335
335
|
fontFamily: 'Poppins',
|
|
336
336
|
fontWeight: 600,
|
|
337
|
+
'@media (max-width:959.95px)': {
|
|
338
|
+
fontSize: '12px',
|
|
339
|
+
padding: '6px 12px ',
|
|
340
|
+
},
|
|
337
341
|
'&:hover': {
|
|
338
342
|
boxShadow: 'none ',
|
|
339
343
|
'@media (hover: none )': {
|
|
@@ -558,6 +562,9 @@ export const getCommonTheme = (mode: Theme) => {
|
|
|
558
562
|
minHeight: '50px',
|
|
559
563
|
paddingBottom: '0px',
|
|
560
564
|
justifyContent: 'flex-start',
|
|
565
|
+
'@media (max-width:959.95px)': {
|
|
566
|
+
fontSize: '12px',
|
|
567
|
+
},
|
|
561
568
|
'&.Mui-selected': {
|
|
562
569
|
color: ColorTokens.text.primary,
|
|
563
570
|
},
|