@sio-group/form-react 0.1.0 → 0.1.3

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +35 -4
  2. package/dist/index.cjs +268 -18
  3. package/dist/index.d.cts +2 -2
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.js +258 -17
  6. package/package.json +4 -3
  7. package/src/assets/scss/components/button.scss +164 -0
  8. package/src/assets/scss/components/checkbox.scss +90 -0
  9. package/src/assets/scss/components/color.scss +29 -0
  10. package/src/assets/scss/components/form-field.scss +34 -0
  11. package/src/assets/scss/components/form-states.scss +80 -0
  12. package/src/assets/scss/components/grid.scss +134 -0
  13. package/src/assets/scss/components/input.scss +112 -0
  14. package/src/assets/scss/components/link.scss +66 -0
  15. package/src/assets/scss/components/radio.scss +104 -0
  16. package/src/assets/scss/components/range.scss +52 -0
  17. package/src/assets/scss/components/select.scss +35 -0
  18. package/src/assets/scss/components/upload.scss +52 -0
  19. package/src/assets/scss/index.scss +19 -0
  20. package/src/assets/scss/tokens/_colors.scss +49 -0
  21. package/src/assets/scss/tokens/_form.scss +6 -0
  22. package/src/assets/scss/utilities/_mixins.scss +6 -0
  23. package/src/components/Button/index.tsx +106 -0
  24. package/src/components/Fields/Checkbox/index.tsx +59 -0
  25. package/src/components/Fields/Input/DateInput/index.tsx +95 -0
  26. package/src/components/Fields/Input/FileInput/index.tsx +169 -0
  27. package/src/components/Fields/Input/Input.tsx +45 -0
  28. package/src/components/Fields/Input/NumberInput/index.tsx +169 -0
  29. package/src/components/Fields/Input/RangeInput/index.tsx +77 -0
  30. package/src/components/Fields/Input/TextInput/index.tsx +65 -0
  31. package/src/components/Fields/InputWrapper/index.tsx +78 -0
  32. package/src/components/Fields/Radio/index.tsx +82 -0
  33. package/src/components/Fields/Select/index.tsx +103 -0
  34. package/src/components/Fields/Textarea/index.tsx +70 -0
  35. package/src/components/Fields/index.tsx +11 -0
  36. package/src/components/Form.tsx +163 -0
  37. package/src/components/Icon/index.tsx +16 -0
  38. package/src/components/Link/index.tsx +106 -0
  39. package/src/hooks/useConnectionStatus.ts +20 -0
  40. package/src/hooks/useForm.ts +230 -0
  41. package/src/index.ts +15 -0
  42. package/src/types/field-props.d.ts +94 -0
  43. package/src/types/field-setters.d.ts +6 -0
  44. package/src/types/field-state.d.ts +21 -0
  45. package/src/types/form-config.d.ts +30 -0
  46. package/src/types/form-layout.d.ts +6 -0
  47. package/src/types/index.ts +18 -0
  48. package/src/types/ui-props.d.ts +33 -0
  49. package/src/types/use-form-options.d.ts +3 -0
  50. package/src/utils/create-field-props.ts +115 -0
  51. package/src/utils/create-field-state.ts +99 -0
  52. package/src/utils/custom-icons.tsx +145 -0
  53. package/src/utils/file-type-icon.ts +63 -0
  54. package/src/utils/get-accept-string.ts +24 -0
  55. package/src/utils/get-column-classes.ts +21 -0
  56. package/src/utils/get-file-size.ts +9 -0
  57. package/src/utils/parse-date.ts +36 -0
  58. package/src/utils/slugify.ts +9 -0
  59. package/tsconfig.json +15 -0
