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.
- package/AGENTS.md +122 -0
- package/LICENSE +21 -0
- package/README.md +103 -0
- package/package.json +99 -0
- package/scaffold/generator.js +409 -0
- package/scaffold/index.js +20 -0
- package/scaffold/prompts.js +108 -0
- package/templates/api/axios/src/api/axios/client.api.ts +30 -0
- package/templates/api/axios/src/api/axios/users.api.ts +15 -0
- package/templates/api/fetch/src/api/fetch/client.api.ts +68 -0
- package/templates/api/fetch/src/api/fetch/users.api.ts +15 -0
- package/templates/api/trpc/src/api/trpc/client.api.ts +4 -0
- package/templates/api/trpc/src/api/trpc/router.api.ts +15 -0
- package/templates/api/trpc/src/api/trpc/server.client.api.ts +4 -0
- package/templates/api/trpc/src/providers/trpc.provider.tsx +24 -0
- package/templates/auth/clerk/src/auth/clerk/auth.service.ts +4 -0
- package/templates/auth/clerk/src/hooks/use-auth.hook.ts +13 -0
- package/templates/auth/clerk/src/middleware.ts +7 -0
- package/templates/auth/clerk/src/providers/auth.provider.tsx +6 -0
- package/templates/auth/next-auth/src/app/api/auth/[...nextauth]/route.ts +5 -0
- package/templates/auth/next-auth/src/auth/next-auth/auth.service.ts +45 -0
- package/templates/auth/next-auth/src/hooks/use-session.hook.ts +13 -0
- package/templates/auth/next-auth/src/providers/session.provider.tsx +6 -0
- package/templates/forms/formik/src/components/forms/login-form.component.tsx +30 -0
- package/templates/forms/formik/src/forms/formik/hooks/use-form-config.hook.ts +7 -0
- package/templates/forms/formik/src/forms/formik/schemas/example.schema.ts +8 -0
- package/templates/forms/react-hook-form/src/components/forms/login-form.component.tsx +27 -0
- package/templates/forms/react-hook-form/src/forms/react-hook-form/hooks/use-form.hook.ts +13 -0
- package/templates/forms/react-hook-form/src/forms/react-hook-form/schemas/example.schema.ts +15 -0
- package/templates/nextjs-base/next.config.ts +5 -0
- package/templates/nextjs-base/postcss.config.mjs +9 -0
- package/templates/nextjs-base/src/app/_components/navbar.tsx +88 -0
- package/templates/nextjs-base/src/app/_components/sidebar.tsx +223 -0
- package/templates/nextjs-base/src/app/error.tsx +39 -0
- package/templates/nextjs-base/src/app/globals.css +71 -0
- package/templates/nextjs-base/src/app/layout.tsx +21 -0
- package/templates/nextjs-base/src/app/loading.tsx +13 -0
- package/templates/nextjs-base/src/app/not-found.tsx +22 -0
- package/templates/nextjs-base/src/app/page.tsx +10 -0
- package/templates/nextjs-base/tailwind.config.ts +69 -0
- package/templates/shared/src/components/common/theme-toggle.component.tsx +31 -0
- package/templates/shared/src/components/common/toast/custom-message.component.tsx +18 -0
- package/templates/shared/src/components/common/toast/index.ts +8 -0
- package/templates/shared/src/components/common/toast/toast-message.component.tsx +112 -0
- package/templates/shared/src/hooks/use-debounce.hook.ts +12 -0
- package/templates/shared/src/hooks/use-fetch.hook.ts +42 -0
- package/templates/shared/src/hooks/use-intersection-observer.hook.ts +39 -0
- package/templates/shared/src/hooks/use-local-storage.hook.ts +30 -0
- package/templates/shared/src/hooks/use-media-query.hook.ts +26 -0
- package/templates/shared/src/hooks/use-toggle.hook.ts +12 -0
- package/templates/shared/src/lib/utils.util.ts +361 -0
- package/templates/shared/src/providers/theme.provider.tsx +17 -0
- package/templates/shared/src/providers/toast.provider.tsx +32 -0
- package/templates/shared/src/types/common.type.ts +34 -0
- package/templates/state/context/src/store/context/auth.context.tsx +47 -0
- package/templates/state/context/src/store/context/counter.context.tsx +41 -0
- package/templates/state/context/src/store/context/index.ts +2 -0
- package/templates/state/redux/src/providers/redux.provider.tsx +7 -0
- package/templates/state/redux/src/store/redux/hooks.store.ts +5 -0
- package/templates/state/redux/src/store/redux/index.ts +4 -0
- package/templates/state/redux/src/store/redux/slices/api.slice.ts +8 -0
- package/templates/state/redux/src/store/redux/slices/counter.slice.ts +24 -0
- package/templates/state/redux/src/store/redux/store.store.ts +13 -0
- package/templates/state/zustand/src/store/zustand/counter.store.ts +15 -0
- package/templates/state/zustand/src/store/zustand/index.ts +2 -0
- package/templates/state/zustand/src/store/zustand/user.store.ts +32 -0
- package/templates/ui/antd/COMPONENT_GUIDE.md +326 -0
- package/templates/ui/antd/src/app/examples/dialog/page.tsx +205 -0
- package/templates/ui/antd/src/app/examples/form/page.tsx +160 -0
- package/templates/ui/antd/src/app/examples/layout.tsx +125 -0
- package/templates/ui/antd/src/app/examples/page.tsx +64 -0
- package/templates/ui/antd/src/app/examples/table/page.tsx +118 -0
- package/templates/ui/antd/src/app/page.tsx +283 -0
- package/templates/ui/antd/src/components/common/DynamicTable/dynamic-table.component.tsx +79 -0
- package/templates/ui/antd/src/components/common/button/action-button.component.tsx +63 -0
- package/templates/ui/antd/src/components/common/dialog/dialog-wrapper.component.tsx +63 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/check-field.component.tsx +55 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/date-picker-field.component.tsx +80 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/limit-field.component.tsx +26 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/multi-check-field.component.tsx +56 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/number-field.component.tsx +100 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/otp-field.component.tsx +63 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/password-field.component.tsx +106 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/phone-number-field.component.tsx +78 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/radio-field.component.tsx +55 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/range-date-picker.component.tsx +66 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/search-field.component.tsx +24 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/select-field.component.tsx +82 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/single-check-field.component.tsx +50 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/single-select-field.component.tsx +86 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/string-number-field.component.tsx +80 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/switch-field.component.tsx +62 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/text-area-field.component.tsx +85 -0
- package/templates/ui/antd/src/components/common/fields/assets/components/text-field.component.tsx +88 -0
- package/templates/ui/antd/src/components/common/fields/assets/interface/input-props.type.ts +233 -0
- package/templates/ui/antd/src/components/common/fields/cusInputField.component.tsx +40 -0
- package/templates/ui/antd/src/components/common/pagination/pagination.component.tsx +27 -0
- package/templates/ui/antd/src/components/ui/avatar.component.tsx +8 -0
- package/templates/ui/antd/src/components/ui/badge.component.tsx +8 -0
- package/templates/ui/antd/src/components/ui/button.component.tsx +8 -0
- package/templates/ui/antd/src/components/ui/card.component.tsx +8 -0
- package/templates/ui/antd/src/components/ui/checkbox.component.tsx +8 -0
- package/templates/ui/antd/src/components/ui/dialog.component.tsx +9 -0
- package/templates/ui/antd/src/components/ui/dropdown-menu.component.tsx +10 -0
- package/templates/ui/antd/src/components/ui/form.component.tsx +12 -0
- package/templates/ui/antd/src/components/ui/input.component.tsx +13 -0
- package/templates/ui/antd/src/components/ui/label.component.tsx +18 -0
- package/templates/ui/antd/src/components/ui/popover.component.tsx +8 -0
- package/templates/ui/antd/src/components/ui/progress.component.tsx +8 -0
- package/templates/ui/antd/src/components/ui/radio-group.component.tsx +10 -0
- package/templates/ui/antd/src/components/ui/scroll-area.component.tsx +25 -0
- package/templates/ui/antd/src/components/ui/select.component.tsx +8 -0
- package/templates/ui/antd/src/components/ui/separator.component.tsx +8 -0
- package/templates/ui/antd/src/components/ui/sheet.component.tsx +8 -0
- package/templates/ui/antd/src/components/ui/switch.component.tsx +8 -0
- package/templates/ui/antd/src/components/ui/table.component.tsx +8 -0
- package/templates/ui/antd/src/components/ui/tabs.component.tsx +8 -0
- package/templates/ui/antd/src/components/ui/textarea.component.tsx +9 -0
- package/templates/ui/antd/src/components/ui/tooltip.component.tsx +8 -0
- package/templates/ui/antd/src/lib/theme.util.ts +40 -0
- package/templates/ui/antd/src/providers/antd.provider.tsx +13 -0
- package/templates/ui/mui/src/app/examples/layout.tsx +113 -0
- package/templates/ui/mui/src/app/examples/page.tsx +716 -0
- package/templates/ui/mui/src/app/page.tsx +298 -0
- package/templates/ui/mui/src/components/common/DynamicTable/dynamic-table.component.tsx +131 -0
- package/templates/ui/mui/src/components/common/button/action-button.component.tsx +57 -0
- package/templates/ui/mui/src/components/common/dialog/dialog-wrapper.component.tsx +55 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/check-field.component.tsx +51 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/date-picker-field.component.tsx +50 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/multi-check-field.component.tsx +14 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/number-field.component.tsx +59 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/password-field.component.tsx +87 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/phone-number-field.component.tsx +48 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/radio-field.component.tsx +37 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/search-field.component.tsx +41 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/select-field.component.tsx +77 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/single-check-field.component.tsx +39 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/single-select-field.component.tsx +56 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/string-number-field.component.tsx +52 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/switch-field.component.tsx +35 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/text-area-field.component.tsx +46 -0
- package/templates/ui/mui/src/components/common/fields/assets/components/text-field.component.tsx +51 -0
- package/templates/ui/mui/src/components/common/fields/assets/interface/input-props.type.ts +193 -0
- package/templates/ui/mui/src/components/common/fields/cusInputField.component.tsx +34 -0
- package/templates/ui/mui/src/components/common/pagination/pagination.component.tsx +59 -0
- package/templates/ui/mui/src/components/ui/avatar.component.tsx +19 -0
- package/templates/ui/mui/src/components/ui/badge.component.tsx +18 -0
- package/templates/ui/mui/src/components/ui/button.component.tsx +22 -0
- package/templates/ui/mui/src/components/ui/card.component.tsx +39 -0
- package/templates/ui/mui/src/components/ui/checkbox.component.tsx +21 -0
- package/templates/ui/mui/src/components/ui/dialog.component.tsx +38 -0
- package/templates/ui/mui/src/components/ui/dropdown-menu.component.tsx +43 -0
- package/templates/ui/mui/src/components/ui/form.component.tsx +98 -0
- package/templates/ui/mui/src/components/ui/input.component.tsx +15 -0
- package/templates/ui/mui/src/components/ui/label.component.tsx +15 -0
- package/templates/ui/mui/src/components/ui/popover.component.tsx +20 -0
- package/templates/ui/mui/src/components/ui/progress.component.tsx +19 -0
- package/templates/ui/mui/src/components/ui/radio-group.component.tsx +25 -0
- package/templates/ui/mui/src/components/ui/scroll-area.component.tsx +27 -0
- package/templates/ui/mui/src/components/ui/select.component.tsx +26 -0
- package/templates/ui/mui/src/components/ui/separator.component.tsx +11 -0
- package/templates/ui/mui/src/components/ui/sheet.component.tsx +44 -0
- package/templates/ui/mui/src/components/ui/switch.component.tsx +23 -0
- package/templates/ui/mui/src/components/ui/table.component.tsx +34 -0
- package/templates/ui/mui/src/components/ui/tabs.component.tsx +38 -0
- package/templates/ui/mui/src/components/ui/textarea.component.tsx +18 -0
- package/templates/ui/mui/src/components/ui/tooltip.component.tsx +24 -0
- package/templates/ui/mui/src/lib/theme.util.ts +73 -0
- package/templates/ui/mui/src/providers/mui.provider.tsx +13 -0
- package/templates/ui/shadcn/COMPONENT_GUIDE.md +306 -0
- package/templates/ui/shadcn/src/app/examples/dialog/page.tsx +122 -0
- package/templates/ui/shadcn/src/app/examples/form/page.tsx +107 -0
- package/templates/ui/shadcn/src/app/examples/layout.tsx +24 -0
- package/templates/ui/shadcn/src/app/examples/page.tsx +30 -0
- package/templates/ui/shadcn/src/app/examples/table/page.tsx +77 -0
- package/templates/ui/shadcn/src/app/page.tsx +20 -0
- package/templates/ui/shadcn/src/components/common/DynamicTable/dynamic-table.component.tsx +136 -0
- package/templates/ui/shadcn/src/components/common/button/action-button.component.tsx +68 -0
- package/templates/ui/shadcn/src/components/common/dialog/dialog-wrapper.component.tsx +58 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/check-field.component.tsx +52 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/date-picker-field.component.tsx +62 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/dynamic-file-upload-field.component.tsx +152 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/limit-field.component.tsx +73 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/multi-check-field.component.tsx +46 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/number-field.component.tsx +124 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/otp-field.component.tsx +61 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/password-field.component.tsx +110 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/phone-number-field.component.tsx +90 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/radio-field.component.tsx +41 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/range-date-picker.component.tsx +71 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/rich-text-editor.component.tsx +91 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/search-field.component.tsx +34 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/select-field.component.tsx +231 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/single-check-field.component.tsx +42 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/single-select-field.component.tsx +82 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/string-number-field.component.tsx +68 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/switch-field.component.tsx +61 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/text-area-field.component.tsx +62 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/text-area-with-file.component.tsx +142 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/text-field.component.tsx +80 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/tiny-editor.component.tsx +51 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/upload-profile-picture.component.tsx +103 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/components/upload-video-file.component.tsx +86 -0
- package/templates/ui/shadcn/src/components/common/fields/assets/interface/input-props.type.ts +198 -0
- package/templates/ui/shadcn/src/components/common/fields/cusInputField.component.tsx +52 -0
- package/templates/ui/shadcn/src/components/common/pagination/pagination.component.tsx +68 -0
- package/templates/ui/shadcn/src/components/ui/avatar.component.tsx +37 -0
- package/templates/ui/shadcn/src/components/ui/badge.component.tsx +28 -0
- package/templates/ui/shadcn/src/components/ui/button.component.tsx +52 -0
- package/templates/ui/shadcn/src/components/ui/card.component.tsx +46 -0
- package/templates/ui/shadcn/src/components/ui/checkbox.component.tsx +25 -0
- package/templates/ui/shadcn/src/components/ui/dialog.component.tsx +98 -0
- package/templates/ui/shadcn/src/components/ui/dropdown-menu.component.tsx +163 -0
- package/templates/ui/shadcn/src/components/ui/form.component.tsx +110 -0
- package/templates/ui/shadcn/src/components/ui/input-otp.component.tsx +64 -0
- package/templates/ui/shadcn/src/components/ui/input.component.tsx +23 -0
- package/templates/ui/shadcn/src/components/ui/label.component.tsx +23 -0
- package/templates/ui/shadcn/src/components/ui/popover.component.tsx +27 -0
- package/templates/ui/shadcn/src/components/ui/progress.component.tsx +22 -0
- package/templates/ui/shadcn/src/components/ui/radio-group.component.tsx +33 -0
- package/templates/ui/shadcn/src/components/ui/scroll-area.component.tsx +37 -0
- package/templates/ui/shadcn/src/components/ui/select.component.tsx +139 -0
- package/templates/ui/shadcn/src/components/ui/separator.component.tsx +23 -0
- package/templates/ui/shadcn/src/components/ui/sheet.component.tsx +89 -0
- package/templates/ui/shadcn/src/components/ui/switch.component.tsx +26 -0
- package/templates/ui/shadcn/src/components/ui/table.component.tsx +71 -0
- package/templates/ui/shadcn/src/components/ui/tabs.component.tsx +52 -0
- package/templates/ui/shadcn/src/components/ui/textarea.component.tsx +20 -0
- 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"> *</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
|
+
};
|
package/templates/ui/shadcn/src/components/common/fields/assets/components/radio-field.component.tsx
ADDED
|
@@ -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"> *</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"> *</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"> *</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
|
+
};
|