nexstruct 1.0.0

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 (229) hide show
  1. package/AGENTS.md +122 -0
  2. package/LICENSE +21 -0
  3. package/README.md +103 -0
  4. package/package.json +99 -0
  5. package/scaffold/generator.js +409 -0
  6. package/scaffold/index.js +20 -0
  7. package/scaffold/prompts.js +108 -0
  8. package/templates/api/axios/src/api/axios/client.api.ts +30 -0
  9. package/templates/api/axios/src/api/axios/users.api.ts +15 -0
  10. package/templates/api/fetch/src/api/fetch/client.api.ts +68 -0
  11. package/templates/api/fetch/src/api/fetch/users.api.ts +15 -0
  12. package/templates/api/trpc/src/api/trpc/client.api.ts +4 -0
  13. package/templates/api/trpc/src/api/trpc/router.api.ts +15 -0
  14. package/templates/api/trpc/src/api/trpc/server.client.api.ts +4 -0
  15. package/templates/api/trpc/src/providers/trpc.provider.tsx +24 -0
  16. package/templates/auth/clerk/src/auth/clerk/auth.service.ts +4 -0
  17. package/templates/auth/clerk/src/hooks/use-auth.hook.ts +13 -0
  18. package/templates/auth/clerk/src/middleware.ts +7 -0
  19. package/templates/auth/clerk/src/providers/auth.provider.tsx +6 -0
  20. package/templates/auth/next-auth/src/app/api/auth/[...nextauth]/route.ts +5 -0
  21. package/templates/auth/next-auth/src/auth/next-auth/auth.service.ts +45 -0
  22. package/templates/auth/next-auth/src/hooks/use-session.hook.ts +13 -0
  23. package/templates/auth/next-auth/src/providers/session.provider.tsx +6 -0
  24. package/templates/forms/formik/src/components/forms/login-form.component.tsx +30 -0
  25. package/templates/forms/formik/src/forms/formik/hooks/use-form-config.hook.ts +7 -0
  26. package/templates/forms/formik/src/forms/formik/schemas/example.schema.ts +8 -0
  27. package/templates/forms/react-hook-form/src/components/forms/login-form.component.tsx +27 -0
  28. package/templates/forms/react-hook-form/src/forms/react-hook-form/hooks/use-form.hook.ts +13 -0
  29. package/templates/forms/react-hook-form/src/forms/react-hook-form/schemas/example.schema.ts +15 -0
  30. package/templates/nextjs-base/next.config.ts +5 -0
  31. package/templates/nextjs-base/postcss.config.mjs +9 -0
  32. package/templates/nextjs-base/src/app/_components/navbar.tsx +88 -0
  33. package/templates/nextjs-base/src/app/_components/sidebar.tsx +223 -0
  34. package/templates/nextjs-base/src/app/error.tsx +39 -0
  35. package/templates/nextjs-base/src/app/globals.css +71 -0
  36. package/templates/nextjs-base/src/app/layout.tsx +21 -0
  37. package/templates/nextjs-base/src/app/loading.tsx +13 -0
  38. package/templates/nextjs-base/src/app/not-found.tsx +22 -0
  39. package/templates/nextjs-base/src/app/page.tsx +10 -0
  40. package/templates/nextjs-base/tailwind.config.ts +69 -0
  41. package/templates/shared/src/components/common/theme-toggle.component.tsx +31 -0
  42. package/templates/shared/src/components/common/toast/custom-message.component.tsx +18 -0
  43. package/templates/shared/src/components/common/toast/index.ts +8 -0
  44. package/templates/shared/src/components/common/toast/toast-message.component.tsx +112 -0
  45. package/templates/shared/src/hooks/use-debounce.hook.ts +12 -0
  46. package/templates/shared/src/hooks/use-fetch.hook.ts +42 -0
  47. package/templates/shared/src/hooks/use-intersection-observer.hook.ts +39 -0
  48. package/templates/shared/src/hooks/use-local-storage.hook.ts +30 -0
  49. package/templates/shared/src/hooks/use-media-query.hook.ts +26 -0
  50. package/templates/shared/src/hooks/use-toggle.hook.ts +12 -0
  51. package/templates/shared/src/lib/utils.util.ts +361 -0
  52. package/templates/shared/src/providers/theme.provider.tsx +17 -0
  53. package/templates/shared/src/providers/toast.provider.tsx +32 -0
  54. package/templates/shared/src/types/common.type.ts +34 -0
  55. package/templates/state/context/src/store/context/auth.context.tsx +47 -0
  56. package/templates/state/context/src/store/context/counter.context.tsx +41 -0
  57. package/templates/state/context/src/store/context/index.ts +2 -0
  58. package/templates/state/redux/src/providers/redux.provider.tsx +7 -0
  59. package/templates/state/redux/src/store/redux/hooks.store.ts +5 -0
  60. package/templates/state/redux/src/store/redux/index.ts +4 -0
  61. package/templates/state/redux/src/store/redux/slices/api.slice.ts +8 -0
  62. package/templates/state/redux/src/store/redux/slices/counter.slice.ts +24 -0
  63. package/templates/state/redux/src/store/redux/store.store.ts +13 -0
  64. package/templates/state/zustand/src/store/zustand/counter.store.ts +15 -0
  65. package/templates/state/zustand/src/store/zustand/index.ts +2 -0
  66. package/templates/state/zustand/src/store/zustand/user.store.ts +32 -0
  67. package/templates/ui/antd/COMPONENT_GUIDE.md +326 -0
  68. package/templates/ui/antd/src/app/examples/dialog/page.tsx +205 -0
  69. package/templates/ui/antd/src/app/examples/form/page.tsx +160 -0
  70. package/templates/ui/antd/src/app/examples/layout.tsx +125 -0
  71. package/templates/ui/antd/src/app/examples/page.tsx +64 -0
  72. package/templates/ui/antd/src/app/examples/table/page.tsx +118 -0
  73. package/templates/ui/antd/src/app/page.tsx +283 -0
  74. package/templates/ui/antd/src/components/common/DynamicTable/dynamic-table.component.tsx +79 -0
  75. package/templates/ui/antd/src/components/common/button/action-button.component.tsx +63 -0
  76. package/templates/ui/antd/src/components/common/dialog/dialog-wrapper.component.tsx +63 -0
  77. package/templates/ui/antd/src/components/common/fields/assets/components/check-field.component.tsx +55 -0
  78. package/templates/ui/antd/src/components/common/fields/assets/components/date-picker-field.component.tsx +80 -0
  79. package/templates/ui/antd/src/components/common/fields/assets/components/limit-field.component.tsx +26 -0
  80. package/templates/ui/antd/src/components/common/fields/assets/components/multi-check-field.component.tsx +56 -0
  81. package/templates/ui/antd/src/components/common/fields/assets/components/number-field.component.tsx +100 -0
  82. package/templates/ui/antd/src/components/common/fields/assets/components/otp-field.component.tsx +63 -0
  83. package/templates/ui/antd/src/components/common/fields/assets/components/password-field.component.tsx +106 -0
  84. package/templates/ui/antd/src/components/common/fields/assets/components/phone-number-field.component.tsx +78 -0
  85. package/templates/ui/antd/src/components/common/fields/assets/components/radio-field.component.tsx +55 -0
  86. package/templates/ui/antd/src/components/common/fields/assets/components/range-date-picker.component.tsx +66 -0
  87. package/templates/ui/antd/src/components/common/fields/assets/components/search-field.component.tsx +24 -0
  88. package/templates/ui/antd/src/components/common/fields/assets/components/select-field.component.tsx +82 -0
  89. package/templates/ui/antd/src/components/common/fields/assets/components/single-check-field.component.tsx +50 -0
  90. package/templates/ui/antd/src/components/common/fields/assets/components/single-select-field.component.tsx +86 -0
  91. package/templates/ui/antd/src/components/common/fields/assets/components/string-number-field.component.tsx +80 -0
  92. package/templates/ui/antd/src/components/common/fields/assets/components/switch-field.component.tsx +62 -0
  93. package/templates/ui/antd/src/components/common/fields/assets/components/text-area-field.component.tsx +85 -0
  94. package/templates/ui/antd/src/components/common/fields/assets/components/text-field.component.tsx +88 -0
  95. package/templates/ui/antd/src/components/common/fields/assets/interface/input-props.type.ts +233 -0
  96. package/templates/ui/antd/src/components/common/fields/cusInputField.component.tsx +40 -0
  97. package/templates/ui/antd/src/components/common/pagination/pagination.component.tsx +27 -0
  98. package/templates/ui/antd/src/components/ui/avatar.component.tsx +8 -0
  99. package/templates/ui/antd/src/components/ui/badge.component.tsx +8 -0
  100. package/templates/ui/antd/src/components/ui/button.component.tsx +8 -0
  101. package/templates/ui/antd/src/components/ui/card.component.tsx +8 -0
  102. package/templates/ui/antd/src/components/ui/checkbox.component.tsx +8 -0
  103. package/templates/ui/antd/src/components/ui/dialog.component.tsx +9 -0
  104. package/templates/ui/antd/src/components/ui/dropdown-menu.component.tsx +10 -0
  105. package/templates/ui/antd/src/components/ui/form.component.tsx +12 -0
  106. package/templates/ui/antd/src/components/ui/input.component.tsx +13 -0
  107. package/templates/ui/antd/src/components/ui/label.component.tsx +18 -0
  108. package/templates/ui/antd/src/components/ui/popover.component.tsx +8 -0
  109. package/templates/ui/antd/src/components/ui/progress.component.tsx +8 -0
  110. package/templates/ui/antd/src/components/ui/radio-group.component.tsx +10 -0
  111. package/templates/ui/antd/src/components/ui/scroll-area.component.tsx +25 -0
  112. package/templates/ui/antd/src/components/ui/select.component.tsx +8 -0
  113. package/templates/ui/antd/src/components/ui/separator.component.tsx +8 -0
  114. package/templates/ui/antd/src/components/ui/sheet.component.tsx +8 -0
  115. package/templates/ui/antd/src/components/ui/switch.component.tsx +8 -0
  116. package/templates/ui/antd/src/components/ui/table.component.tsx +8 -0
  117. package/templates/ui/antd/src/components/ui/tabs.component.tsx +8 -0
  118. package/templates/ui/antd/src/components/ui/textarea.component.tsx +9 -0
  119. package/templates/ui/antd/src/components/ui/tooltip.component.tsx +8 -0
  120. package/templates/ui/antd/src/lib/theme.util.ts +40 -0
  121. package/templates/ui/antd/src/providers/antd.provider.tsx +13 -0
  122. package/templates/ui/mui/src/app/examples/layout.tsx +113 -0
  123. package/templates/ui/mui/src/app/examples/page.tsx +716 -0
  124. package/templates/ui/mui/src/app/page.tsx +298 -0
  125. package/templates/ui/mui/src/components/common/DynamicTable/dynamic-table.component.tsx +131 -0
  126. package/templates/ui/mui/src/components/common/button/action-button.component.tsx +57 -0
  127. package/templates/ui/mui/src/components/common/dialog/dialog-wrapper.component.tsx +55 -0
  128. package/templates/ui/mui/src/components/common/fields/assets/components/check-field.component.tsx +51 -0
  129. package/templates/ui/mui/src/components/common/fields/assets/components/date-picker-field.component.tsx +50 -0
  130. package/templates/ui/mui/src/components/common/fields/assets/components/multi-check-field.component.tsx +14 -0
  131. package/templates/ui/mui/src/components/common/fields/assets/components/number-field.component.tsx +59 -0
  132. package/templates/ui/mui/src/components/common/fields/assets/components/password-field.component.tsx +87 -0
  133. package/templates/ui/mui/src/components/common/fields/assets/components/phone-number-field.component.tsx +48 -0
  134. package/templates/ui/mui/src/components/common/fields/assets/components/radio-field.component.tsx +37 -0
  135. package/templates/ui/mui/src/components/common/fields/assets/components/search-field.component.tsx +41 -0
  136. package/templates/ui/mui/src/components/common/fields/assets/components/select-field.component.tsx +77 -0
  137. package/templates/ui/mui/src/components/common/fields/assets/components/single-check-field.component.tsx +39 -0
  138. package/templates/ui/mui/src/components/common/fields/assets/components/single-select-field.component.tsx +56 -0
  139. package/templates/ui/mui/src/components/common/fields/assets/components/string-number-field.component.tsx +52 -0
  140. package/templates/ui/mui/src/components/common/fields/assets/components/switch-field.component.tsx +35 -0
  141. package/templates/ui/mui/src/components/common/fields/assets/components/text-area-field.component.tsx +46 -0
  142. package/templates/ui/mui/src/components/common/fields/assets/components/text-field.component.tsx +51 -0
  143. package/templates/ui/mui/src/components/common/fields/assets/interface/input-props.type.ts +193 -0
  144. package/templates/ui/mui/src/components/common/fields/cusInputField.component.tsx +34 -0
  145. package/templates/ui/mui/src/components/common/pagination/pagination.component.tsx +59 -0
  146. package/templates/ui/mui/src/components/ui/avatar.component.tsx +19 -0
  147. package/templates/ui/mui/src/components/ui/badge.component.tsx +18 -0
  148. package/templates/ui/mui/src/components/ui/button.component.tsx +22 -0
  149. package/templates/ui/mui/src/components/ui/card.component.tsx +39 -0
  150. package/templates/ui/mui/src/components/ui/checkbox.component.tsx +21 -0
  151. package/templates/ui/mui/src/components/ui/dialog.component.tsx +38 -0
  152. package/templates/ui/mui/src/components/ui/dropdown-menu.component.tsx +43 -0
  153. package/templates/ui/mui/src/components/ui/form.component.tsx +98 -0
  154. package/templates/ui/mui/src/components/ui/input.component.tsx +15 -0
  155. package/templates/ui/mui/src/components/ui/label.component.tsx +15 -0
  156. package/templates/ui/mui/src/components/ui/popover.component.tsx +20 -0
  157. package/templates/ui/mui/src/components/ui/progress.component.tsx +19 -0
  158. package/templates/ui/mui/src/components/ui/radio-group.component.tsx +25 -0
  159. package/templates/ui/mui/src/components/ui/scroll-area.component.tsx +27 -0
  160. package/templates/ui/mui/src/components/ui/select.component.tsx +26 -0
  161. package/templates/ui/mui/src/components/ui/separator.component.tsx +11 -0
  162. package/templates/ui/mui/src/components/ui/sheet.component.tsx +44 -0
  163. package/templates/ui/mui/src/components/ui/switch.component.tsx +23 -0
  164. package/templates/ui/mui/src/components/ui/table.component.tsx +34 -0
  165. package/templates/ui/mui/src/components/ui/tabs.component.tsx +38 -0
  166. package/templates/ui/mui/src/components/ui/textarea.component.tsx +18 -0
  167. package/templates/ui/mui/src/components/ui/tooltip.component.tsx +24 -0
  168. package/templates/ui/mui/src/lib/theme.util.ts +73 -0
  169. package/templates/ui/mui/src/providers/mui.provider.tsx +13 -0
  170. package/templates/ui/shadcn/COMPONENT_GUIDE.md +306 -0
  171. package/templates/ui/shadcn/src/app/examples/dialog/page.tsx +122 -0
  172. package/templates/ui/shadcn/src/app/examples/form/page.tsx +107 -0
  173. package/templates/ui/shadcn/src/app/examples/layout.tsx +24 -0
  174. package/templates/ui/shadcn/src/app/examples/page.tsx +30 -0
  175. package/templates/ui/shadcn/src/app/examples/table/page.tsx +77 -0
  176. package/templates/ui/shadcn/src/app/page.tsx +20 -0
  177. package/templates/ui/shadcn/src/components/common/DynamicTable/dynamic-table.component.tsx +136 -0
  178. package/templates/ui/shadcn/src/components/common/button/action-button.component.tsx +68 -0
  179. package/templates/ui/shadcn/src/components/common/dialog/dialog-wrapper.component.tsx +58 -0
  180. package/templates/ui/shadcn/src/components/common/fields/assets/components/check-field.component.tsx +52 -0
  181. package/templates/ui/shadcn/src/components/common/fields/assets/components/date-picker-field.component.tsx +62 -0
  182. package/templates/ui/shadcn/src/components/common/fields/assets/components/dynamic-file-upload-field.component.tsx +152 -0
  183. package/templates/ui/shadcn/src/components/common/fields/assets/components/limit-field.component.tsx +73 -0
  184. package/templates/ui/shadcn/src/components/common/fields/assets/components/multi-check-field.component.tsx +46 -0
  185. package/templates/ui/shadcn/src/components/common/fields/assets/components/number-field.component.tsx +124 -0
  186. package/templates/ui/shadcn/src/components/common/fields/assets/components/otp-field.component.tsx +61 -0
  187. package/templates/ui/shadcn/src/components/common/fields/assets/components/password-field.component.tsx +110 -0
  188. package/templates/ui/shadcn/src/components/common/fields/assets/components/phone-number-field.component.tsx +90 -0
  189. package/templates/ui/shadcn/src/components/common/fields/assets/components/radio-field.component.tsx +41 -0
  190. package/templates/ui/shadcn/src/components/common/fields/assets/components/range-date-picker.component.tsx +71 -0
  191. package/templates/ui/shadcn/src/components/common/fields/assets/components/rich-text-editor.component.tsx +91 -0
  192. package/templates/ui/shadcn/src/components/common/fields/assets/components/search-field.component.tsx +34 -0
  193. package/templates/ui/shadcn/src/components/common/fields/assets/components/select-field.component.tsx +231 -0
  194. package/templates/ui/shadcn/src/components/common/fields/assets/components/single-check-field.component.tsx +42 -0
  195. package/templates/ui/shadcn/src/components/common/fields/assets/components/single-select-field.component.tsx +82 -0
  196. package/templates/ui/shadcn/src/components/common/fields/assets/components/string-number-field.component.tsx +68 -0
  197. package/templates/ui/shadcn/src/components/common/fields/assets/components/switch-field.component.tsx +61 -0
  198. package/templates/ui/shadcn/src/components/common/fields/assets/components/text-area-field.component.tsx +62 -0
  199. package/templates/ui/shadcn/src/components/common/fields/assets/components/text-area-with-file.component.tsx +142 -0
  200. package/templates/ui/shadcn/src/components/common/fields/assets/components/text-field.component.tsx +80 -0
  201. package/templates/ui/shadcn/src/components/common/fields/assets/components/tiny-editor.component.tsx +51 -0
  202. package/templates/ui/shadcn/src/components/common/fields/assets/components/upload-profile-picture.component.tsx +103 -0
  203. package/templates/ui/shadcn/src/components/common/fields/assets/components/upload-video-file.component.tsx +86 -0
  204. package/templates/ui/shadcn/src/components/common/fields/assets/interface/input-props.type.ts +198 -0
  205. package/templates/ui/shadcn/src/components/common/fields/cusInputField.component.tsx +52 -0
  206. package/templates/ui/shadcn/src/components/common/pagination/pagination.component.tsx +68 -0
  207. package/templates/ui/shadcn/src/components/ui/avatar.component.tsx +37 -0
  208. package/templates/ui/shadcn/src/components/ui/badge.component.tsx +28 -0
  209. package/templates/ui/shadcn/src/components/ui/button.component.tsx +52 -0
  210. package/templates/ui/shadcn/src/components/ui/card.component.tsx +46 -0
  211. package/templates/ui/shadcn/src/components/ui/checkbox.component.tsx +25 -0
  212. package/templates/ui/shadcn/src/components/ui/dialog.component.tsx +98 -0
  213. package/templates/ui/shadcn/src/components/ui/dropdown-menu.component.tsx +163 -0
  214. package/templates/ui/shadcn/src/components/ui/form.component.tsx +110 -0
  215. package/templates/ui/shadcn/src/components/ui/input-otp.component.tsx +64 -0
  216. package/templates/ui/shadcn/src/components/ui/input.component.tsx +23 -0
  217. package/templates/ui/shadcn/src/components/ui/label.component.tsx +23 -0
  218. package/templates/ui/shadcn/src/components/ui/popover.component.tsx +27 -0
  219. package/templates/ui/shadcn/src/components/ui/progress.component.tsx +22 -0
  220. package/templates/ui/shadcn/src/components/ui/radio-group.component.tsx +33 -0
  221. package/templates/ui/shadcn/src/components/ui/scroll-area.component.tsx +37 -0
  222. package/templates/ui/shadcn/src/components/ui/select.component.tsx +139 -0
  223. package/templates/ui/shadcn/src/components/ui/separator.component.tsx +23 -0
  224. package/templates/ui/shadcn/src/components/ui/sheet.component.tsx +89 -0
  225. package/templates/ui/shadcn/src/components/ui/switch.component.tsx +26 -0
  226. package/templates/ui/shadcn/src/components/ui/table.component.tsx +71 -0
  227. package/templates/ui/shadcn/src/components/ui/tabs.component.tsx +52 -0
  228. package/templates/ui/shadcn/src/components/ui/textarea.component.tsx +20 -0
  229. package/templates/ui/shadcn/src/components/ui/tooltip.component.tsx +25 -0
