@addsign/moje-agenda-shared-lib 2.0.72 → 2.0.73

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 (45) hide show
  1. package/dist/Dialog-BmQoVu5C.js.map +1 -1
  2. package/dist/assets/style.css +1772 -1758
  3. package/dist/components/Attachments.js +2 -2
  4. package/dist/components/datatable/DataTable.js +3 -3
  5. package/dist/components/datatable/DataTableServer.js +3 -3
  6. package/dist/components/form/AutocompleteSearchBar.js +2 -2
  7. package/dist/components/form/AutocompleteSearchBarServer.js +2 -2
  8. package/dist/components/form/FileInput.js +3 -3
  9. package/dist/components/form/FileInputForm.d.ts +1 -0
  10. package/dist/components/form/FileInputForm.js +201 -93
  11. package/dist/components/form/FileInputForm.js.map +1 -1
  12. package/dist/components/form/FileInputFormMultiple.d.ts +1 -0
  13. package/dist/components/form/FileInputFormMultiple.js +203 -82
  14. package/dist/components/form/FileInputFormMultiple.js.map +1 -1
  15. package/dist/components/form/FileInputMultiple.js +3 -3
  16. package/dist/components/form/FormField.js +2 -2
  17. package/dist/components/form/PositionsSelectorSingle.js +3 -3
  18. package/dist/components/form/SelectField.js +2 -2
  19. package/dist/components/layout/Neoptimizovano.js +2 -2
  20. package/dist/components/profiles/ProfileOverview.js +2 -2
  21. package/dist/components/ui/Combobox.js +1 -1
  22. package/dist/components/ui/DatePicker.js +2 -2
  23. package/dist/components/ui/DateTimePicker.js +2 -2
  24. package/dist/components/ui/Dialog.js +1 -1
  25. package/dist/components/ui/ScrollArea.js +2 -2
  26. package/dist/components/ui/checkbox.js +4 -4
  27. package/dist/components/ui/command.d.ts +6 -6
  28. package/dist/components/ui/command.js +2 -2
  29. package/dist/components/ui/input.js +8 -107
  30. package/dist/components/ui/input.js.map +1 -1
  31. package/dist/components/ui/multi-select.js +1 -1
  32. package/dist/components/ui/popover.js +1 -1
  33. package/dist/components/ui/radioGroup.js +5 -5
  34. package/dist/components/ui/select.js +7 -7
  35. package/dist/components/ui/toast.js +5 -5
  36. package/dist/components/ui/tooltip.js +6 -6
  37. package/dist/input-Cm_FjJOF.js +111 -0
  38. package/dist/input-Cm_FjJOF.js.map +1 -0
  39. package/dist/main.js +3 -3
  40. package/dist/popover-DpJhfyvx.js.map +1 -1
  41. package/lib/components/form/FileInputForm.tsx +245 -99
  42. package/lib/components/form/FileInputFormMultiple.tsx +233 -65
  43. package/lib/css/tailwind.css +9 -9
  44. package/package.json +1 -1
  45. package/tailwind.config.js +97 -97
@@ -2,10 +2,20 @@ import React, { useState, useCallback, useEffect } from "react";
2
2
  import { useDropzone } from "react-dropzone";
3
3
  import { MdDeleteOutline, MdInsertDriveFile } from "react-icons/md";
4
4
  import { AxiosError } from "axios";
5
+ import { LoaderCircleIcon } from "lucide-react";
5
6
  import { cn } from "../../utils/utils";
6
7
  import { handleErrors } from "@addsign/moje-agenda-shared-lib";
7
8
  import { IAttachment } from "../../types";
8
9
  import { useFederationContext, useFormField } from "../../main";
10
+ import {
11
+ Dialog,
12
+ DialogContent,
13
+ DialogHeader,
14
+ DialogTitle,
15
+ DialogFooter,
16
+ } from "../ui/Dialog";
17
+ import { Input } from "../ui/input";
18
+ import { Button } from "../ui/button";
9
19
 
