cronixui 1.0.6 → 1.1.0
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 +35 -5
- package/package.json +19 -5
- package/packages/go/cronixui/cronixui.go +784 -237
- package/packages/go/cronixui/go.mod +32 -0
- package/packages/go/cronixui/go.sum +666 -0
- package/packages/python/cronixui/__init__.py +59 -3
- package/packages/python/cronixui/alert.py +61 -0
- package/packages/python/cronixui/avatar.py +50 -0
- package/packages/python/cronixui/badge.py +46 -0
- package/packages/python/cronixui/button.py +64 -0
- package/packages/python/cronixui/card.py +62 -0
- package/packages/python/cronixui/form.py +255 -0
- package/packages/python/cronixui/layout.py +143 -0
- package/packages/python/cronixui/list.py +51 -0
- package/packages/python/cronixui/loading.py +36 -0
- package/packages/python/cronixui/progress.py +90 -0
- package/packages/python/cronixui/table.py +48 -0
- package/packages/python/cronixui/tooltip.py +28 -0
- package/packages/react/src/components/Accordion.tsx +82 -0
- package/packages/react/src/components/Alert.tsx +80 -0
- package/packages/react/src/components/Avatar.tsx +54 -0
- package/packages/react/src/components/Badge.tsx +32 -0
- package/packages/react/src/components/Breadcrumb.tsx +50 -0
- package/packages/react/src/components/Button.tsx +47 -0
- package/packages/react/src/components/Card.tsx +69 -0
- package/packages/react/src/components/Checkbox.tsx +30 -0
- package/packages/react/src/components/CommandPalette.tsx +131 -0
- package/packages/react/src/components/Container.tsx +26 -0
- package/packages/react/src/components/Dropdown.tsx +88 -0
- package/packages/react/src/components/FileInput.tsx +86 -0
- package/packages/react/src/components/Footer.tsx +36 -0
- package/packages/react/src/components/FormGroup.tsx +36 -0
- package/packages/react/src/components/Header.tsx +29 -0
- package/packages/react/src/components/Input.tsx +54 -0
- package/packages/react/src/components/List.tsx +55 -0
- package/packages/react/src/components/Modal.tsx +89 -0
- package/packages/react/src/components/Nav.tsx +63 -0
- package/packages/react/src/components/Pagination.tsx +107 -0
- package/packages/react/src/components/Progress.tsx +49 -0
- package/packages/react/src/components/Radio.tsx +64 -0
- package/packages/react/src/components/Search.tsx +95 -0
- package/packages/react/src/components/Select.tsx +41 -0
- package/packages/react/src/components/Sidebar.tsx +64 -0
- package/packages/react/src/components/Skeleton.tsx +39 -0
- package/packages/react/src/components/Slider.tsx +32 -0
- package/packages/react/src/components/Spinner.tsx +24 -0
- package/packages/react/src/components/Stack.tsx +69 -0
- package/packages/react/src/components/Stat.tsx +35 -0
- package/packages/react/src/components/Table.tsx +90 -0
- package/packages/react/src/components/Tabs.tsx +85 -0
- package/packages/react/src/components/Tag.tsx +30 -0
- package/packages/react/src/components/Textarea.tsx +21 -0
- package/packages/react/src/components/Toast.tsx +134 -0
- package/packages/react/src/components/Toggle.tsx +58 -0
- package/packages/react/src/components/Tooltip.tsx +31 -0
- package/packages/react/src/components/Typography.tsx +66 -0
- package/packages/react/src/index.ts +40 -0
- package/packages/react/src/styles.css +2039 -0
- package/packages/react/src/tokens/index.ts +94 -0
- package/packages/rust/cronixui/src/colors.rs +135 -0
- package/packages/rust/cronixui/src/components/accordion.rs +47 -0
- package/packages/rust/cronixui/src/components/alert.rs +95 -0
- package/packages/rust/cronixui/src/components/avatar.rs +85 -0
- package/packages/rust/cronixui/src/components/badge.rs +35 -0
- package/packages/rust/cronixui/src/components/breadcrumb.rs +58 -0
- package/packages/rust/cronixui/src/components/button.rs +70 -0
- package/packages/rust/cronixui/src/components/card.rs +259 -0
- package/packages/rust/cronixui/src/components/command_palette.rs +254 -0
- package/packages/rust/cronixui/src/components/dropdown.rs +179 -0
- package/packages/rust/cronixui/src/components/file_input.rs +74 -0
- package/packages/rust/cronixui/src/components/input.rs +21 -0
- package/packages/rust/cronixui/src/components/list.rs +38 -0
- package/packages/rust/cronixui/src/components/mod.rs +51 -0
- package/packages/rust/cronixui/src/{modal.rs → components/modal.rs} +15 -1
- package/packages/rust/cronixui/src/components/nav.rs +19 -0
- package/packages/rust/cronixui/src/{pagination.rs → components/pagination.rs} +14 -13
- package/packages/rust/cronixui/src/components/progress.rs +50 -0
- package/packages/rust/cronixui/src/components/search.rs +185 -0
- package/packages/rust/cronixui/src/components/skeleton.rs +63 -0
- package/packages/rust/cronixui/src/components/spinner.rs +21 -0
- package/packages/rust/cronixui/src/components/table.rs +56 -0
- package/packages/rust/cronixui/src/components/tabs.rs +43 -0
- package/packages/rust/cronixui/src/components/toast.rs +69 -0
- package/packages/rust/cronixui/src/{toggle.rs → components/toggle.rs} +7 -5
- package/packages/rust/cronixui/src/components/tooltip.rs +11 -0
- package/packages/rust/cronixui/src/lib.rs +111 -64
- package/packages/rust/cronixui/src/tokens.rs +97 -127
- package/packages/web/src/variables.css +81 -81
- package/packages/go/cronixui/tokens.go +0 -129
- package/packages/python/cronixui/pyproject.toml +0 -11
- package/packages/react/src/components/Accordion.jsx +0 -50
- package/packages/react/src/components/Alert.jsx +0 -62
- package/packages/react/src/components/Avatar.jsx +0 -34
- package/packages/react/src/components/Badge.jsx +0 -15
- package/packages/react/src/components/Breadcrumb.jsx +0 -27
- package/packages/react/src/components/Button.jsx +0 -21
- package/packages/react/src/components/Card.jsx +0 -23
- package/packages/react/src/components/Checkbox.jsx +0 -27
- package/packages/react/src/components/CommandPalette.jsx +0 -93
- package/packages/react/src/components/Dropdown.jsx +0 -48
- package/packages/react/src/components/FileInput.jsx +0 -44
- package/packages/react/src/components/Input.jsx +0 -22
- package/packages/react/src/components/List.jsx +0 -29
- package/packages/react/src/components/Modal.jsx +0 -65
- package/packages/react/src/components/Nav.jsx +0 -50
- package/packages/react/src/components/Pagination.jsx +0 -81
- package/packages/react/src/components/Progress.jsx +0 -23
- package/packages/react/src/components/Radio.jsx +0 -50
- package/packages/react/src/components/Search.jsx +0 -70
- package/packages/react/src/components/Select.jsx +0 -33
- package/packages/react/src/components/Skeleton.jsx +0 -15
- package/packages/react/src/components/Slider.jsx +0 -29
- package/packages/react/src/components/Spinner.jsx +0 -5
- package/packages/react/src/components/Stat.jsx +0 -19
- package/packages/react/src/components/Table.jsx +0 -48
- package/packages/react/src/components/Tabs.jsx +0 -65
- package/packages/react/src/components/Tag.jsx +0 -19
- package/packages/react/src/components/Textarea.jsx +0 -17
- package/packages/react/src/components/Toast.jsx +0 -78
- package/packages/react/src/components/Toggle.jsx +0 -34
- package/packages/react/src/components/Tooltip.jsx +0 -12
- package/packages/react/src/index.d.ts +0 -103
- package/packages/react/src/index.js +0 -33
- package/packages/rust/cronixui/src/accordion.rs +0 -49
- package/packages/rust/cronixui/src/command_palette.rs +0 -62
- package/packages/rust/cronixui/src/dropdown.rs +0 -31
- package/packages/rust/cronixui/src/search.rs +0 -49
- package/packages/rust/cronixui/src/tabs.rs +0 -23
- package/packages/rust/cronixui/src/toast.rs +0 -70
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react';
|
|
2
|
-
|
|
3
|
-
export default function Dropdown({
|
|
4
|
-
trigger,
|
|
5
|
-
children,
|
|
6
|
-
className = ''
|
|
7
|
-
}) {
|
|
8
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
9
|
-
const dropdownRef = useRef(null);
|
|
10
|
-
|
|
11
|
-
const toggle = () => setIsOpen((prev) => !prev);
|
|
12
|
-
const close = () => setIsOpen(false);
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
const handleClickOutside = (e) => {
|
|
16
|
-
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
|
17
|
-
close();
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
if (isOpen) {
|
|
22
|
-
document.addEventListener('click', handleClickOutside);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return () => {
|
|
26
|
-
document.removeEventListener('click', handleClickOutside);
|
|
27
|
-
};
|
|
28
|
-
}, [isOpen]);
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<div ref={dropdownRef} className={`cn-dropdown ${isOpen ? 'cn-dropdown-open' : ''} ${className}`}>
|
|
32
|
-
<div className="cn-dropdown-trigger" onClick={toggle}>
|
|
33
|
-
{trigger}
|
|
34
|
-
</div>
|
|
35
|
-
<div className="cn-dropdown-menu" onClick={close}>
|
|
36
|
-
{children}
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function DropdownItem({ children, onClick, className = '', ...props }) {
|
|
43
|
-
return (
|
|
44
|
-
<div className={`cn-dropdown-item ${className}`} onClick={onClick} {...props}>
|
|
45
|
-
{children}
|
|
46
|
-
</div>
|
|
47
|
-
);
|
|
48
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { useState, useRef } from 'react';
|
|
2
|
-
|
|
3
|
-
export default function FileInput({
|
|
4
|
-
onFileSelect,
|
|
5
|
-
accept,
|
|
6
|
-
multiple = false,
|
|
7
|
-
label = 'Drag and drop files here, or click to browse',
|
|
8
|
-
className = ''
|
|
9
|
-
}) {
|
|
10
|
-
const [fileName, setFileName] = useState('');
|
|
11
|
-
const inputRef = useRef(null);
|
|
12
|
-
|
|
13
|
-
const handleChange = (e) => {
|
|
14
|
-
const files = Array.from(e.target.files);
|
|
15
|
-
if (files.length > 0) {
|
|
16
|
-
setFileName(files.map(f => f.name).join(', '));
|
|
17
|
-
onFileSelect?.(multiple ? files : files[0]);
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<div className={`cn-file-input ${fileName ? 'cn-file-input-has-file' : ''} ${className}`}>
|
|
23
|
-
<input
|
|
24
|
-
ref={inputRef}
|
|
25
|
-
type="file"
|
|
26
|
-
accept={accept}
|
|
27
|
-
multiple={multiple}
|
|
28
|
-
onChange={handleChange}
|
|
29
|
-
/>
|
|
30
|
-
<div className="cn-file-input-label">
|
|
31
|
-
<div className="cn-file-input-icon">
|
|
32
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
|
33
|
-
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
34
|
-
<polyline points="17 8 12 3 7 8"></polyline>
|
|
35
|
-
<line x1="12" y1="3" x2="12" y2="15"></line>
|
|
36
|
-
</svg>
|
|
37
|
-
</div>
|
|
38
|
-
<div className="cn-file-input-text">
|
|
39
|
-
{fileName ? <span>{fileName}</span> : label}
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from 'react';
|
|
2
|
-
|
|
3
|
-
const Input = forwardRef(function Input({
|
|
4
|
-
type = 'text',
|
|
5
|
-
size = 'md',
|
|
6
|
-
error = false,
|
|
7
|
-
className = '',
|
|
8
|
-
...props
|
|
9
|
-
}, ref) {
|
|
10
|
-
const sizeClass = size !== 'md' ? `cn-input-${size}` : '';
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<input
|
|
14
|
-
ref={ref}
|
|
15
|
-
type={type}
|
|
16
|
-
className={`cn-input ${sizeClass} ${error ? 'cn-input-error' : ''} ${className}`}
|
|
17
|
-
{...props}
|
|
18
|
-
/>
|
|
19
|
-
);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
export default Input;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export function List({ children, className = '' }) {
|
|
2
|
-
return <div className={`cn-list ${className}`}>{children}</div>;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export function ListItem({
|
|
6
|
-
children,
|
|
7
|
-
icon,
|
|
8
|
-
title,
|
|
9
|
-
subtitle,
|
|
10
|
-
actions,
|
|
11
|
-
clickable = false,
|
|
12
|
-
onClick,
|
|
13
|
-
className = ''
|
|
14
|
-
}) {
|
|
15
|
-
return (
|
|
16
|
-
<div
|
|
17
|
-
className={`cn-list-item ${clickable ? 'cn-list-item-clickable' : ''} ${className}`}
|
|
18
|
-
onClick={onClick}
|
|
19
|
-
>
|
|
20
|
-
{icon && <div className="cn-list-item-icon">{icon}</div>}
|
|
21
|
-
<div className="cn-list-item-content">
|
|
22
|
-
{title && <div className="cn-list-item-title">{title}</div>}
|
|
23
|
-
{subtitle && <div className="cn-list-item-subtitle">{subtitle}</div>}
|
|
24
|
-
{children}
|
|
25
|
-
</div>
|
|
26
|
-
{actions && <div className="cn-list-item-actions">{actions}</div>}
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
|
|
3
|
-
export default function Modal({
|
|
4
|
-
isOpen = false,
|
|
5
|
-
onClose,
|
|
6
|
-
children,
|
|
7
|
-
className = ''
|
|
8
|
-
}) {
|
|
9
|
-
const modalRef = useRef(null);
|
|
10
|
-
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
const handleEscape = (e) => {
|
|
13
|
-
if (e.key === 'Escape' && onClose) {
|
|
14
|
-
onClose();
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
if (isOpen) {
|
|
19
|
-
document.addEventListener('keydown', handleEscape);
|
|
20
|
-
document.body.style.overflow = 'hidden';
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return () => {
|
|
24
|
-
document.removeEventListener('keydown', handleEscape);
|
|
25
|
-
document.body.style.overflow = '';
|
|
26
|
-
};
|
|
27
|
-
}, [isOpen, onClose]);
|
|
28
|
-
|
|
29
|
-
if (!isOpen) return null;
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div
|
|
33
|
-
className={`cn-modal-backdrop cn-modal-open ${className}`}
|
|
34
|
-
onClick={(e) => e.target === e.currentTarget && onClose?.()}
|
|
35
|
-
>
|
|
36
|
-
<div className="cn-modal" ref={modalRef}>
|
|
37
|
-
{children}
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
Modal.Header = function ModalHeader({ children, onClose, className = '' }) {
|
|
44
|
-
return (
|
|
45
|
-
<div className={`cn-modal-header ${className}`}>
|
|
46
|
-
<div className="cn-modal-title">{children}</div>
|
|
47
|
-
{onClose && (
|
|
48
|
-
<button className="cn-modal-close" onClick={onClose}>
|
|
49
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
50
|
-
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
51
|
-
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
52
|
-
</svg>
|
|
53
|
-
</button>
|
|
54
|
-
)}
|
|
55
|
-
</div>
|
|
56
|
-
);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
Modal.Body = function ModalBody({ children, className = '' }) {
|
|
60
|
-
return <div className={`cn-modal-body ${className}`}>{children}</div>;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
Modal.Footer = function ModalFooter({ children, className = '' }) {
|
|
64
|
-
return <div className={`cn-modal-footer ${className}`}>{children}</div>;
|
|
65
|
-
};
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
export default function Nav({
|
|
4
|
-
defaultActive,
|
|
5
|
-
active: controlledActive,
|
|
6
|
-
onChange,
|
|
7
|
-
children,
|
|
8
|
-
className = ''
|
|
9
|
-
}) {
|
|
10
|
-
const [internalActive, setInternalActive] = useState(defaultActive);
|
|
11
|
-
const activeItem = controlledActive !== undefined ? controlledActive : internalActive;
|
|
12
|
-
|
|
13
|
-
const handleClick = (item) => {
|
|
14
|
-
if (onChange) {
|
|
15
|
-
onChange(item);
|
|
16
|
-
} else {
|
|
17
|
-
setInternalActive(item);
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<nav className={`cn-nav ${className}`}>
|
|
23
|
-
{children.map((child, idx) => {
|
|
24
|
-
const isActive = child.props.active !== undefined
|
|
25
|
-
? child.props.active
|
|
26
|
-
: child.props.id === activeItem;
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
...child,
|
|
30
|
-
props: {
|
|
31
|
-
...child.props,
|
|
32
|
-
active: isActive,
|
|
33
|
-
onClick: () => handleClick(child.props.id),
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
})}
|
|
37
|
-
</nav>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function NavItem({ children, active = false, onClick, className = '' }) {
|
|
42
|
-
return (
|
|
43
|
-
<div
|
|
44
|
-
className={`cn-nav-item ${active ? 'cn-nav-active' : ''} ${className}`}
|
|
45
|
-
onClick={onClick}
|
|
46
|
-
>
|
|
47
|
-
{children}
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
|
|
3
|
-
export default function Pagination({
|
|
4
|
-
total,
|
|
5
|
-
current = 1,
|
|
6
|
-
onChange,
|
|
7
|
-
className = ''
|
|
8
|
-
}) {
|
|
9
|
-
const [page, setPage] = useState(current);
|
|
10
|
-
|
|
11
|
-
const activePage = onChange !== undefined ? current : page;
|
|
12
|
-
|
|
13
|
-
const getPages = () => {
|
|
14
|
-
const pages = [];
|
|
15
|
-
const maxVisible = 5;
|
|
16
|
-
|
|
17
|
-
if (total <= maxVisible) {
|
|
18
|
-
for (let i = 1; i <= total; i++) pages.push(i);
|
|
19
|
-
} else {
|
|
20
|
-
if (activePage <= 3) {
|
|
21
|
-
for (let i = 1; i <= 4; i++) pages.push(i);
|
|
22
|
-
pages.push('...');
|
|
23
|
-
pages.push(total);
|
|
24
|
-
} else if (activePage >= total - 2) {
|
|
25
|
-
pages.push(1);
|
|
26
|
-
pages.push('...');
|
|
27
|
-
for (let i = total - 3; i <= total; i++) pages.push(i);
|
|
28
|
-
} else {
|
|
29
|
-
pages.push(1);
|
|
30
|
-
pages.push('...');
|
|
31
|
-
for (let i = activePage - 1; i <= activePage + 1; i++) pages.push(i);
|
|
32
|
-
pages.push('...');
|
|
33
|
-
pages.push(total);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return pages;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const goTo = (p) => {
|
|
41
|
-
if (p < 1 || p > total || p === activePage) return;
|
|
42
|
-
if (onChange) {
|
|
43
|
-
onChange(p);
|
|
44
|
-
} else {
|
|
45
|
-
setPage(p);
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<div className={`cn-pagination ${className}`}>
|
|
51
|
-
<button
|
|
52
|
-
className="cn-pagination-item"
|
|
53
|
-
onClick={() => goTo(activePage - 1)}
|
|
54
|
-
disabled={activePage === 1}
|
|
55
|
-
>
|
|
56
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="16" height="16">
|
|
57
|
-
<polyline points="15 18 9 12 15 6"></polyline>
|
|
58
|
-
</svg>
|
|
59
|
-
</button>
|
|
60
|
-
{getPages().map((p, idx) => (
|
|
61
|
-
<button
|
|
62
|
-
key={idx}
|
|
63
|
-
className={`cn-pagination-item ${p === activePage ? 'cn-pagination-active' : ''}`}
|
|
64
|
-
onClick={() => typeof p === 'number' && goTo(p)}
|
|
65
|
-
disabled={p === '...'}
|
|
66
|
-
>
|
|
67
|
-
{p}
|
|
68
|
-
</button>
|
|
69
|
-
))}
|
|
70
|
-
<button
|
|
71
|
-
className="cn-pagination-item"
|
|
72
|
-
onClick={() => goTo(activePage + 1)}
|
|
73
|
-
disabled={activePage === total}
|
|
74
|
-
>
|
|
75
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="16" height="16">
|
|
76
|
-
<polyline points="9 18 15 12 9 6"></polyline>
|
|
77
|
-
</svg>
|
|
78
|
-
</button>
|
|
79
|
-
</div>
|
|
80
|
-
);
|
|
81
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export default function Progress({
|
|
2
|
-
value = 0,
|
|
3
|
-
max = 100,
|
|
4
|
-
showLabel = false,
|
|
5
|
-
variant = 'default',
|
|
6
|
-
size = 'md',
|
|
7
|
-
className = ''
|
|
8
|
-
}) {
|
|
9
|
-
const percentage = Math.min(Math.max((value / max) * 100, 0), 100);
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<div className={className}>
|
|
13
|
-
{showLabel && (
|
|
14
|
-
<div className="cn-progress-label">
|
|
15
|
-
<span>{percentage.toFixed(0)}%</span>
|
|
16
|
-
</div>
|
|
17
|
-
)}
|
|
18
|
-
<div className={`cn-progress ${size !== 'md' ? `cn-progress-${size}` : ''} ${variant !== 'default' ? `cn-progress-${variant}` : ''}`}>
|
|
19
|
-
<div className="cn-progress-bar" style={{ width: `${percentage}%` }}></div>
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
22
|
-
);
|
|
23
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from 'react';
|
|
2
|
-
|
|
3
|
-
const Radio = forwardRef(function Radio({
|
|
4
|
-
checked = false,
|
|
5
|
-
onChange,
|
|
6
|
-
disabled = false,
|
|
7
|
-
children,
|
|
8
|
-
name,
|
|
9
|
-
className = '',
|
|
10
|
-
...props
|
|
11
|
-
}, ref) {
|
|
12
|
-
return (
|
|
13
|
-
<label className={`cn-radio ${disabled ? 'disabled' : ''} ${className}`}>
|
|
14
|
-
<input
|
|
15
|
-
ref={ref}
|
|
16
|
-
type="radio"
|
|
17
|
-
checked={checked}
|
|
18
|
-
onChange={(e) => onChange?.(e.target.checked)}
|
|
19
|
-
disabled={disabled}
|
|
20
|
-
name={name}
|
|
21
|
-
{...props}
|
|
22
|
-
/>
|
|
23
|
-
<span className="cn-radio-box"></span>
|
|
24
|
-
{children && <span className="cn-radio-label">{children}</span>}
|
|
25
|
-
</label>
|
|
26
|
-
);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
export function RadioGroup({ children, name, value, onChange, className = '' }) {
|
|
30
|
-
return (
|
|
31
|
-
<div className={className} role="radiogroup">
|
|
32
|
-
{children.map((child, idx) => {
|
|
33
|
-
if (child.type?.displayName === 'Radio') {
|
|
34
|
-
return {
|
|
35
|
-
...child,
|
|
36
|
-
props: {
|
|
37
|
-
...child.props,
|
|
38
|
-
name,
|
|
39
|
-
checked: child.props.value === value,
|
|
40
|
-
onChange: () => onChange?.(child.props.value),
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
return child;
|
|
45
|
-
})}
|
|
46
|
-
</div>
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export default Radio;
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react';
|
|
2
|
-
|
|
3
|
-
export default function Search({
|
|
4
|
-
items = [],
|
|
5
|
-
placeholder = 'Search...',
|
|
6
|
-
onSelect,
|
|
7
|
-
className = ''
|
|
8
|
-
}) {
|
|
9
|
-
const [query, setQuery] = useState('');
|
|
10
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
11
|
-
const ref = useRef(null);
|
|
12
|
-
|
|
13
|
-
const filtered = items.filter(item =>
|
|
14
|
-
item.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
15
|
-
(item.subtitle && item.subtitle.toLowerCase().includes(query.toLowerCase()))
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
const handleClickOutside = (e) => {
|
|
20
|
-
if (ref.current && !ref.current.contains(e.target)) {
|
|
21
|
-
setIsOpen(false);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
document.addEventListener('click', handleClickOutside);
|
|
26
|
-
return () => document.removeEventListener('click', handleClickOutside);
|
|
27
|
-
}, []);
|
|
28
|
-
|
|
29
|
-
const handleSelect = (item) => {
|
|
30
|
-
setQuery(item.title);
|
|
31
|
-
setIsOpen(false);
|
|
32
|
-
onSelect?.(item);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div ref={ref} className={`cn-search ${isOpen ? 'cn-search-open' : ''} ${className}`}>
|
|
37
|
-
<svg className="cn-search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
38
|
-
<circle cx="11" cy="11" r="8"></circle>
|
|
39
|
-
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
40
|
-
</svg>
|
|
41
|
-
<input
|
|
42
|
-
type="text"
|
|
43
|
-
className="cn-input cn-search-input"
|
|
44
|
-
placeholder={placeholder}
|
|
45
|
-
value={query}
|
|
46
|
-
onChange={(e) => {
|
|
47
|
-
setQuery(e.target.value);
|
|
48
|
-
setIsOpen(true);
|
|
49
|
-
}}
|
|
50
|
-
onFocus={() => setIsOpen(true)}
|
|
51
|
-
/>
|
|
52
|
-
{isOpen && filtered.length > 0 && (
|
|
53
|
-
<div className="cn-search-results">
|
|
54
|
-
{filtered.map((item, idx) => (
|
|
55
|
-
<div
|
|
56
|
-
key={idx}
|
|
57
|
-
className="cn-search-result"
|
|
58
|
-
onClick={() => handleSelect(item)}
|
|
59
|
-
>
|
|
60
|
-
<div className="cn-search-result-title">{item.title}</div>
|
|
61
|
-
{item.subtitle && (
|
|
62
|
-
<div className="cn-search-result-subtitle">{item.subtitle}</div>
|
|
63
|
-
)}
|
|
64
|
-
</div>
|
|
65
|
-
))}
|
|
66
|
-
</div>
|
|
67
|
-
)}
|
|
68
|
-
</div>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from 'react';
|
|
2
|
-
|
|
3
|
-
const Select = forwardRef(function Select({
|
|
4
|
-
options = [],
|
|
5
|
-
value,
|
|
6
|
-
onChange,
|
|
7
|
-
placeholder = 'Select...',
|
|
8
|
-
disabled = false,
|
|
9
|
-
className = '',
|
|
10
|
-
...props
|
|
11
|
-
}, ref) {
|
|
12
|
-
return (
|
|
13
|
-
<div className={`cn-select-wrapper ${className}`}>
|
|
14
|
-
<select
|
|
15
|
-
ref={ref}
|
|
16
|
-
className="cn-select"
|
|
17
|
-
value={value}
|
|
18
|
-
onChange={(e) => onChange?.(e.target.value)}
|
|
19
|
-
disabled={disabled}
|
|
20
|
-
{...props}
|
|
21
|
-
>
|
|
22
|
-
{placeholder && <option value="" disabled>{placeholder}</option>}
|
|
23
|
-
{options.map((opt, idx) => (
|
|
24
|
-
<option key={idx} value={opt.value}>
|
|
25
|
-
{opt.label}
|
|
26
|
-
</option>
|
|
27
|
-
))}
|
|
28
|
-
</select>
|
|
29
|
-
</div>
|
|
30
|
-
);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
export default Select;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export default function Skeleton({
|
|
2
|
-
variant = 'text',
|
|
3
|
-
width,
|
|
4
|
-
height,
|
|
5
|
-
className = ''
|
|
6
|
-
}) {
|
|
7
|
-
const variantClass = variant !== 'text' ? `cn-skeleton-${variant}` : 'cn-skeleton';
|
|
8
|
-
|
|
9
|
-
const style = {
|
|
10
|
-
width: width,
|
|
11
|
-
height: height,
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
return <div className={`${variantClass} ${className}`} style={style}></div>;
|
|
15
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from 'react';
|
|
2
|
-
|
|
3
|
-
const Slider = forwardRef(function Slider({
|
|
4
|
-
value = 0,
|
|
5
|
-
min = 0,
|
|
6
|
-
max = 100,
|
|
7
|
-
step = 1,
|
|
8
|
-
onChange,
|
|
9
|
-
disabled = false,
|
|
10
|
-
className = '',
|
|
11
|
-
...props
|
|
12
|
-
}, ref) {
|
|
13
|
-
return (
|
|
14
|
-
<input
|
|
15
|
-
ref={ref}
|
|
16
|
-
type="range"
|
|
17
|
-
className={`cn-slider ${className}`}
|
|
18
|
-
value={value}
|
|
19
|
-
min={min}
|
|
20
|
-
max={max}
|
|
21
|
-
step={step}
|
|
22
|
-
onChange={(e) => onChange?.(Number(e.target.value))}
|
|
23
|
-
disabled={disabled}
|
|
24
|
-
{...props}
|
|
25
|
-
/>
|
|
26
|
-
);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
export default Slider;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export default function Stat({
|
|
2
|
-
value,
|
|
3
|
-
label,
|
|
4
|
-
delta,
|
|
5
|
-
deltaType = 'up',
|
|
6
|
-
className = ''
|
|
7
|
-
}) {
|
|
8
|
-
return (
|
|
9
|
-
<div className={`cn-stat ${className}`}>
|
|
10
|
-
<div className="cn-stat-value">{value}</div>
|
|
11
|
-
{label && <div className="cn-stat-label">{label}</div>}
|
|
12
|
-
{delta && (
|
|
13
|
-
<div className={`cn-stat-delta cn-stat-delta-${deltaType}`}>
|
|
14
|
-
{deltaType === 'up' ? '↑' : '↓'} {delta}
|
|
15
|
-
</div>
|
|
16
|
-
)}
|
|
17
|
-
</div>
|
|
18
|
-
);
|
|
19
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { forwardRef } from 'react';
|
|
2
|
-
|
|
3
|
-
const Table = forwardRef(function Table({
|
|
4
|
-
columns = [],
|
|
5
|
-
data = [],
|
|
6
|
-
sortable = false,
|
|
7
|
-
onSort,
|
|
8
|
-
className = ''
|
|
9
|
-
}, ref) {
|
|
10
|
-
const handleSort = (column) => {
|
|
11
|
-
if (!sortable || !column.sortable) return;
|
|
12
|
-
onSort?.(column.key);
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<div className={`cn-table-wrapper ${sortable ? 'cn-table-sortable' : ''} ${className}`}>
|
|
17
|
-
<table className="cn-table" ref={ref}>
|
|
18
|
-
<thead>
|
|
19
|
-
<tr>
|
|
20
|
-
{columns.map((col, idx) => (
|
|
21
|
-
<th
|
|
22
|
-
key={idx}
|
|
23
|
-
onClick={() => handleSort(col)}
|
|
24
|
-
data-sort={col.key}
|
|
25
|
-
>
|
|
26
|
-
{col.header}
|
|
27
|
-
{col.sortable && ' ↕'}
|
|
28
|
-
</th>
|
|
29
|
-
))}
|
|
30
|
-
</tr>
|
|
31
|
-
</thead>
|
|
32
|
-
<tbody>
|
|
33
|
-
{data.map((row, idx) => (
|
|
34
|
-
<tr key={idx}>
|
|
35
|
-
{columns.map((col, colIdx) => (
|
|
36
|
-
<td key={colIdx}>
|
|
37
|
-
{col.render ? col.render(row) : row[col.key]}
|
|
38
|
-
</td>
|
|
39
|
-
))}
|
|
40
|
-
</tr>
|
|
41
|
-
))}
|
|
42
|
-
</tbody>
|
|
43
|
-
</table>
|
|
44
|
-
</div>
|
|
45
|
-
);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
export default Table;
|