@carlonicora/nextjs-jsonapi 0.0.1 → 1.0.4

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 (303) hide show
  1. package/dist/BlockNoteEditor-VFWG6LXI.js.map +1 -1
  2. package/dist/JsonApiRequest-ZZLSP26T.js.map +1 -1
  3. package/dist/atoms/index.js.map +1 -1
  4. package/dist/chunk-2K3Q24UF.js.map +1 -1
  5. package/dist/chunk-3FBCC4G3.js.map +1 -1
  6. package/dist/chunk-4HCRAOS5.js.map +1 -1
  7. package/dist/chunk-6GKHCVF6.js.map +1 -1
  8. package/dist/chunk-7QVYU63E.js.map +1 -1
  9. package/dist/chunk-A5DDIABK.js.map +1 -1
  10. package/dist/chunk-AWONBQQP.js.map +1 -1
  11. package/dist/chunk-CXQOWQSY.js.map +1 -1
  12. package/dist/chunk-DO2HLAZO.js.map +1 -1
  13. package/dist/chunk-EFJEWLRL.js.map +1 -1
  14. package/dist/chunk-FY4SXJGU.js.map +1 -1
  15. package/dist/chunk-H6FMOA6B.js.map +1 -1
  16. package/dist/chunk-I2REI7OA.js.map +1 -1
  17. package/dist/chunk-IBS6NI7D.js.map +1 -1
  18. package/dist/chunk-J4Q36PMP.js.map +1 -1
  19. package/dist/chunk-JC3WJK65.js.map +1 -1
  20. package/dist/chunk-LXKSUWAV.js.map +1 -1
  21. package/dist/chunk-RAF7PNLG.js.map +1 -1
  22. package/dist/chunk-RUR22SVM.js.map +1 -1
  23. package/dist/chunk-TEGF6ZWG.js.map +1 -1
  24. package/dist/chunk-TMVHSY3Y.js.map +1 -1
  25. package/dist/chunk-V2JJPI7N.js.map +1 -1
  26. package/dist/client/index.js.map +1 -1
  27. package/dist/components/index.js.map +1 -1
  28. package/dist/contexts/index.js.map +1 -1
  29. package/dist/core/index.js.map +1 -1
  30. package/dist/features/index.js.map +1 -1
  31. package/dist/hooks/index.js.map +1 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/interfaces/index.js.map +1 -1
  34. package/dist/permissions/index.js.map +1 -1
  35. package/dist/request-QFS7NEIE.js.map +1 -1
  36. package/dist/request-ZYY6RI5X.js.map +1 -1
  37. package/dist/roles/index.js.map +1 -1
  38. package/dist/server/index.js.map +1 -1
  39. package/dist/shadcnui/index.js.map +1 -1
  40. package/dist/token-MJMC26ON.js.map +1 -1
  41. package/dist/token-UYE7CV6X.js.map +1 -1
  42. package/dist/utils/index.js.map +1 -1
  43. package/package.json +6 -1
  44. package/src/atoms/index.ts +1 -0
  45. package/src/atoms/recentPagesAtom.ts +10 -0
  46. package/src/client/context/JsonApiContext.ts +61 -0
  47. package/src/client/context/JsonApiProvider.tsx +27 -0
  48. package/src/client/context/index.ts +2 -0
  49. package/src/client/hooks/index.ts +3 -0
  50. package/src/client/hooks/useJsonApiGet.ts +188 -0
  51. package/src/client/hooks/useJsonApiMutation.ts +193 -0
  52. package/src/client/hooks/useRehydration.ts +47 -0
  53. package/src/client/index.ts +11 -0
  54. package/src/client/request.ts +97 -0
  55. package/src/client/token.ts +10 -0
  56. package/src/components/containers/PageContainer.tsx +15 -0
  57. package/src/components/containers/ReactMarkdownContainer.tsx +119 -0
  58. package/src/components/containers/TabsContainer.tsx +93 -0
  59. package/src/components/containers/index.ts +3 -0
  60. package/src/components/contents/AttributeElement.tsx +20 -0
  61. package/src/components/contents/index.ts +1 -0
  62. package/src/components/details/AllowedUsersDetails.tsx +23 -0
  63. package/src/components/details/index.ts +1 -0
  64. package/src/components/editors/BlockNoteDiffInlineContent.tsx +152 -0
  65. package/src/components/editors/BlockNoteEditor.tsx +404 -0
  66. package/src/components/editors/BlockNoteEditorContainer.tsx +13 -0
  67. package/src/components/editors/BlockNoteEditorFormattingToolbar.tsx +38 -0
  68. package/src/components/editors/index.ts +1 -0
  69. package/src/components/errors/ErrorDetails.tsx +41 -0
  70. package/src/components/errors/errorToast.ts +9 -0
  71. package/src/components/errors/index.ts +2 -0
  72. package/src/components/forms/CommonAssociationForm.tsx +162 -0
  73. package/src/components/forms/CommonDeleter.tsx +94 -0
  74. package/src/components/forms/CommonEditorButtons.tsx +30 -0
  75. package/src/components/forms/CommonEditorHeader.tsx +35 -0
  76. package/src/components/forms/CommonEditorTrigger.tsx +26 -0
  77. package/src/components/forms/DatePickerPopover.tsx +219 -0
  78. package/src/components/forms/DateRangeSelector.tsx +110 -0
  79. package/src/components/forms/FileUploader.tsx +324 -0
  80. package/src/components/forms/FormCheckbox.tsx +66 -0
  81. package/src/components/forms/FormContainerGeneric.tsx +39 -0
  82. package/src/components/forms/FormDate.tsx +247 -0
  83. package/src/components/forms/FormDateTime.tsx +231 -0
  84. package/src/components/forms/FormInput.tsx +110 -0
  85. package/src/components/forms/FormPassword.tsx +54 -0
  86. package/src/components/forms/FormPlaceAutocomplete.tsx +286 -0
  87. package/src/components/forms/FormSelect.tsx +72 -0
  88. package/src/components/forms/FormSlider.tsx +51 -0
  89. package/src/components/forms/FormSwitch.tsx +25 -0
  90. package/src/components/forms/FormTextarea.tsx +44 -0
  91. package/src/components/forms/MultiFileUploader.tsx +107 -0
  92. package/src/components/forms/PasswordInput.tsx +47 -0
  93. package/src/components/forms/index.ts +21 -0
  94. package/src/components/index.ts +11 -0
  95. package/src/components/navigations/Breadcrumb.tsx +83 -0
  96. package/src/components/navigations/ContentTitle.tsx +39 -0
  97. package/src/components/navigations/Header.tsx +27 -0
  98. package/src/components/navigations/ModeToggleSwitch.tsx +25 -0
  99. package/src/components/navigations/PageSection.tsx +64 -0
  100. package/src/components/navigations/RecentPagesNavigator.tsx +52 -0
  101. package/src/components/navigations/index.ts +6 -0
  102. package/src/components/pages/PageContainerContentDetails.tsx +76 -0
  103. package/src/components/pages/PageContentContainer.tsx +31 -0
  104. package/src/components/pages/index.ts +2 -0
  105. package/src/components/tables/ContentListTable.tsx +165 -0
  106. package/src/components/tables/ContentTableSearch.tsx +105 -0
  107. package/src/components/tables/cells/cell.component.tsx +18 -0
  108. package/src/components/tables/cells/cell.date.tsx +16 -0
  109. package/src/components/tables/cells/cell.id.tsx +27 -0
  110. package/src/components/tables/cells/cell.link.tsx +18 -0
  111. package/src/components/tables/cells/cell.text.tsx +12 -0
  112. package/src/components/tables/cells/cell.url.tsx +13 -0
  113. package/src/components/tables/cells/index.ts +5 -0
  114. package/src/components/tables/index.ts +3 -0
  115. package/src/contexts/SharedContext.tsx +35 -0
  116. package/src/contexts/index.ts +2 -0
  117. package/src/core/abstracts/AbstractApiData.ts +138 -0
  118. package/src/core/abstracts/AbstractService.ts +263 -0
  119. package/src/core/abstracts/index.ts +2 -0
  120. package/src/core/endpoint/EndpointCreator.ts +97 -0
  121. package/src/core/endpoint/index.ts +1 -0
  122. package/src/core/factories/JsonApiDataFactory.ts +12 -0
  123. package/src/core/factories/RehydrationFactory.ts +30 -0
  124. package/src/core/factories/index.ts +2 -0
  125. package/src/core/fields/FieldSelector.ts +15 -0
  126. package/src/core/fields/index.ts +1 -0
  127. package/src/core/index.ts +20 -0
  128. package/src/core/interfaces/ApiData.ts +8 -0
  129. package/src/core/interfaces/ApiDataInterface.ts +15 -0
  130. package/src/core/interfaces/ApiRequestDataTypeInterface.ts +14 -0
  131. package/src/core/interfaces/ApiResponseInterface.ts +17 -0
  132. package/src/core/interfaces/JsonApiHydratedDataInterface.ts +5 -0
  133. package/src/core/interfaces/index.ts +5 -0
  134. package/src/core/registry/DataClassRegistry.ts +51 -0
  135. package/src/core/registry/ModuleRegistrar.ts +43 -0
  136. package/src/core/registry/ModuleRegistry.ts +64 -0
  137. package/src/core/registry/index.ts +3 -0
  138. package/src/core/utils/index.ts +2 -0
  139. package/src/core/utils/rehydrate.ts +24 -0
  140. package/src/core/utils/translateResponse.ts +125 -0
  141. package/src/features/auth/auth.module.ts +9 -0
  142. package/src/features/auth/config.ts +57 -0
  143. package/src/features/auth/data/auth.interface.ts +31 -0
  144. package/src/features/auth/data/auth.service.ts +159 -0
  145. package/src/features/auth/data/auth.ts +54 -0
  146. package/src/features/auth/data/index.ts +3 -0
  147. package/src/features/auth/index.ts +3 -0
  148. package/src/features/company/company.module.ts +10 -0
  149. package/src/features/company/data/company.fields.ts +6 -0
  150. package/src/features/company/data/company.interface.ts +28 -0
  151. package/src/features/company/data/company.service.ts +73 -0
  152. package/src/features/company/data/company.ts +104 -0
  153. package/src/features/company/data/index.ts +4 -0
  154. package/src/features/company/index.ts +2 -0
  155. package/src/features/content/content.module.ts +20 -0
  156. package/src/features/content/data/content.fields.ts +13 -0
  157. package/src/features/content/data/content.interface.ts +23 -0
  158. package/src/features/content/data/content.service.ts +75 -0
  159. package/src/features/content/data/content.ts +85 -0
  160. package/src/features/content/data/index.ts +4 -0
  161. package/src/features/content/index.ts +2 -0
  162. package/src/features/feature/components/forms/FormFeatures.tsx +149 -0
  163. package/src/features/feature/components/index.ts +1 -0
  164. package/src/features/feature/data/feature.interface.ts +9 -0
  165. package/src/features/feature/data/feature.service.ts +19 -0
  166. package/src/features/feature/data/feature.ts +33 -0
  167. package/src/features/feature/data/index.ts +3 -0
  168. package/src/features/feature/feature.module.ts +10 -0
  169. package/src/features/feature/index.ts +3 -0
  170. package/src/features/index.ts +12 -0
  171. package/src/features/module/data/index.ts +2 -0
  172. package/src/features/module/data/module.interface.ts +12 -0
  173. package/src/features/module/data/module.ts +42 -0
  174. package/src/features/module/index.ts +2 -0
  175. package/src/features/module/module.module.ts +10 -0
  176. package/src/features/notification/data/index.ts +4 -0
  177. package/src/features/notification/data/notification.fields.ts +8 -0
  178. package/src/features/notification/data/notification.interface.ts +14 -0
  179. package/src/features/notification/data/notification.service.ts +34 -0
  180. package/src/features/notification/data/notification.ts +51 -0
  181. package/src/features/notification/index.ts +2 -0
  182. package/src/features/notification/notification.module.ts +10 -0
  183. package/src/features/push/data/index.ts +3 -0
  184. package/src/features/push/data/push.interface.ts +8 -0
  185. package/src/features/push/data/push.service.ts +17 -0
  186. package/src/features/push/data/push.ts +18 -0
  187. package/src/features/push/index.ts +2 -0
  188. package/src/features/push/push.module.ts +10 -0
  189. package/src/features/role/data/index.ts +4 -0
  190. package/src/features/role/data/role.fields.ts +8 -0
  191. package/src/features/role/data/role.interface.ts +16 -0
  192. package/src/features/role/data/role.service.ts +117 -0
  193. package/src/features/role/data/role.ts +62 -0
  194. package/src/features/role/index.ts +2 -0
  195. package/src/features/role/role.module.ts +10 -0
  196. package/src/features/s3/data/index.ts +3 -0
  197. package/src/features/s3/data/s3.interface.ts +11 -0
  198. package/src/features/s3/data/s3.service.ts +30 -0
  199. package/src/features/s3/data/s3.ts +60 -0
  200. package/src/features/s3/index.ts +2 -0
  201. package/src/features/s3/s3.module.ts +10 -0
  202. package/src/features/search/index.ts +1 -0
  203. package/src/features/search/interfaces/index.ts +1 -0
  204. package/src/features/search/interfaces/search.result.interface.ts +3 -0
  205. package/src/features/user/author.module.ts +10 -0
  206. package/src/features/user/components/index.ts +2 -0
  207. package/src/features/user/components/lists/ContributorsList.tsx +41 -0
  208. package/src/features/user/components/lists/index.ts +1 -0
  209. package/src/features/user/components/widgets/UserAvatar.tsx +86 -0
  210. package/src/features/user/components/widgets/index.ts +1 -0
  211. package/src/features/user/contexts/CurrentUserContext.tsx +156 -0
  212. package/src/features/user/contexts/index.ts +1 -0
  213. package/src/features/user/data/index.ts +4 -0
  214. package/src/features/user/data/user.fields.ts +8 -0
  215. package/src/features/user/data/user.interface.ts +41 -0
  216. package/src/features/user/data/user.service.ts +246 -0
  217. package/src/features/user/data/user.ts +162 -0
  218. package/src/features/user/index.ts +4 -0
  219. package/src/features/user/user.module.ts +21 -0
  220. package/src/hooks/TableGeneratorRegistry.ts +53 -0
  221. package/src/hooks/index.ts +33 -0
  222. package/src/hooks/types.ts +35 -0
  223. package/src/hooks/url.rewriter.ts +22 -0
  224. package/src/hooks/useCustomD3Graph.tsx +705 -0
  225. package/src/hooks/useDataListRetriever.ts +349 -0
  226. package/src/hooks/useDebounce.ts +33 -0
  227. package/src/hooks/usePageUrlGenerator.ts +50 -0
  228. package/src/hooks/useTableGenerator.ts +16 -0
  229. package/src/i18n/config.ts +73 -0
  230. package/src/i18n/index.ts +18 -0
  231. package/src/index.ts +16 -0
  232. package/src/interfaces/breadcrumb.item.data.interface.ts +4 -0
  233. package/src/interfaces/d3.link.interface.ts +7 -0
  234. package/src/interfaces/d3.node.interface.ts +12 -0
  235. package/src/interfaces/index.ts +3 -0
  236. package/src/permissions/check.ts +127 -0
  237. package/src/permissions/index.ts +2 -0
  238. package/src/permissions/types.ts +109 -0
  239. package/src/roles/config.ts +46 -0
  240. package/src/roles/index.ts +1 -0
  241. package/src/server/cache.ts +28 -0
  242. package/src/server/index.ts +3 -0
  243. package/src/server/request.ts +113 -0
  244. package/src/server/token.ts +10 -0
  245. package/src/shadcnui/custom/kanban.tsx +1001 -0
  246. package/src/shadcnui/custom/link.tsx +18 -0
  247. package/src/shadcnui/custom/multi-select.tsx +382 -0
  248. package/src/shadcnui/index.ts +49 -0
  249. package/src/shadcnui/ui/accordion.tsx +52 -0
  250. package/src/shadcnui/ui/alert-dialog.tsx +141 -0
  251. package/src/shadcnui/ui/alert.tsx +43 -0
  252. package/src/shadcnui/ui/avatar.tsx +50 -0
  253. package/src/shadcnui/ui/badge.tsx +40 -0
  254. package/src/shadcnui/ui/breadcrumb.tsx +115 -0
  255. package/src/shadcnui/ui/button.tsx +51 -0
  256. package/src/shadcnui/ui/calendar.tsx +73 -0
  257. package/src/shadcnui/ui/card.tsx +43 -0
  258. package/src/shadcnui/ui/carousel.tsx +225 -0
  259. package/src/shadcnui/ui/chart.tsx +320 -0
  260. package/src/shadcnui/ui/checkbox.tsx +29 -0
  261. package/src/shadcnui/ui/collapsible.tsx +11 -0
  262. package/src/shadcnui/ui/command.tsx +155 -0
  263. package/src/shadcnui/ui/context-menu.tsx +179 -0
  264. package/src/shadcnui/ui/dialog.tsx +96 -0
  265. package/src/shadcnui/ui/drawer.tsx +89 -0
  266. package/src/shadcnui/ui/dropdown-menu.tsx +205 -0
  267. package/src/shadcnui/ui/form.tsx +138 -0
  268. package/src/shadcnui/ui/hover-card.tsx +29 -0
  269. package/src/shadcnui/ui/input.tsx +21 -0
  270. package/src/shadcnui/ui/label.tsx +26 -0
  271. package/src/shadcnui/ui/navigation-menu.tsx +168 -0
  272. package/src/shadcnui/ui/popover.tsx +33 -0
  273. package/src/shadcnui/ui/progress.tsx +25 -0
  274. package/src/shadcnui/ui/radio-group.tsx +37 -0
  275. package/src/shadcnui/ui/resizable.tsx +47 -0
  276. package/src/shadcnui/ui/scroll-area.tsx +40 -0
  277. package/src/shadcnui/ui/select.tsx +164 -0
  278. package/src/shadcnui/ui/separator.tsx +28 -0
  279. package/src/shadcnui/ui/sheet.tsx +139 -0
  280. package/src/shadcnui/ui/sidebar.tsx +677 -0
  281. package/src/shadcnui/ui/skeleton.tsx +13 -0
  282. package/src/shadcnui/ui/slider.tsx +25 -0
  283. package/src/shadcnui/ui/sonner.tsx +25 -0
  284. package/src/shadcnui/ui/switch.tsx +31 -0
  285. package/src/shadcnui/ui/table.tsx +120 -0
  286. package/src/shadcnui/ui/tabs.tsx +55 -0
  287. package/src/shadcnui/ui/textarea.tsx +24 -0
  288. package/src/shadcnui/ui/toggle.tsx +39 -0
  289. package/src/shadcnui/ui/tooltip.tsx +61 -0
  290. package/src/unified/JsonApiRequest.ts +325 -0
  291. package/src/unified/index.ts +1 -0
  292. package/src/utils/blocknote-diff.util.ts +815 -0
  293. package/src/utils/blocknote-word-diff-renderer.util.ts +413 -0
  294. package/src/utils/cn.ts +6 -0
  295. package/src/utils/compose-refs.ts +61 -0
  296. package/src/utils/date-formatter.ts +53 -0
  297. package/src/utils/exists.ts +7 -0
  298. package/src/utils/index.ts +15 -0
  299. package/src/utils/schemas/entity.object.schema.ts +8 -0
  300. package/src/utils/schemas/index.ts +2 -0
  301. package/src/utils/schemas/user.object.schema.ts +9 -0
  302. package/src/utils/table-options.ts +67 -0
  303. package/src/utils/use-mobile.tsx +21 -0