@@ -0,0 +1,115 @@
1
+ import { BaseFieldProps, FieldProps, FieldState, FieldSetters } from "../types";
2
+ import { getColumnClasses } from "./get-column-classes";
3
+
4
+ export const createFieldProps = (field: FieldState, setters: FieldSetters, disabled: boolean, renderLayout: boolean = false): FieldProps => {
5
+ const classes: string = getColumnClasses(field.layout, field.styling?.className);
6
+
7
+ const baseProps: BaseFieldProps = {
8
+ id: field.id,
9
+ name: field.name,
10
+ label: field.label,
11
+ placeholder: field.placeholder,
12
+ value: field.value,
13
+ errors: field.errors,
14
+ required: field.required,
15
+ autocomplete: field.autocomplete,
16
+ touched: field.touched,
17
+ focused: field.focused,
18
+ readOnly: field.readOnly,
19
+ disabled,
20
+ icon: field.icon,
21
+ description: field.description,
22
+ onChange: setters.handleChange,
23
+ setFocused: setters.setFocused,
24
+ setTouched: setters.setTouched,
25
+ className: renderLayout ? classes : field.styling?.className,
26
+ style: field.styling?.style,
27
+ };
28
+
29
+ if (field.type === 'textarea') {
30
+ return {
31
+ ...baseProps,
32
+ type: field.type,
33
+ rows: field.rows,
34
+ cols: field.cols,
35
+ }
36
+ }
37
+
38
+ if (field.type === "file") {
39
+ return {
40
+ ...baseProps,
41
+ type: field.type,
42
+ accept: field.accept,
43
+ multiple: field.multiple ?? false,
44
+ capture: field.capture ?? false,
45
+ onError: setters.setErrors,
46
+ filesize: field.filesize ?? 10240,
47
+ onFileRemove: field.onFileRemove,
48
+ onRemoveAll: field.onRemoveAll,
49
+ };
50
+ }
51
+
52
+ if (field.type === "range" || field.type === "number") {
53
+ const numberProps = {
54
+ ...baseProps,
55
+ type: field.type,
56
+ min: field.min ?? Number.MIN_SAFE_INTEGER,
57
+ max: field.max ?? Number.MAX_SAFE_INTEGER,
58
+ step: field.step ?? 1,
59
+ };
60
+
61
+ if (field.type === "number") {
62
+ return {
63
+ ...numberProps,
64
+ spinner: field.spinner ?? true,
65
+ };
66
+ }
67
+
68
+ if (field.type === "range") {
69
+ return {
70
+ ...numberProps,
71
+ showValue: field.showValue ?? true,
72
+ };
73
+ }
74
+ }
75
+
76
+ if(field.type === "date" || field.type === "time" || field.type === 'datetime-local') {
77
+ return {
78
+ ...baseProps,
79
+ type: field.type,
80
+ min: field.min ?? '',
81
+ max: field.max ?? '',
82
+ step: field.step ?? 1,
83
+ };
84
+ }
85
+
86
+ if (field.type === "url") {
87
+ return {
88
+ ...baseProps,
89
+ type: field.type,
90
+ allowLocalhost: field.allowLocalhost ?? false,
91
+ allowFtp: field.allowFtp ?? false,
92
+ secureOnly: field.secureOnly ?? !(field.allowLocalhost || field.allowFtp),
93
+ };
94
+ }
95
+
96
+ if (field.type === "select" || field.type === "creatable") {
97
+ return {
98
+ ...baseProps,
99
+ type: field.type,
100
+ options: field.options || [],
101
+ multiple: field.multiple,
102
+ };
103
+ }
104
+
105
+ if (field.type === "radio") {
106
+ return {
107
+ ...baseProps,
108
+ type: field.type,
109
+ options: field.options || [],
110
+ inline: field.inline ?? false,
111
+ };
112
+ }
113
+
114
+ return { ...baseProps, type: field.type, };
115
+ }
@@ -0,0 +1,99 @@
1
+ import { FieldConfigMap, FormField } from "@sio-group/form-types";
2
+ import { FieldState, FieldValue } from "../types";
3
+ import { ValidationRule } from "@sio-group/form-types/src/core/valudation-rule";
4
+ import {
5
+ dateIsBiggerThan,
6
+ dateIsSmallerThan,
7
+ isBiggerThan,
8
+ isEmail, isPattern,
9
+ isRequired,
10
+ isSmallerThan,
11
+ isUrl
12
+ } from "@sio-group/form-validation";
13
+ import { parseDateValue } from "./parse-date";
14
+
15
+ export const createFieldState = (name: string, id: string, config: FormField): FieldState => {
16
+ return {
17
+ ...config.config,
18
+ id,
19
+ name,
20
+ type: config.type,
21
+ value: config.config?.defaultValue ?? getDefaultValue(config),
22
+ validations: [
23
+ ...(config.config?.validations ?? []),
24
+ ...getDefaultValidations(config),
25
+ ],
26
+ errors: [],
27
+ touched: false,
28
+ focused: false,
29
+ } as FieldState
30
+ }
31
+
32
+ function getDefaultValue<T extends keyof FieldConfigMap> (config: FormField): FieldValue<T> {
33
+ switch (config.type) {
34
+ case "checkbox":
35
+ return false;
36
+ case "range":
37
+ return config.config.min ?? 0;
38
+ case "select":
39
+ case "creatable":
40
+ return null;
41
+ case "file":
42
+ return [];
43
+ case "color":
44
+ return "#000000";
45
+ case "number":
46
+ case "text":
47
+ default:
48
+ return "";
49
+ }
50
+ }
51
+
52
+ function getDefaultValidations<T extends keyof FieldConfigMap>(config: FormField): ValidationRule<T>[] {
53
+ const validations: ValidationRule<T>[] = [];
54
+
55
+ if (config.config?.required) {
56
+ validations.push(isRequired());
57
+ }
58
+
59
+ switch (config.type) {
60
+ case 'email':
61
+ validations.push(isEmail());
62
+ break;
63
+
64
+ case 'number':
65
+ case 'range':
66
+ if (config.config?.min) validations.push(isBiggerThan(config.config?.min));
67
+ if (config.config?.max) validations.push(isSmallerThan(config.config?.max));
68
+ break;
69
+
70
+ case 'date':
71
+ case 'datetime-local':
72
+ case 'time':
73
+ const min: string | undefined = config.config?.min;
74
+ const max: string | undefined = config.config?.max;
75
+ const parsedMin: Date | null = parseDateValue(min);
76
+ const parsedMax: Date | null = parseDateValue(max);
77
+ if (parsedMin) validations.push(dateIsBiggerThan(parsedMin));
78
+ if (parsedMax) validations.push(dateIsSmallerThan(parsedMax));
79
+ break;
80
+
81
+ case 'url':
82
+ if (config.config?.pattern) validations.push(isPattern(config.config.pattern));
83
+ validations.push(
84
+ isUrl(
85
+ config.config?.allowLocalhost || false,
86
+ config.config?.allowFtp || false,
87
+ config.config?.secureOnly || !(config.config?.allowLocalhost || config.config?.allowFtp),
88
+ ),
89
+ );
90
+ break;
91
+
92
+ case 'text':
93
+ case 'tel':
94
+ if (config.config?.pattern) validations.push(isPattern(config.config.pattern));
95
+ break;
96
+ }
97
+
98
+ return validations;
99
+ }
@@ -0,0 +1,145 @@
1
+ export const CustomIcons = {
2
+ Date: () => (
3
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
4
+ <rect x="3" y="4" width="18" height="18" rx="2" ry="2" stroke="currentColor" fill="none"/>
5
+ <line x1="8" y1="2" x2="8" y2="6" stroke="currentColor"/>
6
+ <line x1="16" y1="2" x2="16" y2="6" stroke="currentColor"/>
7
+ <line x1="3" y1="10" x2="21" y2="10" stroke="currentColor"/>
8
+ </svg>
9
+ ),
10
+ Time: () => (
11
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
12
+ <circle cx="12" cy="12" r="10" />
13
+ <polyline points="12 6 12 12 16 14" />
14
+ <circle cx="12" cy="12" r="1" fill="currentColor" fillOpacity="0.3" stroke="none" />
15
+ </svg>
16
+ ),
17
+ DateTime: () => (
18
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
19
+ <rect x="2" y="3" width="14" height="14" rx="2" ry="2" />
20
+ <line x1="5" y1="2" x2="5" y2="5" />
21
+ <line x1="13" y1="2" x2="13" y2="5" />
22
+ <line x1="2" y1="7" x2="16" y2="7" />
23
+
24
+ <circle cx="19" cy="15" r="4" />
25
+ <polyline points="19 13 19 15 21 16" />
26
+ </svg>
27
+ ),
28
+ FileUpload: () => (
29
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
30
+ <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z" />
31
+ <polyline points="13 2 13 9 20 9" />
32
+ <path d="M12 12v6m0-6 2 2m-2-2-2 2" />
33
+ </svg>
34
+ ),
35
+ CloudUpload: () => (
36
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
37
+ <path d="M12 16v-6m0 0-2 2m2-2 2 2" />
38
+ <path d="M16 16h3a4 4 0 0 0 0-8h-1.5A5.5 5.5 0 0 0 7 9a5 5 0 0 0-1 9.8" />
39
+ </svg>
40
+ ),
41
+ TrashIcon: () => (
42
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
43
+ <path d="M3 6h18" />
44
+ <rect x="6" y="6" width="12" height="14" rx="1" ry="1" />
45
+
46
+ <path d="M9 3h6" />
47
+ <path d="M10 3v3M14 3v3" />
48
+
49
+ <line x1="10" y1="10" x2="10" y2="16" />
50
+ <line x1="14" y1="10" x2="14" y2="16" />
51
+ </svg>
52
+ ),
53
+ SimpleTrashIcon: () => (
54
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
55
+ <path d="M3 6h18" />
56
+ <rect x="6" y="8" width="12" height="12" rx="1" ry="1" />
57
+ <path d="M9 4h6" />
58
+ </svg>
59
+ ),
60
+ DeleteTrashIcon: () => (
61
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
62
+ <path d="M3 6h18" />
63
+ <rect x="6" y="6" width="12" height="14" rx="1" ry="1" />
64
+ <path d="M9 3h6" />
65
+ <path d="M10 3v3M14 3v3" />
66
+ <line x1="9" y1="10" x2="15" y2="16" />
67
+ <line x1="15" y1="10" x2="9" y2="16" />
68
+ </svg>
69
+ ),
70
+ FileIcon: () => (
71
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
72
+ <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z" />
73
+ <polyline points="13 2 13 9 20 9" />
74
+ </svg>
75
+ ),
76
+ Word: () => (
77
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
78
+ <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/>
79
+ <polyline points="13 2 13 9 20 9"/>
80
+ <line x1="8" y1="13" x2="16" y2="13"/>
81
+ <line x1="8" y1="16" x2="16" y2="16"/>
82
+ <line x1="8" y1="19" x2="13" y2="19"/>
83
+ </svg>
84
+ ),
85
+ Excel: () => (
86
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
87
+ <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/>
88
+ <polyline points="13 2 13 9 20 9"/>
89
+ <rect x="7" y="12" width="10" height="7" rx="1"/>
90
+ <line x1="7" y1="15.5" x2="17" y2="15.5"/>
91
+ <line x1="11.5" y1="12" x2="11.5" y2="19"/>
92
+ </svg>
93
+ ),
94
+ PowerPoint: () => (
95
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
96
+ <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/>
97
+ <polyline points="13 2 13 9 20 9"/>
98
+ <rect x="7" y="12" width="10" height="7" rx="1"/>
99
+ <path d="M9 17l2-3 2 2 2-3"/>
100
+ </svg>
101
+ ),
102
+ Pdf: () => (
103
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
104
+ <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/>
105
+ <polyline points="13 2 13 9 20 9"/>
106
+ <rect x="7" y="14" width="10" height="4" rx="1"/>
107
+ </svg>
108
+ ),
109
+ Image: () => (
110
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
111
+ <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/>
112
+ <polyline points="13 2 13 9 20 9"/>
113
+ <circle cx="9" cy="13" r="1.5"/>
114
+ <path d="M7 18l3-3 2 2 3-3 2 4"/>
115
+ </svg>
116
+ ),
117
+ Audio: () => (
118
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
119
+ <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/>
120
+ <polyline points="13 2 13 9 20 9"/>
121
+ <path d="M8 16v-2"/>
122
+ <path d="M10 17v-4"/>
123
+ <path d="M12 18v-6"/>
124
+ <path d="M14 17v-4"/>
125
+ <path d="M16 16v-2"/>
126
+ </svg>
127
+ ),
128
+ Video: () => (
129
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
130
+ <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/>
131
+ <polyline points="13 2 13 9 20 9"/>
132
+ <rect x="7" y="12" width="8" height="6" rx="1"/>
133
+ <polygon points="11 14 14 15 11 16"/>
134
+ </svg>
135
+ ),
136
+ Globe: () => (
137
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none"
138
+ stroke="currentColor" strokeWidth="1.5"
139
+ strokeLinecap="round" strokeLinejoin="round">
140
+ <circle cx="12" cy="12" r="10" />
141
+ <path d="M2 12h20" />
142
+ <path d="M12 2a15.3 15.3 0 0 1 0 20 15.3 15.3 0 0 1 0-20z" />
143
+ </svg>
144
+ ),
145
+ };
@@ -0,0 +1,63 @@
1
+ import { CustomIcons } from "./custom-icons";
2
+
3
+ export const FileTypeIcon = (mime: string) => {
4
+ let icon;
5
+
6
+ switch (mime) {
7
+ case 'text/plain':
8
+ icon = CustomIcons.FileIcon();
9
+ break;
10
+ case 'text/uri-list':
11
+ return CustomIcons.Globe();
12
+ case 'application/xls':
13
+ case 'application/vnd.ms-excel':
14
+ case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
15
+ case 'application/vnd.ms-excel.sheet.macroEnabled.12':
16
+ case 'application/vnd.ms-excel.sheet.binary.macroEnabled.12':
17
+ icon = CustomIcons.Excel();
18
+ break;
19
+ case 'application/msword':
20
+ case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
21
+ case 'application/vnd.ms-word':
22
+ case 'application/vnd.ms-word.document.macroEnabled.12':
23
+ icon = CustomIcons.Word();
24
+ break;
25
+ case 'application/vnd.ms-powerpoint':
26
+ case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
27
+ case 'application/vnd.ms-powerpoint.presentation.macroEnabled.12':
28
+ case 'application/vnd.openxmlformats-officedocument.presentationml.slideshow':
29
+ icon = CustomIcons.PowerPoint();
30
+ break;
31
+ case 'application/pdf':
32
+ icon = CustomIcons.Pdf();
33
+ break;
34
+ case 'audio/mpeg':
35
+ case 'audio/wav':
36
+ case 'audio/x-wav':
37
+ case 'audio/ogg':
38
+ case 'audio/mp4':
39
+ icon = CustomIcons.Audio();
40
+ break;
41
+ case 'image/jpeg':
42
+ case 'image/png':
43
+ case 'image/bmp':
44
+ case 'image/gif':
45
+ case 'image/webp':
46
+ case 'image/svg+xml':
47
+ case 'image/heic':
48
+ case 'image/heif':
49
+ icon = CustomIcons.Image();
50
+ break;
51
+ case 'video/mp4':
52
+ case 'video/quicktime':
53
+ case 'video/webm':
54
+ case 'video/x-msvideo':
55
+ case 'video/x-matroska':
56
+ icon = CustomIcons.Video();
57
+ break;
58
+ default:
59
+ icon = CustomIcons.FileIcon();
60
+ }
61
+
62
+ return icon;
63
+ };
@@ -0,0 +1,24 @@
1
+ import { AcceptType } from "@sio-group/form-types";
2
+
3
+ /**
4
+ * Get the accept attribute for file inputs based on the provided accept-type.
5
+ * @param accept - Accept type which can be a string, an array of strings, or shorthand types like 'image', 'video', or 'audio'.
6
+ */
7
+ export const getAccept = (accept?: AcceptType): string | undefined => {
8
+ if (Array.isArray(accept)) {
9
+ return accept
10
+ .map(item => {
11
+ if (item === 'image') return 'image/*';
12
+ if (item === 'video') return 'video/*';
13
+ if (item === 'audio') return 'audio/*';
14
+ return item;
15
+ })
16
+ .join(', ');
17
+ }
18
+
19
+ if (accept === 'image') return 'image/*';
20
+ if (accept === 'video') return 'video/*';
21
+ if (accept === 'audio') return 'audio/*';
22
+
23
+ return accept;
24
+ };
@@ -0,0 +1,21 @@
1
+ import { LayoutType } from "@sio-group/form-types";
2
+
3
+ export const getColumnClasses = (layout?: LayoutType, className?: string): string => {
4
+ if (!layout) return 'sio-col-xs-12';
5
+
6
+ const classes: string[] = [];
7
+
8
+ if (className) classes.push(className);
9
+
10
+ if (layout.sm) classes.push(`sio-col-sm-${layout.sm}`);
11
+ if (layout.md) classes.push(`sio-col-md-${layout.md}`);
12
+ if (layout.lg) classes.push(`sio-col-lg-${layout.lg}`);
13
+
14
+ if (layout.order) {
15
+ if (layout.order.sm) classes.push(`sio-order-sm-${layout.order.sm}`);
16
+ if (layout.order.md) classes.push(`sio-order-md-${layout.order.md}`);
17
+ if (layout.order.lg) classes.push(`sio-order-lg-${layout.order.lg}`);
18
+ }
19
+
20
+ return classes.join(' ');
21
+ };
@@ -0,0 +1,9 @@
1
+ export const getFileSize = (size: number) => {
2
+ const sizes: string[] = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
3
+
4
+ if (size === 0) return '0 Bytes';
5
+
6
+ const i: number = Math.floor(Math.log(size) / Math.log(1024));
7
+
8
+ return `${Math.round(size / Math.pow(1024, i))} ${sizes[i]}`;
9
+ };
@@ -0,0 +1,36 @@
1
+ export const parseDateValue = (val: unknown): Date | null => {
2
+ if (val instanceof Date) {
3
+ return isNaN(val.getTime()) ? null : val;
4
+ }
5
+
6
+ if (typeof val !== 'string') {
7
+ return null;
8
+ }
9
+
10
+ if (val.trim() === '') {
11
+ return null;
12
+ }
13
+
14
+ const timeRegex = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9](:[0-5][0-9])?$/;
15
+ if (timeRegex.test(val)) {
16
+ const [hours, minutes, seconds = 0] = val.split(':').map(Number);
17
+ const date = new Date();
18
+ date.setHours(hours, minutes, seconds, 0);
19
+ return date;
20
+ }
21
+
22
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
23
+ if (dateRegex.test(val)) {
24
+ const date = new Date(val + 'T00:00:00');
25
+ return isNaN(date.getTime()) ? null : date;
26
+ }
27
+
28
+ const dateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
29
+ if (dateTimeRegex.test(val)) {
30
+ const date = new Date(val);
31
+ return isNaN(date.getTime()) ? null : date;
32
+ }
33
+
34
+ const date = new Date(val);
35
+ return isNaN(date.getTime()) ? null : date;
36
+ };
@@ -0,0 +1,9 @@
1
+ export const slugify = (string: string, separator: string = "_"): string => {
2
+ return string
3
+ .normalize("NFD")
4
+ .replace(/[\u0300-\u036f]/g, "")
5
+ .toLowerCase()
6
+ .trim()
7
+ .replace(/[^a-z0-9 ]/g, "")
8
+ .replace(/\s+/g, separator);
9
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "composite": false,
5
+ "outDir": "dist",
6
+ "rootDir": "src",
7
+ "jsx": "react-jsx",
8
+ "jsxImportSource": "react"
9
+ },
10
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
11
+ "exclude": ["dist", "node_modules"],
12
+ "references": [
13
+ { "path": "../form-types" }
14
+ ]
15
+ }