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,32 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface SliderProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'value' | 'onChange'> {
|
|
4
|
+
value?: number;
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
onChange?: (value: number) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const Slider = React.forwardRef<HTMLInputElement, SliderProps>(
|
|
12
|
+
({ value = 0, min = 0, max = 100, step = 1, onChange, disabled = false, className = '', ...props }, ref) => {
|
|
13
|
+
return (
|
|
14
|
+
<input
|
|
15
|
+
ref={ref}
|
|
16
|
+
type="range"
|
|
17
|
+
className={`cn-slider ${className}`.trim()}
|
|
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
|
+
|
|
30
|
+
Slider.displayName = 'Slider';
|
|
31
|
+
|
|
32
|
+
export default Slider;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type SpinnerSize = 'sm' | 'md' | 'lg';
|
|
4
|
+
|
|
5
|
+
export interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
size?: SpinnerSize;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Spinner: React.FC<SpinnerProps> = ({ size = 'md', className = '', ...props }) => {
|
|
10
|
+
const sizeClass = size !== 'md' ? `cn-spinner-${size}` : '';
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
className={`cn-spinner ${sizeClass} ${className}`.trim()}
|
|
15
|
+
role="status"
|
|
16
|
+
aria-label="Loading"
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
Spinner.displayName = 'Spinner';
|
|
23
|
+
|
|
24
|
+
export default Spinner;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface StackProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
spacing?: '1' | '2' | '4' | '6';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const Stack: React.FC<StackProps> = ({
|
|
8
|
+
children,
|
|
9
|
+
spacing = '4',
|
|
10
|
+
className = '',
|
|
11
|
+
...props
|
|
12
|
+
}) => {
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
className={`cn-stack cn-stack-${spacing} ${className}`.trim()}
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
Stack.displayName = 'Stack';
|
|
24
|
+
|
|
25
|
+
export const HStack: React.FC<StackProps> = ({
|
|
26
|
+
children,
|
|
27
|
+
spacing = '4',
|
|
28
|
+
className = '',
|
|
29
|
+
...props
|
|
30
|
+
}) => {
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
className={`cn-hstack cn-hstack-${spacing} ${className}`.trim()}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
{children}
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
HStack.displayName = 'HStack';
|
|
42
|
+
|
|
43
|
+
export interface DividerProps extends React.HTMLAttributes<HTMLHRElement> {
|
|
44
|
+
orientation?: 'horizontal' | 'vertical';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const Divider: React.FC<DividerProps> = ({
|
|
48
|
+
className = '',
|
|
49
|
+
orientation = 'horizontal',
|
|
50
|
+
style,
|
|
51
|
+
...props
|
|
52
|
+
}) => {
|
|
53
|
+
const dividerStyle: React.CSSProperties = orientation === 'vertical'
|
|
54
|
+
? { width: '1px', height: 'auto', minHeight: '100%', borderRight: 'none', borderBottom: '1px solid var(--cn-border)', ...style }
|
|
55
|
+
: { height: '1px', border: 'none', backgroundColor: 'var(--cn-border)', ...style };
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<hr
|
|
59
|
+
className={`cn-divider ${className}`.trim()}
|
|
60
|
+
style={dividerStyle}
|
|
61
|
+
role="separator"
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
Divider.displayName = 'Divider';
|
|
68
|
+
|
|
69
|
+
export default Stack;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type StatDeltaType = 'up' | 'down';
|
|
4
|
+
|
|
5
|
+
export interface StatProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
value: React.ReactNode;
|
|
7
|
+
label?: string;
|
|
8
|
+
delta?: string;
|
|
9
|
+
deltaType?: StatDeltaType;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const Stat: React.FC<StatProps> = ({
|
|
13
|
+
value,
|
|
14
|
+
label,
|
|
15
|
+
delta,
|
|
16
|
+
deltaType = 'up',
|
|
17
|
+
className = '',
|
|
18
|
+
...props
|
|
19
|
+
}) => {
|
|
20
|
+
return (
|
|
21
|
+
<div className={`cn-stat ${className}`.trim()} {...props}>
|
|
22
|
+
<div className="cn-stat-value">{value}</div>
|
|
23
|
+
{label && <div className="cn-stat-label">{label}</div>}
|
|
24
|
+
{delta && (
|
|
25
|
+
<div className={`cn-stat-delta cn-stat-delta-${deltaType}`.trim()}>
|
|
26
|
+
{deltaType === 'up' ? '↑' : '↓'} {delta}
|
|
27
|
+
</div>
|
|
28
|
+
)}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
Stat.displayName = 'Stat';
|
|
34
|
+
|
|
35
|
+
export default Stat;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface TableColumn<T = Record<string, unknown>> {
|
|
4
|
+
key: string;
|
|
5
|
+
header: string;
|
|
6
|
+
sortable?: boolean;
|
|
7
|
+
render?: (row: T) => React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface TableProps<T = Record<string, unknown>> extends React.HTMLAttributes<HTMLDivElement> {
|
|
11
|
+
columns: TableColumn<T>[];
|
|
12
|
+
data: T[];
|
|
13
|
+
sortable?: boolean;
|
|
14
|
+
onSort?: (key: string) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function Table<T extends Record<string, unknown> = Record<string, unknown>>({
|
|
18
|
+
columns = [],
|
|
19
|
+
data = [],
|
|
20
|
+
sortable = false,
|
|
21
|
+
onSort,
|
|
22
|
+
className = '',
|
|
23
|
+
...props
|
|
24
|
+
}: TableProps<T>): React.ReactElement {
|
|
25
|
+
const [sortState, setSortState] = React.useState<Record<string, 'none' | 'ascending' | 'descending'>>(
|
|
26
|
+
Object.fromEntries(columns.map((col) => [col.key, 'none']))
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const handleSort = (column: TableColumn<T>) => {
|
|
30
|
+
if (!sortable || !column.sortable) return;
|
|
31
|
+
|
|
32
|
+
const next: Record<string, 'none' | 'ascending' | 'descending'> = {};
|
|
33
|
+
columns.forEach((col) => {
|
|
34
|
+
next[col.key] = 'none';
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const current = sortState[column.key] ?? 'none';
|
|
38
|
+
if (current === 'none') {
|
|
39
|
+
next[column.key] = 'ascending';
|
|
40
|
+
} else if (current === 'ascending') {
|
|
41
|
+
next[column.key] = 'descending';
|
|
42
|
+
} else {
|
|
43
|
+
next[column.key] = 'none';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setSortState(next);
|
|
47
|
+
onSort?.(column.key);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className={`cn-table-wrapper ${sortable ? 'cn-table-sortable' : ''} ${className}`.trim()} {...props}>
|
|
52
|
+
<table className="cn-table">
|
|
53
|
+
<thead>
|
|
54
|
+
<tr>
|
|
55
|
+
{columns.map((col, idx) => {
|
|
56
|
+
const sortDirection = sortState[col.key] ?? 'none';
|
|
57
|
+
const sortIndicator = sortDirection === 'ascending' ? ' ↑' : sortDirection === 'descending' ? ' ↓' : col.sortable ? ' ↕' : '';
|
|
58
|
+
return (
|
|
59
|
+
<th
|
|
60
|
+
key={idx}
|
|
61
|
+
onClick={() => handleSort(col)}
|
|
62
|
+
data-sort={col.key}
|
|
63
|
+
aria-sort={col.sortable ? sortDirection : undefined}
|
|
64
|
+
>
|
|
65
|
+
{col.header}
|
|
66
|
+
{sortIndicator}
|
|
67
|
+
</th>
|
|
68
|
+
);
|
|
69
|
+
})}
|
|
70
|
+
</tr>
|
|
71
|
+
</thead>
|
|
72
|
+
<tbody>
|
|
73
|
+
{data.map((row, idx) => (
|
|
74
|
+
<tr key={idx}>
|
|
75
|
+
{columns.map((col, colIdx) => (
|
|
76
|
+
<td key={colIdx}>
|
|
77
|
+
{col.render ? col.render(row) : (row as Record<string, unknown>)[col.key] as React.ReactNode}
|
|
78
|
+
</td>
|
|
79
|
+
))}
|
|
80
|
+
</tr>
|
|
81
|
+
))}
|
|
82
|
+
</tbody>
|
|
83
|
+
</table>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
Table.displayName = 'Table';
|
|
89
|
+
|
|
90
|
+
export default Table;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface TabsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
4
|
+
defaultIndex?: number;
|
|
5
|
+
index?: number;
|
|
6
|
+
onChange?: (index: number) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface TabProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TabPanelProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const Tabs: React.FC<TabsProps> & {
|
|
18
|
+
Tab: React.FC<TabProps>;
|
|
19
|
+
Panel: React.FC<TabPanelProps>;
|
|
20
|
+
} = Object.assign(
|
|
21
|
+
({ defaultIndex = 0, index: controlledIndex, onChange, children, className = '', ...props }: TabsProps) => {
|
|
22
|
+
const [internalIndex, setInternalIndex] = React.useState(defaultIndex);
|
|
23
|
+
const activeIndex = controlledIndex !== undefined ? controlledIndex : internalIndex;
|
|
24
|
+
|
|
25
|
+
const handleTabClick = (idx: number) => {
|
|
26
|
+
if (onChange) {
|
|
27
|
+
onChange(idx);
|
|
28
|
+
} else {
|
|
29
|
+
setInternalIndex(idx);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const tabs: React.ReactElement<TabProps>[] = [];
|
|
34
|
+
const panels: React.ReactElement<TabPanelProps>[] = [];
|
|
35
|
+
|
|
36
|
+
React.Children.forEach(children, (child) => {
|
|
37
|
+
if (React.isValidElement(child)) {
|
|
38
|
+
if ((child.type as React.FC).displayName === 'Tab') {
|
|
39
|
+
tabs.push(child as React.ReactElement<TabProps>);
|
|
40
|
+
} else if ((child.type as React.FC).displayName === 'TabPanel') {
|
|
41
|
+
panels.push(child as React.ReactElement<TabPanelProps>);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className={className} {...props}>
|
|
48
|
+
<div className="cn-tabs">
|
|
49
|
+
{tabs.map((tab, idx) => (
|
|
50
|
+
<div
|
|
51
|
+
key={idx}
|
|
52
|
+
className={`cn-tab ${activeIndex === idx ? 'cn-tab-active' : ''}`.trim()}
|
|
53
|
+
onClick={() => handleTabClick(idx)}
|
|
54
|
+
role="tab"
|
|
55
|
+
aria-selected={activeIndex === idx}
|
|
56
|
+
tabIndex={0}
|
|
57
|
+
>
|
|
58
|
+
{tab.props.children}
|
|
59
|
+
</div>
|
|
60
|
+
))}
|
|
61
|
+
</div>
|
|
62
|
+
{panels.map((panel, idx) => (
|
|
63
|
+
<div
|
|
64
|
+
key={idx}
|
|
65
|
+
className={`cn-tab-panel ${activeIndex === idx ? 'cn-tab-panel-active' : ''}`.trim()}
|
|
66
|
+
role="tabpanel"
|
|
67
|
+
hidden={activeIndex !== idx}
|
|
68
|
+
>
|
|
69
|
+
{panel.props.children}
|
|
70
|
+
</div>
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
Tab: ({ children }: TabProps) => <>{children}</>,
|
|
77
|
+
Panel: ({ children }: TabPanelProps) => <>{children}</>,
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
Tabs.displayName = 'Tabs';
|
|
82
|
+
(Tabs.Tab as React.FC<TabProps> & { displayName?: string }).displayName = 'Tab';
|
|
83
|
+
(Tabs.Panel as React.FC<TabPanelProps> & { displayName?: string }).displayName = 'TabPanel';
|
|
84
|
+
|
|
85
|
+
export default Tabs;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface TagProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
onRemove?: () => void;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const Tag: React.FC<TagProps> = ({
|
|
8
|
+
children,
|
|
9
|
+
onRemove,
|
|
10
|
+
className = '',
|
|
11
|
+
...props
|
|
12
|
+
}) => {
|
|
13
|
+
return (
|
|
14
|
+
<div className={`cn-tag ${className}`.trim()} {...props}>
|
|
15
|
+
<span>{children}</span>
|
|
16
|
+
{onRemove && (
|
|
17
|
+
<button className="cn-tag-remove" onClick={onRemove} aria-label="Remove">
|
|
18
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
19
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
20
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
21
|
+
</svg>
|
|
22
|
+
</button>
|
|
23
|
+
)}
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
Tag.displayName = 'Tag';
|
|
29
|
+
|
|
30
|
+
export default Tag;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
4
|
+
error?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
8
|
+
({ error = false, className = '', ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<textarea
|
|
11
|
+
ref={ref}
|
|
12
|
+
className={`cn-input cn-textarea ${error ? 'cn-input-error' : ''} ${className}`.trim()}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
Textarea.displayName = 'Textarea';
|
|
20
|
+
|
|
21
|
+
export default Textarea;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type ToastType = 'success' | 'error' | 'warning' | 'info';
|
|
4
|
+
|
|
5
|
+
export interface ToastOptions {
|
|
6
|
+
id?: number;
|
|
7
|
+
title?: string;
|
|
8
|
+
message: string;
|
|
9
|
+
type?: ToastType;
|
|
10
|
+
duration?: number;
|
|
11
|
+
closable?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ToastProps {
|
|
15
|
+
toasts: ToastOptions[];
|
|
16
|
+
onRemove: (id: number) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UseToastReturn {
|
|
20
|
+
toasts: ToastOptions[];
|
|
21
|
+
toast: {
|
|
22
|
+
success: (message: string, title?: string) => number;
|
|
23
|
+
error: (message: string, title?: string) => number;
|
|
24
|
+
warning: (message: string, title?: string) => number;
|
|
25
|
+
info: (message: string, title?: string) => number;
|
|
26
|
+
show: (options: ToastOptions) => number;
|
|
27
|
+
remove: (id: number) => void;
|
|
28
|
+
};
|
|
29
|
+
removeToast: (id: number) => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const ToastItem: React.FC<{ toast: ToastOptions; onRemove: (id: number) => void }> = ({ toast, onRemove }) => {
|
|
33
|
+
const [isLeaving, setIsLeaving] = React.useState(false);
|
|
34
|
+
|
|
35
|
+
React.useEffect(() => {
|
|
36
|
+
if (toast.duration && toast.duration > 0) {
|
|
37
|
+
const timer = setTimeout(() => handleRemove(toast.id!), toast.duration);
|
|
38
|
+
return () => clearTimeout(timer);
|
|
39
|
+
}
|
|
40
|
+
}, [toast.id, toast.duration]);
|
|
41
|
+
|
|
42
|
+
const handleRemove = (id: number) => {
|
|
43
|
+
setIsLeaving(true);
|
|
44
|
+
setTimeout(() => onRemove(id), 200);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const icons: Record<ToastType, React.ReactNode> = {
|
|
48
|
+
success: <polyline points="20 6 9 17 4 12" />,
|
|
49
|
+
error: (
|
|
50
|
+
<>
|
|
51
|
+
<circle cx="12" cy="12" r="10" />
|
|
52
|
+
<line x1="15" y1="9" x2="9" y2="15" />
|
|
53
|
+
<line x1="9" y1="9" x2="15" y2="15" />
|
|
54
|
+
</>
|
|
55
|
+
),
|
|
56
|
+
warning: (
|
|
57
|
+
<>
|
|
58
|
+
<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" />
|
|
59
|
+
<line x1="12" y1="9" x2="12" y2="13" />
|
|
60
|
+
<line x1="12" y1="17" x2="12.01" y2="17" />
|
|
61
|
+
</>
|
|
62
|
+
),
|
|
63
|
+
info: (
|
|
64
|
+
<>
|
|
65
|
+
<circle cx="12" cy="12" r="10" />
|
|
66
|
+
<line x1="12" y1="16" x2="12" y2="12" />
|
|
67
|
+
<line x1="12" y1="8" x2="12.01" y2="8" />
|
|
68
|
+
</>
|
|
69
|
+
),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div className={`cn-toast cn-toast-${toast.type} ${isLeaving ? 'cn-toast-leaving' : ''}`.trim()} role="alert">
|
|
74
|
+
<div className="cn-alert-icon">
|
|
75
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
76
|
+
{icons[toast.type || 'info']}
|
|
77
|
+
</svg>
|
|
78
|
+
</div>
|
|
79
|
+
<div className="cn-alert-content">
|
|
80
|
+
{toast.title && <div className="cn-alert-title">{toast.title}</div>}
|
|
81
|
+
{toast.message && <div className="cn-alert-message">{toast.message}</div>}
|
|
82
|
+
</div>
|
|
83
|
+
{toast.closable && (
|
|
84
|
+
<button className="cn-alert-close" onClick={() => handleRemove(toast.id!)} aria-label="Close">
|
|
85
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="16" height="16">
|
|
86
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
87
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
88
|
+
</svg>
|
|
89
|
+
</button>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const Toast: React.FC<ToastProps> = ({ toasts = [], onRemove }) => {
|
|
96
|
+
return (
|
|
97
|
+
<div className="cn-toast-container">
|
|
98
|
+
{toasts.map((toast) => (
|
|
99
|
+
<ToastItem key={toast.id} toast={toast} onRemove={onRemove} />
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
Toast.displayName = 'Toast';
|
|
106
|
+
|
|
107
|
+
export function useToast(): UseToastReturn {
|
|
108
|
+
const [toasts, setToasts] = React.useState<ToastOptions[]>([]);
|
|
109
|
+
const idRef = React.useRef(0);
|
|
110
|
+
|
|
111
|
+
const addToast = (options: ToastOptions): number => {
|
|
112
|
+
const id = ++idRef.current;
|
|
113
|
+
const toast: ToastOptions = { id, duration: 4000, closable: true, type: 'info', ...options };
|
|
114
|
+
setToasts((prev) => [...prev, toast]);
|
|
115
|
+
return id;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const removeToast = (id: number) => {
|
|
119
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const toast = {
|
|
123
|
+
success: (message: string, title?: string) => addToast({ message, title, type: 'success' }),
|
|
124
|
+
error: (message: string, title?: string) => addToast({ message, title, type: 'error' }),
|
|
125
|
+
warning: (message: string, title?: string) => addToast({ message, title, type: 'warning' }),
|
|
126
|
+
info: (message: string, title?: string) => addToast({ message, title, type: 'info' }),
|
|
127
|
+
show: addToast,
|
|
128
|
+
remove: removeToast,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return { toasts, toast, removeToast };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export default Toast;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type ToggleSize = 'sm' | 'md' | 'lg';
|
|
4
|
+
|
|
5
|
+
export interface ToggleProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
on?: boolean;
|
|
7
|
+
onChange?: (on: boolean) => void;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
size?: ToggleSize;
|
|
10
|
+
label?: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const Toggle: React.FC<ToggleProps> = ({
|
|
14
|
+
on: controlledOn,
|
|
15
|
+
onChange,
|
|
16
|
+
disabled = false,
|
|
17
|
+
size = 'md',
|
|
18
|
+
label,
|
|
19
|
+
className = '',
|
|
20
|
+
...props
|
|
21
|
+
}) => {
|
|
22
|
+
const [internalOn, setInternalOn] = React.useState(false);
|
|
23
|
+
const isOn = controlledOn !== undefined ? controlledOn : internalOn;
|
|
24
|
+
|
|
25
|
+
const handleToggle = () => {
|
|
26
|
+
if (disabled) return;
|
|
27
|
+
if (onChange) {
|
|
28
|
+
onChange(!controlledOn);
|
|
29
|
+
} else {
|
|
30
|
+
setInternalOn(!internalOn);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const sizeClass = size === 'sm' ? 'cn-toggle-sm' : size === 'lg' ? 'cn-toggle-lg' : '';
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<label className={`cn-toggle-wrapper ${className}`.trim()} {...props}>
|
|
38
|
+
<div
|
|
39
|
+
className={`cn-toggle ${isOn ? 'on' : ''} ${sizeClass} ${disabled ? 'disabled' : ''}`.trim()}
|
|
40
|
+
onClick={handleToggle}
|
|
41
|
+
role="switch"
|
|
42
|
+
aria-checked={isOn}
|
|
43
|
+
tabIndex={0}
|
|
44
|
+
onKeyDown={(e) => {
|
|
45
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
handleToggle();
|
|
48
|
+
}
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
{label && <span className="cn-toggle-label">{label}</span>}
|
|
52
|
+
</label>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
Toggle.displayName = 'Toggle';
|
|
57
|
+
|
|
58
|
+
export default Toggle;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';
|
|
4
|
+
|
|
5
|
+
export interface TooltipProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
content: React.ReactNode;
|
|
7
|
+
position?: TooltipPosition;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const Tooltip: React.FC<TooltipProps> = ({
|
|
11
|
+
children,
|
|
12
|
+
content,
|
|
13
|
+
position = 'top',
|
|
14
|
+
className = '',
|
|
15
|
+
...props
|
|
16
|
+
}) => {
|
|
17
|
+
const positionClass = position !== 'top' ? `cn-tooltip-${position}` : '';
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className={`cn-tooltip ${positionClass} ${className}`.trim()} {...props}>
|
|
21
|
+
{children}
|
|
22
|
+
<div className="cn-tooltip-content" role="tooltip">
|
|
23
|
+
{content}
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
Tooltip.displayName = 'Tooltip';
|
|
30
|
+
|
|
31
|
+
export default Tooltip;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface HeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {
|
|
4
|
+
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const H1: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({ className = '', ...props }) => (
|
|
8
|
+
<h1 className={`cn-h1 ${className}`.trim()} {...props} />
|
|
9
|
+
);
|
|
10
|
+
H1.displayName = 'H1';
|
|
11
|
+
|
|
12
|
+
export const H2: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({ className = '', ...props }) => (
|
|
13
|
+
<h2 className={`cn-h2 ${className}`.trim()} {...props} />
|
|
14
|
+
);
|
|
15
|
+
H2.displayName = 'H2';
|
|
16
|
+
|
|
17
|
+
export const H3: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({ className = '', ...props }) => (
|
|
18
|
+
<h3 className={`cn-h3 ${className}`.trim()} {...props} />
|
|
19
|
+
);
|
|
20
|
+
H3.displayName = 'H3';
|
|
21
|
+
|
|
22
|
+
export const H4: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({ className = '', ...props }) => (
|
|
23
|
+
<h4 className={`cn-h4 ${className}`.trim()} {...props} />
|
|
24
|
+
);
|
|
25
|
+
H4.displayName = 'H4';
|
|
26
|
+
|
|
27
|
+
export const H5: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({ className = '', ...props }) => (
|
|
28
|
+
<h5 className={`cn-h5 ${className}`.trim()} {...props} />
|
|
29
|
+
);
|
|
30
|
+
H5.displayName = 'H5';
|
|
31
|
+
|
|
32
|
+
export const H6: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({ className = '', ...props }) => (
|
|
33
|
+
<h6 className={`cn-h6 ${className}`.trim()} {...props} />
|
|
34
|
+
);
|
|
35
|
+
H6.displayName = 'H6';
|
|
36
|
+
|
|
37
|
+
export interface TextProps extends React.HTMLAttributes<HTMLParagraphElement> {
|
|
38
|
+
variant?: 'muted' | 'dim' | 'accent' | 'mono';
|
|
39
|
+
size?: 'xs' | 'sm' | 'base' | 'md' | 'lg' | 'xl';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const Text: React.FC<TextProps> = ({
|
|
43
|
+
variant,
|
|
44
|
+
size,
|
|
45
|
+
className = '',
|
|
46
|
+
...props
|
|
47
|
+
}) => {
|
|
48
|
+
const variantClass = variant ? `cn-text-${variant}` : '';
|
|
49
|
+
const sizeClass = size ? `cn-text-${size}` : '';
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<p className={`cn-text-base ${variantClass} ${sizeClass} ${className}`.trim()} {...props} />
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
Text.displayName = 'Text';
|
|
57
|
+
|
|
58
|
+
export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {}
|
|
59
|
+
|
|
60
|
+
export const Label: React.FC<LabelProps> = ({ className = '', ...props }) => (
|
|
61
|
+
<label className={`cn-label ${className}`.trim()} {...props} />
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
Label.displayName = 'Label';
|
|
65
|
+
|
|
66
|
+
export default H1;
|