@komensal/ui 0.1.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 (42) hide show
  1. package/package.json +48 -0
  2. package/src/components/alert.tsx +45 -0
  3. package/src/components/auth-header.tsx +26 -0
  4. package/src/components/button.tsx +121 -0
  5. package/src/components/command.tsx +125 -0
  6. package/src/components/dialog.tsx +118 -0
  7. package/src/components/form-fields/index.ts +5 -0
  8. package/src/components/form-fields/input-field.tsx +131 -0
  9. package/src/components/form-fields/label-field.tsx +20 -0
  10. package/src/components/form-fields/map-picker-field.tsx +49 -0
  11. package/src/components/form-fields/otp-field.tsx +76 -0
  12. package/src/components/form-fields/select-field.tsx +77 -0
  13. package/src/components/form.tsx +138 -0
  14. package/src/components/google-map-picker.tsx +243 -0
  15. package/src/components/input.tsx +40 -0
  16. package/src/components/label.tsx +17 -0
  17. package/src/components/loading.tsx +19 -0
  18. package/src/components/otp-input.tsx +62 -0
  19. package/src/components/password-checklist.tsx +37 -0
  20. package/src/components/popover.tsx +31 -0
  21. package/src/components/select-input.tsx +236 -0
  22. package/src/components/select.tsx +143 -0
  23. package/src/components/spinner.tsx +61 -0
  24. package/src/components/step-header.tsx +16 -0
  25. package/src/components/stepper.tsx +69 -0
  26. package/src/components/toast/index.ts +2 -0
  27. package/src/components/toast/toast-provider.tsx +123 -0
  28. package/src/components/toast/toast.tsx +106 -0
  29. package/src/components/tooltip.tsx +56 -0
  30. package/src/components/wrapper.tsx +12 -0
  31. package/src/index.ts +27 -0
  32. package/src/locales/es/ui.json +28 -0
  33. package/src/locales/index.ts +7 -0
  34. package/src/styles/index.css +1 -0
  35. package/src/styles/theme.css +105 -0
  36. package/src/types/password.ts +12 -0
  37. package/src/types/toast.ts +19 -0
  38. package/src/utils/cn.ts +6 -0
  39. package/src/utils/index.ts +3 -0
  40. package/src/utils/styles.ts +19 -0
  41. package/src/utils/toast-emitter.ts +37 -0
  42. package/src/vite-env.d.ts +3 -0
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@komensal/ui",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./src/index.ts",
7
+ "./styles": "./src/styles/index.css",
8
+ "./locales": "./src/locales/index.ts"
9
+ },
10
+ "peerDependencies": {
11
+ "react": "^19.0.0",
12
+ "react-dom": "^19.0.0"
13
+ },
14
+ "dependencies": {
15
+ "@radix-ui/react-dialog": "^1.1.15",
16
+ "@radix-ui/react-icons": "^1.3.2",
17
+ "@radix-ui/react-label": "^2.1.8",
18
+ "@radix-ui/react-popover": "^1.1.15",
19
+ "@radix-ui/react-select": "^2.2.6",
20
+ "@radix-ui/react-slot": "^1.2.4",
21
+ "@radix-ui/react-tooltip": "^1.2.8",
22
+ "@vis.gl/react-google-maps": "^1.8.3",
23
+ "class-variance-authority": "^0.7.1",
24
+ "clsx": "^2.1.1",
25
+ "cmdk": "^1.1.1",
26
+ "input-otp": "^1.4.2",
27
+ "lucide-react": "^0.575.0",
28
+ "react-hook-form": "^7.71.1",
29
+ "react-i18next": "^16.5.4",
30
+ "react-icons": "^5.5.0",
31
+ "tailwind-merge": "^3.5.0"
32
+ },
33
+ "devDependencies": {
34
+ "@types/google.maps": "^3.64.0",
35
+ "@types/react": "^19.2.14",
36
+ "@types/react-dom": "^19.2.3",
37
+ "typescript": "~5.9.3"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "files": [
43
+ "src"
44
+ ],
45
+ "scripts": {
46
+ "typecheck": "tsc --noEmit"
47
+ }
48
+ }
@@ -0,0 +1,45 @@
1
+ import { AlertCircle, CheckCircle2, Info, TriangleAlert } from 'lucide-react'
2
+ import * as React from 'react'
3
+
4
+ import { cn } from '../utils'
5
+
6
+ interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
7
+ variant?: AlertVariant
8
+ }
9
+
10
+ type AlertVariant = 'error' | 'info' | 'success' | 'warning'
11
+
12
+ const variantStyles: Record<AlertVariant, { container: string; icon: string }> = {
13
+ error: { container: 'bg-destructive-subtle text-destructive', icon: 'text-destructive' },
14
+ info: { container: 'bg-info-subtle text-info', icon: 'text-info' },
15
+ success: { container: 'bg-success-subtle text-success', icon: 'text-success' },
16
+ warning: { container: 'bg-warning-subtle text-warning', icon: 'text-warning' }
17
+ }
18
+
19
+ const variantIcons: Record<AlertVariant, React.ComponentType<{ className?: string }>> = {
20
+ error: AlertCircle,
21
+ info: Info,
22
+ success: CheckCircle2,
23
+ warning: TriangleAlert
24
+ }
25
+
26
+ export const Alert = ({ children, className, variant = 'error', ...props }: AlertProps) => {
27
+ const styles = variantStyles[variant]
28
+ const Icon = variantIcons[variant]
29
+
30
+ return (
31
+ <div
32
+ aria-live='polite'
33
+ className={cn(
34
+ 'flex items-center justify-center gap-2 rounded-[10px] px-4 py-3 text-sm',
35
+ styles.container,
36
+ className
37
+ )}
38
+ role='alert'
39
+ {...props}
40
+ >
41
+ <Icon className={cn('h-5 w-5 shrink-0', styles.icon)} />
42
+ <span>{children}</span>
43
+ </div>
44
+ )
45
+ }
@@ -0,0 +1,26 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import { cn } from '../utils'
4
+
5
+ interface AuthHeaderProps {
6
+ children?: ReactNode
7
+ logoAlt: string
8
+ mobileClassName?: string
9
+ }
10
+
11
+ export const AuthHeader = ({ children, logoAlt, mobileClassName }: AuthHeaderProps) => {
12
+ return (
13
+ <>
14
+ <div className={cn('flex min-h-0 shrink flex-col lg:hidden', mobileClassName)}>
15
+ <div className='flex justify-center py-2'>
16
+ <img alt={logoAlt} className='h-11 md:h-12' src='/logotipo_color.svg' />
17
+ </div>
18
+ {children}
19
+ </div>
20
+
21
+ <div className='absolute hidden p-10 lg:flex'>
22
+ <img alt={logoAlt} className='h-12.5 w-12.5' src='/logo_color.svg' />
23
+ </div>
24
+ </>
25
+ )
26
+ }
@@ -0,0 +1,121 @@
1
+ import { Slot } from '@radix-ui/react-slot'
2
+ import { cva, type VariantProps } from 'class-variance-authority'
3
+ import * as React from 'react'
4
+
5
+ import { cn } from '../utils'
6
+ import { Spinner } from './spinner'
7
+ import { Tooltip } from './tooltip'
8
+
9
+ const buttonVariants = cva(
10
+ 'w-full inline-flex items-center justify-center gap-[10px] whitespace-nowrap rounded-[12px] border border-transparent text-base font-semibold transition-all duration-300 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:bg-inactive disabled:text-inactive-text disabled:border-transparent cursor-pointer',
11
+ {
12
+ defaultVariants: {
13
+ hoverStyle: 'none',
14
+ size: 'default',
15
+ variant: 'primary'
16
+ },
17
+ variants: {
18
+ hoverStyle: {
19
+ highlight: `
20
+ hover:bg-brand-subtle hover:border-primary hover:text-primary
21
+ `,
22
+ none: '',
23
+ shine: `
24
+ relative overflow-hidden
25
+ hover:-translate-y-[1px] hover:shadow-lg
26
+ before:content-[''] before:absolute before:top-0 before:left-[-100%]
27
+ before:w-full before:h-full
28
+ before:bg-[linear-gradient(90deg,transparent,rgba(255,255,255,0.15),transparent)]
29
+ before:transition-all before:duration-300
30
+ hover:before:left-[100%]
31
+ `
32
+ },
33
+ size: {
34
+ default: 'h-14 px-4',
35
+ icon: 'h-12 w-12 p-0',
36
+ lg: 'h-14 px-12',
37
+ sm: 'h-9 px-3 text-sm'
38
+ },
39
+ variant: {
40
+ destructive: 'bg-destructive text-inverse hover:bg-destructive/90',
41
+ ghost: 'bg-transparent border border-border text-foreground hover:bg-hover',
42
+ invisible: 'bg-transparent rounded-none',
43
+ link: 'text-muted underline-offset-4 hover:underline text-sm font-normal disabled:bg-transparent',
44
+ outline: 'bg-surface border border-primary text-primary hover:bg-brand-subtle',
45
+ primary: 'bg-primary text-inverse hover:bg-primary-hover active:bg-primary-dark',
46
+ tertiary: 'bg-subtle border border-border text-foreground hover:bg-hover',
47
+ underline: 'text-primary underline'
48
+ }
49
+ }
50
+ }
51
+ )
52
+
53
+ export interface ButtonProps
54
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
55
+ asChild?: boolean
56
+ childrenClassName?: string
57
+ hoverStyle?: 'highlight' | 'none' | 'shine'
58
+ icon?: React.ReactNode
59
+ iconLeft?: React.ReactNode
60
+ iconRight?: React.ReactNode
61
+ isLoading?: boolean
62
+ sideTooltip?: 'bottom' | 'left' | 'right' | 'top'
63
+ tooltip?: string
64
+ }
65
+
66
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
67
+ (
68
+ {
69
+ asChild = false,
70
+ children,
71
+ childrenClassName,
72
+ className,
73
+ disabled,
74
+ hoverStyle,
75
+ icon,
76
+ iconLeft,
77
+ iconRight,
78
+ isLoading = false,
79
+ sideTooltip,
80
+ size,
81
+ tooltip,
82
+ variant,
83
+ ...props
84
+ },
85
+ ref
86
+ ) => {
87
+ const Comp = asChild ? Slot : 'button'
88
+
89
+ const buttonNode = (
90
+ <Comp
91
+ className={cn(buttonVariants({ hoverStyle, size, variant }), isLoading && 'relative opacity-40', className)}
92
+ disabled={disabled === true || isLoading}
93
+ ref={ref}
94
+ {...props}
95
+ >
96
+ {iconLeft}
97
+ {icon}
98
+ <span className={cn('flex w-full items-center justify-center gap-2', childrenClassName)}>{children}</span>
99
+ {iconRight}
100
+ {isLoading && (
101
+ <div className='absolute inset-0 flex items-center justify-center bg-black/10 backdrop-blur-sm'>
102
+ <Spinner size='sm' />
103
+ </div>
104
+ )}
105
+ </Comp>
106
+ )
107
+
108
+ return tooltip ? (
109
+ <Tooltip childrenClassName='flex items-center justify-center' message={tooltip} sideTooltip={sideTooltip}>
110
+ {buttonNode}
111
+ </Tooltip>
112
+ ) : (
113
+ buttonNode
114
+ )
115
+ }
116
+ )
117
+
118
+ Button.displayName = 'Button'
119
+
120
+ // eslint-disable-next-line react-refresh/only-export-components
121
+ export { Button, buttonVariants }
@@ -0,0 +1,125 @@
1
+ import { type DialogProps } from '@radix-ui/react-dialog'
2
+ import { MagnifyingGlassIcon } from '@radix-ui/react-icons'
3
+ import { Command as CommandPrimitive } from 'cmdk'
4
+ import * as React from 'react'
5
+
6
+ import { cn } from '../utils'
7
+ import { Dialog, DialogContent } from './dialog'
8
+
9
+ const Command = React.forwardRef<
10
+ React.ElementRef<typeof CommandPrimitive>,
11
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive>
12
+ >(({ className, ...props }, ref) => (
13
+ <CommandPrimitive
14
+ className={cn('bg-surface text-foreground flex h-full w-full flex-col overflow-hidden rounded-md', className)}
15
+ ref={ref}
16
+ {...props}
17
+ />
18
+ ))
19
+ Command.displayName = CommandPrimitive.displayName
20
+
21
+ type CommandDialogProps = DialogProps
22
+
23
+ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
24
+ return (
25
+ <Dialog {...props}>
26
+ <DialogContent className='overflow-hidden p-0'>
27
+ <Command className='**:[[cmdk-group-heading]]:text-muted [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5 **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group]]:px-2 **:[[cmdk-input]]:h-12 **:[[cmdk-item]]:px-2 **:[[cmdk-item]]:py-3'>
28
+ {children}
29
+ </Command>
30
+ </DialogContent>
31
+ </Dialog>
32
+ )
33
+ }
34
+
35
+ const CommandInput = React.forwardRef<
36
+ React.ElementRef<typeof CommandPrimitive.Input>,
37
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
38
+ >(({ className, ...props }, ref) => (
39
+ <div className='flex items-center border-b px-3' cmdk-input-wrapper=''>
40
+ <MagnifyingGlassIcon className='mr-2 h-4 w-4 shrink-0 opacity-50' />
41
+ <CommandPrimitive.Input
42
+ className={cn(
43
+ 'placeholder:text-placeholder flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50',
44
+ className
45
+ )}
46
+ ref={ref}
47
+ {...props}
48
+ />
49
+ </div>
50
+ ))
51
+
52
+ CommandInput.displayName = CommandPrimitive.Input.displayName
53
+
54
+ const CommandList = React.forwardRef<
55
+ React.ElementRef<typeof CommandPrimitive.List>,
56
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
57
+ >(({ className, ...props }, ref) => (
58
+ <CommandPrimitive.List className={cn('max-h-75 overflow-x-hidden overflow-y-auto', className)} ref={ref} {...props} />
59
+ ))
60
+
61
+ CommandList.displayName = CommandPrimitive.List.displayName
62
+
63
+ const CommandEmpty = React.forwardRef<
64
+ React.ElementRef<typeof CommandPrimitive.Empty>,
65
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
66
+ >((props, ref) => <CommandPrimitive.Empty className='py-6 text-center text-sm' ref={ref} {...props} />)
67
+
68
+ CommandEmpty.displayName = CommandPrimitive.Empty.displayName
69
+
70
+ const CommandGroup = React.forwardRef<
71
+ React.ElementRef<typeof CommandPrimitive.Group>,
72
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
73
+ >(({ className, ...props }, ref) => (
74
+ <CommandPrimitive.Group
75
+ className={cn(
76
+ 'text-foreground **:[[cmdk-group-heading]]:text-muted overflow-hidden p-1 **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium',
77
+ className
78
+ )}
79
+ ref={ref}
80
+ {...props}
81
+ />
82
+ ))
83
+
84
+ CommandGroup.displayName = CommandPrimitive.Group.displayName
85
+
86
+ const CommandSeparator = React.forwardRef<
87
+ React.ElementRef<typeof CommandPrimitive.Separator>,
88
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
89
+ >(({ className, ...props }, ref) => (
90
+ <CommandPrimitive.Separator className={cn('bg-border -mx-1 h-px', className)} ref={ref} {...props} />
91
+ ))
92
+ CommandSeparator.displayName = CommandPrimitive.Separator.displayName
93
+
94
+ const CommandItem = React.forwardRef<
95
+ React.ElementRef<typeof CommandPrimitive.Item>,
96
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
97
+ >(({ className, ...props }, ref) => (
98
+ <CommandPrimitive.Item
99
+ className={cn(
100
+ 'relative flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50',
101
+ className
102
+ )}
103
+ ref={ref}
104
+ {...props}
105
+ />
106
+ ))
107
+
108
+ CommandItem.displayName = CommandPrimitive.Item.displayName
109
+
110
+ const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
111
+ return <span className={cn('text-muted ml-auto text-xs tracking-widest', className)} {...props} />
112
+ }
113
+ CommandShortcut.displayName = 'CommandShortcut'
114
+
115
+ export {
116
+ Command,
117
+ CommandDialog,
118
+ CommandEmpty,
119
+ CommandGroup,
120
+ CommandInput,
121
+ CommandItem,
122
+ CommandList,
123
+ CommandSeparator,
124
+ CommandShortcut
125
+ }
@@ -0,0 +1,118 @@
1
+ import * as DialogPrimitive from '@radix-ui/react-dialog'
2
+ import { Cross2Icon } from '@radix-ui/react-icons'
3
+ import * as React from 'react'
4
+ import { useTranslation } from 'react-i18next'
5
+
6
+ import { cn } from '../utils'
7
+
8
+ const Dialog = DialogPrimitive.Root
9
+
10
+ const DialogTrigger = DialogPrimitive.Trigger
11
+
12
+ const DialogPortal = DialogPrimitive.Portal
13
+
14
+ const DialogClose = DialogPrimitive.Close
15
+
16
+ const DialogOverlay = React.forwardRef<
17
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
18
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> & { isAbsolute?: boolean }
19
+ >(({ className, isAbsolute = false, ...props }, ref) => (
20
+ <DialogPrimitive.Overlay
21
+ className={cn(
22
+ 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 bg-foreground/10 z-60 backdrop-blur-xs',
23
+ isAbsolute ? 'absolute inset-0' : 'fixed inset-0',
24
+ className
25
+ )}
26
+ ref={ref}
27
+ {...props}
28
+ />
29
+ ))
30
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
31
+
32
+ interface DialogContentProps extends React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
33
+ container?: HTMLElement | null
34
+ isAbsolute?: boolean
35
+ isCloseIcon?: boolean
36
+ overlayClassName?: string
37
+ }
38
+
39
+ const DialogContent = React.forwardRef<React.ElementRef<typeof DialogPrimitive.Content>, DialogContentProps>(
40
+ ({ children, className, container, isAbsolute = false, isCloseIcon = false, overlayClassName, ...props }, ref) => {
41
+ const { t } = useTranslation('ui')
42
+
43
+ return (
44
+ <DialogPortal container={container}>
45
+ <DialogOverlay className={overlayClassName} isAbsolute={isAbsolute} />
46
+
47
+ <DialogPrimitive.Content
48
+ className={cn(
49
+ 'bg-surface data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] z-70 grid w-[calc(100%-2rem)] max-w-lg translate-x-[-50%] translate-y-[-50%] gap-0 rounded-2xl p-6 px-0 shadow-lg duration-200 outline-none lg:px-0',
50
+ isAbsolute ? 'absolute top-[50%] left-[50%]' : 'fixed top-[50%] left-[50%]',
51
+ className
52
+ )}
53
+ ref={ref}
54
+ {...props}
55
+ >
56
+ {children}
57
+ {isCloseIcon && (
58
+ <DialogPrimitive.Close className='data-[state=open]:text-muted ring-offset-surface focus:ring-ring data-[state=open]:bg-hover border-border bg-border absolute top-11 right-8 flex h-8 w-8 min-w-8 cursor-pointer items-center justify-center rounded-full opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none'>
59
+ <Cross2Icon className='text-body hover:text-primary-dark h-4 w-4' />
60
+ <span className='sr-only'>{t('dialog.close')}</span>
61
+ </DialogPrimitive.Close>
62
+ )}
63
+ </DialogPrimitive.Content>
64
+ </DialogPortal>
65
+ )
66
+ }
67
+ )
68
+ DialogContent.displayName = DialogPrimitive.Content.displayName
69
+
70
+ const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
71
+ <div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />
72
+ )
73
+ DialogHeader.displayName = 'DialogHeader'
74
+
75
+ const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
76
+ <div
77
+ className={cn('flex flex-col-reverse justify-center rounded-2xl sm:flex-row sm:space-x-2', className)}
78
+ {...props}
79
+ />
80
+ )
81
+ DialogFooter.displayName = 'DialogFooter'
82
+
83
+ const DialogTitle = React.forwardRef<
84
+ React.ElementRef<typeof DialogPrimitive.Title>,
85
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
86
+ >(({ className, ...props }, ref) => (
87
+ <DialogPrimitive.Title
88
+ className={cn('text-lg leading-none font-semibold tracking-tight', className)}
89
+ ref={ref}
90
+ {...props}
91
+ >
92
+ <span>{props.children}</span>
93
+ </DialogPrimitive.Title>
94
+ ))
95
+ DialogTitle.displayName = DialogPrimitive.Title.displayName
96
+
97
+ const DialogDescription = React.forwardRef<
98
+ React.ElementRef<typeof DialogPrimitive.Description>,
99
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
100
+ >(({ className, ...props }, ref) => (
101
+ <DialogPrimitive.Description className={cn('text-muted text-sm', className)} ref={ref} {...props}>
102
+ <span>{props.children}</span>
103
+ </DialogPrimitive.Description>
104
+ ))
105
+ DialogDescription.displayName = DialogPrimitive.Description.displayName
106
+
107
+ export {
108
+ Dialog,
109
+ DialogClose,
110
+ DialogContent,
111
+ DialogDescription,
112
+ DialogFooter,
113
+ DialogHeader,
114
+ DialogOverlay,
115
+ DialogPortal,
116
+ DialogTitle,
117
+ DialogTrigger
118
+ }
@@ -0,0 +1,5 @@
1
+ export * from './input-field'
2
+ export * from './label-field'
3
+ export * from './map-picker-field'
4
+ export * from './otp-field'
5
+ export * from './select-field'
@@ -0,0 +1,131 @@
1
+ import { useState } from 'react'
2
+ import { Control, FieldValues, Path } from 'react-hook-form'
3
+ import { useTranslation } from 'react-i18next'
4
+ import { FaRegEye, FaRegEyeSlash } from 'react-icons/fa6'
5
+ import { RxCross2 } from 'react-icons/rx'
6
+
7
+ import { cn } from '../../utils'
8
+ import { FormControl, FormField, FormItem, FormMessage } from '../form'
9
+ import { Input } from '../input'
10
+ import { LabelField } from './label-field'
11
+
12
+ interface InputFieldProps<T extends FieldValues = FieldValues> {
13
+ control: Control<T>
14
+ disabled?: boolean
15
+ inputClassName?: string
16
+ isClearable?: boolean
17
+ label?: string
18
+ labelClassName?: string
19
+ mainClassName?: string
20
+ name: Path<T>
21
+ onClear?: () => void
22
+ placeholder?: string
23
+ type?: string
24
+ visualRequired?: boolean
25
+ }
26
+
27
+ export const InputField = <T extends FieldValues = FieldValues>({
28
+ control,
29
+ disabled,
30
+ inputClassName,
31
+ isClearable,
32
+ label,
33
+ labelClassName,
34
+ mainClassName,
35
+ name,
36
+ onClear,
37
+ placeholder,
38
+ type = 'text',
39
+ visualRequired
40
+ }: InputFieldProps<T>) => {
41
+ const { t } = useTranslation('ui')
42
+ const [isPasswordVisible, setIsPasswordVisible] = useState(false)
43
+
44
+ const togglePasswordVisibility = () => {
45
+ setIsPasswordVisible((prev) => !prev)
46
+ }
47
+
48
+ const isPassword = type === 'password'
49
+ const inputType = isPassword ? (isPasswordVisible ? 'text' : 'password') : type === 'plate' ? 'text' : type
50
+
51
+ const isPlate = type === 'plate'
52
+
53
+ return (
54
+ <FormField
55
+ control={control}
56
+ name={name}
57
+ render={({ field, fieldState }) => {
58
+ const hasError = !!fieldState.error
59
+
60
+ return (
61
+ <FormItem className={cn('flex flex-col', mainClassName)}>
62
+ <LabelField
63
+ label={label}
64
+ labelClassName={cn(labelClassName, hasError && 'text-destructive')}
65
+ name={name}
66
+ visualRequired={visualRequired}
67
+ />
68
+ <FormControl>
69
+ <div className='relative'>
70
+ <Input
71
+ {...field}
72
+ autoComplete={isPassword ? 'current-password' : undefined}
73
+ className={cn(
74
+ inputClassName,
75
+ isPlate && 'uppercase',
76
+ hasError && 'border-destructive',
77
+ isPassword && 'pr-10 [&::-ms-reveal]:hidden'
78
+ )}
79
+ data-no-password-toggle
80
+ disabled={disabled}
81
+ maxLength={isPlate ? 6 : undefined}
82
+ min={type === 'number' ? 0 : undefined}
83
+ onChange={(e) => {
84
+ let value = e.target.value
85
+
86
+ if (isPlate) {
87
+ value = value
88
+ .toUpperCase()
89
+ .replace(/[^A-Z0-9]/g, '')
90
+ .slice(0, 6)
91
+ }
92
+
93
+ field.onChange(value)
94
+ }}
95
+ placeholder={placeholder}
96
+ type={inputType}
97
+ value={field.value ?? ''}
98
+ />
99
+
100
+ <div className='absolute inset-y-0 right-0 flex items-center space-x-2 pr-2'>
101
+ {field.value && isClearable && (
102
+ <button
103
+ className='focus:outline-none'
104
+ onClick={onClear}
105
+ title={t('inputField.clear')}
106
+ type='button'
107
+ >
108
+ <RxCross2 size={18} />
109
+ </button>
110
+ )}
111
+
112
+ {type === 'password' && (
113
+ <button
114
+ className='focus:outline-none'
115
+ onClick={togglePasswordVisibility}
116
+ title={isPasswordVisible ? t('inputField.hidePassword') : t('inputField.showPassword')}
117
+ type='button'
118
+ >
119
+ {isPasswordVisible ? <FaRegEye size={18} /> : <FaRegEyeSlash size={20} />}
120
+ </button>
121
+ )}
122
+ </div>
123
+ </div>
124
+ </FormControl>
125
+ <FormMessage />
126
+ </FormItem>
127
+ )
128
+ }}
129
+ />
130
+ )
131
+ }
@@ -0,0 +1,20 @@
1
+ import { cn } from '../../utils'
2
+ import { Label } from '../label'
3
+
4
+ interface LabelFieldProps {
5
+ label?: string
6
+ labelClassName?: string
7
+ name: string
8
+ visualRequired?: boolean
9
+ }
10
+
11
+ export const LabelField = ({ label, labelClassName, name, visualRequired }: LabelFieldProps) => {
12
+ return (
13
+ <Label
14
+ className={cn('text-body mb-2 text-start text-sm leading-none font-normal tracking-normal', labelClassName)}
15
+ htmlFor={name}
16
+ >
17
+ <span>{label}</span> {visualRequired && <span className='text-destructive'> *</span>}
18
+ </Label>
19
+ )
20
+ }