@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 CHANGED
@@ -1,15 +1,14 @@
1
1
  {
2
2
  "name": "@mci-ui/mci-ui",
3
3
  "private": false,
4
- "version": "0.0.4",
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
- "src",
12
- "index.css"
11
+ "dist/*.css"
13
12
  ],
14
13
  "scripts": {
15
14
  "dev": "vite",
package/src/App.tsx DELETED
@@ -1,5 +0,0 @@
1
- function App() {
2
- return <></>
3
- }
4
-
5
- export default App
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,10 +0,0 @@
1
- import { StrictMode } from 'react'
2
- import { createRoot } from 'react-dom/client'
3
- import App from './App.tsx'
4
- import './styles/index.css'
5
-
6
- createRoot(document.getElementById('root')!).render(
7
- <StrictMode>
8
- <App />
9
- </StrictMode>
10
- )
@@ -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
- }
@@ -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,10 +0,0 @@
1
- import React from "react";
2
-
3
- export type MciTableColumn<T> = {
4
- key: keyof T
5
- title: string
6
- sortable?: boolean
7
- align?: 'left' | 'center' | 'right'
8
- width?: string | number
9
- render?: (value: T[keyof T], row: T) => React.ReactNode
10
- }
@@ -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
- }