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,90 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ 'use client';
3
+
4
+ import { FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form.component';
5
+ import { LabelAndPlaceholderTextFormat, maskString } from '@/lib/utils.util';
6
+ import PhoneInput from 'react-phone-input-2';
7
+ import 'react-phone-input-2/lib/style.css';
8
+ import type { InputInterface } from '../interface/input-props.type';
9
+
10
+ export const PhoneNumber = ({
11
+ form, name, labelName, placeholder, required = false, disabled = false,
12
+ viewOnly = false, disableLabelFormatting = false, customMessage, defaultCountry = 'us',
13
+ disableCountryCode = true, disableDropdown = false, onValueChange, isLoading = false,
14
+ hasPhone = false,
15
+ }: InputInterface['PhoneNumber']) => {
16
+ const placeholderText = disableLabelFormatting
17
+ ? placeholder || labelName
18
+ : LabelAndPlaceholderTextFormat(placeholder || labelName || '');
19
+
20
+ const LabelEl = () =>
21
+ labelName ? (
22
+ <label className="font-semibold leading-6 text-[14px] tracking-[0.02em]">
23
+ {disableLabelFormatting ? labelName : LabelAndPlaceholderTextFormat(labelName)}
24
+ {required && <span className="text-destructive">&nbsp;*</span>}
25
+ </label>
26
+ ) : null;
27
+
28
+ if (form) {
29
+ return (
30
+ <FormField
31
+ control={form.control}
32
+ name={name || 'phone'}
33
+ render={({ field }) => {
34
+ const error = form.formState.errors?.[name || ''];
35
+ const isError = !!error;
36
+
37
+ return (
38
+ <FormItem>
39
+ <LabelEl />
40
+ {viewOnly ? (
41
+ <div className="py-2 px-3 text-sm text-foreground bg-background rounded-md border border-border min-h-10">
42
+ {hasPhone ? maskString(field.value) : field.value || ''}
43
+ </div>
44
+ ) : (
45
+ <>
46
+ <FormControl>
47
+ <div className="[&_.react-tel-input]:w-full [&_.form-control]:!w-full [&_.form-control]:!h-11 [&_.form-control]:!rounded-lg [&_.form-control]:!border-border [&_.form-control]:!bg-background [&_.form-control]:!text-sm [&_.flag-dropdown]:!rounded-l-lg [&_.flag-dropdown]:!border-border">
48
+ <PhoneInput
49
+ country={defaultCountry}
50
+ value={field.value}
51
+ disabled={disabled || isLoading}
52
+ disableCountryCode={disableCountryCode}
53
+ disableDropdown={disableDropdown}
54
+ placeholder={LabelAndPlaceholderTextFormat(placeholder || 'Enter phone number')}
55
+ inputStyle={isError ? { borderColor: '#dc2626' } : undefined}
56
+ onChange={(value) => {
57
+ field.onChange(value);
58
+ onValueChange?.(value);
59
+ }}
60
+ onBlur={field.onBlur}
61
+ searchStyle={{ width: '100%' }}
62
+ />
63
+ </div>
64
+ </FormControl>
65
+ <FormMessage>
66
+ {isError ? String(error?.message || '') : customMessage || ''}
67
+ </FormMessage>
68
+ </>
69
+ )}
70
+ </FormItem>
71
+ );
72
+ }}
73
+ />
74
+ );
75
+ }
76
+
77
+ return (
78
+ <div>
79
+ <LabelEl />
80
+ <PhoneInput
81
+ country={defaultCountry}
82
+ disabled={disabled}
83
+ disableCountryCode={disableCountryCode}
84
+ disableDropdown={disableDropdown}
85
+ placeholder={placeholderText}
86
+ inputClass="!w-full !h-11 !rounded-lg !border-border !bg-background !text-sm"
87
+ />
88
+ </div>
89
+ );
90
+ };
@@ -0,0 +1,41 @@
1
+ import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form.component';
2
+ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group.component';
3
+ import { LabelAndPlaceholderTextFormat } from '@/lib/utils.util';
4
+ import type { RadioProps } from '../interface/input-props.type';
5
+
6
+ export const RadioField = ({
7
+ form, name, labelName, required = false, disabled = false, options = [],
8
+ }: RadioProps) => {
9
+ const LabelEl = () =>
10
+ labelName ? (
11
+ <label className="font-semibold leading-6 text-[14px] tracking-[0.02em]">
12
+ {LabelAndPlaceholderTextFormat(labelName)}
13
+ {required && <span className="text-destructive">&nbsp;*</span>}
14
+ </label>
15
+ ) : null;
16
+
17
+ if (!form) return null;
18
+
19
+ return (
20
+ <FormField
21
+ control={form.control}
22
+ name={name}
23
+ render={({ field }) => (
24
+ <FormItem>
25
+ <LabelEl />
26
+ <FormControl>
27
+ <RadioGroup onValueChange={field.onChange} defaultValue={field.value} className="flex flex-col space-y-1" disabled={disabled}>
28
+ {options.map((opt) => (
29
+ <div key={opt.value} className="flex items-center space-x-2">
30
+ <RadioGroupItem value={opt.value} id={`${name}-${opt.value}`} />
31
+ <FormLabel htmlFor={`${name}-${opt.value}`} className="font-normal">{opt.label}</FormLabel>
32
+ </div>
33
+ ))}
34
+ </RadioGroup>
35
+ </FormControl>
36
+ <FormMessage />
37
+ </FormItem>
38
+ )}
39
+ />
40
+ );
41
+ };
@@ -0,0 +1,71 @@
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 { Calendar } from 'lucide-react';
5
+
6
+ interface RangeDatePickerProps {
7
+ form: any;
8
+ name: string;
9
+ labelName?: string;
10
+ required?: boolean;
11
+ disabled?: boolean;
12
+ disableLabelFormatting?: boolean;
13
+ customMessage?: string;
14
+ }
15
+
16
+ export const RangeDatePicker = ({
17
+ form, name, labelName, required = false, disabled = false,
18
+ disableLabelFormatting = false, customMessage,
19
+ }: RangeDatePickerProps) => {
20
+ const LabelEl = () =>
21
+ labelName ? (
22
+ <label className="font-semibold leading-6 text-[14px] tracking-[0.02em]">
23
+ {disableLabelFormatting ? labelName : LabelAndPlaceholderTextFormat(labelName)}
24
+ {required && <span className="text-destructive">&nbsp;*</span>}
25
+ </label>
26
+ ) : null;
27
+
28
+ return (
29
+ <FormField
30
+ control={form.control}
31
+ name={name}
32
+ render={({ field }) => {
33
+ const error = form.formState.errors?.[name];
34
+ const value = field.value || {};
35
+ return (
36
+ <FormItem>
37
+ <LabelEl />
38
+ <div className="flex items-center gap-2">
39
+ <div className="relative flex-1">
40
+ <Calendar className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
41
+ <FormControl>
42
+ <Input
43
+ type="date"
44
+ className="pl-10"
45
+ disabled={disabled}
46
+ value={value.from || ''}
47
+ onChange={(e) => field.onChange({ ...value, from: e.target.value })}
48
+ />
49
+ </FormControl>
50
+ </div>
51
+ <span className="text-muted-foreground">to</span>
52
+ <div className="relative flex-1">
53
+ <Calendar className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
54
+ <FormControl>
55
+ <Input
56
+ type="date"
57
+ className="pl-10"
58
+ disabled={disabled}
59
+ value={value.to || ''}
60
+ onChange={(e) => field.onChange({ ...value, to: e.target.value })}
61
+ />
62
+ </FormControl>
63
+ </div>
64
+ </div>
65
+ <FormMessage>{error ? String(error?.message || '') : customMessage || ''}</FormMessage>
66
+ </FormItem>
67
+ );
68
+ }}
69
+ />
70
+ );
71
+ };
@@ -0,0 +1,91 @@
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
+ import { useState } from 'react';
7
+ import { Bold, Italic, List, Heading } from 'lucide-react';
8
+ import { Button } from '@/components/ui/button.component';
9
+ import { cn } from '@/lib/utils.util';
10
+
11
+ interface RichTextEditorProps {
12
+ form?: any;
13
+ name?: string;
14
+ labelName?: string;
15
+ required?: boolean;
16
+ placeholder?: string;
17
+ disabled?: boolean;
18
+ disableLabelFormatting?: boolean;
19
+ customMessage?: React.ReactNode;
20
+ }
21
+
22
+ export const RichTextEditor = ({
23
+ form, name = 'content', labelName, required = false, placeholder = 'Write something...',
24
+ disabled = false, disableLabelFormatting = false, customMessage,
25
+ }: RichTextEditorProps) => {
26
+ const [activeTags, setActiveTags] = useState<string[]>([]);
27
+
28
+ const wrapSelection = (tag: string) => {
29
+ if (!form) return;
30
+ const textarea = document.getElementById(name) as HTMLTextAreaElement;
31
+ if (!textarea) return;
32
+ const start = textarea.selectionStart;
33
+ const end = textarea.selectionEnd;
34
+ const text = textarea.value;
35
+ const selected = text.substring(start, end);
36
+ const wrapped = tag === 'h1' ? `# ${selected}` :
37
+ tag === 'h2' ? `## ${selected}` :
38
+ tag === 'li' ? `- ${selected}` :
39
+ `**${selected}**`;
40
+
41
+ const newText = text.substring(0, start) + wrapped + text.substring(end);
42
+ form.setValue(name, newText, { shouldValidate: true });
43
+ setActiveTags((prev) => prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]);
44
+ };
45
+
46
+ const editorButtons = [
47
+ { tag: 'bold', icon: Bold, label: 'Bold' },
48
+ { tag: 'italic', icon: Italic, label: 'Italic' },
49
+ { tag: 'h1', icon: Heading, label: 'Heading' },
50
+ { tag: 'li', icon: List, label: 'List' },
51
+ ];
52
+
53
+ if (!form) {
54
+ return <Textarea placeholder={placeholder} disabled={disabled} />;
55
+ }
56
+
57
+ return (
58
+ <FormField
59
+ control={form.control}
60
+ name={name || 'content'}
61
+ render={({ field }) => {
62
+ const error = form.formState.errors?.[name || ''];
63
+ return (
64
+ <FormItem>
65
+ {labelName && (
66
+ <label className="font-semibold leading-6 text-[14px] tracking-[0.02em]">
67
+ {disableLabelFormatting ? labelName : LabelAndPlaceholderTextFormat(labelName)}
68
+ {required && <span className="text-destructive">&nbsp;*</span>}
69
+ </label>
70
+ )}
71
+ <div className="border rounded-md">
72
+ <div className="flex items-center gap-1 p-2 border-b bg-muted/30">
73
+ {editorButtons.map((btn) => (
74
+ <Button key={btn.tag} type="button" size="icon" variant="ghost" className={cn('h-8 w-8', activeTags.includes(btn.tag) && 'bg-accent')}
75
+ onClick={() => wrapSelection(btn.tag)} disabled={disabled}>
76
+ <btn.icon className="h-4 w-4" />
77
+ </Button>
78
+ ))}
79
+ </div>
80
+ <FormControl>
81
+ <Textarea id={name} {...field} placeholder={placeholder} disabled={disabled}
82
+ className="min-h-[200px] border-0 focus-visible:ring-0 rounded-t-none font-mono text-sm" />
83
+ </FormControl>
84
+ </div>
85
+ <FormMessage>{error ? String(error?.message || '') : customMessage || ''}</FormMessage>
86
+ </FormItem>
87
+ );
88
+ }}
89
+ />
90
+ );
91
+ };
@@ -0,0 +1,34 @@
1
+ import { Search, X } from 'lucide-react';
2
+ import { Input } from '@/components/ui/input.component';
3
+ import type { SearchFieldProps } from '../interface/input-props.type';
4
+
5
+ export const SearchField = ({
6
+ placeholder = 'Search...',
7
+ onSearch,
8
+ value: externalValue,
9
+ setValue: setExternalValue,
10
+ }: SearchFieldProps) => {
11
+ return (
12
+ <div className="relative w-full">
13
+ <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
14
+ <Input
15
+ className="pl-10 pr-10"
16
+ placeholder={placeholder}
17
+ value={externalValue}
18
+ onChange={(e) => {
19
+ setExternalValue?.(e.target.value);
20
+ onSearch?.(e.target.value);
21
+ }}
22
+ />
23
+ {externalValue && (
24
+ <button
25
+ type="button"
26
+ onClick={() => { setExternalValue?.(''); onSearch?.(''); }}
27
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
28
+ >
29
+ <X className="h-4 w-4" />
30
+ </button>
31
+ )}
32
+ </div>
33
+ );
34
+ };
@@ -0,0 +1,231 @@
1
+ 'use client';
2
+
3
+ import { cn } from '@/lib/utils.util';
4
+ import { Check, ChevronDown, Loader2, Search, X } from 'lucide-react';
5
+ import Image from 'next/image';
6
+ import * as React from 'react';
7
+
8
+ interface Option {
9
+ value: string;
10
+ label: string;
11
+ image?: string;
12
+ flag?: string;
13
+ disabled?: boolean;
14
+ }
15
+
16
+ type RawOption = string | Option;
17
+
18
+ interface SelectFieldProps {
19
+ form: any;
20
+ name: string;
21
+ labelName?: string;
22
+ required?: boolean;
23
+ disabled?: boolean;
24
+ options?: RawOption[];
25
+ placeholder?: string;
26
+ showSearch?: boolean;
27
+ isImageShow?: boolean;
28
+ isFlag?: boolean;
29
+ type?: 'single' | 'multiple';
30
+ viewOnly?: boolean;
31
+ onValueChange?: (value: string | string[]) => void;
32
+ isLoading?: boolean;
33
+ onSearch?: (query: string) => void;
34
+ customMessage?: string;
35
+ }
36
+
37
+ export const SelectField = React.forwardRef<HTMLDivElement, SelectFieldProps>(
38
+ ({
39
+ form, name, labelName, required = false, disabled = false,
40
+ options = [], placeholder = 'Select an option', showSearch = true,
41
+ isImageShow = false, isFlag = false, type = 'single', viewOnly = false,
42
+ onValueChange, isLoading = false, onSearch, customMessage,
43
+ }, ref) => {
44
+ const normalizedOptions: Option[] = React.useMemo(
45
+ () => (options || []).map((opt: any) =>
46
+ typeof opt === 'string' ? { label: opt, value: opt } : opt
47
+ ), [options]
48
+ );
49
+
50
+ const [isOpen, setIsOpen] = React.useState(false);
51
+ const [searchValue, setSearchValue] = React.useState('');
52
+ const [showMore, setShowMore] = React.useState(false);
53
+ const triggerRef = React.useRef<HTMLButtonElement>(null);
54
+ const dropdownRef = React.useRef<HTMLDivElement>(null);
55
+ const searchInputRef = React.useRef<HTMLInputElement>(null);
56
+
57
+ const fieldValue = form.watch(name);
58
+ const selectedValues = React.useMemo<string[]>(() => {
59
+ if (type === 'multiple') return Array.isArray(fieldValue) ? fieldValue : [];
60
+ return fieldValue ? [fieldValue as string] : [];
61
+ }, [fieldValue, type]);
62
+
63
+ const selectedOptions = React.useMemo(
64
+ () => normalizedOptions.filter((opt) => selectedValues.includes(opt.value)),
65
+ [normalizedOptions, selectedValues]
66
+ );
67
+
68
+ const filteredOptions = React.useMemo(() => {
69
+ if (!showSearch || !searchValue) return normalizedOptions;
70
+ return normalizedOptions.filter(
71
+ (opt) =>
72
+ opt.label.toLowerCase().includes(searchValue.toLowerCase()) ||
73
+ opt.value.toLowerCase().includes(searchValue.toLowerCase())
74
+ );
75
+ }, [normalizedOptions, searchValue, showSearch]);
76
+
77
+ const handleSelect = (value: string) => {
78
+ if (type === 'multiple') {
79
+ const newValues = selectedValues.includes(value)
80
+ ? selectedValues.filter((v) => v !== value)
81
+ : [...selectedValues, value];
82
+ form.setValue(name, newValues, { shouldValidate: true, shouldDirty: true, shouldTouch: true });
83
+ onValueChange?.(newValues);
84
+ } else {
85
+ form.setValue(name, value, { shouldValidate: true, shouldDirty: true, shouldTouch: true });
86
+ onValueChange?.(value);
87
+ setIsOpen(false);
88
+ }
89
+ };
90
+
91
+ const handleClear = () => {
92
+ form.setValue(name, type === 'multiple' ? [] : '');
93
+ onValueChange?.(type === 'multiple' ? [] : '');
94
+ setShowMore(false);
95
+ };
96
+
97
+ React.useEffect(() => {
98
+ const handler = (e: MouseEvent) => {
99
+ if (
100
+ dropdownRef.current && !dropdownRef.current.contains(e.target as Node) &&
101
+ !triggerRef.current?.contains(e.target as Node)
102
+ ) setIsOpen(false);
103
+ };
104
+ if (isOpen) {
105
+ document.addEventListener('mousedown', handler);
106
+ searchInputRef.current?.focus();
107
+ }
108
+ return () => document.removeEventListener('mousedown', handler);
109
+ }, [isOpen]);
110
+
111
+ const isInvalid = form.formState.errors[name];
112
+
113
+ return (
114
+ <div ref={ref} className="w-full space-y-2">
115
+ {labelName && (
116
+ <label className="text-sm font-medium">
117
+ {labelName}
118
+ {required && <span className="text-destructive ml-1">*</span>}
119
+ </label>
120
+ )}
121
+
122
+ {viewOnly ? (
123
+ <div className="py-2 px-3 text-sm text-foreground bg-background rounded-md border border-border min-h-10">
124
+ {type === 'multiple'
125
+ ? selectedOptions.map((o) => o.label).join(', ')
126
+ : selectedOptions[0]?.label || ''}
127
+ </div>
128
+ ) : (
129
+ <div className="relative">
130
+ <button
131
+ ref={triggerRef}
132
+ type="button"
133
+ disabled={disabled}
134
+ onClick={() => !disabled && setIsOpen((p) => !p)}
135
+ className={cn(
136
+ 'w-full min-h-11 rounded-lg border px-3 py-2 flex items-center justify-between gap-2',
137
+ 'hover:bg-muted/50 transition-colors text-sm',
138
+ isInvalid && 'border-destructive',
139
+ disabled && 'opacity-50 cursor-not-allowed',
140
+ )}
141
+ >
142
+ <div className="flex flex-wrap gap-2 flex-1">
143
+ {selectedOptions.length === 0 && (
144
+ <span className="text-muted-foreground">{placeholder}</span>
145
+ )}
146
+ {type === 'single' && selectedOptions[0] && (
147
+ <div className="flex items-center gap-2">
148
+ {isFlag && selectedOptions[0].flag && <span>{selectedOptions[0].flag}</span>}
149
+ {isImageShow && selectedOptions[0].image && (
150
+ <Image src={selectedOptions[0].image} width={20} height={20} className="w-5 h-5 rounded-full object-cover" alt="" />
151
+ )}
152
+ <span>{selectedOptions[0].label}</span>
153
+ </div>
154
+ )}
155
+ {type === 'multiple' && selectedOptions.slice(0, showMore ? undefined : 2).map((opt) => (
156
+ <span key={opt.value} className="flex items-center gap-1 px-2 py-1 text-xs bg-primary/10 text-primary rounded">
157
+ {opt.label}
158
+ <X className="w-3 h-3 cursor-pointer hover:text-destructive" onClick={(e) => { e.stopPropagation(); handleSelect(opt.value); }} />
159
+ </span>
160
+ ))}
161
+ {type === 'multiple' && !showMore && selectedOptions.length > 2 && (
162
+ <span className="text-xs text-primary cursor-pointer" onClick={(e) => { e.stopPropagation(); setShowMore(true); }}>
163
+ +{selectedOptions.length - 2} more
164
+ </span>
165
+ )}
166
+ {type === 'multiple' && showMore && selectedOptions.length > 2 && (
167
+ <span className="text-xs text-primary cursor-pointer" onClick={(e) => { e.stopPropagation(); setShowMore(false); }}>
168
+ show less
169
+ </span>
170
+ )}
171
+ </div>
172
+ <div className="flex items-center gap-1">
173
+ {selectedOptions.length > 0 && (
174
+ <X className="w-4 h-4 cursor-pointer hover:text-destructive" onClick={handleClear} />
175
+ )}
176
+ {isLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <ChevronDown className="w-4 h-4" />}
177
+ </div>
178
+ </button>
179
+
180
+ {isOpen && (
181
+ <div ref={dropdownRef} className="absolute z-50 w-full mt-1 bg-popover border rounded-lg shadow-md">
182
+ {showSearch && (
183
+ <div className="p-2 border-b">
184
+ <div className="relative">
185
+ <Search className="absolute left-2 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
186
+ <input
187
+ ref={searchInputRef}
188
+ className="w-full pl-8 pr-3 py-2 border rounded-md text-sm bg-background"
189
+ placeholder="Search..."
190
+ value={searchValue}
191
+ onChange={(e) => { setSearchValue(e.target.value); onSearch?.(e.target.value); }}
192
+ />
193
+ </div>
194
+ </div>
195
+ )}
196
+ <div className="max-h-60 overflow-y-auto">
197
+ {filteredOptions.length === 0 && (
198
+ <p className="p-3 text-sm text-center text-muted-foreground">{customMessage || 'No options found'}</p>
199
+ )}
200
+ {filteredOptions.map((opt, idx) => {
201
+ const active = selectedValues.includes(opt.value);
202
+ return (
203
+ <button key={opt.value + idx} disabled={opt.disabled} onClick={() => handleSelect(opt.value)}
204
+ className={cn('w-full px-3 py-2 text-left text-sm flex items-center gap-2 hover:bg-muted', active && 'bg-primary/10 text-primary', opt.disabled && 'opacity-50 cursor-not-allowed')}
205
+ >
206
+ {type === 'multiple' && (
207
+ <span className="w-4 h-4 border rounded flex items-center justify-center">
208
+ {active && <Check className="w-3 h-3" />}
209
+ </span>
210
+ )}
211
+ {isFlag && opt.flag && <span>{opt.flag}</span>}
212
+ {isImageShow && opt.image && (
213
+ <Image src={opt.image} width={20} height={20} className="w-5 h-5 rounded-full" alt="" />
214
+ )}
215
+ <span className="flex-1">{opt.label}</span>
216
+ {type === 'single' && active && <Check className="w-4 h-4" />}
217
+ </button>
218
+ );
219
+ })}
220
+ </div>
221
+ </div>
222
+ )}
223
+ </div>
224
+ )}
225
+
226
+ {isInvalid && <p className="text-xs text-destructive">{String(form.formState.errors[name]?.message)}</p>}
227
+ </div>
228
+ );
229
+ }
230
+ );
231
+ SelectField.displayName = 'SelectField';
@@ -0,0 +1,42 @@
1
+ import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form.component';
2
+ import { Checkbox } from '@/components/ui/checkbox.component';
3
+
4
+ export const SingleCheckField = ({
5
+ form, name, labelName, required = false, disabled = false,
6
+ description,
7
+ }: {
8
+ form?: any; name?: string; labelName?: string; required?: boolean; disabled?: boolean; description?: string;
9
+ }) => {
10
+ if (form) {
11
+ return (
12
+ <FormField
13
+ control={form.control}
14
+ name={name || 'checkbox'}
15
+ render={({ field }) => (
16
+ <FormItem>
17
+ <div className="flex items-center space-x-2">
18
+ <FormControl>
19
+ <Checkbox checked={field.value} onCheckedChange={field.onChange} disabled={disabled} />
20
+ </FormControl>
21
+ <FormLabel className="font-normal">
22
+ {labelName}
23
+ {required && <span className="text-destructive ml-1">*</span>}
24
+ </FormLabel>
25
+ </div>
26
+ {description && <p className="text-sm text-muted-foreground ml-6">{description}</p>}
27
+ <FormMessage />
28
+ </FormItem>
29
+ )}
30
+ />
31
+ );
32
+ }
33
+
34
+ return (
35
+ <div className="flex items-center space-x-2">
36
+ <Checkbox disabled={disabled} />
37
+ <label className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
38
+ {labelName}
39
+ </label>
40
+ </div>
41
+ );
42
+ };