@@ -0,0 +1,324 @@
1
+ "use client";
2
+
3
+ import { Trash2 as RemoveIcon } from "lucide-react";
4
+ import { useTranslations } from "next-intl";
5
+ import {
6
+ createContext,
7
+ Dispatch,
8
+ forwardRef,
9
+ SetStateAction,
10
+ useCallback,
11
+ useContext,
12
+ useEffect,
13
+ useRef,
14
+ useState,
15
+ } from "react";
16
+ import { DropzoneOptions, DropzoneState, FileRejection, useDropzone } from "react-dropzone";
17
+ import { toast } from "sonner";
18
+ import { buttonVariants, Input } from "../../shadcnui";
19
+ import { cn } from "../../utils";
20
+
21
+ type DirectionOptions = "rtl" | "ltr" | undefined;
22
+
23
+ type FileUploaderContextType = {
24
+ dropzoneState: DropzoneState;
25
+ isLOF: boolean;
26
+ isFileTooBig: boolean;
27
+ removeFileFromSet: (index: number) => void;
28
+ activeIndex: number;
29
+ setActiveIndex: Dispatch<SetStateAction<number>>;
30
+ orientation: "horizontal" | "vertical";
31
+ direction: DirectionOptions;
32
+ };
33
+
34
+ const FileUploaderContext = createContext<FileUploaderContextType | null>(null);
35
+
36
+ export const useFileUpload = () => {
37
+ const context = useContext(FileUploaderContext);
38
+ if (!context) {
39
+ throw new Error("useFileUpload must be used within a FileUploaderProvider");
40
+ }
41
+ return context;
42
+ };
43
+
44
+ type FileUploaderProps = {
45
+ value: File[] | null;
46
+ reSelect?: boolean;
47
+ onValueChange: (value: File[] | null) => void;
48
+ dropzoneOptions: DropzoneOptions;
49
+ orientation?: "horizontal" | "vertical";
50
+ };
51
+
52
+ export const FileUploader = forwardRef<HTMLDivElement, FileUploaderProps & React.HTMLAttributes<HTMLDivElement>>(
53
+ (
54
+ { className, dropzoneOptions, value, onValueChange, reSelect, orientation = "vertical", children, dir, ...props },
55
+ ref,
56
+ ) => {
57
+ const [isFileTooBig, setIsFileTooBig] = useState(false);
58
+ const [isLOF, setIsLOF] = useState(false);
59
+ const [activeIndex, setActiveIndex] = useState(-1);
60
+ const { maxFiles = 1, maxSize = 4 * 1024 * 1024, multiple = true } = dropzoneOptions;
61
+ const t = useTranslations();
62
+
63
+ const reSelectAll = maxFiles === 1 ? true : reSelect;
64
+ const direction: DirectionOptions = dir === "rtl" ? "rtl" : "ltr";
65
+
66
+ const removeFileFromSet = useCallback(
67
+ (i: number) => {
68
+ if (!value) return;
69
+ const newFiles = value.filter((_, index) => index !== i);
70
+ onValueChange(newFiles);
71
+ },
72
+ [value, onValueChange],
73
+ );
74
+
75
+ const handleKeyDown = useCallback(
76
+ (e: React.KeyboardEvent<HTMLDivElement>) => {
77
+ e.preventDefault();
78
+ e.stopPropagation();
79
+
80
+ if (!value) return;
81
+
82
+ const moveNext = () => {
83
+ const nextIndex = activeIndex + 1;
84
+ setActiveIndex(nextIndex > value.length - 1 ? 0 : nextIndex);
85
+ };
86
+
87
+ const movePrev = () => {
88
+ const nextIndex = activeIndex - 1;
89
+ setActiveIndex(nextIndex < 0 ? value.length - 1 : nextIndex);
90
+ };
91
+
92
+ const prevKey = orientation === "horizontal" ? (direction === "ltr" ? "ArrowLeft" : "ArrowRight") : "ArrowUp";
93
+
94
+ const nextKey = orientation === "horizontal" ? (direction === "ltr" ? "ArrowRight" : "ArrowLeft") : "ArrowDown";
95
+
96
+ if (e.key === nextKey) {
97
+ moveNext();
98
+ } else if (e.key === prevKey) {
99
+ movePrev();
100
+ } else if (e.key === "Enter" || e.key === "Space") {
101
+ if (activeIndex === -1) {
102
+ dropzoneState.inputRef.current?.click();
103
+ }
104
+ } else if (e.key === "Delete" || e.key === "Backspace") {
105
+ if (activeIndex !== -1) {
106
+ removeFileFromSet(activeIndex);
107
+ if (value.length - 1 === 0) {
108
+ setActiveIndex(-1);
109
+ return;
110
+ }
111
+ movePrev();
112
+ }
113
+ } else if (e.key === "Escape") {
114
+ setActiveIndex(-1);
115
+ }
116
+ },
117
+ [value, activeIndex, removeFileFromSet],
118
+ );
119
+
120
+ const onDrop = useCallback(
121
+ (acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
122
+ const files = acceptedFiles;
123
+
124
+ if (!files) {
125
+ toast.error(t("generic.errors.file"), {
126
+ description: t("generic.errors.file_large"),
127
+ });
128
+ return;
129
+ }
130
+
131
+ const newValues: File[] = value ? [...value] : [];
132
+
133
+ if (reSelectAll) {
134
+ newValues.splice(0, newValues.length);
135
+ }
136
+
137
+ files.forEach((file) => {
138
+ if (newValues.length < maxFiles) {
139
+ newValues.push(file);
140
+ }
141
+ });
142
+
143
+ onValueChange(newValues);
144
+
145
+ if (rejectedFiles.length > 0) {
146
+ for (let i = 0; i < rejectedFiles.length; i++) {
147
+ if (rejectedFiles[i].errors[0]?.code === "file-too-large") {
148
+ toast.error(t("generic.errors.file"), {
149
+ description: t(`generic.errors.file_max`, { size: maxSize / 1024 / 1024 }),
150
+ });
151
+ break;
152
+ }
153
+ if (rejectedFiles[i].errors[0]?.message) {
154
+ toast.error(t(`generic.errors.file`), {
155
+ description: rejectedFiles[i].errors[0].message,
156
+ });
157
+ break;
158
+ }
159
+ }
160
+ }
161
+ },
162
+ [reSelectAll, value],
163
+ );
164
+
165
+ useEffect(() => {
166
+ if (!value) return;
167
+ if (value.length === maxFiles) {
168
+ // setIsLOF(true);
169
+ return;
170
+ }
171
+ setIsLOF(false);
172
+ }, [value, maxFiles]);
173
+
174
+ const opts = dropzoneOptions ? dropzoneOptions : { maxFiles, maxSize, multiple };
175
+
176
+ const dropzoneState = useDropzone({
177
+ ...opts,
178
+ onDrop,
179
+ onDropRejected: () => setIsFileTooBig(true),
180
+ onDropAccepted: () => setIsFileTooBig(false),
181
+ });
182
+
183
+ const { isDragActive } = dropzoneState; // Correctly get isDragActive
184
+
185
+ return (
186
+ <FileUploaderContext.Provider
187
+ value={{
188
+ dropzoneState,
189
+ isLOF,
190
+ isFileTooBig,
191
+ removeFileFromSet,
192
+ activeIndex,
193
+ setActiveIndex,
194
+ orientation,
195
+ direction,
196
+ }}
197
+ >
198
+ <div
199
+ ref={ref}
200
+ tabIndex={0}
201
+ onKeyDownCapture={handleKeyDown}
202
+ className={cn(
203
+ "grid w-full overflow-hidden focus:outline-none",
204
+ className, // Original className from props
205
+ {
206
+ "gap-2": value && value.length > 0,
207
+ "bg-muted border-primary border-dashed": isDragActive, // Apply drag-active styles to the main FileUploader div
208
+ },
209
+ )}
210
+ dir={dir}
211
+ {...props}
212
+ >
213
+ {children}
214
+ </div>
215
+ </FileUploaderContext.Provider>
216
+ );
217
+ },
218
+ );
219
+
220
+ FileUploader.displayName = "FileUploader";
221
+
222
+ export const FileUploaderContent = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
223
+ ({ children, className, ...props }, ref) => {
224
+ const { orientation } = useFileUpload();
225
+ const containerRef = useRef<HTMLDivElement>(null);
226
+
227
+ return (
228
+ <div className={cn("w-full px-1")} ref={containerRef} aria-description="content file holder">
229
+ <div
230
+ {...props}
231
+ ref={ref}
232
+ className={cn(
233
+ "flex gap-1 rounded-xl",
234
+ orientation === "horizontal" ? "flex-raw flex-wrap" : "flex-col",
235
+ className,
236
+ )}
237
+ >
238
+ {children}
239
+ </div>
240
+ </div>
241
+ );
242
+ },
243
+ );
244
+
245
+ FileUploaderContent.displayName = "FileUploaderContent";
246
+
247
+ export const FileUploaderItem = forwardRef<HTMLDivElement, { index: number } & React.HTMLAttributes<HTMLDivElement>>(
248
+ ({ className, index, children, ...props }, ref) => {
249
+ const { removeFileFromSet, activeIndex, direction } = useFileUpload();
250
+ const isSelected = index === activeIndex;
251
+ const t = useTranslations();
252
+
253
+ return (
254
+ <div
255
+ ref={ref}
256
+ className={cn(
257
+ buttonVariants({ variant: "ghost" }),
258
+ "relative h-6 cursor-pointer justify-between p-1",
259
+ className,
260
+ isSelected ? "bg-muted" : "",
261
+ )}
262
+ {...props}
263
+ >
264
+ <div className="flex h-full w-full items-center gap-1.5 leading-none font-medium tracking-tight">
265
+ {children}
266
+ </div>
267
+ <button
268
+ type="button"
269
+ className={cn("absolute", direction === "rtl" ? "top-1 left-1" : "top-1 right-1")}
270
+ onClick={() => removeFileFromSet(index)}
271
+ >
272
+ <span className="sr-only">{t(`generic.remove_item`, { index: index })}</span>
273
+ <RemoveIcon className="hover:stroke-destructive h-4 w-4 duration-200 ease-in-out" />
274
+ </button>
275
+ </div>
276
+ );
277
+ },
278
+ );
279
+
280
+ FileUploaderItem.displayName = "FileUploaderItem";
281
+
282
+ export const FileInput = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
283
+ ({ className, children, ...props }, ref) => {
284
+ const { dropzoneState, isFileTooBig, isLOF } = useFileUpload();
285
+ const rootProps = isLOF ? {} : dropzoneState.getRootProps();
286
+ // Get isDragActive from the context for FileInput as well, to ensure it can react if needed, or to simplify its own styling.
287
+ const { isDragActive: parentIsDragActive } = dropzoneState;
288
+
289
+ return (
290
+ <div
291
+ ref={ref}
292
+ {...props}
293
+ className={cn(`relative w-full ${isLOF ? "cursor-not-allowed opacity-50" : "cursor-pointer"}`, className)}
294
+ >
295
+ <div
296
+ className={cn(
297
+ "w-full rounded-lg duration-300 ease-in-out",
298
+ // Simpler border logic: if parent is drag-active, it controls the border.
299
+ // Otherwise, FileInput can show its own accept/reject/default border.
300
+ {
301
+ "border-green-500": dropzoneState.isDragAccept && !parentIsDragActive,
302
+ "border-red-500": (dropzoneState.isDragReject || isFileTooBig) && !parentIsDragActive,
303
+ "border-gray-300":
304
+ !dropzoneState.isDragAccept && !dropzoneState.isDragReject && !isFileTooBig && !parentIsDragActive,
305
+ },
306
+ // className from props should be last to allow overrides if necessary
307
+ className,
308
+ )}
309
+ {...rootProps}
310
+ >
311
+ {children}
312
+ </div>
313
+ <Input
314
+ ref={dropzoneState.inputRef}
315
+ disabled={isLOF}
316
+ {...dropzoneState.getInputProps()}
317
+ className={`${isLOF ? "cursor-not-allowed" : ""}`}
318
+ />
319
+ </div>
320
+ );
321
+ },
322
+ );
323
+
324
+ FileInput.displayName = "FileInput";
@@ -0,0 +1,66 @@
1
+ "use client";
2
+
3
+ import {
4
+ Checkbox,
5
+ FormControl,
6
+ FormField,
7
+ FormItem,
8
+ FormLabel,
9
+ FormMessage,
10
+ Tooltip,
11
+ TooltipContent,
12
+ TooltipTrigger,
13
+ } from "../../shadcnui";
14
+
15
+ type FormCheckboxProps = {
16
+ form: any;
17
+ id: string;
18
+ name: string;
19
+ labelBefore?: boolean;
20
+ description?: string;
21
+ isRequired?: boolean;
22
+ };
23
+
24
+ export function FormCheckbox({ form, id, name, labelBefore, description, isRequired }: FormCheckboxProps) {
25
+ const simpleLabel = () => {
26
+ return (
27
+ <FormLabel htmlFor={id} className={`font-normal ${labelBefore ? "" : "ml-3"}`}>
28
+ {name}
29
+ </FormLabel>
30
+ );
31
+ };
32
+
33
+ const label = () => {
34
+ if (description) return simpleLabel();
35
+ else
36
+ return (
37
+ <Tooltip>
38
+ <TooltipTrigger asChild>{simpleLabel()}</TooltipTrigger>
39
+ <TooltipContent>{description}</TooltipContent>
40
+ </Tooltip>
41
+ );
42
+ };
43
+
44
+ return (
45
+ <div className="flex w-full flex-col">
46
+ <FormField
47
+ control={form.control}
48
+ name={id}
49
+ render={({ field }) => (
50
+ <FormItem className={`${name ? "mb-5" : "mb-1"}`}>
51
+ <FormControl>
52
+ <div className="flex gap-x-4">
53
+ {labelBefore && label()}
54
+ {labelBefore && isRequired && <span className="text-destructive ml-2 font-semibold">*</span>}
55
+ <Checkbox id={id} defaultChecked={field.value} onCheckedChange={field.onChange} />
56
+ {!labelBefore && label()}
57
+ {!labelBefore && isRequired && <span className="text-destructive ml-2 font-semibold">*</span>}
58
+ </div>
59
+ </FormControl>
60
+ <FormMessage />
61
+ </FormItem>
62
+ )}
63
+ />
64
+ </div>
65
+ );
66
+ }
@@ -0,0 +1,39 @@
1
+ "use client";
2
+
3
+ import { ReactElement } from "react";
4
+ import { FormControl, FormField, FormItem, FormLabel, FormMessage } from "../../shadcnui";
5
+
6
+ export function FormContainerGeneric({
7
+ form,
8
+ id,
9
+ name,
10
+ children,
11
+ isRequired = false,
12
+ }: {
13
+ form: any;
14
+ id: string;
15
+ name?: string;
16
+ children: ReactElement<any>;
17
+ isRequired?: boolean;
18
+ }) {
19
+ return (
20
+ <div className="flex w-full flex-col">
21
+ <FormField
22
+ control={form.control}
23
+ name={id}
24
+ render={({ field }) => (
25
+ <FormItem className={`${name ? "mb-5" : "mb-1"}`}>
26
+ {name && (
27
+ <FormLabel className="flex items-center">
28
+ <span>{name}</span>
29
+ {isRequired && <span className="text-destructive ml-2 font-semibold">*</span>}
30
+ </FormLabel>
31
+ )}
32
+ <FormControl>{children}</FormControl>
33
+ <FormMessage />
34
+ </FormItem>
35
+ )}
36
+ />
37
+ </div>
38
+ );
39
+ }
@@ -0,0 +1,247 @@
1
+ "use client";
2
+
3
+ import { isValid, parse } from "date-fns";
4
+ import { Calendar as CalendarIcon, CircleXIcon } from "lucide-react";
5
+ import { useMemo, useState } from "react";
6
+ import { useI18nDateFnsLocale, useI18nLocale } from "../../i18n";
7
+ import {
8
+ Calendar,
9
+ FormControl,
10
+ FormField,
11
+ FormItem,
12
+ FormLabel,
13
+ FormMessage,
14
+ Input,
15
+ Popover,
16
+ PopoverContent,
17
+ PopoverTrigger,
18
+ Select,
19
+ SelectContent,
20
+ SelectItem,
21
+ SelectTrigger,
22
+ SelectValue,
23
+ } from "../../shadcnui";
24
+
25
+ export function FormDate({
26
+ form,
27
+ id,
28
+ name,
29
+ minDate,
30
+ onChange,
31
+ isRequired = false,
32
+ }: {
33
+ form: any;
34
+ id: string;
35
+ name?: string;
36
+ placeholder?: string;
37
+ minDate?: Date;
38
+ onChange?: (date?: Date) => Promise<void>;
39
+ isRequired?: boolean;
40
+ }) {
41
+ const locale = useI18nLocale();
42
+ const dateFnsLocale = useI18nDateFnsLocale();
43
+ const [open, setOpen] = useState<boolean>(false);
44
+ const [displayMonth, setDisplayMonth] = useState<Date>(() => {
45
+ const currentValue = form.getValues(id);
46
+ return currentValue || new Date();
47
+ });
48
+
49
+ // Locale-aware date formatter
50
+ const dateFormatter = useMemo(
51
+ () => new Intl.DateTimeFormat(locale, { day: "2-digit", month: "2-digit", year: "numeric" }),
52
+ [locale],
53
+ );
54
+
55
+ // Format date for display
56
+ const formatDate = (date: Date): string => dateFormatter.format(date);
57
+
58
+ // Get placeholder based on locale format
59
+ const datePlaceholder = useMemo(() => {
60
+ const parts = dateFormatter.formatToParts(new Date(2000, 0, 1));
61
+ return parts
62
+ .map((part) => {
63
+ if (part.type === "day") return "dd";
64
+ if (part.type === "month") return "mm";
65
+ if (part.type === "year") return "yyyy";
66
+ return part.value;
67
+ })
68
+ .join("");
69
+ }, [dateFormatter]);
70
+
71
+ // Get date-fns format string from locale
72
+ const dateFormatPattern = useMemo(() => {
73
+ const parts = dateFormatter.formatToParts(new Date(2000, 0, 1));
74
+ return parts
75
+ .map((part) => {
76
+ if (part.type === "day") return "dd";
77
+ if (part.type === "month") return "MM";
78
+ if (part.type === "year") return "yyyy";
79
+ return part.value;
80
+ })
81
+ .join("");
82
+ }, [dateFormatter]);
83
+
84
+ const [inputValue, setInputValue] = useState<string>(() => {
85
+ const currentValue = form.getValues(id);
86
+ return currentValue ? formatDate(currentValue) : "";
87
+ });
88
+
89
+ // Generate year options (1900 to current year)
90
+ const currentYear = new Date().getFullYear();
91
+ const yearOptions = Array.from({ length: currentYear - 1900 + 1 }, (_, i) => 1900 + i);
92
+
93
+ // Generate month names dynamically based on current locale
94
+ const monthNames = useMemo(() => {
95
+ const formatter = new Intl.DateTimeFormat(locale, { month: "long" });
96
+ return Array.from({ length: 12 }, (_, i) => {
97
+ const monthName = formatter.format(new Date(2000, i, 1));
98
+ return monthName.charAt(0).toUpperCase() + monthName.slice(1);
99
+ });
100
+ }, [locale]);
101
+
102
+ // Handle text input change
103
+ const handleInputChange = (value: string, field: any) => {
104
+ setInputValue(value);
105
+
106
+ // Try to parse the date using locale format
107
+ const parsedDate = parse(value, dateFormatPattern, new Date());
108
+
109
+ if (isValid(parsedDate)) {
110
+ field.onChange(parsedDate);
111
+ setDisplayMonth(parsedDate);
112
+ if (onChange) onChange(parsedDate);
113
+ } else if (value === "") {
114
+ field.onChange(undefined);
115
+ if (onChange) onChange(undefined);
116
+ }
117
+ };
118
+
119
+ // Handle calendar selection
120
+ const handleCalendarSelect = (selectedDate: Date | undefined, field: any) => {
121
+ field.onChange(selectedDate);
122
+ if (selectedDate) {
123
+ setInputValue(formatDate(selectedDate));
124
+ setDisplayMonth(selectedDate);
125
+ } else {
126
+ setInputValue("");
127
+ }
128
+ if (onChange) onChange(selectedDate);
129
+ };
130
+
131
+ return (
132
+ <div className="flex w-full flex-col">
133
+ <FormField
134
+ control={form.control}
135
+ name={id}
136
+ render={({ field }) => (
137
+ <FormItem className={`${name ? "mb-5" : "mb-1"} w-full`}>
138
+ {name && (
139
+ <FormLabel className="dlex items-center">
140
+ {name} {isRequired && <span className="text-destructive ml-2 font-semibold">*</span>}
141
+ </FormLabel>
142
+ )}
143
+ <FormControl>
144
+ <div className="relative">
145
+ <Popover open={open} onOpenChange={setOpen} modal={true}>
146
+ <div className="relative">
147
+ <Input
148
+ value={inputValue}
149
+ onChange={(e) => handleInputChange(e.target.value, field)}
150
+ placeholder={datePlaceholder}
151
+ className="pr-16"
152
+ />
153
+ <div className="absolute right-1 top-1/2 flex -translate-y-1/2 items-center space-x-1">
154
+ <PopoverTrigger asChild>
155
+ <button
156
+ type="button"
157
+ className="hover:bg-muted flex h-8 w-8 items-center justify-center rounded-md"
158
+ >
159
+ <CalendarIcon className="h-4 w-4 opacity-50" />
160
+ </button>
161
+ </PopoverTrigger>
162
+ {field.value && (
163
+ <button
164
+ type="button"
165
+ className="hover:bg-muted flex h-8 w-8 items-center justify-center rounded-md"
166
+ onClick={() => {
167
+ field.onChange(undefined);
168
+ setInputValue("");
169
+ if (onChange) onChange(undefined);
170
+ }}
171
+ >
172
+ <CircleXIcon className="h-4 w-4 opacity-50 hover:opacity-100" />
173
+ </button>
174
+ )}
175
+ </div>
176
+ </div>
177
+ <PopoverContent className="w-auto p-0" align="start">
178
+ <div className="p-3">
179
+ {/* Year and Month Selectors */}
180
+ <div className="mb-3 flex gap-2">
181
+ <Select
182
+ value={displayMonth.getMonth().toString()}
183
+ onValueChange={(value) => {
184
+ const newMonth = parseInt(value);
185
+ const newDate = new Date(displayMonth.getFullYear(), newMonth, 1);
186
+ setDisplayMonth(newDate);
187
+ }}
188
+ >
189
+ <SelectTrigger className="w-[130px]">
190
+ <SelectValue />
191
+ </SelectTrigger>
192
+ <SelectContent>
193
+ {monthNames.map((month, index) => (
194
+ <SelectItem key={index} value={index.toString()}>
195
+ {month}
196
+ </SelectItem>
197
+ ))}
198
+ </SelectContent>
199
+ </Select>
200
+
201
+ <Select
202
+ value={displayMonth.getFullYear().toString()}
203
+ onValueChange={(value) => {
204
+ const newYear = parseInt(value);
205
+ const newDate = new Date(newYear, displayMonth.getMonth(), 1);
206
+ setDisplayMonth(newDate);
207
+ }}
208
+ >
209
+ <SelectTrigger className="w-[80px]">
210
+ <SelectValue />
211
+ </SelectTrigger>
212
+ <SelectContent>
213
+ {yearOptions.reverse().map((year) => (
214
+ <SelectItem key={year} value={year.toString()}>
215
+ {year}
216
+ </SelectItem>
217
+ ))}
218
+ </SelectContent>
219
+ </Select>
220
+ </div>
221
+
222
+ {/* Calendar */}
223
+ <Calendar
224
+ mode="single"
225
+ selected={field.value}
226
+ onSelect={(e) => {
227
+ handleCalendarSelect(e, field);
228
+ setOpen(false);
229
+ }}
230
+ disabled={(date) => (minDate && date < minDate ? true : false)}
231
+ locale={dateFnsLocale}
232
+ weekStartsOn={1}
233
+ month={displayMonth}
234
+ onMonthChange={setDisplayMonth}
235
+ />
236
+ </div>
237
+ </PopoverContent>
238
+ </Popover>
239
+ </div>
240
+ </FormControl>
241
+ <FormMessage />
242
+ </FormItem>
243
+ )}
244
+ />
245
+ </div>
246
+ );
247
+ }