cronixui 1.1.2 → 1.1.3
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 +1 -1
- package/package.json +71 -71
- package/packages/flutter/.qwen/settings.json +7 -0
- package/packages/flutter/pubspec.yaml +20 -20
- package/packages/go/cronixui/cronixui.go +926 -926
- package/packages/python/README.md +142 -0
- package/packages/python/cronixui/__init__.py +15 -6
- package/packages/python/cronixui/__pycache__/__init__.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/accordion.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/alert.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/avatar.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/badge.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/button.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/card.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/command_palette.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/core.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/dropdown.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/form.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/layout.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/list.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/loading.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/modal.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/nav.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/pagination.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/progress.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/search.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/table.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/tabs.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/toast.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/toggle.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/tokens.cpython-314.pyc +0 -0
- package/packages/python/cronixui/__pycache__/tooltip.cpython-314.pyc +0 -0
- package/packages/python/cronixui/alert.py +119 -36
- package/packages/python/cronixui/avatar.py +129 -22
- package/packages/python/cronixui/badge.py +161 -24
- package/packages/python/cronixui/button.py +96 -27
- package/packages/python/cronixui/card.py +206 -33
- package/packages/python/cronixui/core.py +212 -23
- package/packages/python/cronixui/form.py +552 -141
- package/packages/python/cronixui/layout.py +358 -96
- package/packages/python/cronixui/list.py +140 -37
- package/packages/python/cronixui/loading.py +107 -17
- package/packages/python/cronixui/progress.py +189 -47
- package/packages/python/cronixui/table.py +118 -31
- package/packages/python/cronixui/tooltip.py +117 -15
- package/packages/react/src/components/Accordion.tsx +82 -82
- package/packages/react/src/components/Button.tsx +47 -47
- package/packages/react/src/components/Card.tsx +69 -69
- package/packages/react/src/components/CommandPalette.tsx +131 -131
- package/packages/react/src/components/Dropdown.tsx +88 -88
- package/packages/react/src/components/FileInput.tsx +86 -86
- package/packages/react/src/components/FormGroup.tsx +36 -36
- package/packages/react/src/components/List.tsx +55 -55
- package/packages/react/src/components/Pagination.tsx +107 -107
- package/packages/react/src/components/Progress.tsx +49 -49
- package/packages/react/src/components/Search.tsx +95 -95
- package/packages/react/src/components/Sidebar.tsx +64 -64
- package/packages/react/src/components/Stack.tsx +69 -69
- package/packages/react/src/components/Table.tsx +90 -90
- package/packages/react/src/components/Toast.tsx +134 -134
- package/packages/react/src/components/Typography.tsx +66 -66
- package/packages/react/src/index.ts +40 -40
- package/packages/react/src/styles.css +2039 -2039
- package/packages/rust/cronixui/src/components/avatar.rs +85 -85
- package/packages/rust/cronixui/src/components/breadcrumb.rs +58 -58
- package/packages/rust/cronixui/src/components/card.rs +259 -259
- package/packages/rust/cronixui/src/components/command_palette.rs +254 -254
- package/packages/rust/cronixui/src/components/dropdown.rs +179 -179
- package/packages/rust/cronixui/src/components/file_input.rs +74 -74
- package/packages/rust/cronixui/src/components/mod.rs +51 -51
- package/packages/rust/cronixui/src/components/search.rs +185 -185
- package/packages/rust/cronixui/src/components/skeleton.rs +63 -63
- package/packages/rust/cronixui/src/components/table.rs +56 -56
- package/packages/rust/cronixui/src/lib.rs +128 -128
- package/packages/web/dist/cronixui.css +97 -93
- package/packages/web/dist/cronixui.min.css +1 -1
|
@@ -1,86 +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;
|
|
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;
|
|
@@ -1,36 +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;
|
|
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;
|
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
4
|
-
|
|
5
|
-
export interface ListItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
-
icon?: React.ReactNode;
|
|
7
|
-
title?: string;
|
|
8
|
-
subtitle?: string;
|
|
9
|
-
actions?: React.ReactNode;
|
|
10
|
-
clickable?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const List: React.FC<ListProps> = ({ children, className = '', ...props }) => {
|
|
14
|
-
return (
|
|
15
|
-
<div className={`cn-list ${className}`.trim()} role="list" {...props}>
|
|
16
|
-
{children}
|
|
17
|
-
</div>
|
|
18
|
-
);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
List.displayName = 'List';
|
|
22
|
-
|
|
23
|
-
export const ListItem: React.FC<ListItemProps> = ({
|
|
24
|
-
children,
|
|
25
|
-
icon,
|
|
26
|
-
title,
|
|
27
|
-
subtitle,
|
|
28
|
-
actions,
|
|
29
|
-
clickable = false,
|
|
30
|
-
onClick,
|
|
31
|
-
className = '',
|
|
32
|
-
...props
|
|
33
|
-
}) => {
|
|
34
|
-
return (
|
|
35
|
-
<div
|
|
36
|
-
className={`cn-list-item ${clickable ? 'cn-list-item-clickable' : ''} ${className}`.trim()}
|
|
37
|
-
onClick={onClick}
|
|
38
|
-
role={clickable ? 'button' : 'listitem'}
|
|
39
|
-
tabIndex={clickable ? 0 : undefined}
|
|
40
|
-
{...props}
|
|
41
|
-
>
|
|
42
|
-
{icon && <div className="cn-list-item-icon">{icon}</div>}
|
|
43
|
-
<div className="cn-list-item-content">
|
|
44
|
-
{title && <div className="cn-list-item-title">{title}</div>}
|
|
45
|
-
{subtitle && <div className="cn-list-item-subtitle">{subtitle}</div>}
|
|
46
|
-
{children}
|
|
47
|
-
</div>
|
|
48
|
-
{actions && <div className="cn-list-item-actions">{actions}</div>}
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
ListItem.displayName = 'ListItem';
|
|
54
|
-
|
|
55
|
-
export default List;
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface ListProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
4
|
+
|
|
5
|
+
export interface ListItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
icon?: React.ReactNode;
|
|
7
|
+
title?: string;
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
actions?: React.ReactNode;
|
|
10
|
+
clickable?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const List: React.FC<ListProps> = ({ children, className = '', ...props }) => {
|
|
14
|
+
return (
|
|
15
|
+
<div className={`cn-list ${className}`.trim()} role="list" {...props}>
|
|
16
|
+
{children}
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
List.displayName = 'List';
|
|
22
|
+
|
|
23
|
+
export const ListItem: React.FC<ListItemProps> = ({
|
|
24
|
+
children,
|
|
25
|
+
icon,
|
|
26
|
+
title,
|
|
27
|
+
subtitle,
|
|
28
|
+
actions,
|
|
29
|
+
clickable = false,
|
|
30
|
+
onClick,
|
|
31
|
+
className = '',
|
|
32
|
+
...props
|
|
33
|
+
}) => {
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
className={`cn-list-item ${clickable ? 'cn-list-item-clickable' : ''} ${className}`.trim()}
|
|
37
|
+
onClick={onClick}
|
|
38
|
+
role={clickable ? 'button' : 'listitem'}
|
|
39
|
+
tabIndex={clickable ? 0 : undefined}
|
|
40
|
+
{...props}
|
|
41
|
+
>
|
|
42
|
+
{icon && <div className="cn-list-item-icon">{icon}</div>}
|
|
43
|
+
<div className="cn-list-item-content">
|
|
44
|
+
{title && <div className="cn-list-item-title">{title}</div>}
|
|
45
|
+
{subtitle && <div className="cn-list-item-subtitle">{subtitle}</div>}
|
|
46
|
+
{children}
|
|
47
|
+
</div>
|
|
48
|
+
{actions && <div className="cn-list-item-actions">{actions}</div>}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
ListItem.displayName = 'ListItem';
|
|
54
|
+
|
|
55
|
+
export default List;
|
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
export interface PaginationProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
4
|
-
total: number;
|
|
5
|
-
current?: number;
|
|
6
|
-
onChange?: (page: number) => void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const Pagination: React.FC<PaginationProps> = ({
|
|
10
|
-
total,
|
|
11
|
-
current = 1,
|
|
12
|
-
onChange,
|
|
13
|
-
className = '',
|
|
14
|
-
...props
|
|
15
|
-
}) => {
|
|
16
|
-
const [page, setPage] = React.useState(current);
|
|
17
|
-
|
|
18
|
-
const activePage = onChange !== undefined ? current : page;
|
|
19
|
-
|
|
20
|
-
const getPages = (): (number | string)[] => {
|
|
21
|
-
const pages: (number | string)[] = [];
|
|
22
|
-
const maxVisible = 5;
|
|
23
|
-
|
|
24
|
-
if (total <= maxVisible) {
|
|
25
|
-
for (let i = 1; i <= total; i++) pages.push(i);
|
|
26
|
-
} else {
|
|
27
|
-
if (activePage <= 3) {
|
|
28
|
-
for (let i = 1; i <= 4; i++) pages.push(i);
|
|
29
|
-
pages.push('...');
|
|
30
|
-
pages.push(total);
|
|
31
|
-
} else if (activePage >= total - 2) {
|
|
32
|
-
pages.push(1);
|
|
33
|
-
pages.push('...');
|
|
34
|
-
for (let i = total - 3; i <= total; i++) pages.push(i);
|
|
35
|
-
} else {
|
|
36
|
-
pages.push(1);
|
|
37
|
-
pages.push('...');
|
|
38
|
-
for (let i = activePage - 1; i <= activePage + 1; i++) pages.push(i);
|
|
39
|
-
pages.push('...');
|
|
40
|
-
pages.push(total);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return pages;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const goTo = (p: number) => {
|
|
48
|
-
if (p < 1 || p > total || p === activePage) return;
|
|
49
|
-
if (onChange) {
|
|
50
|
-
onChange(p);
|
|
51
|
-
} else {
|
|
52
|
-
setPage(p);
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<nav className={`cn-pagination ${className}`.trim()} aria-label="Pagination" {...props}>
|
|
58
|
-
<button
|
|
59
|
-
className="cn-pagination-item"
|
|
60
|
-
onClick={() => goTo(activePage - 1)}
|
|
61
|
-
disabled={activePage === 1}
|
|
62
|
-
aria-label="Previous page"
|
|
63
|
-
>
|
|
64
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="16" height="16">
|
|
65
|
-
<polyline points="15 18 9 12 15 6" />
|
|
66
|
-
</svg>
|
|
67
|
-
</button>
|
|
68
|
-
{getPages().map((p, idx) =>
|
|
69
|
-
p === '...' ? (
|
|
70
|
-
<button
|
|
71
|
-
key={idx}
|
|
72
|
-
className="cn-pagination-item cn-pagination-ellipsis"
|
|
73
|
-
disabled
|
|
74
|
-
aria-label="More pages"
|
|
75
|
-
aria-hidden="true"
|
|
76
|
-
tabIndex={-1}
|
|
77
|
-
>
|
|
78
|
-
{p}
|
|
79
|
-
</button>
|
|
80
|
-
) : (
|
|
81
|
-
<button
|
|
82
|
-
key={idx}
|
|
83
|
-
className={`cn-pagination-item ${p === activePage ? 'cn-pagination-active' : ''}`.trim()}
|
|
84
|
-
onClick={() => goTo(p)}
|
|
85
|
-
aria-current={p === activePage ? 'page' : undefined}
|
|
86
|
-
>
|
|
87
|
-
{p}
|
|
88
|
-
</button>
|
|
89
|
-
)
|
|
90
|
-
)}
|
|
91
|
-
<button
|
|
92
|
-
className="cn-pagination-item"
|
|
93
|
-
onClick={() => goTo(activePage + 1)}
|
|
94
|
-
disabled={activePage === total}
|
|
95
|
-
aria-label="Next page"
|
|
96
|
-
>
|
|
97
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="16" height="16">
|
|
98
|
-
<polyline points="9 18 15 12 9 6" />
|
|
99
|
-
</svg>
|
|
100
|
-
</button>
|
|
101
|
-
</nav>
|
|
102
|
-
);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
Pagination.displayName = 'Pagination';
|
|
106
|
-
|
|
107
|
-
export default Pagination;
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface PaginationProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
4
|
+
total: number;
|
|
5
|
+
current?: number;
|
|
6
|
+
onChange?: (page: number) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Pagination: React.FC<PaginationProps> = ({
|
|
10
|
+
total,
|
|
11
|
+
current = 1,
|
|
12
|
+
onChange,
|
|
13
|
+
className = '',
|
|
14
|
+
...props
|
|
15
|
+
}) => {
|
|
16
|
+
const [page, setPage] = React.useState(current);
|
|
17
|
+
|
|
18
|
+
const activePage = onChange !== undefined ? current : page;
|
|
19
|
+
|
|
20
|
+
const getPages = (): (number | string)[] => {
|
|
21
|
+
const pages: (number | string)[] = [];
|
|
22
|
+
const maxVisible = 5;
|
|
23
|
+
|
|
24
|
+
if (total <= maxVisible) {
|
|
25
|
+
for (let i = 1; i <= total; i++) pages.push(i);
|
|
26
|
+
} else {
|
|
27
|
+
if (activePage <= 3) {
|
|
28
|
+
for (let i = 1; i <= 4; i++) pages.push(i);
|
|
29
|
+
pages.push('...');
|
|
30
|
+
pages.push(total);
|
|
31
|
+
} else if (activePage >= total - 2) {
|
|
32
|
+
pages.push(1);
|
|
33
|
+
pages.push('...');
|
|
34
|
+
for (let i = total - 3; i <= total; i++) pages.push(i);
|
|
35
|
+
} else {
|
|
36
|
+
pages.push(1);
|
|
37
|
+
pages.push('...');
|
|
38
|
+
for (let i = activePage - 1; i <= activePage + 1; i++) pages.push(i);
|
|
39
|
+
pages.push('...');
|
|
40
|
+
pages.push(total);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return pages;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const goTo = (p: number) => {
|
|
48
|
+
if (p < 1 || p > total || p === activePage) return;
|
|
49
|
+
if (onChange) {
|
|
50
|
+
onChange(p);
|
|
51
|
+
} else {
|
|
52
|
+
setPage(p);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<nav className={`cn-pagination ${className}`.trim()} aria-label="Pagination" {...props}>
|
|
58
|
+
<button
|
|
59
|
+
className="cn-pagination-item"
|
|
60
|
+
onClick={() => goTo(activePage - 1)}
|
|
61
|
+
disabled={activePage === 1}
|
|
62
|
+
aria-label="Previous page"
|
|
63
|
+
>
|
|
64
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="16" height="16">
|
|
65
|
+
<polyline points="15 18 9 12 15 6" />
|
|
66
|
+
</svg>
|
|
67
|
+
</button>
|
|
68
|
+
{getPages().map((p, idx) =>
|
|
69
|
+
p === '...' ? (
|
|
70
|
+
<button
|
|
71
|
+
key={idx}
|
|
72
|
+
className="cn-pagination-item cn-pagination-ellipsis"
|
|
73
|
+
disabled
|
|
74
|
+
aria-label="More pages"
|
|
75
|
+
aria-hidden="true"
|
|
76
|
+
tabIndex={-1}
|
|
77
|
+
>
|
|
78
|
+
{p}
|
|
79
|
+
</button>
|
|
80
|
+
) : (
|
|
81
|
+
<button
|
|
82
|
+
key={idx}
|
|
83
|
+
className={`cn-pagination-item ${p === activePage ? 'cn-pagination-active' : ''}`.trim()}
|
|
84
|
+
onClick={() => goTo(p)}
|
|
85
|
+
aria-current={p === activePage ? 'page' : undefined}
|
|
86
|
+
>
|
|
87
|
+
{p}
|
|
88
|
+
</button>
|
|
89
|
+
)
|
|
90
|
+
)}
|
|
91
|
+
<button
|
|
92
|
+
className="cn-pagination-item"
|
|
93
|
+
onClick={() => goTo(activePage + 1)}
|
|
94
|
+
disabled={activePage === total}
|
|
95
|
+
aria-label="Next page"
|
|
96
|
+
>
|
|
97
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="16" height="16">
|
|
98
|
+
<polyline points="9 18 15 12 9 6" />
|
|
99
|
+
</svg>
|
|
100
|
+
</button>
|
|
101
|
+
</nav>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
Pagination.displayName = 'Pagination';
|
|
106
|
+
|
|
107
|
+
export default Pagination;
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
export type ProgressVariant = 'default' | 'success' | 'warning' | 'error';
|
|
4
|
-
export type ProgressSize = 'sm' | 'md' | 'lg';
|
|
5
|
-
|
|
6
|
-
export interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
7
|
-
value?: number;
|
|
8
|
-
max?: number;
|
|
9
|
-
showLabel?: boolean;
|
|
10
|
-
label?: string;
|
|
11
|
-
variant?: ProgressVariant;
|
|
12
|
-
size?: ProgressSize;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const Progress: React.FC<ProgressProps> = ({
|
|
16
|
-
value = 0,
|
|
17
|
-
max = 100,
|
|
18
|
-
showLabel = false,
|
|
19
|
-
label,
|
|
20
|
-
variant = 'default',
|
|
21
|
-
size = 'md',
|
|
22
|
-
className = '',
|
|
23
|
-
...props
|
|
24
|
-
}) => {
|
|
25
|
-
const percentage = Math.min(Math.max((value / max) * 100, 0), 100);
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div className={className} {...props}>
|
|
29
|
-
{showLabel && (
|
|
30
|
-
<div className="cn-progress-label">
|
|
31
|
-
<span>{label ?? `${percentage.toFixed(0)}%`}</span>
|
|
32
|
-
</div>
|
|
33
|
-
)}
|
|
34
|
-
<div
|
|
35
|
-
className={`cn-progress ${size !== 'md' ? `cn-progress-${size}` : ''} ${variant !== 'default' ? `cn-progress-${variant}` : ''}`.trim()}
|
|
36
|
-
role="progressbar"
|
|
37
|
-
aria-valuenow={percentage}
|
|
38
|
-
aria-valuemin={0}
|
|
39
|
-
aria-valuemax={100}
|
|
40
|
-
>
|
|
41
|
-
<div className="cn-progress-bar" style={{ width: `${percentage}%` }} />
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
Progress.displayName = 'Progress';
|
|
48
|
-
|
|
49
|
-
export default Progress;
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type ProgressVariant = 'default' | 'success' | 'warning' | 'error';
|
|
4
|
+
export type ProgressSize = 'sm' | 'md' | 'lg';
|
|
5
|
+
|
|
6
|
+
export interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
value?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
showLabel?: boolean;
|
|
10
|
+
label?: string;
|
|
11
|
+
variant?: ProgressVariant;
|
|
12
|
+
size?: ProgressSize;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const Progress: React.FC<ProgressProps> = ({
|
|
16
|
+
value = 0,
|
|
17
|
+
max = 100,
|
|
18
|
+
showLabel = false,
|
|
19
|
+
label,
|
|
20
|
+
variant = 'default',
|
|
21
|
+
size = 'md',
|
|
22
|
+
className = '',
|
|
23
|
+
...props
|
|
24
|
+
}) => {
|
|
25
|
+
const percentage = Math.min(Math.max((value / max) * 100, 0), 100);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className={className} {...props}>
|
|
29
|
+
{showLabel && (
|
|
30
|
+
<div className="cn-progress-label">
|
|
31
|
+
<span>{label ?? `${percentage.toFixed(0)}%`}</span>
|
|
32
|
+
</div>
|
|
33
|
+
)}
|
|
34
|
+
<div
|
|
35
|
+
className={`cn-progress ${size !== 'md' ? `cn-progress-${size}` : ''} ${variant !== 'default' ? `cn-progress-${variant}` : ''}`.trim()}
|
|
36
|
+
role="progressbar"
|
|
37
|
+
aria-valuenow={percentage}
|
|
38
|
+
aria-valuemin={0}
|
|
39
|
+
aria-valuemax={100}
|
|
40
|
+
>
|
|
41
|
+
<div className="cn-progress-bar" style={{ width: `${percentage}%` }} />
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
Progress.displayName = 'Progress';
|
|
48
|
+
|
|
49
|
+
export default Progress;
|