@@ -0,0 +1,82 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form.component';
5
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select.component';
6
+ import { LabelAndPlaceholderTextFormat } from '@/lib/utils.util';
7
+ import type { InputInterface } from '../interface/input-props.type';
8
+
9
+ export const SingleSelectField = ({
10
+ form, name, labelName, placeholder, required = false, disabled = false,
11
+ options, viewOnly = false, onValueChange, isLoading = false, defaultValue = '',
12
+ customMessage, disableLabelFormatting = false,
13
+ }: InputInterface['SingleSelect']) => {
14
+ const [localValue, setLocalValue] = useState(defaultValue);
15
+ const placeholderText = disableLabelFormatting ? placeholder || 'Select an option' : LabelAndPlaceholderTextFormat(`${placeholder || 'Select an option'}`);
16
+
17
+ const renderSelect = (value: string, onChange: (val: string) => void) => (
18
+ <Select onValueChange={(val) => { onChange(val); onValueChange?.(val); }} value={value} disabled={disabled}>
19
+ <SelectTrigger>
20
+ <SelectValue placeholder={placeholderText} />
21
+ </SelectTrigger>
22
+ <SelectContent className="capitalize">
23
+ {isLoading ? (
24
+ <SelectItem value="loading" disabled>Loading...</SelectItem>
25
+ ) : options && options.length > 0 ? (
26
+ options.map((option) => (
27
+ <SelectItem key={option} value={option} className="capitalize">{option}</SelectItem>
28
+ ))
29
+ ) : (
30
+ <SelectItem value="no-options" disabled>
31
+ {disableLabelFormatting ? labelName : LabelAndPlaceholderTextFormat(labelName || '')} options not available
32
+ </SelectItem>
33
+ )}
34
+ </SelectContent>
35
+ </Select>
36
+ );
37
+
38
+ const LabelEl = () =>
39
+ labelName ? (
40
+ <label className="font-semibold leading-6 text-[14px] tracking-[0.02em]">
41
+ {disableLabelFormatting ? labelName : LabelAndPlaceholderTextFormat(labelName)}
42
+ {required && <span className="text-muted-foreground">&nbsp;*</span>}
43
+ </label>
44
+ ) : null;
45
+
46
+ if (form) {
47
+ return (
48
+ <FormField
49
+ control={form.control}
50
+ name={name || 'select'}
51
+ render={({ field }) => (
52
+ <FormItem>
53
+ <LabelEl />
54
+ {viewOnly ? (
55
+ <div className="py-2 px-3 text-sm text-foreground capitalize bg-background rounded-md border border-border min-h-10">
56
+ {field.value || ''}
57
+ </div>
58
+ ) : (
59
+ <>
60
+ {renderSelect(field.value, field.onChange)}
61
+ <FormMessage>{customMessage || ''}</FormMessage>
62
+ </>
63
+ )}
64
+ </FormItem>
65
+ )}
66
+ />
67
+ );
68
+ }
69
+
70
+ return (
71
+ <div className="flex flex-col gap-1">
72
+ <LabelEl />
73
+ {viewOnly ? (
74
+ <div className="py-2 px-3 text-sm text-foreground capitalize bg-background rounded-md border border-border min-h-10">
75
+ {localValue || ''}
76
+ </div>
77
+ ) : (
78
+ renderSelect(localValue, setLocalValue)
79
+ )}
80
+ </div>
81
+ );
82
+ };
@@ -0,0 +1,68 @@
1
+ import { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form.component';
2
+ import { Input } from '@/components/ui/input.component';
3
+ import { LabelAndPlaceholderTextFormat } from '@/lib/utils.util';
4
+ import type { InputInterface } from '../interface/input-props.type';
5
+
6
+ export const StringNumber = ({
7
+ form, name, labelName, placeholder, required = false, disabled = false,
8
+ viewOnly = false, disableLabelFormatting = false, customMessage,
9
+ }: InputInterface['Number']) => {
10
+ const placeholderText = disableLabelFormatting
11
+ ? placeholder || labelName
12
+ : LabelAndPlaceholderTextFormat(placeholder || labelName || '');
13
+
14
+ const LabelEl = () =>
15
+ labelName ? (
16
+ <label className="font-semibold leading-6 text-[14px] tracking-[0.02em]">
17
+ {disableLabelFormatting ? labelName : LabelAndPlaceholderTextFormat(labelName)}
18
+ {required && <span className="text-destructive">&nbsp;*</span>}
19
+ </label>
20
+ ) : null;
21
+
22
+ if (form) {
23
+ return (
24
+ <FormField
25
+ control={form.control}
26
+ name={name || 'string-number'}
27
+ render={({ field }) => {
28
+ const error = form.formState.errors?.[name || ''];
29
+ return (
30
+ <FormItem>
31
+ <LabelEl />
32
+ {viewOnly ? (
33
+ <div className="py-2 px-3 text-sm text-foreground bg-background rounded-md border border-border min-h-10">
34
+ {field.value || ''}
35
+ </div>
36
+ ) : (
37
+ <>
38
+ <FormControl>
39
+ <Input
40
+ type="text"
41
+ inputMode="numeric"
42
+ pattern="[0-9]*"
43
+ placeholder={placeholderText}
44
+ disabled={disabled}
45
+ {...field}
46
+ onChange={(e) => {
47
+ const val = e.target.value.replace(/[^0-9]/g, '');
48
+ field.onChange(val);
49
+ }}
50
+ />
51
+ </FormControl>
52
+ <FormMessage>{error ? String(error?.message || '') : customMessage || ''}</FormMessage>
53
+ </>
54
+ )}
55
+ </FormItem>
56
+ );
57
+ }}
58
+ />
59
+ );
60
+ }
61
+
62
+ return (
63
+ <>
64
+ <LabelEl />
65
+ <Input type="text" inputMode="numeric" pattern="[0-9]*" placeholder={placeholderText} />
66
+ </>
67
+ );
68
+ };
@@ -0,0 +1,61 @@
1
+ import { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form.component';
2
+ import { Switch } from '@/components/ui/switch.component';
3
+ import { LabelAndPlaceholderTextFormat } from '@/lib/utils.util';
4
+ import type { InputInterface } from '../interface/input-props.type';
5
+
6
+ export const SwitchField = ({
7
+ form, name, labelName, required = false, disabled = false,
8
+ viewOnly = false, disableLabelFormatting = false, customMessage,
9
+ description, border = false, value, setValue, onCheckedChange,
10
+ }: InputInterface['Switch']) => {
11
+ const LabelEl = () =>
12
+ labelName ? (
13
+ <label className="font-semibold leading-6 text-[14px] tracking-[0.02em]">
14
+ {disableLabelFormatting ? labelName : LabelAndPlaceholderTextFormat(labelName)}
15
+ {required && <span className="text-destructive">&nbsp;*</span>}
16
+ </label>
17
+ ) : null;
18
+
19
+ if (form) {
20
+ return (
21
+ <FormField
22
+ control={form.control}
23
+ name={name || 'switch'}
24
+ render={({ field }) => {
25
+ const error = form.formState.errors?.[name || ''];
26
+ return (
27
+ <FormItem>
28
+ <div className={`flex items-center justify-between ${border ? 'p-4 border rounded-lg' : ''}`}>
29
+ <div className="space-y-0.5">
30
+ <LabelEl />
31
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
32
+ </div>
33
+ <FormControl>
34
+ <Switch
35
+ checked={field.value}
36
+ onCheckedChange={(checked) => {
37
+ field.onChange(checked);
38
+ onCheckedChange?.(checked);
39
+ }}
40
+ disabled={disabled || viewOnly}
41
+ />
42
+ </FormControl>
43
+ </div>
44
+ <FormMessage>{error ? String(error?.message || '') : customMessage || ''}</FormMessage>
45
+ </FormItem>
46
+ );
47
+ }}
48
+ />
49
+ );
50
+ }
51
+
52
+ return (
53
+ <div className={`flex items-center justify-between ${border ? 'p-4 border rounded-lg' : ''}`}>
54
+ <div className="space-y-0.5">
55
+ <LabelEl />
56
+ {description && <p className="text-sm text-muted-foreground">{description}</p>}
57
+ </div>
58
+ <Switch checked={value} onCheckedChange={(checked) => { setValue?.(checked); onCheckedChange?.(checked); }} disabled={disabled || viewOnly} />
59
+ </div>
60
+ );
61
+ };
@@ -0,0 +1,62 @@
1
+ import { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form.component';
2
+ import { Textarea } from '@/components/ui/textarea.component';
3
+ import { LabelAndPlaceholderTextFormat } from '@/lib/utils.util';
4
+ import type { InputInterface } from '../interface/input-props.type';
5
+
6
+ export const TextArea = ({
7
+ form, name, labelName, placeholder, required = false, disabled = false,
8
+ viewOnly = false, rows = 4, disableLabelFormatting = false, customMessage, value, setValue,
9
+ }: InputInterface['TextArea']) => {
10
+ const placeholderText = disableLabelFormatting
11
+ ? placeholder || labelName
12
+ : LabelAndPlaceholderTextFormat(placeholder || labelName || '');
13
+
14
+ const LabelEl = () =>
15
+ labelName ? (
16
+ <label className="font-semibold leading-6 text-[14px] tracking-[0.02em]">
17
+ {disableLabelFormatting ? labelName : LabelAndPlaceholderTextFormat(labelName)}
18
+ {required && <span className="text-destructive">&nbsp;*</span>}
19
+ </label>
20
+ ) : null;
21
+
22
+ if (form) {
23
+ return (
24
+ <FormField
25
+ control={form.control}
26
+ name={name || 'textarea'}
27
+ render={({ field }) => {
28
+ const error = form.formState.errors?.[name || ''];
29
+ return (
30
+ <FormItem>
31
+ <LabelEl />
32
+ {viewOnly ? (
33
+ <div className="py-2 px-3 text-sm text-foreground bg-background rounded-md border border-border min-h-10 whitespace-pre-wrap">
34
+ {field.value || ''}
35
+ </div>
36
+ ) : (
37
+ <>
38
+ <FormControl>
39
+ <Textarea
40
+ placeholder={placeholderText}
41
+ disabled={disabled}
42
+ rows={rows}
43
+ {...field}
44
+ />
45
+ </FormControl>
46
+ <FormMessage>{error ? String(error?.message || '') : customMessage || ''}</FormMessage>
47
+ </>
48
+ )}
49
+ </FormItem>
50
+ );
51
+ }}
52
+ />
53
+ );
54
+ }
55
+
56
+ return (
57
+ <>
58
+ <LabelEl />
59
+ <Textarea value={value} onChange={(e) => setValue?.(e.target.value)} placeholder={placeholderText} rows={rows} />
60
+ </>
61
+ );
62
+ };
@@ -0,0 +1,142 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ 'use client';
3
+
4
+ import { useRef, useState } from 'react';
5
+ import { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form.component';
6
+ import { Textarea } from '@/components/ui/textarea.component';
7
+ import { Button } from '@/components/ui/button.component';
8
+ import { Paperclip, X, Eye } from 'lucide-react';
9
+ import Image from 'next/image';
10
+
11
+ interface FileItem {
12
+ id: string;
13
+ name: string;
14
+ size: number;
15
+ type: string;
16
+ previewUrl: string;
17
+ file: File;
18
+ }
19
+
20
+ interface TextAreaWithFileProps {
21
+ form: any;
22
+ name: string;
23
+ fileName?: string;
24
+ placeholder?: string;
25
+ optional?: boolean;
26
+ disabled?: boolean;
27
+ viewOnly?: boolean;
28
+ rows?: number;
29
+ onSend?: (data: { text: string; files: FileItem[] }) => void;
30
+ }
31
+
32
+ /**
33
+ * TextAreaWithFile — Textarea with attached file uploads.
34
+ *
35
+ * ⚠️ Stores file metadata (id, name, size, type) in the form value,
36
+ * NOT raw File objects. Actual File references are kept in local state
37
+ * for submission via onSend callback.
38
+ */
39
+ export const TextAreaWithFile = ({
40
+ form, name, fileName = 'files', placeholder, optional = true,
41
+ disabled = false, viewOnly = false, rows = 3, onSend,
42
+ }: TextAreaWithFileProps) => {
43
+ const fileInputRef = useRef<HTMLInputElement | null>(null);
44
+ const [fileItems, setFileItems] = useState<FileItem[]>([]);
45
+
46
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
47
+ const selectedFiles = Array.from(e.target.files || []);
48
+ if (!selectedFiles.length) return;
49
+
50
+ const newItems: FileItem[] = selectedFiles.map((file) => ({
51
+ id: `${file.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
52
+ name: file.name,
53
+ size: file.size,
54
+ type: file.type,
55
+ previewUrl: URL.createObjectURL(file),
56
+ file,
57
+ }));
58
+
59
+ const updated = [...fileItems, ...newItems];
60
+ setFileItems(updated);
61
+
62
+ // Store metadata in form (strings only — no File objects)
63
+ const metadata = updated.map((item) => ({
64
+ id: item.id,
65
+ name: item.name,
66
+ size: item.size,
67
+ type: item.type,
68
+ }));
69
+ form.setValue(fileName, metadata);
70
+ };
71
+
72
+ const handleRemoveFile = (id: string) => {
73
+ setFileItems((prev) => {
74
+ const removed = prev.find((p) => p.id === id);
75
+ if (removed) URL.revokeObjectURL(removed.previewUrl);
76
+ const updated = prev.filter((p) => p.id !== id);
77
+ // Update form with metadata only
78
+ const metadata = updated.map((item) => ({
79
+ id: item.id, name: item.name, size: item.size, type: item.type,
80
+ }));
81
+ form.setValue(fileName, metadata);
82
+ return updated;
83
+ });
84
+ };
85
+
86
+ return (
87
+ <div className="space-y-2">
88
+ <FormField
89
+ control={form.control}
90
+ name={name}
91
+ render={({ field }) => (
92
+ <FormItem>
93
+ <FormControl>
94
+ <div className="relative">
95
+ <Textarea {...field} placeholder={placeholder} disabled={disabled} rows={rows} />
96
+ <div className="absolute bottom-2 right-2 flex items-center gap-1">
97
+ <Button type="button" size="icon" variant="ghost" className="h-8 w-8" disabled={disabled}
98
+ onClick={() => fileInputRef.current?.click()}>
99
+ <Paperclip className="h-4 w-4" />
100
+ </Button>
101
+ </div>
102
+ </div>
103
+ </FormControl>
104
+ <FormMessage />
105
+ </FormItem>
106
+ )}
107
+ />
108
+ <input type="file" ref={fileInputRef} className="hidden" multiple onChange={handleFileChange} />
109
+
110
+ {fileItems.length > 0 && (
111
+ <div className="flex flex-wrap gap-2">
112
+ {fileItems.map((item) => (
113
+ <div key={item.id} className="relative group border rounded-lg p-2 pr-8">
114
+ {item.type.startsWith('image/') ? (
115
+ <Image src={item.previewUrl} alt={item.name} width={48} height={48}
116
+ className="object-cover rounded cursor-pointer"
117
+ onClick={() => window.open(item.previewUrl, '_blank')} />
118
+ ) : (
119
+ <div className="flex items-center gap-1 text-xs">
120
+ <Eye className="h-4 w-4 cursor-pointer"
121
+ onClick={() => window.open(item.previewUrl, '_blank')} />
122
+ <span className="truncate max-w-[100px]">{item.name}</span>
123
+ </div>
124
+ )}
125
+ <button type="button" onClick={() => handleRemoveFile(item.id)}
126
+ className="absolute top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity">
127
+ <X className="h-3 w-3 text-destructive" />
128
+ </button>
129
+ </div>
130
+ ))}
131
+ </div>
132
+ )}
133
+
134
+ {onSend && (
135
+ <Button type="button" size="sm"
136
+ onClick={() => onSend({ text: form.watch(name), files: fileItems })}>
137
+ Send
138
+ </Button>
139
+ )}
140
+ </div>
141
+ );
142
+ };
@@ -0,0 +1,80 @@
1
+ import { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form.component';
2
+ import { Input } from '@/components/ui/input.component';
3
+ import { LabelAndPlaceholderTextFormat } from '@/lib/utils.util';
4
+ import type { InputInterface } from '../interface/input-props.type';
5
+
6
+ export const Text = ({
7
+ form, name, placeholder, labelName, required = false, disabled = false,
8
+ viewOnly = false, disableLabelFormatting = false, customMessage,
9
+ isArray = false, leftIcon, rightIcon, value, setValue,
10
+ }: InputInterface['Text']) => {
11
+ const placeholderText = disableLabelFormatting
12
+ ? placeholder || labelName
13
+ : LabelAndPlaceholderTextFormat(placeholder || labelName || '');
14
+
15
+ const LabelEl = () =>
16
+ labelName ? (
17
+ <label className="font-semibold leading-6 text-[14px] tracking-[0.02em]">
18
+ {disableLabelFormatting ? labelName : LabelAndPlaceholderTextFormat(labelName)}
19
+ {required && <span className="text-destructive">&nbsp;*</span>}
20
+ </label>
21
+ ) : null;
22
+
23
+ if (form) {
24
+ return (
25
+ <FormField
26
+ control={form.control}
27
+ name={name || 'text'}
28
+ render={({ field }) => {
29
+ const error = form.formState.errors?.[name || ''];
30
+ const isError = !!error;
31
+ return (
32
+ <FormItem>
33
+ <LabelEl />
34
+ {viewOnly ? (
35
+ <div className="py-2 px-3 text-sm text-foreground bg-background rounded-md border border-border min-h-10">
36
+ {field.value || ''}
37
+ </div>
38
+ ) : (
39
+ <>
40
+ <FormControl>
41
+ <div className="w-full relative">
42
+ {leftIcon && <div className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground">{leftIcon}</div>}
43
+ <Input
44
+ className={`${leftIcon ? 'pl-10' : 'pl-4'} ${rightIcon ? 'pr-10' : 'pr-4'} ${isError ? 'border-destructive' : ''}`}
45
+ placeholder={placeholderText}
46
+ disabled={disabled}
47
+ {...field}
48
+ type="text"
49
+ onChange={(event) => {
50
+ if (isArray) {
51
+ const arr = event.target.value.split(/[\s,]+/).map(v => v.trim()).filter(v => v.length > 0);
52
+ form.setValue(name, arr, { shouldValidate: true, shouldDirty: true });
53
+ }
54
+ field.onChange(event);
55
+ }}
56
+ />
57
+ {rightIcon && <div className="absolute right-4 top-1/2 -translate-y-1/2 text-muted-foreground cursor-pointer">{rightIcon}</div>}
58
+ </div>
59
+ </FormControl>
60
+ <FormMessage>{isError ? String(error?.message || '') : customMessage || ''}</FormMessage>
61
+ </>
62
+ )}
63
+ </FormItem>
64
+ );
65
+ }}
66
+ />
67
+ );
68
+ }
69
+
70
+ return (
71
+ <>
72
+ <LabelEl />
73
+ <div className="w-full relative">
74
+ {leftIcon && <div className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground">{leftIcon}</div>}
75
+ <Input className={`${leftIcon ? 'pl-10' : 'pl-4'} ${rightIcon ? 'pr-10' : 'pr-4'}`} value={value} onChange={(e) => setValue?.(e.target.value || '')} type="text" placeholder={placeholderText} />
76
+ {rightIcon && <div className="absolute right-4 top-1/2 -translate-y-1/2 text-muted-foreground cursor-pointer">{rightIcon}</div>}
77
+ </div>
78
+ </>
79
+ );
80
+ };
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+
3
+ import { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form.component';
4
+ import { Textarea } from '@/components/ui/textarea.component';
5
+ import { LabelAndPlaceholderTextFormat } from '@/lib/utils.util';
6
+
7
+ interface TinyEditorProps {
8
+ form?: any;
9
+ name?: string;
10
+ labelName?: string;
11
+ required?: boolean;
12
+ placeholder?: string;
13
+ disabled?: boolean;
14
+ disableLabelFormatting?: boolean;
15
+ customMessage?: React.ReactNode;
16
+ height?: number;
17
+ }
18
+
19
+ export const TinyEditor = ({
20
+ form, name = 'content', labelName, required = false, placeholder = 'Enter content...',
21
+ disabled = false, disableLabelFormatting = false, customMessage, height = 300,
22
+ }: TinyEditorProps) => {
23
+ if (!form) {
24
+ return <Textarea placeholder={placeholder} disabled={disabled} style={{ minHeight: height }} />;
25
+ }
26
+
27
+ return (
28
+ <FormField
29
+ control={form.control}
30
+ name={name || 'content'}
31
+ render={({ field }) => {
32
+ const error = form.formState.errors?.[name || ''];
33
+ return (
34
+ <FormItem>
35
+ {labelName && (
36
+ <label className="font-semibold leading-6 text-[14px] tracking-[0.02em]">
37
+ {disableLabelFormatting ? labelName : LabelAndPlaceholderTextFormat(labelName)}
38
+ {required && <span className="text-destructive">&nbsp;*</span>}
39
+ </label>
40
+ )}
41
+ <FormControl>
42
+ <Textarea {...field} placeholder={placeholder} disabled={disabled}
43
+ className="font-mono text-sm" style={{ minHeight: height }} />
44
+ </FormControl>
45
+ <FormMessage>{error ? String(error?.message || '') : customMessage || ''}</FormMessage>
46
+ </FormItem>
47
+ );
48
+ }}
49
+ />
50
+ );
51
+ };
@@ -0,0 +1,103 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ 'use client';
3
+
4
+ import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar.component';
5
+ import { Button } from '@/components/ui/button.component';
6
+ import { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form.component';
7
+ import { Camera, User, X } from 'lucide-react';
8
+ import { useRef, useState } from 'react';
9
+
10
+ interface UploadProfilePictureProps {
11
+ form?: any;
12
+ name?: string;
13
+ labelName?: string;
14
+ optional?: boolean;
15
+ viewOnly?: boolean;
16
+ removeImage?: boolean;
17
+ }
18
+
19
+ /**
20
+ * UploadProfilePicture — Avatar upload with preview.
21
+ *
22
+ * ⚠️ Stores a data URL string in the form value, NOT a raw File object.
23
+ * Access the actual File via a ref or submit handler if needed.
24
+ */
25
+ export const UploadProfilePicture = ({
26
+ form, name = 'avatar', labelName, optional = true,
27
+ viewOnly = false, removeImage = true,
28
+ }: UploadProfilePictureProps) => {
29
+ const fileInputRef = useRef<HTMLInputElement | null>(null);
30
+ const fileRef = useRef<File | null>(null);
31
+ const [preview, setPreview] = useState<string | null>(null);
32
+
33
+ const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
34
+ const file = e.target.files?.[0];
35
+ if (!file) return;
36
+
37
+ // Store actual File in ref for later upload
38
+ fileRef.current = file;
39
+
40
+ // Store data URL string in form — NOT the File object
41
+ const reader = new FileReader();
42
+ reader.onload = () => {
43
+ const dataUrl = reader.result as string;
44
+ setPreview(dataUrl);
45
+ form?.setValue(name, dataUrl, { shouldValidate: true });
46
+ };
47
+ reader.readAsDataURL(file);
48
+ };
49
+
50
+ const handleRemove = () => {
51
+ setPreview(null);
52
+ fileRef.current = null;
53
+ form?.setValue(name, null, { shouldValidate: true });
54
+ if (fileInputRef.current) fileInputRef.current.value = '';
55
+ };
56
+
57
+ const imageSrc = preview || (form?.watch(name) ?? null);
58
+
59
+ if (!form) {
60
+ return (
61
+ <div className="flex flex-col items-center gap-4">
62
+ {labelName && <label className="font-semibold text-[14px]">{labelName}</label>}
63
+ <Avatar className="h-24 w-24">
64
+ {imageSrc ? <AvatarImage src={imageSrc} /> : <AvatarFallback><User className="h-8 w-8" /></AvatarFallback>}
65
+ </Avatar>
66
+ </div>
67
+ );
68
+ }
69
+
70
+ return (
71
+ <FormField
72
+ control={form.control}
73
+ name={name || 'avatar'}
74
+ render={() => (
75
+ <FormItem>
76
+ <div className="flex flex-col items-center gap-4">
77
+ {labelName && <label className="font-semibold text-[14px]">{labelName}</label>}
78
+ <div className="relative">
79
+ <Avatar className="h-24 w-24 cursor-pointer" onClick={() => !viewOnly && fileInputRef.current?.click()}>
80
+ {imageSrc
81
+ ? <AvatarImage src={imageSrc} />
82
+ : <AvatarFallback><User className="h-8 w-8" /></AvatarFallback>}
83
+ </Avatar>
84
+ {!viewOnly && (
85
+ <Button type="button" size="icon" variant="ghost" className="absolute bottom-0 right-0 h-8 w-8 rounded-full bg-background shadow"
86
+ onClick={(e) => { e.stopPropagation(); fileInputRef.current?.click(); }}>
87
+ <Camera className="h-4 w-4" />
88
+ </Button>
89
+ )}
90
+ </div>
91
+ {removeImage && !viewOnly && (preview || form?.watch(name)) && (
92
+ <Button type="button" variant="ghost" size="sm" onClick={handleRemove}>
93
+ <X className="h-4 w-4 mr-1" /> Remove
94
+ </Button>
95
+ )}
96
+ <input ref={fileInputRef} type="file" accept="image/*" className="hidden" onChange={handleFileSelect} />
97
+ </div>
98
+ <FormMessage />
99
+ </FormItem>
100
+ )}
101
+ />
102
+ );
103
+ };