@mci-ui/mci-ui 0.0.6 → 0.0.8
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/README.md +3 -71
- package/dist/index.css +1 -36
- package/dist/index.es.js +4254 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +160 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/types/index.d.ts +205 -0
- package/dist/vite.svg +1 -0
- package/package.json +16 -12
- package/dist/index.cjs +0 -44
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -188
- package/dist/index.d.ts +0 -188
- package/dist/index.js +0 -44
- package/dist/index.js.map +0 -1
- package/dist/tailwind-config.css +0 -294
- 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
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { X } from 'lucide-react'
|
|
2
|
-
import React, { useEffect, useRef } from 'react'
|
|
3
|
-
import { cn, useClickOutside, useEscapeKey } from '../../lib/utils'
|
|
4
|
-
|
|
5
|
-
interface ModalProps {
|
|
6
|
-
show: boolean
|
|
7
|
-
setShow: (show: boolean) => void
|
|
8
|
-
title?: string
|
|
9
|
-
Header?: React.ReactNode
|
|
10
|
-
Body?: React.ReactNode
|
|
11
|
-
footer?: React.ReactNode
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default function Modal({
|
|
15
|
-
show,
|
|
16
|
-
setShow,
|
|
17
|
-
title,
|
|
18
|
-
Header,
|
|
19
|
-
Body,
|
|
20
|
-
footer,
|
|
21
|
-
}: ModalProps) {
|
|
22
|
-
const modalRef = useRef<HTMLDivElement>(null!)
|
|
23
|
-
|
|
24
|
-
// ESC
|
|
25
|
-
const { handleEscape } = useEscapeKey(() => setShow(false))
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
if (show) {
|
|
28
|
-
document.addEventListener('keydown', handleEscape)
|
|
29
|
-
document.body.style.overflow = 'hidden'
|
|
30
|
-
} else {
|
|
31
|
-
document.body.style.overflow = 'unset'
|
|
32
|
-
}
|
|
33
|
-
return () => document.removeEventListener('keydown', handleEscape)
|
|
34
|
-
}, [show, handleEscape])
|
|
35
|
-
|
|
36
|
-
// Click outside
|
|
37
|
-
const { handleClick } = useClickOutside(modalRef, () => setShow(false))
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
if (show) document.addEventListener('mousedown', handleClick)
|
|
40
|
-
return () => document.removeEventListener('mousedown', handleClick)
|
|
41
|
-
}, [show, handleClick])
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
<div
|
|
45
|
-
className={cn(
|
|
46
|
-
'fixed inset-0 z-50 flex items-center justify-center p-4 bg-secondary-100/50 backdrop-blur-sm transition-all duration-500 ease-in-out',
|
|
47
|
-
show
|
|
48
|
-
? 'opacity-100 pointer-events-auto'
|
|
49
|
-
: 'opacity-0 pointer-events-none'
|
|
50
|
-
)}
|
|
51
|
-
>
|
|
52
|
-
<div
|
|
53
|
-
ref={modalRef}
|
|
54
|
-
className={cn(
|
|
55
|
-
'relative w-full max-w-lg rounded-[7px] shadow-xl bg-secondary-50 text-gray-800',
|
|
56
|
-
'transform transition-all duration-500 ease-out',
|
|
57
|
-
show ? 'opacity-100 scale-100' : 'opacity-0 scale-90'
|
|
58
|
-
)}
|
|
59
|
-
>
|
|
60
|
-
{/* Header */}
|
|
61
|
-
{(Header || title) && (
|
|
62
|
-
<div className='flex items-center justify-between px-6 py-4 border-b border-secondary-300'>
|
|
63
|
-
{Header || (
|
|
64
|
-
<h2 className='text-xl font-semibold text-gray-800'>{title}</h2>
|
|
65
|
-
)}
|
|
66
|
-
|
|
67
|
-
<button
|
|
68
|
-
onClick={() => setShow(false)}
|
|
69
|
-
className='text-gray-800 hover:text-secondary-300 active:animate-spin transition'
|
|
70
|
-
>
|
|
71
|
-
<X className='w-5 h-5' />
|
|
72
|
-
</button>
|
|
73
|
-
</div>
|
|
74
|
-
)}
|
|
75
|
-
|
|
76
|
-
{/* Body */}
|
|
77
|
-
{Body && (
|
|
78
|
-
<div className='px-6 py-4 max-h-96 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-300'>
|
|
79
|
-
{Body}
|
|
80
|
-
</div>
|
|
81
|
-
)}
|
|
82
|
-
|
|
83
|
-
{/* Footer */}
|
|
84
|
-
{footer && (
|
|
85
|
-
<div className='flex items-center justify-end gap-3 px-6 py-4 border-t border-secondary-300'>
|
|
86
|
-
{footer}
|
|
87
|
-
</div>
|
|
88
|
-
)}
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
)
|
|
92
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
|
2
|
-
|
|
3
|
-
interface PaginationProps {
|
|
4
|
-
totalItems: number
|
|
5
|
-
currentPage: number
|
|
6
|
-
perPage: number
|
|
7
|
-
onPageChange: (page: number) => void
|
|
8
|
-
onPerPageChange?: (value: number) => void
|
|
9
|
-
siblingCount?: number
|
|
10
|
-
perPageOptions?: number[]
|
|
11
|
-
showPerPage?: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default function Pagination({
|
|
15
|
-
totalItems,
|
|
16
|
-
currentPage,
|
|
17
|
-
perPage,
|
|
18
|
-
onPageChange,
|
|
19
|
-
onPerPageChange,
|
|
20
|
-
siblingCount = 1,
|
|
21
|
-
perPageOptions = [10, 20, 30],
|
|
22
|
-
showPerPage = true,
|
|
23
|
-
}: PaginationProps) {
|
|
24
|
-
const totalPages = Math.ceil(totalItems / perPage)
|
|
25
|
-
|
|
26
|
-
const createPageRange = () => {
|
|
27
|
-
const totalPageNumbers = siblingCount * 2 + 5
|
|
28
|
-
if (totalPages <= totalPageNumbers) {
|
|
29
|
-
return Array.from({ length: totalPages }, (_, i) => i + 1)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const leftSiblingIndex = Math.max(currentPage - siblingCount, 2)
|
|
33
|
-
const rightSiblingIndex = Math.min(
|
|
34
|
-
currentPage + siblingCount,
|
|
35
|
-
totalPages - 1
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
const showLeftDots = leftSiblingIndex > 2
|
|
39
|
-
const showRightDots = rightSiblingIndex < totalPages - 1
|
|
40
|
-
|
|
41
|
-
const range: (number | string)[] = [1]
|
|
42
|
-
|
|
43
|
-
if (showLeftDots) range.push('...')
|
|
44
|
-
|
|
45
|
-
for (let i = leftSiblingIndex; i <= rightSiblingIndex; i++) {
|
|
46
|
-
range.push(i)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (showRightDots) range.push('...')
|
|
50
|
-
|
|
51
|
-
range.push(totalPages)
|
|
52
|
-
|
|
53
|
-
return range
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const pages = createPageRange()
|
|
57
|
-
|
|
58
|
-
const handlePrev = () => {
|
|
59
|
-
if (currentPage > 1) onPageChange(currentPage - 1)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const handleNext = () => {
|
|
63
|
-
if (currentPage < totalPages) onPageChange(currentPage + 1)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<div className='w-full flex flex-col md:flex-row md:justify-between md:items-center gap-4 py-4'>
|
|
68
|
-
{/* Per Page Select */}
|
|
69
|
-
{showPerPage && onPerPageChange && (
|
|
70
|
-
<div className='flex justify-center md:justify-start w-full md:w-auto'>
|
|
71
|
-
<select
|
|
72
|
-
value={perPage}
|
|
73
|
-
onChange={e => onPerPageChange(Number(e.target.value))}
|
|
74
|
-
className='border rounded-lg px-3 py-2 text-sm bg-white
|
|
75
|
-
hover:border-secondary-500
|
|
76
|
-
focus:outline-none focus:ring-2 focus:ring-secondary-500
|
|
77
|
-
transition-all duration-200'
|
|
78
|
-
>
|
|
79
|
-
{perPageOptions.map(option => (
|
|
80
|
-
<option key={option} value={option}>
|
|
81
|
-
{option} / page
|
|
82
|
-
</option>
|
|
83
|
-
))}
|
|
84
|
-
</select>
|
|
85
|
-
</div>
|
|
86
|
-
)}
|
|
87
|
-
|
|
88
|
-
{/* Pagination */}
|
|
89
|
-
<div className='flex items-center justify-center md:justify-end w-full'>
|
|
90
|
-
{/* Prev */}
|
|
91
|
-
<button
|
|
92
|
-
onClick={handlePrev}
|
|
93
|
-
disabled={currentPage === 1}
|
|
94
|
-
className={`flex items-center justify-center rounded-xl border px-3 py-2 transition-all duration-200
|
|
95
|
-
${
|
|
96
|
-
currentPage === 1
|
|
97
|
-
? 'opacity-40 cursor-not-allowed'
|
|
98
|
-
: 'hover:bg-secondary-100 hover:text-secondary-700'
|
|
99
|
-
}`}
|
|
100
|
-
>
|
|
101
|
-
<ChevronLeft size={18} />
|
|
102
|
-
</button>
|
|
103
|
-
|
|
104
|
-
{/* Page numbers */}
|
|
105
|
-
<div className='flex items-center gap-1 mx-2'>
|
|
106
|
-
{pages.map((page, idx) => (
|
|
107
|
-
<button
|
|
108
|
-
key={idx}
|
|
109
|
-
onClick={() => typeof page === 'number' && onPageChange(page)}
|
|
110
|
-
disabled={page === '...'}
|
|
111
|
-
className={`min-w-[40px] rounded-xl border px-3 py-2 text-sm font-medium transition-all duration-200
|
|
112
|
-
${
|
|
113
|
-
page === currentPage
|
|
114
|
-
? 'bg-secondary-500 text-white shadow-md'
|
|
115
|
-
: page === '...'
|
|
116
|
-
? 'cursor-default border-none text-gray-400'
|
|
117
|
-
: 'bg-white text-secondary-700 hover:bg-secondary-100'
|
|
118
|
-
}`}
|
|
119
|
-
>
|
|
120
|
-
{page}
|
|
121
|
-
</button>
|
|
122
|
-
))}
|
|
123
|
-
</div>
|
|
124
|
-
|
|
125
|
-
{/* Next */}
|
|
126
|
-
<button
|
|
127
|
-
onClick={handleNext}
|
|
128
|
-
disabled={currentPage === totalPages}
|
|
129
|
-
className={`flex items-center justify-center rounded-xl border px-3 py-2 transition-all duration-200
|
|
130
|
-
${
|
|
131
|
-
currentPage === totalPages
|
|
132
|
-
? 'opacity-40 cursor-not-allowed'
|
|
133
|
-
: 'hover:bg-secondary-100 hover:text-secondary-700'
|
|
134
|
-
}`}
|
|
135
|
-
>
|
|
136
|
-
<ChevronRight size={18} />
|
|
137
|
-
</button>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
)
|
|
141
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { cn } from '../../lib/utils.ts'
|
|
2
|
-
|
|
3
|
-
type SkeletonProps = {
|
|
4
|
-
className?: string
|
|
5
|
-
variant?: 'default' | 'rounded' | 'circle'
|
|
6
|
-
width?: string | number
|
|
7
|
-
height?: string | number
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export default function Skeleton({
|
|
11
|
-
className,
|
|
12
|
-
variant = 'default',
|
|
13
|
-
width = '100%',
|
|
14
|
-
height = '1rem',
|
|
15
|
-
}: SkeletonProps) {
|
|
16
|
-
return (
|
|
17
|
-
<div
|
|
18
|
-
className={cn(
|
|
19
|
-
'relative overflow-hidden bg-secondary-200 dark:bg-secondary-800',
|
|
20
|
-
'rounded-sm',
|
|
21
|
-
variant === 'circle' && 'rounded-full',
|
|
22
|
-
variant === 'rounded' && 'rounded-md',
|
|
23
|
-
className
|
|
24
|
-
)}
|
|
25
|
-
style={{
|
|
26
|
-
width: typeof width === 'number' ? `${width}px` : width,
|
|
27
|
-
height: typeof height === 'number' ? `${height}px` : height,
|
|
28
|
-
}}
|
|
29
|
-
>
|
|
30
|
-
<div className='absolute inset-0 shimmer-mask' />
|
|
31
|
-
</div>
|
|
32
|
-
)
|
|
33
|
-
}
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { cn } from '../../lib/utils.ts'
|
|
3
|
-
|
|
4
|
-
type Tab = {
|
|
5
|
-
id: string
|
|
6
|
-
label: string
|
|
7
|
-
icon?: React.ReactNode
|
|
8
|
-
content: React.ReactNode
|
|
9
|
-
disabled?: boolean
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type TabsProps = {
|
|
13
|
-
tabs: Tab[]
|
|
14
|
-
defaultTab?: string
|
|
15
|
-
position?: 'top' | 'bottom' | 'left' | 'right'
|
|
16
|
-
variant?: 'primary' | 'secondary' | 'accent'
|
|
17
|
-
className?: string
|
|
18
|
-
onChange?: (tabId: string | number) => void
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export default function Tabs({
|
|
22
|
-
tabs,
|
|
23
|
-
defaultTab,
|
|
24
|
-
position = 'top',
|
|
25
|
-
variant = 'primary',
|
|
26
|
-
className,
|
|
27
|
-
onChange,
|
|
28
|
-
}: TabsProps) {
|
|
29
|
-
const [activeTab, setActiveTab] = useState(defaultTab || tabs[0]?.id)
|
|
30
|
-
const [indicatorStyle, setIndicatorStyle] = useState({})
|
|
31
|
-
const tabsRef = useRef<(HTMLButtonElement | null)[]>([])
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
const activeIndex = tabs.findIndex(tab => tab.id === activeTab)
|
|
35
|
-
const activeTabElement = tabsRef.current[activeIndex]
|
|
36
|
-
|
|
37
|
-
if (activeTabElement) {
|
|
38
|
-
const { offsetLeft, offsetTop, offsetWidth, offsetHeight } =
|
|
39
|
-
activeTabElement
|
|
40
|
-
|
|
41
|
-
setIndicatorStyle({
|
|
42
|
-
left: offsetLeft,
|
|
43
|
-
top: offsetTop,
|
|
44
|
-
width: offsetWidth,
|
|
45
|
-
height: offsetHeight,
|
|
46
|
-
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
}, [activeTab, position, tabs])
|
|
50
|
-
|
|
51
|
-
const containerClasses = {
|
|
52
|
-
top: 'flex-col',
|
|
53
|
-
bottom: 'flex-col-reverse',
|
|
54
|
-
left: 'flex-row',
|
|
55
|
-
right: 'flex-row-reverse',
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const tabsContainerClasses = {
|
|
59
|
-
top: 'flex-row',
|
|
60
|
-
bottom: 'flex-row',
|
|
61
|
-
left: 'flex-col',
|
|
62
|
-
right: 'flex-col',
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const contentClasses = {
|
|
66
|
-
top: 'mt-6',
|
|
67
|
-
bottom: 'mb-6',
|
|
68
|
-
left: 'ml-6',
|
|
69
|
-
right: 'mr-6',
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const variantClasses = {
|
|
73
|
-
primary: {
|
|
74
|
-
active: 'text-secondary-600',
|
|
75
|
-
inactive:
|
|
76
|
-
'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300',
|
|
77
|
-
disabled: 'text-gray-300 dark:text-gray-600 cursor-not-allowed',
|
|
78
|
-
bg: 'bg-secondary-100 dark:bg-secondary-900',
|
|
79
|
-
},
|
|
80
|
-
secondary: {
|
|
81
|
-
active: 'text-accent-600',
|
|
82
|
-
inactive:
|
|
83
|
-
'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300',
|
|
84
|
-
disabled: 'text-gray-300 dark:text-gray-600 cursor-not-allowed',
|
|
85
|
-
bg: 'bg-accent-100 dark:bg-accent-900',
|
|
86
|
-
},
|
|
87
|
-
accent: {
|
|
88
|
-
active: 'text-gray-800 dark:text-gray-200',
|
|
89
|
-
inactive:
|
|
90
|
-
'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300',
|
|
91
|
-
disabled: 'text-gray-300 dark:text-gray-600 cursor-not-allowed',
|
|
92
|
-
bg: 'bg-gray-100 dark:bg-gray-800',
|
|
93
|
-
},
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const isVertical = position === 'left' || position === 'right'
|
|
97
|
-
|
|
98
|
-
return (
|
|
99
|
-
<div className={cn('flex w-full', containerClasses[position], className)}>
|
|
100
|
-
{/* Tabs Container */}
|
|
101
|
-
<div
|
|
102
|
-
className={cn(
|
|
103
|
-
'flex relative bg-gray-50 dark:bg-gray-900 rounded-lg p-1',
|
|
104
|
-
tabsContainerClasses[position],
|
|
105
|
-
isVertical ? 'min-w-48' : 'w-full'
|
|
106
|
-
)}
|
|
107
|
-
>
|
|
108
|
-
{/* Animated Background Indicator */}
|
|
109
|
-
<div
|
|
110
|
-
className={cn(
|
|
111
|
-
'absolute rounded-md transition-all duration-300 ease-out',
|
|
112
|
-
variantClasses[variant].bg,
|
|
113
|
-
isVertical ? 'w-full' : 'h-full'
|
|
114
|
-
)}
|
|
115
|
-
style={indicatorStyle}
|
|
116
|
-
/>
|
|
117
|
-
|
|
118
|
-
{tabs.map((tab, index) => {
|
|
119
|
-
const isActive = activeTab === tab.id
|
|
120
|
-
const variantConfig = variantClasses[variant]
|
|
121
|
-
|
|
122
|
-
return (
|
|
123
|
-
<button
|
|
124
|
-
key={tab.id}
|
|
125
|
-
ref={(el) => {
|
|
126
|
-
tabsRef.current[index] = el
|
|
127
|
-
}}
|
|
128
|
-
onClick={() => {
|
|
129
|
-
if (!tab.disabled) {
|
|
130
|
-
setActiveTab(tab.id)
|
|
131
|
-
onChange?.(tab.id)
|
|
132
|
-
}
|
|
133
|
-
}}
|
|
134
|
-
disabled={tab.disabled}
|
|
135
|
-
className={cn(
|
|
136
|
-
'relative flex items-center justify-center gap-2 px-4 py-3 font-medium',
|
|
137
|
-
'text-sm whitespace-nowrap transition-all duration-200 z-10',
|
|
138
|
-
'focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500',
|
|
139
|
-
'rounded-md',
|
|
140
|
-
isActive
|
|
141
|
-
? [variantConfig.active, 'font-semibold']
|
|
142
|
-
: [
|
|
143
|
-
variantConfig.inactive,
|
|
144
|
-
'hover:bg-white/50 dark:hover:bg-gray-800/50',
|
|
145
|
-
],
|
|
146
|
-
tab.disabled && variantConfig.disabled,
|
|
147
|
-
isVertical ? 'w-full justify-start' : 'flex-1'
|
|
148
|
-
)}
|
|
149
|
-
>
|
|
150
|
-
{tab.icon && (
|
|
151
|
-
<span
|
|
152
|
-
className={cn(
|
|
153
|
-
'flex-shrink-0 transition-transform duration-200',
|
|
154
|
-
isActive && 'scale-110'
|
|
155
|
-
)}
|
|
156
|
-
>
|
|
157
|
-
{tab.icon}
|
|
158
|
-
</span>
|
|
159
|
-
)}
|
|
160
|
-
<span className='relative z-10'>{tab.label}</span>
|
|
161
|
-
</button>
|
|
162
|
-
)
|
|
163
|
-
})}
|
|
164
|
-
</div>
|
|
165
|
-
|
|
166
|
-
{/* Tab Content with Smooth Transition */}
|
|
167
|
-
<div className={cn('flex-1 overflow-hidden', contentClasses[position])}>
|
|
168
|
-
<div key={activeTab} className='animate-fade-in'>
|
|
169
|
-
{tabs.find(tab => tab.id === activeTab)?.content}
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
type TabPanelProps = {
|
|
177
|
-
children: React.ReactNode
|
|
178
|
-
className?: string
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function TabPanel({ children, className }: TabPanelProps) {
|
|
182
|
-
return (
|
|
183
|
-
<div
|
|
184
|
-
className={cn(
|
|
185
|
-
'p-6 rounded-lg bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700',
|
|
186
|
-
className
|
|
187
|
-
)}
|
|
188
|
-
>
|
|
189
|
-
{children}
|
|
190
|
-
</div>
|
|
191
|
-
)
|
|
192
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { X } from 'lucide-react'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import { cn } from '../../lib/utils.ts'
|
|
4
|
-
|
|
5
|
-
type TagProps = {
|
|
6
|
-
children: React.ReactNode
|
|
7
|
-
variant?: 'default' | 'secondary' | 'success' | 'error' | 'info' | 'accent'
|
|
8
|
-
size?: 'sm' | 'md' | 'lg'
|
|
9
|
-
icon?: React.ReactNode
|
|
10
|
-
iconPosition?: 'left' | 'right'
|
|
11
|
-
rounded?: boolean
|
|
12
|
-
closable?: boolean
|
|
13
|
-
onClose?: () => void
|
|
14
|
-
className?: string
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const sizes = {
|
|
18
|
-
sm: 'text-xs px-2 py-0.5',
|
|
19
|
-
md: 'text-sm px-3 py-1',
|
|
20
|
-
lg: 'text-base px-4 py-1.5',
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const iconSizes = {
|
|
24
|
-
sm: 'w-3 h-3',
|
|
25
|
-
md: 'w-4 h-4',
|
|
26
|
-
lg: 'w-5 h-5',
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const variants = {
|
|
30
|
-
default:
|
|
31
|
-
'bg-gray-100 text-gray-800 border border-gray-200 dark:bg-gray-800 dark:text-gray-100 dark:border-gray-700',
|
|
32
|
-
secondary:
|
|
33
|
-
'bg-secondary-100 text-secondary-800 border border-secondary-200 dark:bg-secondary-800 dark:text-secondary-50 dark:border-secondary-700',
|
|
34
|
-
success:
|
|
35
|
-
'bg-success-100 text-success-800 border border-success-200 dark:bg-success-800 dark:text-success-50 dark:border-success-700',
|
|
36
|
-
error:
|
|
37
|
-
'bg-error-100 text-error-800 border border-error-200 dark:bg-error-800 dark:text-error-50 dark:border-error-700',
|
|
38
|
-
info: 'bg-info-100 text-info-800 border border-info-200 dark:bg-info-800 dark:text-info-50 dark:border-info-700',
|
|
39
|
-
accent:
|
|
40
|
-
'bg-accent-100 text-accent-800 border border-accent-200 dark:bg-accent-800 dark:text-accent-50 dark:border-accent-700',
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default function Tag({
|
|
44
|
-
children,
|
|
45
|
-
variant = 'default',
|
|
46
|
-
size = 'md',
|
|
47
|
-
icon,
|
|
48
|
-
iconPosition = 'left',
|
|
49
|
-
closable = false,
|
|
50
|
-
onClose,
|
|
51
|
-
className,
|
|
52
|
-
}: TagProps) {
|
|
53
|
-
return (
|
|
54
|
-
<span
|
|
55
|
-
className={cn(
|
|
56
|
-
'inline-flex items-center font-medium animate-[fadeIn_0.3s_ease-out] rounded-[7px]',
|
|
57
|
-
sizes[size],
|
|
58
|
-
variants[variant],
|
|
59
|
-
icon && 'gap-1.5',
|
|
60
|
-
className
|
|
61
|
-
)}
|
|
62
|
-
>
|
|
63
|
-
{/* ICON LEFT */}
|
|
64
|
-
{icon && iconPosition === 'left' && (
|
|
65
|
-
<span className={`flex items-center ${cn(iconSizes[size])}`}>
|
|
66
|
-
{icon}
|
|
67
|
-
</span>
|
|
68
|
-
)}
|
|
69
|
-
|
|
70
|
-
{/* TEXT */}
|
|
71
|
-
<span>{children}</span>
|
|
72
|
-
|
|
73
|
-
{/* ICON RIGHT */}
|
|
74
|
-
{icon && iconPosition === 'right' && (
|
|
75
|
-
<span className={`flex items-center ${cn(iconSizes[size])}`}>
|
|
76
|
-
{icon}
|
|
77
|
-
</span>
|
|
78
|
-
)}
|
|
79
|
-
|
|
80
|
-
{/* CLOSE BUTTON */}
|
|
81
|
-
{closable && (
|
|
82
|
-
<button
|
|
83
|
-
type='button'
|
|
84
|
-
onClick={onClose}
|
|
85
|
-
className='ml-1 rounded-full p-0.5 hover:bg-black/10 dark:hover:bg-white/10 transition-colors'
|
|
86
|
-
>
|
|
87
|
-
<X className={cn(iconSizes[size])} />
|
|
88
|
-
</button>
|
|
89
|
-
)}
|
|
90
|
-
</span>
|
|
91
|
-
)
|
|
92
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import { cn } from '../../lib/utils.ts'
|
|
3
|
-
|
|
4
|
-
type TextareaProps = {
|
|
5
|
-
label?: string
|
|
6
|
-
placeholder?: string
|
|
7
|
-
value?: string
|
|
8
|
-
onChange?: (value: string) => void
|
|
9
|
-
required?: boolean
|
|
10
|
-
disabled?: boolean
|
|
11
|
-
error?: string
|
|
12
|
-
className?: string
|
|
13
|
-
rows?: number
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export default function Textarea({
|
|
17
|
-
label,
|
|
18
|
-
placeholder,
|
|
19
|
-
value = '',
|
|
20
|
-
onChange,
|
|
21
|
-
required = false,
|
|
22
|
-
disabled = false,
|
|
23
|
-
error,
|
|
24
|
-
className,
|
|
25
|
-
rows = 4,
|
|
26
|
-
}: TextareaProps) {
|
|
27
|
-
const [focused, setFocused] = useState(false)
|
|
28
|
-
|
|
29
|
-
const isFloating = focused || value.length > 0
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div className='relative w-full'>
|
|
33
|
-
{/* Label */}
|
|
34
|
-
{label && (
|
|
35
|
-
<label
|
|
36
|
-
className={cn(
|
|
37
|
-
'absolute left-3 top-2 text-gray-500 dark:text-gray-400 transition-all duration-200 pointer-events-none',
|
|
38
|
-
isFloating
|
|
39
|
-
? 'text-xs -translate-y-3 bg-white dark:bg-gray-900 px-1'
|
|
40
|
-
: 'text-sm translate-y-0'
|
|
41
|
-
)}
|
|
42
|
-
>
|
|
43
|
-
{label}
|
|
44
|
-
{required && <span className='text-error-500 ml-0.5'>*</span>}
|
|
45
|
-
</label>
|
|
46
|
-
)}
|
|
47
|
-
|
|
48
|
-
{/* Textarea */}
|
|
49
|
-
<textarea
|
|
50
|
-
rows={rows}
|
|
51
|
-
placeholder={label ? undefined : placeholder}
|
|
52
|
-
value={value}
|
|
53
|
-
onChange={e => onChange?.(e.target.value)}
|
|
54
|
-
onFocus={() => setFocused(true)}
|
|
55
|
-
onBlur={() => setFocused(false)}
|
|
56
|
-
disabled={disabled}
|
|
57
|
-
className={cn(
|
|
58
|
-
'w-full rounded-md border border-gray-300 dark:border-gray-700 bg-transparent text-gray-900 dark:text-gray-100 px-3 py-2 outline-none transition-all resize-none',
|
|
59
|
-
'focus:border-primary-500 focus:ring-1 focus:ring-primary-500',
|
|
60
|
-
disabled && 'opacity-50 cursor-not-allowed',
|
|
61
|
-
error &&
|
|
62
|
-
'border-error-500 focus:ring-error-500 focus:border-error-500',
|
|
63
|
-
label && 'pt-5',
|
|
64
|
-
className
|
|
65
|
-
)}
|
|
66
|
-
/>
|
|
67
|
-
|
|
68
|
-
{/* Error message */}
|
|
69
|
-
{error && <p className='mt-1 text-sm text-error-500'>{error}</p>}
|
|
70
|
-
</div>
|
|
71
|
-
)
|
|
72
|
-
}
|