@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.
Files changed (38) hide show
  1. package/README.md +3 -71
  2. package/dist/index.css +1 -36
  3. package/dist/index.es.js +4254 -0
  4. package/dist/index.es.js.map +1 -0
  5. package/dist/index.umd.js +160 -0
  6. package/dist/index.umd.js.map +1 -0
  7. package/dist/types/index.d.ts +205 -0
  8. package/dist/vite.svg +1 -0
  9. package/package.json +16 -12
  10. package/dist/index.cjs +0 -44
  11. package/dist/index.cjs.map +0 -1
  12. package/dist/index.d.cts +0 -188
  13. package/dist/index.d.ts +0 -188
  14. package/dist/index.js +0 -44
  15. package/dist/index.js.map +0 -1
  16. package/dist/tailwind-config.css +0 -294
  17. package/src/App.tsx +0 -5
  18. package/src/index.ts +0 -32
  19. package/src/main.tsx +0 -10
  20. package/src/shared/hooks/toast.ts +0 -32
  21. package/src/shared/lib/utils.ts +0 -37
  22. package/src/shared/types/MciTableType.ts +0 -10
  23. package/src/shared/ui/breadcrumb/Breadcrumb.tsx +0 -68
  24. package/src/shared/ui/button/Button.tsx +0 -105
  25. package/src/shared/ui/collapse/Collapse.tsx +0 -98
  26. package/src/shared/ui/inputMain/InputMain.tsx +0 -241
  27. package/src/shared/ui/mciTable/MciTable.tsx +0 -166
  28. package/src/shared/ui/modal/Modal.tsx +0 -92
  29. package/src/shared/ui/pagination/Pagination.tsx +0 -141
  30. package/src/shared/ui/skeleton/Skeleton.tsx +0 -33
  31. package/src/shared/ui/tabs/Tabs.tsx +0 -192
  32. package/src/shared/ui/tag/Tag.tsx +0 -92
  33. package/src/shared/ui/textarea/Textarea.tsx +0 -72
  34. package/src/shared/ui/toast/Toast.tsx +0 -150
  35. package/src/shared/ui/toast/ToastContainer.tsx +0 -19
  36. package/src/shared/ui/tooltip/Tooltip.tsx +0 -58
  37. package/src/styles/index.css +0 -36
  38. 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
- }