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
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface BreadcrumbProps extends React.HTMLAttributes<HTMLElement> {}
|
|
4
|
+
|
|
5
|
+
export interface BreadcrumbItemProps extends React.HTMLAttributes<HTMLAnchorElement | HTMLSpanElement> {
|
|
6
|
+
href?: string;
|
|
7
|
+
active?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const Breadcrumb: React.FC<BreadcrumbProps> & {
|
|
11
|
+
Item: React.FC<BreadcrumbItemProps>;
|
|
12
|
+
} = Object.assign(
|
|
13
|
+
({ children, className = '', ...props }: BreadcrumbProps) => {
|
|
14
|
+
const items = React.Children.toArray(children);
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<nav className={`cn-breadcrumb ${className}`.trim()} aria-label="Breadcrumb" {...props}>
|
|
18
|
+
{items.map((child, idx) => (
|
|
19
|
+
<span key={idx}>
|
|
20
|
+
{child}
|
|
21
|
+
{idx < items.length - 1 && (
|
|
22
|
+
<span className="cn-breadcrumb-separator" aria-hidden="true">/</span>
|
|
23
|
+
)}
|
|
24
|
+
</span>
|
|
25
|
+
))}
|
|
26
|
+
</nav>
|
|
27
|
+
);
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
Item: ({ children, href, active = false, className = '', ...props }: BreadcrumbItemProps) => {
|
|
31
|
+
if (active) {
|
|
32
|
+
return (
|
|
33
|
+
<span className={`cn-breadcrumb-current ${className}`.trim()} aria-current="page" {...props}>
|
|
34
|
+
{children}
|
|
35
|
+
</span>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return (
|
|
39
|
+
<a href={href} className={`cn-breadcrumb-item ${className}`.trim()} {...(props as React.AnchorHTMLAttributes<HTMLAnchorElement>)}>
|
|
40
|
+
{children}
|
|
41
|
+
</a>
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
Breadcrumb.displayName = 'Breadcrumb';
|
|
48
|
+
(Breadcrumb.Item as React.FC<BreadcrumbItemProps> & { displayName?: string }).displayName = 'BreadcrumbItem';
|
|
49
|
+
|
|
50
|
+
export default Breadcrumb;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type ButtonVariant = 'primary' | 'ghost' | 'outline' | 'danger' | 'success' | 'default';
|
|
4
|
+
export type ButtonSize = 'sm' | 'md' | 'lg';
|
|
5
|
+
|
|
6
|
+
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
7
|
+
variant?: ButtonVariant;
|
|
8
|
+
size?: ButtonSize;
|
|
9
|
+
icon?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ButtonGroupProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
13
|
+
|
|
14
|
+
export const Button: React.FC<ButtonProps> & {
|
|
15
|
+
Group: React.FC<ButtonGroupProps>;
|
|
16
|
+
} = Object.assign(
|
|
17
|
+
React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
18
|
+
({ children, variant = 'default', size = 'md', icon = false, disabled = false, className = '', ...props }, ref) => {
|
|
19
|
+
const variantClass = variant !== 'default' ? `cn-btn-${variant}` : '';
|
|
20
|
+
const sizeClass = size !== 'md' ? `cn-btn-${size}` : '';
|
|
21
|
+
const iconClass = icon ? 'cn-btn-icon' : '';
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<button
|
|
25
|
+
ref={ref}
|
|
26
|
+
className={`cn-btn ${variantClass} ${sizeClass} ${iconClass} ${className}`.trim()}
|
|
27
|
+
disabled={disabled}
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</button>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
),
|
|
35
|
+
{
|
|
36
|
+
Group: ({ children, className = '', ...props }: ButtonGroupProps) => (
|
|
37
|
+
<div className={`cn-btn-group ${className}`.trim()} {...props}>
|
|
38
|
+
{children}
|
|
39
|
+
</div>
|
|
40
|
+
),
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
Button.displayName = 'Button';
|
|
45
|
+
Button.Group.displayName = 'Button.Group';
|
|
46
|
+
|
|
47
|
+
export default Button;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
clickable?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
8
|
+
|
|
9
|
+
export interface CardBodyProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
10
|
+
|
|
11
|
+
export interface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
12
|
+
|
|
13
|
+
export interface CardIconProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
14
|
+
|
|
15
|
+
export interface CardTitleProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
16
|
+
|
|
17
|
+
export interface CardSubtitleProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
18
|
+
|
|
19
|
+
export const Card: React.FC<CardProps> & {
|
|
20
|
+
Header: React.FC<CardHeaderProps>;
|
|
21
|
+
Body: React.FC<CardBodyProps>;
|
|
22
|
+
Footer: React.FC<CardFooterProps>;
|
|
23
|
+
Icon: React.FC<CardIconProps>;
|
|
24
|
+
Title: React.FC<CardTitleProps>;
|
|
25
|
+
Subtitle: React.FC<CardSubtitleProps>;
|
|
26
|
+
} = Object.assign(
|
|
27
|
+
({ children, clickable = false, className = '', ...props }: CardProps) => {
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
className={`cn-card ${clickable ? 'cn-card-clickable' : ''} ${className}`.trim()}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
Header: ({ children, className = '', ...props }: CardHeaderProps) => (
|
|
39
|
+
<div className={`cn-card-header ${className}`.trim()} {...props}>{children}</div>
|
|
40
|
+
),
|
|
41
|
+
Body: ({ children, className = '', ...props }: CardBodyProps) => (
|
|
42
|
+
<div className={`cn-card-body ${className}`.trim()} {...props}>{children}</div>
|
|
43
|
+
),
|
|
44
|
+
Footer: ({ children, className = '', ...props }: CardFooterProps) => (
|
|
45
|
+
<div className={`cn-card-footer ${className}`.trim()} {...props}>{children}</div>
|
|
46
|
+
),
|
|
47
|
+
Icon: ({ children, className = '', ...props }: CardIconProps) => (
|
|
48
|
+
<div className={`cn-card-icon ${className}`.trim()} {...props}>
|
|
49
|
+
{children}
|
|
50
|
+
</div>
|
|
51
|
+
),
|
|
52
|
+
Title: ({ children, className = '', ...props }: CardTitleProps) => (
|
|
53
|
+
<div className={`cn-card-title ${className}`.trim()} {...props}>{children}</div>
|
|
54
|
+
),
|
|
55
|
+
Subtitle: ({ children, className = '', ...props }: CardSubtitleProps) => (
|
|
56
|
+
<div className={`cn-card-subtitle ${className}`.trim()} {...props}>{children}</div>
|
|
57
|
+
),
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
Card.displayName = 'Card';
|
|
62
|
+
Card.Header.displayName = 'Card.Header';
|
|
63
|
+
Card.Body.displayName = 'Card.Body';
|
|
64
|
+
Card.Footer.displayName = 'Card.Footer';
|
|
65
|
+
Card.Icon.displayName = 'Card.Icon';
|
|
66
|
+
Card.Title.displayName = 'Card.Title';
|
|
67
|
+
Card.Subtitle.displayName = 'Card.Subtitle';
|
|
68
|
+
|
|
69
|
+
export default Card;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'checked' | 'onChange'> {
|
|
4
|
+
checked?: boolean;
|
|
5
|
+
onChange?: (checked: boolean) => void;
|
|
6
|
+
label?: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
|
10
|
+
({ checked = false, onChange, disabled = false, children, label, className = '', ...props }, ref) => {
|
|
11
|
+
return (
|
|
12
|
+
<label className={`cn-checkbox ${disabled ? 'disabled' : ''} ${className}`.trim()}>
|
|
13
|
+
<input
|
|
14
|
+
ref={ref}
|
|
15
|
+
type="checkbox"
|
|
16
|
+
checked={checked}
|
|
17
|
+
onChange={(e) => onChange?.(e.target.checked)}
|
|
18
|
+
disabled={disabled}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
<span className="cn-checkbox-box" />
|
|
22
|
+
{(label || children) && <span className="cn-checkbox-label">{label || children}</span>}
|
|
23
|
+
</label>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
Checkbox.displayName = 'Checkbox';
|
|
29
|
+
|
|
30
|
+
export default Checkbox;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface CommandPaletteItem {
|
|
4
|
+
title: string;
|
|
5
|
+
subtitle?: string;
|
|
6
|
+
icon?: React.ReactNode;
|
|
7
|
+
kbd?: string;
|
|
8
|
+
action?: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CommandPaletteProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onSelect'> {
|
|
12
|
+
items?: CommandPaletteItem[];
|
|
13
|
+
isOpen?: boolean;
|
|
14
|
+
onClose?: () => void;
|
|
15
|
+
onSelect?: (item: CommandPaletteItem) => void;
|
|
16
|
+
placeholder?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const CommandPalette: React.FC<CommandPaletteProps> = ({
|
|
20
|
+
items = [],
|
|
21
|
+
isOpen = false,
|
|
22
|
+
onClose,
|
|
23
|
+
onSelect,
|
|
24
|
+
placeholder = 'Search commands...',
|
|
25
|
+
className = '',
|
|
26
|
+
...props
|
|
27
|
+
}) => {
|
|
28
|
+
const [query, setQuery] = React.useState('');
|
|
29
|
+
const [activeIndex, setActiveIndex] = React.useState(0);
|
|
30
|
+
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
31
|
+
|
|
32
|
+
const filtered = items.filter(
|
|
33
|
+
(item) =>
|
|
34
|
+
item.title.toLowerCase().includes(query.toLowerCase()) ||
|
|
35
|
+
(item.subtitle && item.subtitle.toLowerCase().includes(query.toLowerCase()))
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
if (isOpen) {
|
|
40
|
+
setQuery('');
|
|
41
|
+
setActiveIndex(0);
|
|
42
|
+
setTimeout(() => inputRef.current?.focus(), 100);
|
|
43
|
+
}
|
|
44
|
+
}, [isOpen]);
|
|
45
|
+
|
|
46
|
+
React.useEffect(() => {
|
|
47
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
48
|
+
if (!isOpen) return;
|
|
49
|
+
|
|
50
|
+
if (e.key === 'Escape') {
|
|
51
|
+
onClose?.();
|
|
52
|
+
} else if (e.key === 'ArrowDown') {
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
setActiveIndex((i) => Math.min(i + 1, filtered.length - 1));
|
|
55
|
+
} else if (e.key === 'ArrowUp') {
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
setActiveIndex((i) => Math.max(i - 1, 0));
|
|
58
|
+
} else if (e.key === 'Enter' && filtered[activeIndex]) {
|
|
59
|
+
handleSelect(filtered[activeIndex]);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
64
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
65
|
+
}, [isOpen, filtered, activeIndex, onClose]);
|
|
66
|
+
|
|
67
|
+
const handleSelect = (item: CommandPaletteItem) => {
|
|
68
|
+
item.action?.();
|
|
69
|
+
onSelect?.(item);
|
|
70
|
+
onClose?.();
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (!isOpen) return null;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
className={`cn-command-palette cn-command-palette-open ${className}`.trim()}
|
|
78
|
+
onClick={(e) => e.target === e.currentTarget && onClose?.()}
|
|
79
|
+
role="dialog"
|
|
80
|
+
aria-modal="true"
|
|
81
|
+
aria-label="Command palette"
|
|
82
|
+
{...props}
|
|
83
|
+
>
|
|
84
|
+
<div className="cn-command-palette-inner">
|
|
85
|
+
<input
|
|
86
|
+
ref={inputRef}
|
|
87
|
+
type="text"
|
|
88
|
+
className="cn-command-palette-input"
|
|
89
|
+
placeholder={placeholder}
|
|
90
|
+
value={query}
|
|
91
|
+
onChange={(e) => {
|
|
92
|
+
setQuery(e.target.value);
|
|
93
|
+
setActiveIndex(0);
|
|
94
|
+
}}
|
|
95
|
+
role="combobox"
|
|
96
|
+
aria-autocomplete="list"
|
|
97
|
+
aria-expanded={filtered.length > 0}
|
|
98
|
+
/>
|
|
99
|
+
<div className="cn-command-palette-results" role="listbox">
|
|
100
|
+
{filtered.length > 0 ? (
|
|
101
|
+
filtered.map((item, idx) => (
|
|
102
|
+
<div
|
|
103
|
+
key={idx}
|
|
104
|
+
className={`cn-command-item ${idx === activeIndex ? 'cn-command-item-active' : ''}`.trim()}
|
|
105
|
+
onClick={() => handleSelect(item)}
|
|
106
|
+
onMouseEnter={() => setActiveIndex(idx)}
|
|
107
|
+
role="option"
|
|
108
|
+
aria-selected={idx === activeIndex}
|
|
109
|
+
>
|
|
110
|
+
{item.icon && <div className="cn-command-item-icon">{item.icon}</div>}
|
|
111
|
+
<div className="cn-command-item-content">
|
|
112
|
+
<div className="cn-command-item-title">{item.title}</div>
|
|
113
|
+
{item.subtitle && (
|
|
114
|
+
<div className="cn-command-item-subtitle">{item.subtitle}</div>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
{item.kbd && <div className="cn-command-item-kbd">{item.kbd}</div>}
|
|
118
|
+
</div>
|
|
119
|
+
))
|
|
120
|
+
) : (
|
|
121
|
+
<div className="cn-command-empty">No commands found</div>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
CommandPalette.displayName = 'CommandPalette';
|
|
130
|
+
|
|
131
|
+
export default CommandPalette;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type ContainerSize = 'sm' | 'md' | 'lg' | 'xl' | 'fluid';
|
|
4
|
+
|
|
5
|
+
export interface ContainerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
size?: ContainerSize;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Container: React.FC<ContainerProps> = ({
|
|
10
|
+
size = 'md',
|
|
11
|
+
children,
|
|
12
|
+
className = '',
|
|
13
|
+
...props
|
|
14
|
+
}) => {
|
|
15
|
+
const sizeClass = size !== 'md' ? `cn-container-${size}` : '';
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className={`cn-container ${sizeClass} ${className}`.trim()} {...props}>
|
|
19
|
+
{children}
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
Container.displayName = 'Container';
|
|
25
|
+
|
|
26
|
+
export default Container;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface DropdownProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
trigger: React.ReactNode;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface DropdownItemProps extends React.HTMLAttributes<HTMLDivElement | HTMLButtonElement> {
|
|
8
|
+
icon?: React.ReactNode;
|
|
9
|
+
divider?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const Dropdown: React.FC<DropdownProps> & {
|
|
13
|
+
Item: React.FC<DropdownItemProps>;
|
|
14
|
+
Divider: React.FC;
|
|
15
|
+
} = Object.assign(
|
|
16
|
+
({ trigger, children, className = '', ...props }: DropdownProps) => {
|
|
17
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
18
|
+
const dropdownRef = React.useRef<HTMLDivElement>(null);
|
|
19
|
+
|
|
20
|
+
const toggle = () => setIsOpen((prev) => !prev);
|
|
21
|
+
const close = () => setIsOpen(false);
|
|
22
|
+
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
const handleClickOutside = (e: MouseEvent) => {
|
|
25
|
+
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
|
|
26
|
+
close();
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (isOpen) {
|
|
31
|
+
document.addEventListener('click', handleClickOutside);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return () => {
|
|
35
|
+
document.removeEventListener('click', handleClickOutside);
|
|
36
|
+
};
|
|
37
|
+
}, [isOpen]);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
ref={dropdownRef}
|
|
42
|
+
className={`cn-dropdown ${isOpen ? 'cn-dropdown-open' : ''} ${className}`.trim()}
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
<div
|
|
46
|
+
className="cn-dropdown-trigger"
|
|
47
|
+
onClick={toggle}
|
|
48
|
+
role="button"
|
|
49
|
+
aria-haspopup="true"
|
|
50
|
+
aria-expanded={isOpen}
|
|
51
|
+
tabIndex={0}
|
|
52
|
+
onKeyDown={(e) => {
|
|
53
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
toggle();
|
|
56
|
+
}
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
{trigger}
|
|
60
|
+
</div>
|
|
61
|
+
<div className="cn-dropdown-menu" role="menu" onClick={close}>
|
|
62
|
+
{children}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
Item: ({ children, icon, onClick, className = '', ...props }: DropdownItemProps) => (
|
|
69
|
+
<div
|
|
70
|
+
className={`cn-dropdown-item ${className}`.trim()}
|
|
71
|
+
onClick={onClick}
|
|
72
|
+
role="menuitem"
|
|
73
|
+
tabIndex={0}
|
|
74
|
+
{...props}
|
|
75
|
+
>
|
|
76
|
+
{icon && <span className="cn-dropdown-item-icon">{icon}</span>}
|
|
77
|
+
{children}
|
|
78
|
+
</div>
|
|
79
|
+
),
|
|
80
|
+
Divider: () => <div className="cn-dropdown-divider" role="separator" />,
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
Dropdown.displayName = 'Dropdown';
|
|
85
|
+
Dropdown.Item.displayName = 'Dropdown.Item';
|
|
86
|
+
Dropdown.Divider.displayName = 'Dropdown.Divider';
|
|
87
|
+
|
|
88
|
+
export default Dropdown;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface FileInputProps {
|
|
4
|
+
onFileSelect?: (file: File | File[]) => void;
|
|
5
|
+
accept?: string;
|
|
6
|
+
multiple?: boolean;
|
|
7
|
+
label?: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const FileInput: React.FC<FileInputProps> = ({
|
|
12
|
+
onFileSelect,
|
|
13
|
+
accept,
|
|
14
|
+
multiple = false,
|
|
15
|
+
label = 'Drag and drop files here, or click to browse',
|
|
16
|
+
className = '',
|
|
17
|
+
}) => {
|
|
18
|
+
const [fileName, setFileName] = React.useState('');
|
|
19
|
+
const [isDragging, setIsDragging] = React.useState(false);
|
|
20
|
+
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
21
|
+
|
|
22
|
+
const processFiles = React.useCallback((files: File[]) => {
|
|
23
|
+
if (files.length > 0) {
|
|
24
|
+
setFileName(files.map(f => f.name).join(', '));
|
|
25
|
+
onFileSelect?.(multiple ? files : files[0]);
|
|
26
|
+
}
|
|
27
|
+
}, [multiple, onFileSelect]);
|
|
28
|
+
|
|
29
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
30
|
+
const files = Array.from(e.target.files || []);
|
|
31
|
+
processFiles(files);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleDragOver = (e: React.DragEvent<HTMLLabelElement>) => {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
e.stopPropagation();
|
|
37
|
+
setIsDragging(true);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const handleDragLeave = (e: React.DragEvent<HTMLLabelElement>) => {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
e.stopPropagation();
|
|
43
|
+
setIsDragging(false);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleDrop = (e: React.DragEvent<HTMLLabelElement>) => {
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
e.stopPropagation();
|
|
49
|
+
setIsDragging(false);
|
|
50
|
+
const files = Array.from(e.dataTransfer.files);
|
|
51
|
+
processFiles(files);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className={`cn-file-input ${fileName ? 'cn-file-input-has-file' : ''} ${isDragging ? 'cn-file-input-dragging' : ''} ${className}`.trim()}>
|
|
56
|
+
<label
|
|
57
|
+
className="cn-file-input-label"
|
|
58
|
+
onDragOver={handleDragOver}
|
|
59
|
+
onDragLeave={handleDragLeave}
|
|
60
|
+
onDrop={handleDrop}
|
|
61
|
+
>
|
|
62
|
+
<input
|
|
63
|
+
ref={inputRef}
|
|
64
|
+
type="file"
|
|
65
|
+
accept={accept}
|
|
66
|
+
multiple={multiple}
|
|
67
|
+
onChange={handleChange}
|
|
68
|
+
/>
|
|
69
|
+
<div className="cn-file-input-icon">
|
|
70
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
|
71
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
72
|
+
<polyline points="17 8 12 3 7 8" />
|
|
73
|
+
<line x1="12" y1="3" x2="12" y2="15" />
|
|
74
|
+
</svg>
|
|
75
|
+
</div>
|
|
76
|
+
<div className="cn-file-input-text">
|
|
77
|
+
{fileName ? <span>{fileName}</span> : label}
|
|
78
|
+
</div>
|
|
79
|
+
</label>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
FileInput.displayName = 'FileInput';
|
|
85
|
+
|
|
86
|
+
export default FileInput;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface FooterProps extends React.HTMLAttributes<HTMLElement> {
|
|
4
|
+
links?: Array<{ label: string; href: string }>;
|
|
5
|
+
copyright?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const Footer: React.FC<FooterProps> = ({
|
|
9
|
+
links,
|
|
10
|
+
copyright,
|
|
11
|
+
children,
|
|
12
|
+
className = '',
|
|
13
|
+
...props
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<footer className={`cn-footer ${className}`.trim()} {...props}>
|
|
17
|
+
<div className="cn-footer-content">
|
|
18
|
+
{links && (
|
|
19
|
+
<div className="cn-footer-links">
|
|
20
|
+
{links.map((link, idx) => (
|
|
21
|
+
<a key={idx} href={link.href} className="cn-footer-link">
|
|
22
|
+
{link.label}
|
|
23
|
+
</a>
|
|
24
|
+
))}
|
|
25
|
+
</div>
|
|
26
|
+
)}
|
|
27
|
+
{copyright && <div className="cn-footer-copyright">{copyright}</div>}
|
|
28
|
+
{children}
|
|
29
|
+
</div>
|
|
30
|
+
</footer>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
Footer.displayName = 'Footer';
|
|
35
|
+
|
|
36
|
+
export default Footer;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface FormGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
label?: React.ReactNode;
|
|
5
|
+
error?: React.ReactNode;
|
|
6
|
+
help?: React.ReactNode;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const FormGroup: React.FC<FormGroupProps> = ({
|
|
11
|
+
children,
|
|
12
|
+
label,
|
|
13
|
+
error,
|
|
14
|
+
help,
|
|
15
|
+
required = false,
|
|
16
|
+
className = '',
|
|
17
|
+
...props
|
|
18
|
+
}) => {
|
|
19
|
+
return (
|
|
20
|
+
<div className={`cn-form-group ${className}`.trim()} {...props}>
|
|
21
|
+
{label && (
|
|
22
|
+
<label className="cn-form-label">
|
|
23
|
+
{label}
|
|
24
|
+
{required && <span style={{ color: 'var(--cn-error-text)', marginLeft: 'var(--cn-space-1)' }}>*</span>}
|
|
25
|
+
</label>
|
|
26
|
+
)}
|
|
27
|
+
{children}
|
|
28
|
+
{error && <div className="cn-form-error">{error}</div>}
|
|
29
|
+
{!error && help && <div className="cn-form-help">{help}</div>}
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
FormGroup.displayName = 'FormGroup';
|
|
35
|
+
|
|
36
|
+
export default FormGroup;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface HeaderProps extends React.HTMLAttributes<HTMLElement> {
|
|
4
|
+
brand?: React.ReactNode;
|
|
5
|
+
nav?: React.ReactNode;
|
|
6
|
+
actions?: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Header: React.FC<HeaderProps> = ({
|
|
10
|
+
brand,
|
|
11
|
+
nav,
|
|
12
|
+
actions,
|
|
13
|
+
children,
|
|
14
|
+
className = '',
|
|
15
|
+
...props
|
|
16
|
+
}) => {
|
|
17
|
+
return (
|
|
18
|
+
<header className={`cn-header ${className}`.trim()} {...props}>
|
|
19
|
+
{brand && <div className="cn-header-brand">{brand}</div>}
|
|
20
|
+
{nav && <div className="cn-header-nav">{nav}</div>}
|
|
21
|
+
{actions && <div className="cn-header-actions">{actions}</div>}
|
|
22
|
+
{children}
|
|
23
|
+
</header>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
Header.displayName = 'Header';
|
|
28
|
+
|
|
29
|
+
export default Header;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type InputSize = 'sm' | 'md' | 'lg';
|
|
4
|
+
|
|
5
|
+
export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
|
|
6
|
+
size?: InputSize;
|
|
7
|
+
error?: boolean;
|
|
8
|
+
icon?: React.ReactNode;
|
|
9
|
+
action?: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
13
|
+
({ size = 'md', error = false, icon, action, className = '', ...props }, ref) => {
|
|
14
|
+
const sizeClass = size !== 'md' ? `cn-input-${size}` : '';
|
|
15
|
+
|
|
16
|
+
if (icon) {
|
|
17
|
+
return (
|
|
18
|
+
<div className="cn-input-icon-wrapper">
|
|
19
|
+
<div className="cn-input-icon">{icon}</div>
|
|
20
|
+
<input
|
|
21
|
+
ref={ref}
|
|
22
|
+
className={`cn-input ${sizeClass} ${error ? 'cn-input-error' : ''} ${className}`.trim()}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (action) {
|
|
30
|
+
return (
|
|
31
|
+
<div className="cn-input-action-wrapper">
|
|
32
|
+
<input
|
|
33
|
+
ref={ref}
|
|
34
|
+
className={`cn-input ${sizeClass} ${error ? 'cn-input-error' : ''} ${className}`.trim()}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
<div className="cn-input-action">{action}</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<input
|
|
44
|
+
ref={ref}
|
|
45
|
+
className={`cn-input ${sizeClass} ${error ? 'cn-input-error' : ''} ${className}`.trim()}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
Input.displayName = 'Input';
|
|
53
|
+
|
|
54
|
+
export default Input;
|