@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.
- package/package.json +48 -0
- package/src/components/alert.tsx +45 -0
- package/src/components/auth-header.tsx +26 -0
- package/src/components/button.tsx +121 -0
- package/src/components/command.tsx +125 -0
- package/src/components/dialog.tsx +118 -0
- package/src/components/form-fields/index.ts +5 -0
- package/src/components/form-fields/input-field.tsx +131 -0
- package/src/components/form-fields/label-field.tsx +20 -0
- package/src/components/form-fields/map-picker-field.tsx +49 -0
- package/src/components/form-fields/otp-field.tsx +76 -0
- package/src/components/form-fields/select-field.tsx +77 -0
- package/src/components/form.tsx +138 -0
- package/src/components/google-map-picker.tsx +243 -0
- package/src/components/input.tsx +40 -0
- package/src/components/label.tsx +17 -0
- package/src/components/loading.tsx +19 -0
- package/src/components/otp-input.tsx +62 -0
- package/src/components/password-checklist.tsx +37 -0
- package/src/components/popover.tsx +31 -0
- package/src/components/select-input.tsx +236 -0
- package/src/components/select.tsx +143 -0
- package/src/components/spinner.tsx +61 -0
- package/src/components/step-header.tsx +16 -0
- package/src/components/stepper.tsx +69 -0
- package/src/components/toast/index.ts +2 -0
- package/src/components/toast/toast-provider.tsx +123 -0
- package/src/components/toast/toast.tsx +106 -0
- package/src/components/tooltip.tsx +56 -0
- package/src/components/wrapper.tsx +12 -0
- package/src/index.ts +27 -0
- package/src/locales/es/ui.json +28 -0
- package/src/locales/index.ts +7 -0
- package/src/styles/index.css +1 -0
- package/src/styles/theme.css +105 -0
- package/src/types/password.ts +12 -0
- package/src/types/toast.ts +19 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/index.ts +3 -0
- package/src/utils/styles.ts +19 -0
- package/src/utils/toast-emitter.ts +37 -0
- 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,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
|
+
}
|