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,48 +1,135 @@
|
|
|
1
|
-
"""CronixUI Table Component
|
|
1
|
+
"""CronixUI Table Component.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Generates HTML for data tables with optional sorting indication.
|
|
4
|
+
No browser DOM APIs are used - all output is HTML strings or data structures.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class TableElement:
|
|
15
|
+
"""Represents a rendered table element."""
|
|
16
|
+
|
|
17
|
+
tag: str = "div"
|
|
18
|
+
classes: List[str] = field(default_factory=list)
|
|
19
|
+
attributes: Dict[str, str] = field(default_factory=dict)
|
|
20
|
+
inner_html: str = ""
|
|
21
|
+
|
|
22
|
+
def render_html(self) -> str:
|
|
23
|
+
"""Render as HTML string.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Complete HTML for the table element
|
|
27
|
+
"""
|
|
28
|
+
class_str = " ".join(self.classes)
|
|
29
|
+
class_attr = f' class="{class_str}"' if class_str else ""
|
|
30
|
+
attrs_str = "".join(f' {k}="{v}"' for k, v in self.attributes.items())
|
|
31
|
+
return f"<{self.tag}{class_attr}{attrs_str}>{self.inner_html}</{self.tag}>"
|
|
32
|
+
|
|
33
|
+
def render(self) -> "TableElement":
|
|
34
|
+
"""Return self for API compatibility."""
|
|
35
|
+
return self
|
|
5
36
|
|
|
6
37
|
|
|
7
38
|
class Table:
|
|
8
|
-
"""Table component.
|
|
39
|
+
"""Table component for displaying tabular data.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
headers: List of column header strings
|
|
43
|
+
rows: List of rows, where each row is a list of cell strings
|
|
44
|
+
sortable: Whether to add sortable class to the table (default: False)
|
|
45
|
+
caption: Optional table caption/summary
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
>>> table = Table(
|
|
49
|
+
... headers=["Name", "Email", "Role"],
|
|
50
|
+
... rows=[
|
|
51
|
+
... ["Alice", "alice@example.com", "Admin"],
|
|
52
|
+
... ["Bob", "bob@example.com", "User"],
|
|
53
|
+
... ],
|
|
54
|
+
... )
|
|
55
|
+
>>> print(table.render_html())
|
|
56
|
+
<div class="cn-table-wrapper">
|
|
57
|
+
<table class="cn-table">
|
|
58
|
+
<thead><tr><th>Name</th><th>Email</th><th>Role</th></tr></thead>
|
|
59
|
+
<tbody>
|
|
60
|
+
<tr><td>Alice</td><td>alice@example.com</td><td>Admin</td></tr>
|
|
61
|
+
<tr><td>Bob</td><td>bob@example.com</td><td>User</td></tr>
|
|
62
|
+
</tbody>
|
|
63
|
+
</table>
|
|
64
|
+
</div>
|
|
65
|
+
"""
|
|
9
66
|
|
|
10
67
|
def __init__(
|
|
11
68
|
self,
|
|
12
69
|
headers: List[str],
|
|
13
70
|
rows: List[List[str]],
|
|
14
71
|
sortable: bool = False,
|
|
72
|
+
caption: Optional[str] = None,
|
|
15
73
|
):
|
|
74
|
+
if not headers:
|
|
75
|
+
raise ValueError("headers cannot be empty")
|
|
76
|
+
|
|
16
77
|
self.headers = headers
|
|
17
78
|
self.rows = rows
|
|
18
79
|
self.sortable = sortable
|
|
19
|
-
self.
|
|
80
|
+
self.caption = caption
|
|
81
|
+
|
|
82
|
+
def render(self) -> TableElement:
|
|
83
|
+
"""Render the table as a TableElement.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
TableElement containing the complete table markup
|
|
87
|
+
"""
|
|
88
|
+
parts = []
|
|
89
|
+
|
|
90
|
+
# Optional caption
|
|
91
|
+
if self.caption:
|
|
92
|
+
parts.append(f"<caption>{self._esc(self.caption)}</caption>")
|
|
93
|
+
|
|
94
|
+
# Header
|
|
95
|
+
header_cells = "".join(
|
|
96
|
+
f"<th>{self._esc(h)}</th>" for h in self.headers
|
|
97
|
+
)
|
|
98
|
+
parts.append(f"<thead><tr>{header_cells}</tr></thead>")
|
|
20
99
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
100
|
+
# Body
|
|
101
|
+
body_rows = []
|
|
102
|
+
for row in self.rows:
|
|
103
|
+
cells = "".join(f"<td>{self._esc(str(cell))}</td>" for cell in row)
|
|
104
|
+
body_rows.append(f"<tr>{cells}</tr>")
|
|
105
|
+
parts.append(f"<tbody>{''.join(body_rows)}</tbody>")
|
|
24
106
|
|
|
107
|
+
table_classes = ["cn-table"]
|
|
25
108
|
if self.sortable:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
109
|
+
table_classes.append("cn-table-sortable")
|
|
110
|
+
|
|
111
|
+
table_html = f'<table class="{" ".join(table_classes)}">{"".join(parts)}</table>'
|
|
112
|
+
|
|
113
|
+
return TableElement(
|
|
114
|
+
classes=["cn-table-wrapper"],
|
|
115
|
+
inner_html=table_html,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def render_html(self) -> str:
|
|
119
|
+
"""Render the table as an HTML string.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
HTML string representation of the table
|
|
123
|
+
"""
|
|
124
|
+
return self.render().render_html()
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def _esc(text: str) -> str:
|
|
128
|
+
"""Escape HTML special characters."""
|
|
129
|
+
return (
|
|
130
|
+
text.replace("&", "&")
|
|
131
|
+
.replace("<", "<")
|
|
132
|
+
.replace(">", ">")
|
|
133
|
+
.replace('"', """)
|
|
134
|
+
.replace("'", "'")
|
|
135
|
+
)
|
|
@@ -1,28 +1,130 @@
|
|
|
1
|
-
"""CronixUI Tooltip Component
|
|
1
|
+
"""CronixUI Tooltip Component.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Generates HTML for tooltips that can wrap other elements.
|
|
4
|
+
No browser DOM APIs are used - all output is HTML strings or data structures.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class TooltipElement:
|
|
15
|
+
"""Represents a rendered tooltip element."""
|
|
16
|
+
|
|
17
|
+
tag: str = "div"
|
|
18
|
+
classes: List[str] = field(default_factory=list)
|
|
19
|
+
attributes: Dict[str, str] = field(default_factory=dict)
|
|
20
|
+
inner_html: str = ""
|
|
21
|
+
|
|
22
|
+
def render_html(self) -> str:
|
|
23
|
+
"""Render as HTML string.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Complete HTML for the tooltip element
|
|
27
|
+
"""
|
|
28
|
+
class_str = " ".join(self.classes)
|
|
29
|
+
class_attr = f' class="{class_str}"' if class_str else ""
|
|
30
|
+
attrs_str = "".join(f' {k}="{v}"' for k, v in self.attributes.items())
|
|
31
|
+
return f"<{self.tag}{class_attr}{attrs_str}>{self.inner_html}</{self.tag}>"
|
|
32
|
+
|
|
33
|
+
def render(self) -> "TooltipElement":
|
|
34
|
+
"""Return self for API compatibility."""
|
|
35
|
+
return self
|
|
4
36
|
|
|
5
37
|
|
|
6
38
|
class Tooltip:
|
|
7
|
-
"""Tooltip component.
|
|
39
|
+
"""Tooltip component for displaying contextual information on hover.
|
|
40
|
+
|
|
41
|
+
The tooltip content is generated as a data attribute and HTML structure
|
|
42
|
+
that can be styled with CSS to appear on hover.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
content: Tooltip text content
|
|
46
|
+
position: Tooltip position - top, bottom, left, right (default: top)
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
>>> tooltip = Tooltip("Click to edit", position="top")
|
|
50
|
+
>>> print(tooltip.render_html())
|
|
51
|
+
<div class="cn-tooltip cn-tooltip-top"><div class="cn-tooltip-content">Click to edit</div></div>
|
|
52
|
+
|
|
53
|
+
>>> # Wrap an element with tooltip
|
|
54
|
+
>>> wrapped = tooltip.wrap_html("<button>Edit</button>")
|
|
55
|
+
>>> print(wrapped)
|
|
56
|
+
<span class="cn-tooltip-wrapper" data-tooltip="Click to edit" data-position="top"><button>Edit</button></span>
|
|
57
|
+
"""
|
|
8
58
|
|
|
9
59
|
POSITIONS = ("top", "bottom", "left", "right")
|
|
10
60
|
|
|
11
61
|
def __init__(self, content: str, position: str = "top"):
|
|
62
|
+
if not content:
|
|
63
|
+
raise ValueError("content cannot be empty")
|
|
64
|
+
if position not in self.POSITIONS:
|
|
65
|
+
raise ValueError(
|
|
66
|
+
f"Invalid position '{position}'. Must be one of {self.POSITIONS}"
|
|
67
|
+
)
|
|
68
|
+
|
|
12
69
|
self.content = content
|
|
13
|
-
self.position = position
|
|
14
|
-
|
|
70
|
+
self.position = position
|
|
71
|
+
|
|
72
|
+
def render(self) -> TooltipElement:
|
|
73
|
+
"""Render the tooltip as a TooltipElement.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
TooltipElement representing the tooltip
|
|
77
|
+
"""
|
|
78
|
+
inner = (
|
|
79
|
+
f'<div class="cn-tooltip-content">{self._esc(self.content)}</div>'
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return TooltipElement(
|
|
83
|
+
classes=["cn-tooltip", f"cn-tooltip-{self.position}"],
|
|
84
|
+
inner_html=inner,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def render_html(self) -> str:
|
|
88
|
+
"""Render the tooltip as an HTML string.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
HTML string representation of the tooltip
|
|
92
|
+
"""
|
|
93
|
+
return self.render().render_html()
|
|
94
|
+
|
|
95
|
+
def wrap_html(self, element_html: str) -> str:
|
|
96
|
+
"""Wrap an HTML element with tooltip data attributes.
|
|
97
|
+
|
|
98
|
+
This generates a wrapper with data attributes that CSS can use
|
|
99
|
+
to display the tooltip on hover.
|
|
15
100
|
|
|
16
|
-
|
|
17
|
-
|
|
101
|
+
Args:
|
|
102
|
+
element_html: HTML string of the element to wrap
|
|
18
103
|
|
|
19
|
-
|
|
20
|
-
|
|
104
|
+
Returns:
|
|
105
|
+
HTML string with the element wrapped in a tooltip wrapper
|
|
21
106
|
|
|
22
|
-
|
|
23
|
-
|
|
107
|
+
Example:
|
|
108
|
+
>>> tooltip = Tooltip("Click to save")
|
|
109
|
+
>>> wrapped = tooltip.wrap_html("<button>Save</button>")
|
|
110
|
+
>>> print(wrapped)
|
|
111
|
+
<span class="cn-tooltip-wrapper" data-tooltip="Click to save" data-position="top"><button>Save</button></span>
|
|
112
|
+
"""
|
|
113
|
+
return (
|
|
114
|
+
f'<span class="cn-tooltip-wrapper"'
|
|
115
|
+
f' data-tooltip="{self._esc(self.content)}"'
|
|
116
|
+
f' data-position="{self.position}">'
|
|
117
|
+
f"{element_html}"
|
|
118
|
+
f"</span>"
|
|
119
|
+
)
|
|
24
120
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return
|
|
121
|
+
@staticmethod
|
|
122
|
+
def _esc(text: str) -> str:
|
|
123
|
+
"""Escape HTML special characters."""
|
|
124
|
+
return (
|
|
125
|
+
text.replace("&", "&")
|
|
126
|
+
.replace("<", "<")
|
|
127
|
+
.replace(">", ">")
|
|
128
|
+
.replace('"', """)
|
|
129
|
+
.replace("'", "'")
|
|
130
|
+
)
|
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
|
|
3
|
-
export interface AccordionProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
-
allowMultiple?: boolean;
|
|
5
|
-
defaultOpen?: number[];
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface AccordionItemProps {
|
|
9
|
-
title: string;
|
|
10
|
-
children: React.ReactNode;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const Accordion: React.FC<AccordionProps> & {
|
|
14
|
-
Item: React.FC<AccordionItemProps>;
|
|
15
|
-
} = Object.assign(
|
|
16
|
-
({ allowMultiple = false, defaultOpen = [], children, className = '', ...props }: AccordionProps) => {
|
|
17
|
-
const [openItems, setOpenItems] = React.useState(new Set(defaultOpen));
|
|
18
|
-
|
|
19
|
-
const toggleItem = (index: number) => {
|
|
20
|
-
setOpenItems((prev) => {
|
|
21
|
-
const next = new Set(prev);
|
|
22
|
-
if (next.has(index)) {
|
|
23
|
-
next.delete(index);
|
|
24
|
-
} else {
|
|
25
|
-
if (!allowMultiple) {
|
|
26
|
-
next.clear();
|
|
27
|
-
}
|
|
28
|
-
next.add(index);
|
|
29
|
-
}
|
|
30
|
-
return next;
|
|
31
|
-
});
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const items = React.Children.toArray(children);
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<div className={`cn-accordion ${className}`.trim()} {...props}>
|
|
38
|
-
{items.map((child, idx) => {
|
|
39
|
-
if (React.isValidElement<AccordionItemProps>(child)) {
|
|
40
|
-
return (
|
|
41
|
-
<div
|
|
42
|
-
key={idx}
|
|
43
|
-
className={`cn-accordion-item ${openItems.has(idx) ? 'cn-accordion-open' : ''}`.trim()}
|
|
44
|
-
>
|
|
45
|
-
<div
|
|
46
|
-
className="cn-accordion-header"
|
|
47
|
-
onClick={() => toggleItem(idx)}
|
|
48
|
-
role="button"
|
|
49
|
-
tabIndex={0}
|
|
50
|
-
aria-expanded={openItems.has(idx)}
|
|
51
|
-
onKeyDown={(e) => {
|
|
52
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
53
|
-
e.preventDefault();
|
|
54
|
-
toggleItem(idx);
|
|
55
|
-
}
|
|
56
|
-
}}
|
|
57
|
-
>
|
|
58
|
-
<span className="cn-accordion-title">{child.props.title}</span>
|
|
59
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="20" height="20" className="cn-accordion-icon">
|
|
60
|
-
<polyline points="6 9 12 15 18 9" />
|
|
61
|
-
</svg>
|
|
62
|
-
</div>
|
|
63
|
-
<div className="cn-accordion-content">
|
|
64
|
-
{child.props.children}
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
return child;
|
|
70
|
-
})}
|
|
71
|
-
</div>
|
|
72
|
-
);
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
Item: ({ children }: AccordionItemProps) => <>{children}</>,
|
|
76
|
-
}
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
Accordion.displayName = 'Accordion';
|
|
80
|
-
(Accordion.Item as React.FC<AccordionItemProps> & { displayName?: string }).displayName = 'AccordionItem';
|
|
81
|
-
|
|
82
|
-
export default Accordion;
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export interface AccordionProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
4
|
+
allowMultiple?: boolean;
|
|
5
|
+
defaultOpen?: number[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface AccordionItemProps {
|
|
9
|
+
title: string;
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const Accordion: React.FC<AccordionProps> & {
|
|
14
|
+
Item: React.FC<AccordionItemProps>;
|
|
15
|
+
} = Object.assign(
|
|
16
|
+
({ allowMultiple = false, defaultOpen = [], children, className = '', ...props }: AccordionProps) => {
|
|
17
|
+
const [openItems, setOpenItems] = React.useState(new Set(defaultOpen));
|
|
18
|
+
|
|
19
|
+
const toggleItem = (index: number) => {
|
|
20
|
+
setOpenItems((prev) => {
|
|
21
|
+
const next = new Set(prev);
|
|
22
|
+
if (next.has(index)) {
|
|
23
|
+
next.delete(index);
|
|
24
|
+
} else {
|
|
25
|
+
if (!allowMultiple) {
|
|
26
|
+
next.clear();
|
|
27
|
+
}
|
|
28
|
+
next.add(index);
|
|
29
|
+
}
|
|
30
|
+
return next;
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const items = React.Children.toArray(children);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className={`cn-accordion ${className}`.trim()} {...props}>
|
|
38
|
+
{items.map((child, idx) => {
|
|
39
|
+
if (React.isValidElement<AccordionItemProps>(child)) {
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
key={idx}
|
|
43
|
+
className={`cn-accordion-item ${openItems.has(idx) ? 'cn-accordion-open' : ''}`.trim()}
|
|
44
|
+
>
|
|
45
|
+
<div
|
|
46
|
+
className="cn-accordion-header"
|
|
47
|
+
onClick={() => toggleItem(idx)}
|
|
48
|
+
role="button"
|
|
49
|
+
tabIndex={0}
|
|
50
|
+
aria-expanded={openItems.has(idx)}
|
|
51
|
+
onKeyDown={(e) => {
|
|
52
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
toggleItem(idx);
|
|
55
|
+
}
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
<span className="cn-accordion-title">{child.props.title}</span>
|
|
59
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="20" height="20" className="cn-accordion-icon">
|
|
60
|
+
<polyline points="6 9 12 15 18 9" />
|
|
61
|
+
</svg>
|
|
62
|
+
</div>
|
|
63
|
+
<div className="cn-accordion-content">
|
|
64
|
+
{child.props.children}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return child;
|
|
70
|
+
})}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
Item: ({ children }: AccordionItemProps) => <>{children}</>,
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
Accordion.displayName = 'Accordion';
|
|
80
|
+
(Accordion.Item as React.FC<AccordionItemProps> & { displayName?: string }).displayName = 'AccordionItem';
|
|
81
|
+
|
|
82
|
+
export default Accordion;
|
|
@@ -1,47 +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;
|
|
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;
|