10
20
  interface FileInputFormMultipleProps {
11
21
  name: string;
@@ -17,6 +27,7 @@ interface FileInputFormMultipleProps {
17
27
  initialFileIds?: number[];
18
28
  attachmentName?: string;
19
29
  attachmentType?: string;
30
+ askForAttachmentName?: boolean;
20
31
  }
21
32
 
22
33
  interface IAttachmentReadOnly extends IAttachment {
@@ -35,6 +46,7 @@ const FileInputFormMultiple: React.FC<FileInputFormMultipleProps> = ({
35
46
  initialFileIds,
36
47
  attachmentName,
37
48
  attachmentType,
49
+ askForAttachmentName = false,
38
50
  }) => {
39
51
  const [fileDataList, setFileDataList] = useState<IAttachmentReadOnly[]>(
40
52
  value?.map((file) => ({
@@ -43,6 +55,11 @@ const FileInputFormMultiple: React.FC<FileInputFormMultipleProps> = ({
43
55
  })) || []
44
56
  );
45
57
 
58
+ const [pendingFiles, setPendingFiles] = useState<File[]>([]);
59
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
60
+ const [fileNames, setFileNames] = useState<Record<string, string>>({});
61
+ const [isUploading, setIsUploading] = useState(false);
62
+
46
63
  const federationContext = useFederationContext();
47
64
 
48
65
  const { error } = useFormField();
@@ -61,10 +78,11 @@ const FileInputFormMultiple: React.FC<FileInputFormMultipleProps> = ({
61
78
  );
62
79
  }, [value, initialFilesReadOnly, initialFileIds]);
63
80
 
64
- const onDrop = useCallback(
65
- async (acceptedFiles: File[]) => {
81
+ const uploadFiles = useCallback(
82
+ async (files: File[], customNames?: Record<string, string>) => {
83
+ setIsUploading(true);
66
84
  const uploadedFiles = await Promise.all(
67
- acceptedFiles.map(async (file) => {
85
+ files.map(async (file) => {
68
86
  if (file.size > MAX_FILE_SIZE) {
69
87
  // Handle the case when the file size is exceeded
70
88
  federationContext.emitter.emit("message", {
@@ -82,7 +100,9 @@ const FileInputFormMultiple: React.FC<FileInputFormMultipleProps> = ({
82
100
 
83
101
  // Add the JSON data object
84
102
  const dataObject = {
85
- ...(attachmentName && { attachmentName }),
103
+ ...(customNames?.[file.name]
104
+ ? { attachmentName: customNames[file.name] }
105
+ : attachmentName && { attachmentName }),
86
106
  ...(attachmentType && { attachmentType }),
87
107
  };
88
108
 
@@ -118,6 +138,7 @@ const FileInputFormMultiple: React.FC<FileInputFormMultipleProps> = ({
118
138
  const updatedFileDataList = [...fileDataList, ...validFiles];
119
139
  setFileDataList(updatedFileDataList);
120
140
  onFilesChanged(updatedFileDataList);
141
+ setIsUploading(false);
121
142
  },
122
143
  [
123
144
  federationContext,
@@ -128,9 +149,67 @@ const FileInputFormMultiple: React.FC<FileInputFormMultipleProps> = ({
128
149
  ]
129
150
  );
130
151
 
152
+ const onDrop = useCallback(
153
+ async (acceptedFiles: File[]) => {
154
+ // Filter out files that exceed size limit
155
+ const validFiles = acceptedFiles.filter((file) => {
156
+ if (file.size > MAX_FILE_SIZE) {
157
+ federationContext.emitter.emit("message", {
158
+ title: "Velikost souboru byla překročena",
159
+ message: `Maximální povolená velikost je ${MAX_FILE_SIZE / (1024 * 1024)} MB.`,
160
+ classes: "bg-danger ",
161
+ timeout: 0,
162
+ type: "error",
163
+ });
164
+ return false;
165
+ }
166
+ return true;
167
+ });
168
+
169
+ if (validFiles.length === 0) {
170
+ return;
171
+ }
172
+
173
+ if (askForAttachmentName) {
174
+ // Initialize file names with original file names
175
+ const initialNames: Record<string, string> = {};
176
+ validFiles.forEach((file) => {
177
+ initialNames[file.name] = file.name;
178
+ });
179
+ setFileNames(initialNames);
180
+ setPendingFiles(validFiles);
181
+ setIsDialogOpen(true);
182
+ } else {
183
+ await uploadFiles(validFiles);
184
+ }
185
+ },
186
+ [askForAttachmentName, uploadFiles, federationContext]
187
+ );
188
+
189
+ const handleDialogSubmit = async () => {
190
+ setIsDialogOpen(false);
191
+ await uploadFiles(pendingFiles, fileNames);
192
+ setPendingFiles([]);
193
+ setFileNames({});
194
+ };
195
+
196
+ const handleDialogCancel = () => {
197
+ setIsDialogOpen(false);
198
+ setPendingFiles([]);
199
+ setFileNames({});
200
+ };
201
+
202
+ const formatFileSize = (bytes: number): string => {
203
+ if (bytes === 0) return "0 Bytes";
204
+ const k = 1024;
205
+ const sizes = ["Bytes", "KB", "MB", "GB"];
206
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
207
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
208
+ };
209
+
131
210
  const { getRootProps, getInputProps, isDragActive } = useDropzone({
132
211
  onDrop,
133
- disabled,
212
+ disabled: disabled || isUploading,
134
213
  });
135
214
 
136
215
  const handleRemove = (fileId: number) => {
@@ -146,74 +225,163 @@ const FileInputFormMultiple: React.FC<FileInputFormMultipleProps> = ({
146
225
  }
147
226
 
148
227
  return (
149
- <div
150
- className="w-full min-h-30 flex-col justify-start items-start gap-1.5 inline-flex sharedLibrary"
151
- data-cy={"file-input-form-multiple-" + name}
152
- >
153
- <div className="self-stretch flex-col justify-start items-start gap-1.5 flex">
154
- <div
155
- className={cn(
156
- `self-stretch px-3 py-2 rounded-lg justify-start items-center gap-2 outline-none border bg-transparent `,
157
- hasError &&
228
+ <>
229
+ <div
230
+ className="w-full min-h-30 flex-col justify-start items-start gap-1.5 inline-flex sharedLibrary"
231
+ data-cy={"file-input-form-multiple-" + name}
232
+ >
233
+ <div className="self-stretch flex-col justify-start items-start gap-1.5 flex">
234
+ <div
235
+ className={cn(
236
+ `self-stretch px-3 py-2 rounded-lg justify-start items-center gap-2 outline-none border bg-transparent relative`,
237
+ hasError &&
158
238
  "outline-4 outline-red-200 outline-offset-0 border-none ",
159
- disabled ? "bg-gray-100" : "bg-transparent"
160
- )}
161
- >
162
- {!disabled && (
163
- <div
164
- {...getRootProps()}
165
- className={`w-full p-4 border-dashed cursor-pointer
166
- border-2 rounded-lg text-center hover:bg-gray-100
167
- ${isDragActive ? "border-indigo-300 bg-indigo-50" : "border-gray-300"}`}
168
- >
169
- <input {...getInputProps()} id={name} />
170
- <p className="text-gray-500">
171
- {isDragActive
172
- ? "Sem přetáhněte soubory"
173
- : "Klikněte pro nahrání, nebo nahrajte přetažením souborů"}
174
- </p>
175
- </div>
176
- )}
177
- <div className="w-full">
178
- {fileDataList.map((file) => (
239
+ disabled ? "bg-gray-100" : "bg-transparent"
240
+ )}
241
+ >
242
+ {isUploading && (
243
+ <div className="absolute inset-0 bg-white/80 backdrop-blur-sm rounded-lg z-10 flex items-center justify-center">
244
+ <div className="flex flex-col items-center gap-2">
245
+ <LoaderCircleIcon className="h-8 w-8 animate-spin text-primary" />
246
+ <p className="text-sm text-gray-600">Nahrávání...</p>
247
+ </div>
248
+ </div>
249
+ )}
250
+ {!disabled && (
179
251
  <div
180
- key={file.id}
181
- className="w-full flex items-center justify-between py-2 border-b "
252
+ {...getRootProps()}
253
+ className={cn(
254
+ `w-full p-4 border-dashed border-2 rounded-lg text-center`,
255
+ isUploading
256
+ ? "cursor-not-allowed opacity-50"
257
+ : "cursor-pointer hover:bg-gray-100",
258
+ isDragActive ? "border-indigo-300 bg-indigo-50" : "border-gray-300"
259
+ )}
182
260
  >
183
- <div className="flex items-center content-center">
184
- <MdInsertDriveFile style={{ fontSize: "2rem" }} />
185
- <a
186
- href={`/api/files/download/${file.id}`}
187
- className="pl-2 text-left underline text-primary"
188
- target="_blank"
189
- >
190
- {file.filename}
191
- </a>
261
+ <input {...getInputProps()} id={name} />
262
+ <p className="text-gray-500">
263
+ {isDragActive
264
+ ? "Sem přetáhněte soubory"
265
+ : "Klikněte pro nahrání, nebo nahrajte přetažením souborů"}
266
+ </p>
267
+ </div>
268
+ )}
269
+ <div className="w-full">
270
+ {fileDataList.map((file) => (
271
+ <div
272
+ key={file.id}
273
+ className="w-full flex items-center justify-between py-2 border-b "
274
+ >
275
+ <div className="flex flex-col flex-1">
276
+ <div className="flex items-center content-center gap-2">
277
+ <MdInsertDriveFile style={{ fontSize: "2rem" }} />
278
+
279
+ <div className="flex flex-col flex-1">
280
+ <a
281
+ href={`/api/files/download/${file.id}`}
282
+ className="text-left underline text-primary text-sm"
283
+ target="_blank"
284
+ >
285
+ {file.filename}
286
+ </a>
287
+ {file.attachmentName && (
288
+ <div className="text-sm text-gray-600">
289
+ {file.attachmentName}
290
+ </div>
291
+ )}
292
+ </div>
293
+ </div>
294
+
295
+ </div>
296
+ {!disabled && file.readonly !== true && !isUploading && (
297
+ <div
298
+ onClick={() => handleRemove(file.id)}
299
+ className="text-gray-600 cursor-pointer hover:text-primary hover:bg-gray-200 rounded-full ml-4"
300
+ >
301
+ <MdDeleteOutline
302
+ style={{ fontSize: "1.5rem", margin: "10px" }}
303
+ />
304
+ </div>
305
+ )}
192
306
  </div>
193
- {!disabled && file.readonly !== true && (
194
- <div
195
- onClick={() => handleRemove(file.id)}
196
- className="text-gray-600 cursor-pointer hover:text-primary hover:bg-gray-200 rounded-full ml-4"
197
- >
198
- <MdDeleteOutline
199
- style={{ fontSize: "1.5rem", margin: "10px" }}
200
- />
307
+ ))}
308
+ </div>
309
+ </div>
310
+ </div>
311
+ {description && (
312
+ <div
313
+ className="HintText self-stretch text-slate-600 text-sm font-normal leading-tight"
314
+ id={name + ":description"}
315
+ >
316
+ {description}
317
+ </div>
318
+ )}
319
+ </div>
320
+
321
+ <Dialog
322
+ open={isDialogOpen}
323
+ onOpenChange={(open) => {
324
+ setIsDialogOpen(open);
325
+ if (!open) {
326
+ handleDialogCancel();
327
+ }
328
+ }}
329
+ >
330
+ <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
331
+ <DialogHeader>
332
+ <DialogTitle>Zadejte popisy příloh</DialogTitle>
333
+ </DialogHeader>
334
+ <div className="space-y-4 py-4">
335
+ {pendingFiles.map((file) => (
336
+ <div
337
+ key={file.name}
338
+ className="flex flex-col gap-2 p-4 border rounded-lg"
339
+ >
340
+ <div className="flex items-center gap-2">
341
+ <MdInsertDriveFile style={{ fontSize: "1.5rem" }} />
342
+ <div className="flex-1">
343
+ <div className="font-medium text-sm">{file.name}</div>
344
+ <div className="text-xs text-gray-500">
345
+ {formatFileSize(file.size)}
346
+ </div>
201
347
  </div>
202
- )}
348
+ </div>
349
+ <div className="mt-2">
350
+ <label className="text-sm font-medium mb-1 block">
351
+ Popis přílohy:
352
+ </label>
353
+ <Input
354
+ value={fileNames[file.name] || file.name}
355
+ onChange={(e) =>
356
+ setFileNames({
357
+ ...fileNames,
358
+ [file.name]: e.target.value,
359
+ })
360
+ }
361
+ placeholder="Zadejte popis přílohy"
362
+ />
363
+ </div>
203
364
  </div>
204
365
  ))}
205
366
  </div>
206
- </div>
207
- </div>
208
- {description && (
209
- <div
210
- className="HintText self-stretch text-slate-600 text-sm font-normal leading-tight"
211
- id={name + ":description"}
212
- >
213
- {description}
214
- </div>
215
- )}
216
- </div>
367
+ <DialogFooter>
368
+ <Button variant="outline" onClick={handleDialogCancel} disabled={isUploading}>
369
+ Zrušit
370
+ </Button>
371
+ <Button onClick={handleDialogSubmit} disabled={isUploading}>
372
+ {isUploading ? (
373
+ <>
374
+ <LoaderCircleIcon className="h-4 w-4 animate-spin mr-2" />
375
+ Nahrávání...
376
+ </>
377
+ ) : (
378
+ "Nahrát"
379
+ )}
380
+ </Button>
381
+ </DialogFooter>
382
+ </DialogContent>
383
+ </Dialog>
384
+ </>
217
385
  );
218
386
  };
219
387
 
@@ -1,10 +1,10 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
4
-
5
-
6
-
7
- :root {
8
- /* --color-primary: green;
9
- Example color */
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+
6
+
7
+ :root {
8
+ /* --color-primary: green;
9
+ Example color */
10
10
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@addsign/moje-agenda-shared-lib",
3
3
  "private": false,
4
- "version": "2.0.72",
4
+ "version": "2.0.73",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
7
7
  "types": "dist/main.d.ts",
@@ -1,97 +1,97 @@
1
-
2
- /** @type {import('tailwindcss').Config.theme.extend} */
3
- export const TAILWIND_THEME_EXTEND = {
4
- colors: {
5
- border: "var(--border)",
6
- input: "var(--input)",
7
- ring: "var(--ring)",
8
- background: "var(--background)",
9
- foreground: "var(--foreground)",
10
-
11
- // Using modern `rgb`
12
- primary: {
13
- DEFAULT: "var(--color-primary)",
14
- hover: "var(--color-primary-hover)",
15
- text: "var(--color-primary-text)",
16
- foreground: "var(--color-primary-text)",
17
- border: "var(--color-primary-border)",
18
- },
19
- secondary: {
20
- DEFAULT: "var(--color-secondary)",
21
- hover: "var(--color-secondary-hover)",
22
- text: "var(--color-secondary-text)",
23
- foreground: "var(--secondary-foreground)",
24
- border: "var(--color-secondary-border)",
25
- },
26
- danger: {
27
- DEFAULT: "var(--color-danger)",
28
- hover: "var(--color-danger-hover)",
29
- text: "var(--color-danger-text)",
30
- border: "var(--color-danger-border)",
31
- },
32
- destructive: {
33
- DEFAULT: "var(--destructive)",
34
- foreground: "var(--destructive-foreground)",
35
- },
36
- warning: {
37
- DEFAULT: "var(--color-warning)",
38
- hover: "var(--color-warning-hover)",
39
- text: "var(--color-warning-text)",
40
- border: "var(--color-warning-border)",
41
- },
42
- success: {
43
- DEFAULT: "var(--color-success)",
44
- hover: "var(--color-success-hover)",
45
- text: "var(--color-success-text)",
46
- border: "var(--color-success-border)",
47
- },
48
-
49
- gray: {
50
- 150: "rgb(238, 240, 243)",
51
- },
52
-
53
- muted: {
54
- DEFAULT: "var(--muted)",
55
- foreground: "var(--muted-foreground)",
56
- },
57
- accent: {
58
- DEFAULT: "var(--accent)",
59
- foreground: "var(--accent-foreground)",
60
- },
61
- popover: {
62
- DEFAULT: "var(--popover)",
63
- foreground: "var(--popover-foreground)",
64
- },
65
- card: {
66
- DEFAULT: "var(--card)",
67
- foreground: "var(--card-foreground)",
68
- },
69
- },
70
-
71
- animation: {
72
- spin: 'spin 2s linear infinite',
73
- },
74
- }
75
-
76
- /** @type {import('tailwindcss').Config} */
77
- export default {
78
- darkMode: "class",
79
- important: false, // Ensures all classes use !important
80
-
81
- content: [
82
- "./lib/**/*.{js,ts,jsx,tsx}",
83
- "./lib/**/*.{ts,tsx}",
84
- "./lib/components/**/*.{ts,tsx}",
85
- "./node_modules/react-tailwindcss-datepicker/dist/index.esm.js",
86
- ],
87
- theme: {
88
- extend: TAILWIND_THEME_EXTEND,
89
- },
90
- plugins: [],
91
- safelist: [
92
- "bg-danger", // Add any other dynamic classes here
93
- "lg:grid-cols-3",
94
- "lg:grid-cols-2",
95
- "lg:grid-cols-4",
96
- ],
97
- };
1
+
2
+ /** @type {import('tailwindcss').Config.theme.extend} */
3
+ export const TAILWIND_THEME_EXTEND = {
4
+ colors: {
5
+ border: "var(--border)",
6
+ input: "var(--input)",
7
+ ring: "var(--ring)",
8
+ background: "var(--background)",
9
+ foreground: "var(--foreground)",
10
+
11
+ // Using modern `rgb`
12
+ primary: {
13
+ DEFAULT: "var(--color-primary)",
14
+ hover: "var(--color-primary-hover)",
15
+ text: "var(--color-primary-text)",
16
+ foreground: "var(--color-primary-text)",
17
+ border: "var(--color-primary-border)",
18
+ },
19
+ secondary: {
20
+ DEFAULT: "var(--color-secondary)",
21
+ hover: "var(--color-secondary-hover)",
22
+ text: "var(--color-secondary-text)",
23
+ foreground: "var(--secondary-foreground)",
24
+ border: "var(--color-secondary-border)",
25
+ },
26
+ danger: {
27
+ DEFAULT: "var(--color-danger)",
28
+ hover: "var(--color-danger-hover)",
29
+ text: "var(--color-danger-text)",
30
+ border: "var(--color-danger-border)",
31
+ },
32
+ destructive: {
33
+ DEFAULT: "var(--destructive)",
34
+ foreground: "var(--destructive-foreground)",
35
+ },
36
+ warning: {
37
+ DEFAULT: "var(--color-warning)",
38
+ hover: "var(--color-warning-hover)",
39
+ text: "var(--color-warning-text)",
40
+ border: "var(--color-warning-border)",
41
+ },
42
+ success: {
43
+ DEFAULT: "var(--color-success)",
44
+ hover: "var(--color-success-hover)",
45
+ text: "var(--color-success-text)",
46
+ border: "var(--color-success-border)",
47
+ },
48
+
49
+ gray: {
50
+ 150: "rgb(238, 240, 243)",
51
+ },
52
+
53
+ muted: {
54
+ DEFAULT: "var(--muted)",
55
+ foreground: "var(--muted-foreground)",
56
+ },
57
+ accent: {
58
+ DEFAULT: "var(--accent)",
59
+ foreground: "var(--accent-foreground)",
60
+ },
61
+ popover: {
62
+ DEFAULT: "var(--popover)",
63
+ foreground: "var(--popover-foreground)",
64
+ },
65
+ card: {
66
+ DEFAULT: "var(--card)",
67
+ foreground: "var(--card-foreground)",
68
+ },
69
+ },
70
+
71
+ animation: {
72
+ spin: 'spin 2s linear infinite',
73
+ },
74
+ }
75
+
76
+ /** @type {import('tailwindcss').Config} */
77
+ export default {
78
+ darkMode: "class",
79
+ important: false, // Ensures all classes use !important
80
+
81
+ content: [
82
+ "./lib/**/*.{js,ts,jsx,tsx}",
83
+ "./lib/**/*.{ts,tsx}",
84
+ "./lib/components/**/*.{ts,tsx}",
85
+ "./node_modules/react-tailwindcss-datepicker/dist/index.esm.js",
86
+ ],
87
+ theme: {
88
+ extend: TAILWIND_THEME_EXTEND,
89
+ },
90
+ plugins: [],
91
+ safelist: [
92
+ "bg-danger", // Add any other dynamic classes here
93
+ "lg:grid-cols-3",
94
+ "lg:grid-cols-2",
95
+ "lg:grid-cols-4",
96
+ ],
97
+ };