@mci-ui/mci-ui 0.0.0 → 0.0.2
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/dist/index.cjs +12 -1158
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +12 -1125
- package/dist/index.js.map +1 -1
- package/package.json +63 -61
- package/src/App.tsx +5 -0
- package/src/index.ts +31 -0
- package/src/main.tsx +10 -0
- package/src/shared/hooks/toast.ts +32 -0
- package/src/shared/lib/utils.ts +37 -0
- package/src/shared/types/MciTableType.ts +10 -0
- package/src/shared/ui/breadcrumb/Breadcrumb.tsx +68 -0
- package/src/shared/ui/button/Button.tsx +105 -0
- package/src/shared/ui/collapse/Collapse.tsx +98 -0
- package/src/shared/ui/index.ts +30 -0
- package/src/shared/ui/inputMain/InputMain.tsx +241 -0
- package/src/shared/ui/mciTable/MciTable.tsx +166 -0
- package/src/shared/ui/modal/Modal.tsx +92 -0
- package/src/shared/ui/pagination/Pagination.tsx +141 -0
- package/src/shared/ui/skeleton/Skeleton.tsx +33 -0
- package/src/shared/ui/tabs/Tabs.tsx +192 -0
- package/src/shared/ui/tag/Tag.tsx +92 -0
- package/src/shared/ui/textarea/Textarea.tsx +72 -0
- package/src/shared/ui/toast/Toast.tsx +150 -0
- package/src/shared/ui/toast/ToastContainer.tsx +19 -0
- package/src/shared/ui/tooltip/Tooltip.tsx +58 -0
- package/src/styles/index.css +36 -0
- package/src/styles/tailwind-config.css +294 -0
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AlertCircle,
|
|
3
|
+
CheckCircle2,
|
|
4
|
+
Info,
|
|
5
|
+
Loader2,
|
|
6
|
+
X,
|
|
7
|
+
XCircle,
|
|
8
|
+
} from 'lucide-react'
|
|
9
|
+
import { useEffect, useState } from 'react'
|
|
10
|
+
import { cn } from '../../lib/utils.ts'
|
|
11
|
+
|
|
12
|
+
// --- TYPES ---
|
|
13
|
+
type ToastType = 'success' | 'error' | 'warning' | 'info' | 'loading'
|
|
14
|
+
|
|
15
|
+
export type ToastProps = {
|
|
16
|
+
id?: string
|
|
17
|
+
title: string
|
|
18
|
+
description?: string
|
|
19
|
+
type?: ToastType
|
|
20
|
+
duration?: number
|
|
21
|
+
position?:
|
|
22
|
+
| 'top-right'
|
|
23
|
+
| 'top-left'
|
|
24
|
+
| 'bottom-right'
|
|
25
|
+
| 'bottom-left'
|
|
26
|
+
| 'top-center'
|
|
27
|
+
| 'bottom-center'
|
|
28
|
+
onClose?: (id: string) => void
|
|
29
|
+
action?: {
|
|
30
|
+
label: string
|
|
31
|
+
onClick: () => void
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// --- MAIN TOAST COMPONENT ---
|
|
36
|
+
export function Toast({
|
|
37
|
+
id = Math.random().toString(36),
|
|
38
|
+
title,
|
|
39
|
+
description,
|
|
40
|
+
type = 'info',
|
|
41
|
+
duration = 5000,
|
|
42
|
+
position = 'top-right',
|
|
43
|
+
onClose,
|
|
44
|
+
action,
|
|
45
|
+
}: ToastProps) {
|
|
46
|
+
const [isVisible, setIsVisible] = useState(false)
|
|
47
|
+
const [isLeaving, setIsLeaving] = useState(false)
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const showTimer = setTimeout(() => setIsVisible(true), 100)
|
|
51
|
+
if (duration !== Infinity) {
|
|
52
|
+
const closeTimer = setTimeout(() => handleClose(), duration)
|
|
53
|
+
return () => {
|
|
54
|
+
clearTimeout(showTimer)
|
|
55
|
+
clearTimeout(closeTimer)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return () => clearTimeout(showTimer)
|
|
59
|
+
}, [duration])
|
|
60
|
+
|
|
61
|
+
const handleClose = () => {
|
|
62
|
+
setIsLeaving(true)
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
setIsVisible(false)
|
|
65
|
+
onClose?.(id)
|
|
66
|
+
}, 300)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const handleAction = () => {
|
|
70
|
+
action?.onClick()
|
|
71
|
+
handleClose()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const icons = {
|
|
75
|
+
success: <CheckCircle2 className='w-5 h-5' />,
|
|
76
|
+
error: <XCircle className='w-5 h-5' />,
|
|
77
|
+
warning: <AlertCircle className='w-5 h-5' />,
|
|
78
|
+
info: <Info className='w-5 h-5' />,
|
|
79
|
+
loading: <Loader2 className='w-5 h-5 animate-spin' />,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const variantClasses = {
|
|
83
|
+
success: 'bg-success-50 border-success-200 text-success-800',
|
|
84
|
+
error: 'bg-error-50 border-error-200 text-error-800',
|
|
85
|
+
warning: 'bg-secondary-50 border-secondary-200 text-secondary-800',
|
|
86
|
+
info: 'bg-info-50 border-info-200 text-info-800',
|
|
87
|
+
loading: 'bg-gray-50 border-gray-200 text-gray-800',
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const iconColors = {
|
|
91
|
+
success: 'text-success-600',
|
|
92
|
+
error: 'text-error-600',
|
|
93
|
+
warning: 'text-secondary-600',
|
|
94
|
+
info: 'text-info-600',
|
|
95
|
+
loading: 'text-gray-600',
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const positionClasses = {
|
|
99
|
+
'top-right': 'top-4 right-4',
|
|
100
|
+
'top-left': 'top-4 left-4',
|
|
101
|
+
'bottom-right': 'bottom-4 right-4',
|
|
102
|
+
'bottom-left': 'bottom-4 left-4',
|
|
103
|
+
'top-center': 'top-4 left-1/2 -translate-x-1/2',
|
|
104
|
+
'bottom-center': 'bottom-4 left-1/2 -translate-x-1/2',
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!isVisible) return null
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<div
|
|
111
|
+
className={cn(
|
|
112
|
+
'fixed z-50 transform transition-all duration-300',
|
|
113
|
+
positionClasses[position],
|
|
114
|
+
isLeaving ? 'opacity-0 scale-95' : 'opacity-100 scale-100',
|
|
115
|
+
position.includes('top') && !isLeaving && 'animate-slide-down',
|
|
116
|
+
position.includes('bottom') && !isLeaving && 'animate-slide-up'
|
|
117
|
+
)}
|
|
118
|
+
>
|
|
119
|
+
<div
|
|
120
|
+
className={cn(
|
|
121
|
+
'flex items-start gap-3 p-4 rounded-lg border shadow-lg max-w-sm',
|
|
122
|
+
'backdrop-blur-sm bg-white/95',
|
|
123
|
+
variantClasses[type]
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
126
|
+
<div className={cn('flex-shrink-0 mt-0.5', iconColors[type])}>
|
|
127
|
+
{icons[type]}
|
|
128
|
+
</div>
|
|
129
|
+
<div className='flex-1 min-w-0'>
|
|
130
|
+
<h4 className='font-semibold text-sm mb-1'>{title}</h4>
|
|
131
|
+
{description && <p className='text-sm opacity-90'>{description}</p>}
|
|
132
|
+
{action && (
|
|
133
|
+
<button
|
|
134
|
+
onClick={handleAction}
|
|
135
|
+
className='mt-2 text-sm font-medium underline underline-offset-2 hover:no-underline'
|
|
136
|
+
>
|
|
137
|
+
{action.label}
|
|
138
|
+
</button>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
<button
|
|
142
|
+
onClick={handleClose}
|
|
143
|
+
className='flex-shrink-0 p-1 rounded-full hover:bg-black/5 transition-colors'
|
|
144
|
+
>
|
|
145
|
+
<X className='w-4 h-4' />
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Toast, type ToastProps } from './Toast.tsx'
|
|
2
|
+
|
|
3
|
+
export function ToastContainer({
|
|
4
|
+
toasts,
|
|
5
|
+
onRemove,
|
|
6
|
+
position = 'top-right',
|
|
7
|
+
}: {
|
|
8
|
+
toasts: ToastProps[]
|
|
9
|
+
onRemove: (id: string) => void
|
|
10
|
+
position?: ToastProps['position']
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<>
|
|
14
|
+
{toasts.map(t => (
|
|
15
|
+
<Toast key={t.id} {...t} onClose={onRemove} position={position} />
|
|
16
|
+
))}
|
|
17
|
+
</>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { cn } from '../../lib/utils.ts'
|
|
3
|
+
|
|
4
|
+
type TooltipProps = {
|
|
5
|
+
content: React.ReactNode
|
|
6
|
+
children: React.ReactNode
|
|
7
|
+
position?: 'top' | 'bottom' | 'left' | 'right'
|
|
8
|
+
delay?: number
|
|
9
|
+
className?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function Tooltip({
|
|
13
|
+
content,
|
|
14
|
+
children,
|
|
15
|
+
position = 'top',
|
|
16
|
+
delay = 200,
|
|
17
|
+
className,
|
|
18
|
+
}: TooltipProps) {
|
|
19
|
+
const positionClasses = {
|
|
20
|
+
top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
|
|
21
|
+
bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
|
|
22
|
+
left: 'right-full top-1/2 -translate-y-1/2 mr-2',
|
|
23
|
+
right: 'left-full top-1/2 -translate-y-1/2 ml-2',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const arrowClasses = {
|
|
27
|
+
top: 'left-1/2 -translate-x-1/2 top-full',
|
|
28
|
+
bottom: 'left-1/2 -translate-x-1/2 bottom-full',
|
|
29
|
+
left: 'top-1/2 -translate-y-1/2 left-full',
|
|
30
|
+
right: 'top-1/2 -translate-y-1/2 right-full',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className='relative inline-block group'>
|
|
35
|
+
{children}
|
|
36
|
+
|
|
37
|
+
<div
|
|
38
|
+
className={cn(
|
|
39
|
+
'absolute z-50 whitespace-nowrap rounded-md bg-gray-800 text-white text-xs px-2 py-1 shadow-md dark:bg-gray-900',
|
|
40
|
+
'transition-all opacity-0 scale-95 group-hover:opacity-100 group-hover:scale-100',
|
|
41
|
+
'group-hover:delay-200 duration-200 ease-out',
|
|
42
|
+
positionClasses[position],
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
style={{ transitionDelay: `${delay}ms` }}
|
|
46
|
+
>
|
|
47
|
+
{content}
|
|
48
|
+
|
|
49
|
+
<span
|
|
50
|
+
className={cn(
|
|
51
|
+
'absolute w-2 h-2 bg-gray-800 dark:bg-gray-900 rotate-45',
|
|
52
|
+
arrowClasses[position]
|
|
53
|
+
)}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
@import './tailwind-config.css';
|
|
2
|
+
|
|
3
|
+
* {
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
html,
|
|
8
|
+
body {
|
|
9
|
+
font-family: system-ui, Avenir, sans-serif;
|
|
10
|
+
-webkit-font-smoothing: antialiased;
|
|
11
|
+
-moz-osx-font-smoothing: grayscale;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
button {
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
a {
|
|
20
|
+
text-decoration: none;
|
|
21
|
+
color: inherit;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
ul,
|
|
25
|
+
ol {
|
|
26
|
+
list-style: none;
|
|
27
|
+
padding: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
input,
|
|
31
|
+
textarea,
|
|
32
|
+
button,
|
|
33
|
+
select {
|
|
34
|
+
font: inherit;
|
|
35
|
+
outline: none;
|
|
36
|
+
}
|