@mci-ui/mci-ui 0.0.4 → 0.0.5
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 +2 -3
- package/src/App.tsx +0 -5
- package/src/index.ts +0 -32
- package/src/main.tsx +0 -10
- package/src/shared/hooks/toast.ts +0 -32
- package/src/shared/lib/utils.ts +0 -37
- package/src/shared/types/MciTableType.ts +0 -10
- package/src/shared/ui/breadcrumb/Breadcrumb.tsx +0 -68
- package/src/shared/ui/button/Button.tsx +0 -105
- package/src/shared/ui/collapse/Collapse.tsx +0 -98
- package/src/shared/ui/inputMain/InputMain.tsx +0 -241
- package/src/shared/ui/mciTable/MciTable.tsx +0 -166
- package/src/shared/ui/modal/Modal.tsx +0 -92
- package/src/shared/ui/pagination/Pagination.tsx +0 -141
- package/src/shared/ui/skeleton/Skeleton.tsx +0 -33
- package/src/shared/ui/tabs/Tabs.tsx +0 -192
- package/src/shared/ui/tag/Tag.tsx +0 -92
- package/src/shared/ui/textarea/Textarea.tsx +0 -72
- package/src/shared/ui/toast/Toast.tsx +0 -150
- package/src/shared/ui/toast/ToastContainer.tsx +0 -19
- package/src/shared/ui/tooltip/Tooltip.tsx +0 -58
- package/src/styles/index.css +0 -36
- package/src/styles/tailwind-config.css +0 -294
package/package.json
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mci-ui/mci-ui",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
-
"
|
|
12
|
-
"index.css"
|
|
11
|
+
"dist/*.css"
|
|
13
12
|
],
|
|
14
13
|
"scripts": {
|
|
15
14
|
"dev": "vite",
|
package/src/App.tsx
DELETED
package/src/index.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import './styles/index.css'
|
|
2
|
-
|
|
3
|
-
import Breadcrumb from './shared/ui/breadcrumb/Breadcrumb.tsx'
|
|
4
|
-
import Button from './shared/ui/button/Button.tsx'
|
|
5
|
-
import Collapse from './shared/ui/collapse/Collapse.tsx'
|
|
6
|
-
import InputMain from './shared/ui/inputMain/InputMain.tsx'
|
|
7
|
-
import MciTable from './shared/ui/mciTable/MciTable.tsx'
|
|
8
|
-
import Modal from './shared/ui/modal/Modal.tsx'
|
|
9
|
-
import Pagination from './shared/ui/pagination/Pagination.tsx'
|
|
10
|
-
import Skeleton from './shared/ui/skeleton/Skeleton.tsx'
|
|
11
|
-
import Tabs, { TabPanel } from './shared/ui/tabs/Tabs.tsx'
|
|
12
|
-
import Tag from './shared/ui/tag/Tag.tsx'
|
|
13
|
-
import Textarea from './shared/ui/textarea/Textarea.tsx'
|
|
14
|
-
import { ToastContainer } from './shared/ui/toast/ToastContainer.tsx'
|
|
15
|
-
import Tooltip from './shared/ui/tooltip/Tooltip.tsx'
|
|
16
|
-
|
|
17
|
-
export {
|
|
18
|
-
Breadcrumb,
|
|
19
|
-
Button,
|
|
20
|
-
Collapse,
|
|
21
|
-
InputMain,
|
|
22
|
-
MciTable,
|
|
23
|
-
Modal,
|
|
24
|
-
Pagination,
|
|
25
|
-
Skeleton,
|
|
26
|
-
TabPanel,
|
|
27
|
-
Tabs,
|
|
28
|
-
Tag,
|
|
29
|
-
Textarea,
|
|
30
|
-
ToastContainer,
|
|
31
|
-
Tooltip,
|
|
32
|
-
}
|
package/src/main.tsx
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import type { ToastProps } from '../ui/toast/Toast.tsx'
|
|
3
|
-
|
|
4
|
-
export function useToast() {
|
|
5
|
-
const [toasts, setToasts] = useState<ToastProps[]>([])
|
|
6
|
-
|
|
7
|
-
const toast = (props: Omit<ToastProps, 'id'>) => {
|
|
8
|
-
const id = Math.random().toString(36)
|
|
9
|
-
setToasts(prev => [...prev, { ...props, id }])
|
|
10
|
-
return id
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const removeToast = (id: string) => {
|
|
14
|
-
setToasts(prev => prev.filter(t => t.id !== id))
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
toasts,
|
|
19
|
-
toast,
|
|
20
|
-
success: (t: string, d?: string) =>
|
|
21
|
-
toast({ title: t, description: d, type: 'success' }),
|
|
22
|
-
error: (t: string, d?: string) =>
|
|
23
|
-
toast({ title: t, description: d, type: 'error' }),
|
|
24
|
-
warning: (t: string, d?: string) =>
|
|
25
|
-
toast({ title: t, description: d, type: 'warning' }),
|
|
26
|
-
info: (t: string, d?: string) =>
|
|
27
|
-
toast({ title: t, description: d, type: 'info' }),
|
|
28
|
-
loading: (t: string, d?: string) =>
|
|
29
|
-
toast({ title: t, description: d, type: 'loading', duration: Infinity }),
|
|
30
|
-
removeToast,
|
|
31
|
-
}
|
|
32
|
-
}
|
package/src/shared/lib/utils.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { clsx, type ClassValue } from 'clsx'
|
|
2
|
-
import type { RefObject } from 'react'
|
|
3
|
-
import { twMerge } from 'tailwind-merge'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Tailwind classlarni merge qilish uchun utility
|
|
7
|
-
*/
|
|
8
|
-
export function cn(...inputs: ClassValue[]) {
|
|
9
|
-
return twMerge(clsx(inputs))
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* ESC tugmasini bosishni handle qilish
|
|
14
|
-
*/
|
|
15
|
-
export function useEscapeKey(callback: () => void) {
|
|
16
|
-
const handleEscape = (e: KeyboardEvent) => {
|
|
17
|
-
if (e.key === 'Escape') callback()
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return { handleEscape }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Click outside handle qilish
|
|
25
|
-
*/
|
|
26
|
-
export function useClickOutside(
|
|
27
|
-
ref: RefObject<HTMLDivElement>,
|
|
28
|
-
callback: () => void
|
|
29
|
-
) {
|
|
30
|
-
const handleClick = (e: MouseEvent) => {
|
|
31
|
-
if (ref.current && !ref.current.contains(e.target as Node)) {
|
|
32
|
-
callback()
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return { handleClick }
|
|
37
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { ChevronRight } from 'lucide-react'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import { cn } from '../../lib/utils.ts'
|
|
4
|
-
|
|
5
|
-
type BreadcrumbItem = {
|
|
6
|
-
label: string
|
|
7
|
-
href?: string
|
|
8
|
-
onClick?: () => void
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
type BreadcrumbProps = {
|
|
12
|
-
items: BreadcrumbItem[]
|
|
13
|
-
className?: string
|
|
14
|
-
separatorIcon?: React.ReactNode
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export default function Breadcrumb({
|
|
18
|
-
items,
|
|
19
|
-
className,
|
|
20
|
-
separatorIcon = (
|
|
21
|
-
<ChevronRight className='w-4 h-4 text-gray-400 dark:text-gray-500' />
|
|
22
|
-
),
|
|
23
|
-
}: BreadcrumbProps) {
|
|
24
|
-
return (
|
|
25
|
-
<nav
|
|
26
|
-
className={cn(
|
|
27
|
-
'flex items-center space-x-2 text-sm font-medium text-gray-600 dark:text-gray-300',
|
|
28
|
-
className
|
|
29
|
-
)}
|
|
30
|
-
aria-label='Breadcrumb'
|
|
31
|
-
>
|
|
32
|
-
{items.map((item, index) => {
|
|
33
|
-
const isLast = index === items.length - 1
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div key={index} className='flex items-center'>
|
|
37
|
-
{item.href ? (
|
|
38
|
-
<a
|
|
39
|
-
href={item.href}
|
|
40
|
-
onClick={item.onClick}
|
|
41
|
-
className={cn(
|
|
42
|
-
'hover:text-primary-600 dark:hover:text-primary-400 transition-colors',
|
|
43
|
-
isLast && 'text-primary-600 dark:text-primary-400'
|
|
44
|
-
)}
|
|
45
|
-
>
|
|
46
|
-
{item.label}
|
|
47
|
-
</a>
|
|
48
|
-
) : (
|
|
49
|
-
<span
|
|
50
|
-
className={cn(
|
|
51
|
-
isLast
|
|
52
|
-
? 'text-primary-600 dark:text-primary-400'
|
|
53
|
-
: 'text-gray-500 dark:text-gray-400'
|
|
54
|
-
)}
|
|
55
|
-
>
|
|
56
|
-
{item.label}
|
|
57
|
-
</span>
|
|
58
|
-
)}
|
|
59
|
-
|
|
60
|
-
{!isLast && (
|
|
61
|
-
<span className='mx-2 flex items-center'>{separatorIcon}</span>
|
|
62
|
-
)}
|
|
63
|
-
</div>
|
|
64
|
-
)
|
|
65
|
-
})}
|
|
66
|
-
</nav>
|
|
67
|
-
)
|
|
68
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { Loader } from 'lucide-react'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import { cn } from '../../lib/utils.ts'
|
|
4
|
-
|
|
5
|
-
type ButtonProps = {
|
|
6
|
-
text?: string
|
|
7
|
-
icon?: React.ReactNode
|
|
8
|
-
iconPosition?: 'left' | 'right'
|
|
9
|
-
size?: 'sm' | 'md' | 'lg'
|
|
10
|
-
variant?: 'primary' | 'secondary' | 'accent'
|
|
11
|
-
loading?: boolean
|
|
12
|
-
disabled?: boolean
|
|
13
|
-
onClick?: () => void
|
|
14
|
-
className?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export default function Button({
|
|
18
|
-
text,
|
|
19
|
-
icon,
|
|
20
|
-
iconPosition = 'left',
|
|
21
|
-
size = 'md',
|
|
22
|
-
variant = 'accent',
|
|
23
|
-
loading = false,
|
|
24
|
-
disabled = false,
|
|
25
|
-
onClick,
|
|
26
|
-
className,
|
|
27
|
-
}: ButtonProps) {
|
|
28
|
-
const sizes = {
|
|
29
|
-
sm: 'h-[38px] px-3 text-sm gap-2',
|
|
30
|
-
md: 'h-[48px] px-4 text-base gap-2.5',
|
|
31
|
-
lg: 'h-[56px] px-5 text-lg gap-3',
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const variants = {
|
|
35
|
-
primary: `
|
|
36
|
-
bg-secondary-500
|
|
37
|
-
hover:bg-secondary-600
|
|
38
|
-
active:bg-secondary-700
|
|
39
|
-
hover:shadow-lg
|
|
40
|
-
hover:shadow-secondary-500/30
|
|
41
|
-
active:scale-[0.98]
|
|
42
|
-
text-white
|
|
43
|
-
`,
|
|
44
|
-
secondary: `
|
|
45
|
-
bg-secondary-50
|
|
46
|
-
hover:bg-secondary-100
|
|
47
|
-
active:bg-secondary-200
|
|
48
|
-
hover:shadow-md
|
|
49
|
-
hover:shadow-secondary-500/10
|
|
50
|
-
active:scale-[0.98]
|
|
51
|
-
text-secondary-700
|
|
52
|
-
border border-secondary-200
|
|
53
|
-
hover:border-secondary-300
|
|
54
|
-
`,
|
|
55
|
-
accent: `
|
|
56
|
-
bg-accent-500
|
|
57
|
-
hover:bg-accent-600
|
|
58
|
-
active:bg-accent-700
|
|
59
|
-
hover:shadow-lg
|
|
60
|
-
hover:shadow-accent-500/30
|
|
61
|
-
active:scale-[0.98]
|
|
62
|
-
text-white
|
|
63
|
-
`,
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<button
|
|
68
|
-
onClick={onClick}
|
|
69
|
-
disabled={disabled || loading}
|
|
70
|
-
className={cn(
|
|
71
|
-
'inline-flex items-center justify-center rounded-[7px] font-medium',
|
|
72
|
-
'transition-all duration-300 ease-in-out',
|
|
73
|
-
'transform hover:-translate-y-0.5',
|
|
74
|
-
'disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:translate-y-0 disabled:hover:shadow-none',
|
|
75
|
-
'focus:outline-none focus:ring-2 focus:ring-offset-2',
|
|
76
|
-
variant === 'primary' && 'focus:ring-secondary-500',
|
|
77
|
-
variant === 'secondary' && 'focus:ring-secondary-400',
|
|
78
|
-
variant === 'accent' && 'focus:ring-accent-500',
|
|
79
|
-
variants[variant],
|
|
80
|
-
sizes[size],
|
|
81
|
-
className
|
|
82
|
-
)}
|
|
83
|
-
>
|
|
84
|
-
{/* ICON LEFT */}
|
|
85
|
-
{icon && iconPosition === 'left' && (
|
|
86
|
-
<span className='flex items-center justify-center transition-transform duration-300 ease-in-out group-hover:scale-110'>
|
|
87
|
-
{loading ? <Loader className='animate-spin' size={20} /> : icon}
|
|
88
|
-
</span>
|
|
89
|
-
)}
|
|
90
|
-
|
|
91
|
-
{/* TEXT */}
|
|
92
|
-
{text && <span className='transition-all duration-300'>{text}</span>}
|
|
93
|
-
|
|
94
|
-
{/* ICON RIGHT */}
|
|
95
|
-
{icon && iconPosition === 'right' && (
|
|
96
|
-
<span className='flex items-center justify-center transition-transform duration-300 ease-in-out group-hover:scale-110'>
|
|
97
|
-
{loading ? <Loader className='animate-spin' size={20} /> : icon}
|
|
98
|
-
</span>
|
|
99
|
-
)}
|
|
100
|
-
|
|
101
|
-
{/* AGAR TEXT VA ICON YO'Q BO'LSA */}
|
|
102
|
-
{loading && <Loader className='animate-spin' size={20} />}
|
|
103
|
-
</button>
|
|
104
|
-
)
|
|
105
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { ChevronDownIcon } from 'lucide-react'
|
|
2
|
-
import React, { useEffect, useRef, useState } from 'react'
|
|
3
|
-
import { cn } from '../../lib/utils.ts'
|
|
4
|
-
|
|
5
|
-
type CollapseProps = {
|
|
6
|
-
title: string
|
|
7
|
-
children: React.ReactNode
|
|
8
|
-
defaultOpen?: boolean
|
|
9
|
-
icon?: React.ReactNode
|
|
10
|
-
variant?: 'primary' | 'secondary' | 'accent'
|
|
11
|
-
className?: string
|
|
12
|
-
titleClassName?: string
|
|
13
|
-
contentClassName?: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default function Collapse({
|
|
17
|
-
title,
|
|
18
|
-
children,
|
|
19
|
-
defaultOpen = false,
|
|
20
|
-
icon,
|
|
21
|
-
variant = 'primary',
|
|
22
|
-
className,
|
|
23
|
-
titleClassName,
|
|
24
|
-
contentClassName,
|
|
25
|
-
}: CollapseProps) {
|
|
26
|
-
const [isOpen, setIsOpen] = useState(defaultOpen)
|
|
27
|
-
const [contentHeight, setContentHeight] = useState(0)
|
|
28
|
-
const contentRef = useRef<HTMLDivElement>(null)
|
|
29
|
-
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
if (contentRef.current) {
|
|
32
|
-
setContentHeight(isOpen ? contentRef.current.scrollHeight : 0)
|
|
33
|
-
}
|
|
34
|
-
}, [isOpen])
|
|
35
|
-
|
|
36
|
-
const variantClasses = {
|
|
37
|
-
primary: {
|
|
38
|
-
title:
|
|
39
|
-
'bg-secondary-50 hover:bg-secondary-100 border-secondary-200 text-secondary-800',
|
|
40
|
-
content: 'bg-secondary-25 border-secondary-100',
|
|
41
|
-
},
|
|
42
|
-
secondary: {
|
|
43
|
-
title:
|
|
44
|
-
'bg-accent-50 hover:bg-accent-100 border-accent-200 text-accent-800',
|
|
45
|
-
content: 'bg-accent-25 border-accent-100',
|
|
46
|
-
},
|
|
47
|
-
accent: {
|
|
48
|
-
title: 'bg-gray-50 hover:bg-gray-100 border-gray-200 text-gray-800',
|
|
49
|
-
content: 'bg-gray-25 border-gray-100',
|
|
50
|
-
},
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const variantConfig = variantClasses[variant]
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<div className={cn('border rounded-lg overflow-hidden', className)}>
|
|
57
|
-
{/* Header */}
|
|
58
|
-
<button
|
|
59
|
-
onClick={() => setIsOpen(!isOpen)}
|
|
60
|
-
className={cn(
|
|
61
|
-
'w-full flex items-center justify-between p-4 transition-all duration-300',
|
|
62
|
-
'focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-blue-500',
|
|
63
|
-
'border-b',
|
|
64
|
-
variantConfig.title
|
|
65
|
-
)}
|
|
66
|
-
>
|
|
67
|
-
<div className='flex items-center gap-3'>
|
|
68
|
-
{icon && <span className='flex-shrink-0'>{icon}</span>}
|
|
69
|
-
<span className={cn('font-medium text-left', titleClassName)}>
|
|
70
|
-
{title}
|
|
71
|
-
</span>
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
<ChevronDownIcon
|
|
75
|
-
className={cn(
|
|
76
|
-
'w-5 h-5 transition-transform duration-300 flex-shrink-0',
|
|
77
|
-
isOpen && 'rotate-180'
|
|
78
|
-
)}
|
|
79
|
-
/>
|
|
80
|
-
</button>
|
|
81
|
-
|
|
82
|
-
{/* Content */}
|
|
83
|
-
<div
|
|
84
|
-
className={cn(
|
|
85
|
-
'transition-all duration-300 ease-out overflow-hidden',
|
|
86
|
-
variantConfig.content
|
|
87
|
-
)}
|
|
88
|
-
style={{
|
|
89
|
-
height: `${contentHeight}px`,
|
|
90
|
-
}}
|
|
91
|
-
>
|
|
92
|
-
<div ref={contentRef} className={cn('p-4', contentClassName)}>
|
|
93
|
-
{children}
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
)
|
|
98
|
-
}
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
import { Eye, EyeOff } from 'lucide-react'
|
|
2
|
-
import React, { useMemo, useState } from 'react'
|
|
3
|
-
import { cn } from '../../lib/utils.ts'
|
|
4
|
-
|
|
5
|
-
type InputProps = {
|
|
6
|
-
label?: string
|
|
7
|
-
placeholder?: string
|
|
8
|
-
type?: 'text' | 'email' | 'password' | 'number' | 'tel'
|
|
9
|
-
icon?: React.ReactNode
|
|
10
|
-
iconPosition?: 'left' | 'right'
|
|
11
|
-
size?: 'sm' | 'md' | 'lg'
|
|
12
|
-
error?: string
|
|
13
|
-
success?: string
|
|
14
|
-
disabled?: boolean
|
|
15
|
-
required?: boolean
|
|
16
|
-
value?: string
|
|
17
|
-
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
|
|
18
|
-
onFocus?: () => void
|
|
19
|
-
onBlur?: () => void
|
|
20
|
-
className?: string
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export default function InputMain({
|
|
24
|
-
label,
|
|
25
|
-
placeholder,
|
|
26
|
-
type = 'text',
|
|
27
|
-
icon,
|
|
28
|
-
iconPosition = 'left',
|
|
29
|
-
size = 'md',
|
|
30
|
-
error,
|
|
31
|
-
success,
|
|
32
|
-
disabled = false,
|
|
33
|
-
required = false,
|
|
34
|
-
value,
|
|
35
|
-
onChange,
|
|
36
|
-
onFocus,
|
|
37
|
-
onBlur,
|
|
38
|
-
className,
|
|
39
|
-
}: InputProps) {
|
|
40
|
-
const [isFocused, setIsFocused] = useState(false)
|
|
41
|
-
const [internalValue, setInternalValue] = useState('')
|
|
42
|
-
const [showPassword, setShowPassword] = useState(false)
|
|
43
|
-
const [autoFilled, setAutoFilled] = useState(false)
|
|
44
|
-
|
|
45
|
-
const currentValue = value ?? internalValue
|
|
46
|
-
const hasValue = Boolean(currentValue) || autoFilled
|
|
47
|
-
const isPassword = type === 'password'
|
|
48
|
-
|
|
49
|
-
const handleFocus = () => {
|
|
50
|
-
setIsFocused(true)
|
|
51
|
-
onFocus?.()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const handleBlur = () => {
|
|
55
|
-
setIsFocused(false)
|
|
56
|
-
onBlur?.()
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
60
|
-
if (value === undefined) setInternalValue(e.target.value)
|
|
61
|
-
onChange?.(e)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const togglePassword = () => setShowPassword(p => !p)
|
|
65
|
-
|
|
66
|
-
const sizes = useMemo(
|
|
67
|
-
() => ({
|
|
68
|
-
sm: {
|
|
69
|
-
input: 'h-[38px] text-sm',
|
|
70
|
-
padding: icon
|
|
71
|
-
? iconPosition === 'left'
|
|
72
|
-
? 'pl-10 pr-3'
|
|
73
|
-
: 'pl-3 pr-10'
|
|
74
|
-
: 'px-3',
|
|
75
|
-
icon: 'w-4 h-4',
|
|
76
|
-
iconWrapper: iconPosition === 'left' ? 'left-3' : 'right-3',
|
|
77
|
-
labelFont: 'text-xs',
|
|
78
|
-
},
|
|
79
|
-
md: {
|
|
80
|
-
input: 'h-[48px] text-base',
|
|
81
|
-
padding: icon
|
|
82
|
-
? iconPosition === 'left'
|
|
83
|
-
? 'pl-12 pr-4'
|
|
84
|
-
: 'pl-4 pr-12'
|
|
85
|
-
: 'px-4',
|
|
86
|
-
icon: 'w-5 h-5',
|
|
87
|
-
iconWrapper: iconPosition === 'left' ? 'left-3.5' : 'right-3.5',
|
|
88
|
-
labelFont: 'text-sm',
|
|
89
|
-
},
|
|
90
|
-
lg: {
|
|
91
|
-
input: 'h-[56px] text-lg',
|
|
92
|
-
padding: icon
|
|
93
|
-
? iconPosition === 'left'
|
|
94
|
-
? 'pl-14 pr-5'
|
|
95
|
-
: 'pl-5 pr-14'
|
|
96
|
-
: 'px-5',
|
|
97
|
-
icon: 'w-6 h-6',
|
|
98
|
-
iconWrapper: iconPosition === 'left' ? 'left-4' : 'right-4',
|
|
99
|
-
labelFont: 'text-base',
|
|
100
|
-
},
|
|
101
|
-
}),
|
|
102
|
-
[icon, iconPosition]
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
const stateStyles = useMemo(() => {
|
|
106
|
-
if (error)
|
|
107
|
-
return 'border-error-500 focus:border-error-600 focus:ring-error-500/20'
|
|
108
|
-
if (success)
|
|
109
|
-
return 'border-success-500 focus:border-success-600 focus:ring-success-500/20'
|
|
110
|
-
return 'border-gray-300 hover:border-gray-400 focus:border-accent-500 focus:ring-accent-500/20'
|
|
111
|
-
}, [error, success])
|
|
112
|
-
|
|
113
|
-
// Autofill holatini aniqlash
|
|
114
|
-
const handleAutoFill = (e: React.AnimationEvent<HTMLInputElement>) => {
|
|
115
|
-
if (e.animationName === 'onAutoFillStart') {
|
|
116
|
-
setAutoFilled(true)
|
|
117
|
-
} else if (e.animationName === 'onAutoFillCancel') {
|
|
118
|
-
setAutoFilled(false)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return (
|
|
123
|
-
<div className={cn('w-full', className)}>
|
|
124
|
-
<div className='relative flex items-center'>
|
|
125
|
-
<input
|
|
126
|
-
type={isPassword && showPassword ? 'text' : type}
|
|
127
|
-
value={currentValue}
|
|
128
|
-
onChange={handleChange}
|
|
129
|
-
onFocus={handleFocus}
|
|
130
|
-
onBlur={handleBlur}
|
|
131
|
-
onAnimationStart={handleAutoFill}
|
|
132
|
-
disabled={disabled}
|
|
133
|
-
placeholder={label ? '' : placeholder}
|
|
134
|
-
required={required}
|
|
135
|
-
className={cn(
|
|
136
|
-
'w-full rounded-[7px] border bg-white font-medium outline-none transition-all duration-300 ease-in-out',
|
|
137
|
-
'focus:ring-4 disabled:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-60 autofill:shadow-[inset_0_0_0px_1000px_white]',
|
|
138
|
-
sizes[size].input,
|
|
139
|
-
sizes[size].padding,
|
|
140
|
-
stateStyles
|
|
141
|
-
)}
|
|
142
|
-
autoComplete='on'
|
|
143
|
-
/>
|
|
144
|
-
|
|
145
|
-
{/* FLOATING LABEL */}
|
|
146
|
-
{label && (
|
|
147
|
-
<label
|
|
148
|
-
className={cn(
|
|
149
|
-
'absolute pointer-events-none transition-all duration-300 ease-in-out font-medium',
|
|
150
|
-
icon && iconPosition === 'left' && !hasValue && !isFocused
|
|
151
|
-
? sizes[size].padding
|
|
152
|
-
: 'left-4',
|
|
153
|
-
isFocused || hasValue
|
|
154
|
-
? cn(
|
|
155
|
-
'top-0 -translate-y-1/2 bg-white px-2',
|
|
156
|
-
sizes[size].labelFont,
|
|
157
|
-
error
|
|
158
|
-
? 'text-error-600'
|
|
159
|
-
: success
|
|
160
|
-
? 'text-success-600'
|
|
161
|
-
: 'text-accent-600'
|
|
162
|
-
)
|
|
163
|
-
: cn(
|
|
164
|
-
'top-1/2 -translate-y-1/2 text-gray-500',
|
|
165
|
-
sizes[size].input.includes('38') && 'text-sm',
|
|
166
|
-
sizes[size].input.includes('48') && 'text-base',
|
|
167
|
-
sizes[size].input.includes('56') && 'text-lg'
|
|
168
|
-
)
|
|
169
|
-
)}
|
|
170
|
-
>
|
|
171
|
-
{label}
|
|
172
|
-
{required && <span className='text-error-500 ml-1'>*</span>}
|
|
173
|
-
</label>
|
|
174
|
-
)}
|
|
175
|
-
|
|
176
|
-
{/* ICON */}
|
|
177
|
-
{icon && (
|
|
178
|
-
<div
|
|
179
|
-
className={cn(
|
|
180
|
-
'absolute top-1/2 -translate-y-1/2 flex items-center transition-all duration-300',
|
|
181
|
-
sizes[size].iconWrapper,
|
|
182
|
-
error
|
|
183
|
-
? 'text-error-500'
|
|
184
|
-
: success
|
|
185
|
-
? 'text-success-500'
|
|
186
|
-
: isFocused
|
|
187
|
-
? 'text-accent-500'
|
|
188
|
-
: 'text-gray-400'
|
|
189
|
-
)}
|
|
190
|
-
>
|
|
191
|
-
<span className={cn(sizes[size].icon, 'mr-2')}>{icon}</span>
|
|
192
|
-
</div>
|
|
193
|
-
)}
|
|
194
|
-
|
|
195
|
-
{/* PASSWORD TOGGLE */}
|
|
196
|
-
{isPassword && (
|
|
197
|
-
<button
|
|
198
|
-
type='button'
|
|
199
|
-
onClick={togglePassword}
|
|
200
|
-
className={cn(
|
|
201
|
-
'absolute top-1/2 -translate-y-1/2 flex items-center justify-center text-gray-400 hover:text-gray-600 transition-all',
|
|
202
|
-
'right-3'
|
|
203
|
-
)}
|
|
204
|
-
>
|
|
205
|
-
{showPassword ? (
|
|
206
|
-
<EyeOff className={cn(sizes[size].icon)} />
|
|
207
|
-
) : (
|
|
208
|
-
<Eye className={cn(sizes[size].icon)} />
|
|
209
|
-
)}
|
|
210
|
-
</button>
|
|
211
|
-
)}
|
|
212
|
-
</div>
|
|
213
|
-
|
|
214
|
-
{/* ERROR MESSAGE */}
|
|
215
|
-
{error && (
|
|
216
|
-
<p className='mt-1.5 text-sm text-error-600 flex items-center gap-1 animate-[slideDown_0.3s_ease-out]'>
|
|
217
|
-
{error}
|
|
218
|
-
</p>
|
|
219
|
-
)}
|
|
220
|
-
|
|
221
|
-
{/* SUCCESS MESSAGE */}
|
|
222
|
-
{success && !error && (
|
|
223
|
-
<p className='mt-1.5 text-sm text-success-600 flex items-center gap-1 animate-[slideDown_0.3s_ease-out]'>
|
|
224
|
-
{success}
|
|
225
|
-
</p>
|
|
226
|
-
)}
|
|
227
|
-
|
|
228
|
-
{/* Autofill animatsiyasi */}
|
|
229
|
-
<style>{`
|
|
230
|
-
input {
|
|
231
|
-
animation-name: onAutoFillCancel;
|
|
232
|
-
}
|
|
233
|
-
input:-webkit-autofill {
|
|
234
|
-
animation-name: onAutoFillStart;
|
|
235
|
-
}
|
|
236
|
-
@keyframes onAutoFillStart {}
|
|
237
|
-
@keyframes onAutoFillCancel {}
|
|
238
|
-
`}</style>
|
|
239
|
-
</div>
|
|
240
|
-
)
|
|
241
|
-
}
|