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,143 @@
|
|
|
1
|
+
"""CronixUI Layout Components"""
|
|
2
|
+
|
|
3
|
+
from .core import create_el
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Header:
|
|
7
|
+
"""Header component."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, brand: str = ""):
|
|
10
|
+
self.brand = brand
|
|
11
|
+
self.element = self._render()
|
|
12
|
+
|
|
13
|
+
def _render(self):
|
|
14
|
+
el = create_el("header", "cn-header")
|
|
15
|
+
|
|
16
|
+
brand_el = create_el("a", "cn-header-brand")
|
|
17
|
+
brand_el.textContent = self.brand
|
|
18
|
+
el.appendChild(brand_el)
|
|
19
|
+
|
|
20
|
+
self.nav = create_el("nav", "cn-header-nav")
|
|
21
|
+
el.appendChild(self.nav)
|
|
22
|
+
|
|
23
|
+
self.actions = create_el("div", "cn-header-actions")
|
|
24
|
+
el.appendChild(self.actions)
|
|
25
|
+
|
|
26
|
+
return el
|
|
27
|
+
|
|
28
|
+
def add_nav_item(self, text: str, href: str = "#"):
|
|
29
|
+
link = create_el("a", "cn-btn cn-btn-ghost")
|
|
30
|
+
link.textContent = text
|
|
31
|
+
link.setAttribute("href", href)
|
|
32
|
+
self.nav.appendChild(link)
|
|
33
|
+
|
|
34
|
+
def add_action(self, element):
|
|
35
|
+
self.actions.appendChild(element)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Sidebar:
|
|
39
|
+
"""Sidebar navigation component."""
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
self.element = self._render()
|
|
43
|
+
|
|
44
|
+
def _render(self):
|
|
45
|
+
el = create_el("aside", "cn-sidebar")
|
|
46
|
+
|
|
47
|
+
self.header = create_el("div", "cn-sidebar-header")
|
|
48
|
+
el.appendChild(self.header)
|
|
49
|
+
|
|
50
|
+
self.nav = create_el("nav", "cn-sidebar-nav")
|
|
51
|
+
el.appendChild(self.nav)
|
|
52
|
+
|
|
53
|
+
self.footer = create_el("div", "cn-sidebar-footer")
|
|
54
|
+
el.appendChild(self.footer)
|
|
55
|
+
|
|
56
|
+
return el
|
|
57
|
+
|
|
58
|
+
def add_item(
|
|
59
|
+
self, text: str, icon: str = None, href: str = "#", active: bool = False
|
|
60
|
+
):
|
|
61
|
+
item = create_el("a", "cn-sidebar-item")
|
|
62
|
+
item.setAttribute("href", href)
|
|
63
|
+
if active:
|
|
64
|
+
item.classList.add("cn-sidebar-active")
|
|
65
|
+
|
|
66
|
+
if icon:
|
|
67
|
+
icon_el = create_el("span")
|
|
68
|
+
icon_el.innerHTML = icon
|
|
69
|
+
item.appendChild(icon_el)
|
|
70
|
+
|
|
71
|
+
text_el = create_el("span")
|
|
72
|
+
text_el.textContent = text
|
|
73
|
+
item.appendChild(text_el)
|
|
74
|
+
|
|
75
|
+
self.nav.appendChild(item)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Footer:
|
|
79
|
+
"""Footer component."""
|
|
80
|
+
|
|
81
|
+
def __init__(self, copyright: str = ""):
|
|
82
|
+
self.copyright = copyright
|
|
83
|
+
self.element = self._render()
|
|
84
|
+
|
|
85
|
+
def _render(self):
|
|
86
|
+
el = create_el("footer", "cn-footer")
|
|
87
|
+
|
|
88
|
+
content = create_el("div", "cn-footer-content")
|
|
89
|
+
|
|
90
|
+
self.links = create_el("div", "cn-footer-links")
|
|
91
|
+
content.appendChild(self.links)
|
|
92
|
+
|
|
93
|
+
copyright_el = create_el("div", "cn-footer-copyright")
|
|
94
|
+
copyright_el.textContent = self.copyright
|
|
95
|
+
content.appendChild(copyright_el)
|
|
96
|
+
|
|
97
|
+
el.appendChild(content)
|
|
98
|
+
return el
|
|
99
|
+
|
|
100
|
+
def add_link(self, text: str, href: str = "#"):
|
|
101
|
+
link = create_el("a", "cn-footer-link")
|
|
102
|
+
link.textContent = text
|
|
103
|
+
link.setAttribute("href", href)
|
|
104
|
+
self.links.appendChild(link)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class Container:
|
|
108
|
+
"""Container component."""
|
|
109
|
+
|
|
110
|
+
SIZES = ("sm", "md", "lg", "xl", "fluid")
|
|
111
|
+
|
|
112
|
+
def __init__(self, size: str = "lg"):
|
|
113
|
+
self.size = size if size in self.SIZES else "lg"
|
|
114
|
+
self.element = self._render()
|
|
115
|
+
|
|
116
|
+
def _render(self):
|
|
117
|
+
classes = "cn-container"
|
|
118
|
+
if self.size != "lg":
|
|
119
|
+
classes += f" cn-container-{self.size}"
|
|
120
|
+
return create_el("div", classes)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class Divider:
|
|
124
|
+
"""Divider component."""
|
|
125
|
+
|
|
126
|
+
def __init__(self):
|
|
127
|
+
self.element = create_el("div", "cn-divider")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class Section:
|
|
131
|
+
"""Section spacing component."""
|
|
132
|
+
|
|
133
|
+
SIZES = ("sm", "md", "lg")
|
|
134
|
+
|
|
135
|
+
def __init__(self, size: str = "md"):
|
|
136
|
+
self.size = size if size in self.SIZES else "md"
|
|
137
|
+
self.element = self._render()
|
|
138
|
+
|
|
139
|
+
def _render(self):
|
|
140
|
+
classes = "cn-section"
|
|
141
|
+
if self.size != "md":
|
|
142
|
+
classes += f" cn-section-{self.size}"
|
|
143
|
+
return create_el("section", classes)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""CronixUI List Component"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Callable
|
|
4
|
+
from .core import create_el
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class List:
|
|
8
|
+
"""List component."""
|
|
9
|
+
|
|
10
|
+
def __init__(self, items: list, clickable: bool = False):
|
|
11
|
+
self.items = items
|
|
12
|
+
self.clickable = clickable
|
|
13
|
+
self.element = self._render()
|
|
14
|
+
|
|
15
|
+
def _render(self):
|
|
16
|
+
el = create_el("div", "cn-list")
|
|
17
|
+
for item in self.items:
|
|
18
|
+
item_el = create_el("div", "cn-list-item")
|
|
19
|
+
if self.clickable:
|
|
20
|
+
item_el.classList.add("cn-list-item-clickable")
|
|
21
|
+
|
|
22
|
+
if isinstance(item, dict):
|
|
23
|
+
if icon := item.get("icon"):
|
|
24
|
+
icon_el = create_el("span", "cn-list-item-icon")
|
|
25
|
+
icon_el.innerHTML = icon
|
|
26
|
+
item_el.appendChild(icon_el)
|
|
27
|
+
|
|
28
|
+
content = create_el("div", "cn-list-item-content")
|
|
29
|
+
if title := item.get("title"):
|
|
30
|
+
title_el = create_el("div", "cn-list-item-title")
|
|
31
|
+
title_el.textContent = title
|
|
32
|
+
content.appendChild(title_el)
|
|
33
|
+
|
|
34
|
+
if subtitle := item.get("subtitle"):
|
|
35
|
+
subtitle_el = create_el("div", "cn-list-item-subtitle")
|
|
36
|
+
subtitle_el.textContent = subtitle
|
|
37
|
+
content.appendChild(subtitle_el)
|
|
38
|
+
|
|
39
|
+
item_el.appendChild(content)
|
|
40
|
+
|
|
41
|
+
if actions := item.get("actions"):
|
|
42
|
+
actions_el = create_el("div", "cn-list-item-actions")
|
|
43
|
+
actions_el.innerHTML = actions
|
|
44
|
+
item_el.appendChild(actions_el)
|
|
45
|
+
else:
|
|
46
|
+
content = create_el("div", "cn-list-item-content")
|
|
47
|
+
content.textContent = str(item)
|
|
48
|
+
item_el.appendChild(content)
|
|
49
|
+
|
|
50
|
+
el.appendChild(item_el)
|
|
51
|
+
return el
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""CronixUI Loading Components"""
|
|
2
|
+
|
|
3
|
+
from .core import create_el
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Spinner:
|
|
7
|
+
"""Loading spinner component."""
|
|
8
|
+
|
|
9
|
+
SIZES = ("sm", "md", "lg")
|
|
10
|
+
|
|
11
|
+
def __init__(self, size: str = "md"):
|
|
12
|
+
self.size = size if size in self.SIZES else "md"
|
|
13
|
+
self.element = self._render()
|
|
14
|
+
|
|
15
|
+
def _render(self):
|
|
16
|
+
el = create_el("div", "cn-spinner")
|
|
17
|
+
if self.size != "md":
|
|
18
|
+
el.classList.add(f"cn-spinner-{self.size}")
|
|
19
|
+
return el
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Skeleton:
|
|
23
|
+
"""Skeleton loading placeholder."""
|
|
24
|
+
|
|
25
|
+
VARIANTS = ("text", "title", "avatar")
|
|
26
|
+
|
|
27
|
+
def __init__(self, variant: str = "text", width: str = None):
|
|
28
|
+
self.variant = variant if variant in self.VARIANTS else "text"
|
|
29
|
+
self.width = width
|
|
30
|
+
self.element = self._render()
|
|
31
|
+
|
|
32
|
+
def _render(self):
|
|
33
|
+
el = create_el("div", f"cn-skeleton cn-skeleton-{self.variant}")
|
|
34
|
+
if self.width:
|
|
35
|
+
el.style.width = self.width
|
|
36
|
+
return el
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""CronixUI Progress Components"""
|
|
2
|
+
|
|
3
|
+
from .core import create_el
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Progress:
|
|
7
|
+
"""Progress bar component."""
|
|
8
|
+
|
|
9
|
+
VARIANTS = ("default", "success", "warning", "error")
|
|
10
|
+
SIZES = ("sm", "md", "lg")
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
value: float = 0,
|
|
15
|
+
max: float = 100,
|
|
16
|
+
variant: str = "default",
|
|
17
|
+
size: str = "md",
|
|
18
|
+
show_label: bool = False,
|
|
19
|
+
):
|
|
20
|
+
self.value = value
|
|
21
|
+
self.max = max
|
|
22
|
+
self.variant = variant if variant in self.VARIANTS else "default"
|
|
23
|
+
self.size = size if size in self.SIZES else "md"
|
|
24
|
+
self.show_label = show_label
|
|
25
|
+
self.element = self._render()
|
|
26
|
+
|
|
27
|
+
def _render(self):
|
|
28
|
+
container = create_el("div")
|
|
29
|
+
|
|
30
|
+
if self.show_label:
|
|
31
|
+
label = create_el("div", "cn-progress-label")
|
|
32
|
+
current = create_el("span")
|
|
33
|
+
current.textContent = str(int(self.value))
|
|
34
|
+
total = create_el("span")
|
|
35
|
+
total.textContent = str(int(self.max))
|
|
36
|
+
label.appendChild(current)
|
|
37
|
+
label.appendChild(total)
|
|
38
|
+
container.appendChild(label)
|
|
39
|
+
|
|
40
|
+
progress = create_el("div", "cn-progress")
|
|
41
|
+
if self.size != "md":
|
|
42
|
+
progress.classList.add(f"cn-progress-{self.size}")
|
|
43
|
+
if self.variant != "default":
|
|
44
|
+
progress.classList.add(f"cn-progress-{self.variant}")
|
|
45
|
+
|
|
46
|
+
bar = create_el("div", "cn-progress-bar")
|
|
47
|
+
bar.style.width = f"{(self.value / self.max) * 100}%"
|
|
48
|
+
progress.appendChild(bar)
|
|
49
|
+
|
|
50
|
+
container.appendChild(progress)
|
|
51
|
+
return container
|
|
52
|
+
|
|
53
|
+
def set_value(self, value: float):
|
|
54
|
+
self.value = value
|
|
55
|
+
bar = self.element.querySelector(".cn-progress-bar")
|
|
56
|
+
if bar:
|
|
57
|
+
bar.style.width = f"{(self.value / self.max) * 100}%"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Stat:
|
|
61
|
+
"""Stat component for displaying metrics."""
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self, value: str, label: str, delta: str = None, delta_type: str = None
|
|
65
|
+
):
|
|
66
|
+
self.value = value
|
|
67
|
+
self.label = label
|
|
68
|
+
self.delta = delta
|
|
69
|
+
self.delta_type = delta_type
|
|
70
|
+
self.element = self._render()
|
|
71
|
+
|
|
72
|
+
def _render(self):
|
|
73
|
+
el = create_el("div", "cn-stat")
|
|
74
|
+
|
|
75
|
+
value_el = create_el("div", "cn-stat-value")
|
|
76
|
+
value_el.textContent = self.value
|
|
77
|
+
el.appendChild(value_el)
|
|
78
|
+
|
|
79
|
+
label_el = create_el("div", "cn-stat-label")
|
|
80
|
+
label_el.textContent = self.label
|
|
81
|
+
el.appendChild(label_el)
|
|
82
|
+
|
|
83
|
+
if self.delta:
|
|
84
|
+
delta_el = create_el(
|
|
85
|
+
"div", f"cn-stat-delta cn-stat-delta-{self.delta_type}"
|
|
86
|
+
)
|
|
87
|
+
delta_el.textContent = self.delta
|
|
88
|
+
el.appendChild(delta_el)
|
|
89
|
+
|
|
90
|
+
return el
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""CronixUI Table Component"""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
from .core import create_el
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Table:
|
|
8
|
+
"""Table component."""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
headers: List[str],
|
|
13
|
+
rows: List[List[str]],
|
|
14
|
+
sortable: bool = False,
|
|
15
|
+
):
|
|
16
|
+
self.headers = headers
|
|
17
|
+
self.rows = rows
|
|
18
|
+
self.sortable = sortable
|
|
19
|
+
self.element = self._render()
|
|
20
|
+
|
|
21
|
+
def _render(self):
|
|
22
|
+
wrapper = create_el("div", "cn-table-wrapper")
|
|
23
|
+
table = create_el("table", "cn-table")
|
|
24
|
+
|
|
25
|
+
if self.sortable:
|
|
26
|
+
table.classList.add("cn-table-sortable")
|
|
27
|
+
|
|
28
|
+
thead = create_el("thead")
|
|
29
|
+
header_row = create_el("tr")
|
|
30
|
+
for header in self.headers:
|
|
31
|
+
th = create_el("th")
|
|
32
|
+
th.textContent = header
|
|
33
|
+
header_row.appendChild(th)
|
|
34
|
+
thead.appendChild(header_row)
|
|
35
|
+
table.appendChild(thead)
|
|
36
|
+
|
|
37
|
+
tbody = create_el("tbody")
|
|
38
|
+
for row_data in self.rows:
|
|
39
|
+
row = create_el("tr")
|
|
40
|
+
for cell in row_data:
|
|
41
|
+
td = create_el("td")
|
|
42
|
+
td.textContent = cell
|
|
43
|
+
row.appendChild(td)
|
|
44
|
+
tbody.appendChild(row)
|
|
45
|
+
table.appendChild(tbody)
|
|
46
|
+
|
|
47
|
+
wrapper.appendChild(table)
|
|
48
|
+
return wrapper
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""CronixUI Tooltip Component"""
|
|
2
|
+
|
|
3
|
+
from .core import create_el
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Tooltip:
|
|
7
|
+
"""Tooltip component."""
|
|
8
|
+
|
|
9
|
+
POSITIONS = ("top", "bottom", "left", "right")
|
|
10
|
+
|
|
11
|
+
def __init__(self, content: str, position: str = "top"):
|
|
12
|
+
self.content = content
|
|
13
|
+
self.position = position if position in self.POSITIONS else "top"
|
|
14
|
+
self.element = self._render()
|
|
15
|
+
|
|
16
|
+
def _render(self):
|
|
17
|
+
el = create_el("div", "cn-tooltip")
|
|
18
|
+
|
|
19
|
+
tooltip_content = create_el("div", "cn-tooltip-content")
|
|
20
|
+
tooltip_content.textContent = self.content
|
|
21
|
+
|
|
22
|
+
el.appendChild(tooltip_content)
|
|
23
|
+
return el
|
|
24
|
+
|
|
25
|
+
def wrap(self, element):
|
|
26
|
+
"""Wrap an element with tooltip."""
|
|
27
|
+
self.element.insertBefore(element, self.element.firstChild)
|
|
28
|
+
return self.element
|
|
@@ -0,0 +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;
|
|
@@ -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;
|