cronixui 1.0.5 → 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 +21 -3
- package/packages/go/cronixui/cronixui.go +784 -237
- package/packages/go/cronixui/go.mod +34 -9
- package/packages/go/cronixui/go.sum +666 -0
- package/packages/python/cronixui/__init__.py +131 -1
- 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/tokens.py +200 -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 -62
- package/packages/rust/cronixui/src/tokens.rs +107 -0
- package/packages/web/src/tokens.ts +120 -0
- package/packages/web/src/variables.css +81 -81
- 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,80 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type AlertVariant = 'info' | 'success' | 'warning' | 'error';
|
|
4
|
+
|
|
5
|
+
export interface AlertProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
variant?: AlertVariant;
|
|
7
|
+
title?: string;
|
|
8
|
+
dismissible?: boolean;
|
|
9
|
+
onClose?: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const Alert: React.FC<AlertProps> = ({
|
|
13
|
+
variant = 'info',
|
|
14
|
+
title,
|
|
15
|
+
children,
|
|
16
|
+
dismissible = true,
|
|
17
|
+
onClose,
|
|
18
|
+
className = '',
|
|
19
|
+
...props
|
|
20
|
+
}) => {
|
|
21
|
+
const [visible, setVisible] = React.useState(true);
|
|
22
|
+
|
|
23
|
+
const handleClose = () => {
|
|
24
|
+
setVisible(false);
|
|
25
|
+
onClose?.();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (!visible) return null;
|
|
29
|
+
|
|
30
|
+
const icons: Record<AlertVariant, React.ReactNode> = {
|
|
31
|
+
success: <polyline points="20 6 9 17 4 12" />,
|
|
32
|
+
error: (
|
|
33
|
+
<>
|
|
34
|
+
<circle cx="12" cy="12" r="10" />
|
|
35
|
+
<line x1="15" y1="9" x2="9" y2="15" />
|
|
36
|
+
<line x1="9" y1="9" x2="15" y2="15" />
|
|
37
|
+
</>
|
|
38
|
+
),
|
|
39
|
+
warning: (
|
|
40
|
+
<>
|
|
41
|
+
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
|
42
|
+
<line x1="12" y1="9" x2="12" y2="13" />
|
|
43
|
+
<line x1="12" y1="17" x2="12.01" y2="17" />
|
|
44
|
+
</>
|
|
45
|
+
),
|
|
46
|
+
info: (
|
|
47
|
+
<>
|
|
48
|
+
<circle cx="12" cy="12" r="10" />
|
|
49
|
+
<line x1="12" y1="16" x2="12" y2="12" />
|
|
50
|
+
<line x1="12" y1="8" x2="12.01" y2="8" />
|
|
51
|
+
</>
|
|
52
|
+
),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className={`cn-alert cn-alert-${variant} ${className}`.trim()} role="alert" {...props}>
|
|
57
|
+
<div className="cn-alert-icon">
|
|
58
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
59
|
+
{icons[variant]}
|
|
60
|
+
</svg>
|
|
61
|
+
</div>
|
|
62
|
+
<div className="cn-alert-content">
|
|
63
|
+
{title && <div className="cn-alert-title">{title}</div>}
|
|
64
|
+
<div className="cn-alert-message">{children}</div>
|
|
65
|
+
</div>
|
|
66
|
+
{dismissible && (
|
|
67
|
+
<button className="cn-alert-close" onClick={handleClose} aria-label="Close">
|
|
68
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="16" height="16">
|
|
69
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
70
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
71
|
+
</svg>
|
|
72
|
+
</button>
|
|
73
|
+
)}
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
Alert.displayName = 'Alert';
|
|
79
|
+
|
|
80
|
+
export default Alert;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
4
|
+
|
|
5
|
+
export interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
src?: string;
|
|
7
|
+
alt?: string;
|
|
8
|
+
initials?: string;
|
|
9
|
+
size?: AvatarSize;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
13
|
+
max?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Avatar: React.FC<AvatarProps> = ({
|
|
17
|
+
src,
|
|
18
|
+
alt = '',
|
|
19
|
+
initials,
|
|
20
|
+
size = 'md',
|
|
21
|
+
className = '',
|
|
22
|
+
...props
|
|
23
|
+
}) => {
|
|
24
|
+
const sizeClass = size !== 'md' ? `cn-avatar-${size}` : '';
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className={`cn-avatar ${sizeClass} ${className}`.trim()} {...props}>
|
|
28
|
+
{src ? (
|
|
29
|
+
<img src={src} alt={alt} />
|
|
30
|
+
) : initials ? (
|
|
31
|
+
initials
|
|
32
|
+
) : null}
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
Avatar.displayName = 'Avatar';
|
|
38
|
+
|
|
39
|
+
export const AvatarGroup: React.FC<AvatarGroupProps> = ({ children, max, className = '', ...props }) => {
|
|
40
|
+
const items = React.Children.toArray(children);
|
|
41
|
+
const visible = max ? items.slice(0, max) : items;
|
|
42
|
+
const remaining = max ? items.length - max : 0;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className={`cn-avatar-group ${className}`.trim()} {...props}>
|
|
46
|
+
{visible}
|
|
47
|
+
{remaining > 0 && <div className="cn-avatar">+{remaining}</div>}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
AvatarGroup.displayName = 'AvatarGroup';
|
|
53
|
+
|
|
54
|
+
export default Avatar;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type BadgeVariant = 'default' | 'accent' | 'success' | 'warning' | 'error' | 'info';
|
|
4
|
+
|
|
5
|
+
export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
6
|
+
variant?: BadgeVariant;
|
|
7
|
+
solid?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const Badge: React.FC<BadgeProps> = ({
|
|
11
|
+
children,
|
|
12
|
+
variant = 'default',
|
|
13
|
+
solid = false,
|
|
14
|
+
className = '',
|
|
15
|
+
...props
|
|
16
|
+
}) => {
|
|
17
|
+
const variantClass = variant !== 'default' ? `cn-badge-${variant}` : '';
|
|
18
|
+
const solidClass = solid ? 'cn-badge-solid' : '';
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<span
|
|
22
|
+
className={`cn-badge ${variantClass} ${solidClass} ${className}`.trim()}
|
|
23
|
+
{...props}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
</span>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
Badge.displayName = 'Badge';
|
|
31
|
+
|
|
32
|
+
export default Badge;
|
|
@@ -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;
|