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
|
@@ -12,6 +12,18 @@ from .pagination import Pagination
|
|
|
12
12
|
from .command_palette import CommandPalette, CommandPaletteItem
|
|
13
13
|
from .search import Search, SearchItem
|
|
14
14
|
from .nav import Nav
|
|
15
|
+
from .button import Button, ButtonGroup
|
|
16
|
+
from .card import Card, CardIcon
|
|
17
|
+
from .badge import Badge, Tag
|
|
18
|
+
from .avatar import Avatar, AvatarGroup
|
|
19
|
+
from .alert import Alert
|
|
20
|
+
from .loading import Spinner, Skeleton
|
|
21
|
+
from .table import Table
|
|
22
|
+
from .list import List
|
|
23
|
+
from .tooltip import Tooltip
|
|
24
|
+
from .layout import Header, Sidebar, Footer, Container, Divider, Section
|
|
25
|
+
from .form import Input, Textarea, FormField, Checkbox, Radio, Select, Slider, FileInput
|
|
26
|
+
from .progress import Progress, Stat
|
|
15
27
|
from .core import init, query, query_all, create_el
|
|
16
28
|
from .tokens import (
|
|
17
29
|
BG,
|
|
@@ -49,9 +61,17 @@ from .tokens import (
|
|
|
49
61
|
transition,
|
|
50
62
|
z_index,
|
|
51
63
|
layout,
|
|
64
|
+
Color,
|
|
65
|
+
Typography,
|
|
66
|
+
Spacing,
|
|
67
|
+
Radius,
|
|
68
|
+
Shadow,
|
|
69
|
+
Transition,
|
|
70
|
+
ZIndex,
|
|
71
|
+
Layout,
|
|
52
72
|
)
|
|
53
73
|
|
|
54
|
-
__version__ = "1.0.
|
|
74
|
+
__version__ = "1.0.6"
|
|
55
75
|
__all__ = [
|
|
56
76
|
"Toast",
|
|
57
77
|
"Toggle",
|
|
@@ -65,11 +85,40 @@ __all__ = [
|
|
|
65
85
|
"Search",
|
|
66
86
|
"SearchItem",
|
|
67
87
|
"Nav",
|
|
88
|
+
"Button",
|
|
89
|
+
"ButtonGroup",
|
|
90
|
+
"Card",
|
|
91
|
+
"CardIcon",
|
|
92
|
+
"Badge",
|
|
93
|
+
"Tag",
|
|
94
|
+
"Avatar",
|
|
95
|
+
"AvatarGroup",
|
|
96
|
+
"Alert",
|
|
97
|
+
"Spinner",
|
|
98
|
+
"Skeleton",
|
|
99
|
+
"Table",
|
|
100
|
+
"List",
|
|
101
|
+
"Tooltip",
|
|
102
|
+
"Header",
|
|
103
|
+
"Sidebar",
|
|
104
|
+
"Footer",
|
|
105
|
+
"Container",
|
|
106
|
+
"Divider",
|
|
107
|
+
"Section",
|
|
108
|
+
"Input",
|
|
109
|
+
"Textarea",
|
|
110
|
+
"FormField",
|
|
111
|
+
"Checkbox",
|
|
112
|
+
"Radio",
|
|
113
|
+
"Select",
|
|
114
|
+
"Slider",
|
|
115
|
+
"FileInput",
|
|
116
|
+
"Progress",
|
|
117
|
+
"Stat",
|
|
68
118
|
"init",
|
|
69
119
|
"query",
|
|
70
120
|
"query_all",
|
|
71
121
|
"create_el",
|
|
72
|
-
# Colors
|
|
73
122
|
"BG",
|
|
74
123
|
"SURFACE",
|
|
75
124
|
"SURFACE_2",
|
|
@@ -98,7 +147,14 @@ __all__ = [
|
|
|
98
147
|
"BORDER",
|
|
99
148
|
"BORDER_HOVER",
|
|
100
149
|
"BORDER_FOCUS",
|
|
101
|
-
|
|
150
|
+
"Color",
|
|
151
|
+
"Typography",
|
|
152
|
+
"Spacing",
|
|
153
|
+
"Radius",
|
|
154
|
+
"Shadow",
|
|
155
|
+
"Transition",
|
|
156
|
+
"ZIndex",
|
|
157
|
+
"Layout",
|
|
102
158
|
"typography",
|
|
103
159
|
"spacing",
|
|
104
160
|
"radius",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""CronixUI Alert Component"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from .core import create_el
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Alert:
|
|
8
|
+
"""Alert component for messages."""
|
|
9
|
+
|
|
10
|
+
VARIANTS = ("info", "success", "warning", "error")
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
message: str,
|
|
15
|
+
title: Optional[str] = None,
|
|
16
|
+
variant: str = "info",
|
|
17
|
+
dismissible: bool = False,
|
|
18
|
+
):
|
|
19
|
+
self.message = message
|
|
20
|
+
self.title = title
|
|
21
|
+
self.variant = variant if variant in self.VARIANTS else "info"
|
|
22
|
+
self.dismissible = dismissible
|
|
23
|
+
self.element = self._render()
|
|
24
|
+
|
|
25
|
+
def _render(self):
|
|
26
|
+
el = create_el("div", f"cn-alert cn-alert-{self.variant}")
|
|
27
|
+
|
|
28
|
+
icon_svg = self._get_icon_svg()
|
|
29
|
+
if icon_svg:
|
|
30
|
+
icon = create_el("span", "cn-alert-icon")
|
|
31
|
+
icon.innerHTML = icon_svg
|
|
32
|
+
el.appendChild(icon)
|
|
33
|
+
|
|
34
|
+
content = create_el("div", "cn-alert-content")
|
|
35
|
+
if self.title:
|
|
36
|
+
title_el = create_el("div", "cn-alert-title")
|
|
37
|
+
title_el.textContent = self.title
|
|
38
|
+
content.appendChild(title_el)
|
|
39
|
+
|
|
40
|
+
message_el = create_el("div", "cn-alert-message")
|
|
41
|
+
message_el.textContent = self.message
|
|
42
|
+
content.appendChild(message_el)
|
|
43
|
+
|
|
44
|
+
el.appendChild(content)
|
|
45
|
+
|
|
46
|
+
if self.dismissible:
|
|
47
|
+
close_btn = create_el("button", "cn-alert-close")
|
|
48
|
+
close_btn.innerHTML = "×"
|
|
49
|
+
close_btn.addEventListener("click", lambda: el.remove())
|
|
50
|
+
el.appendChild(close_btn)
|
|
51
|
+
|
|
52
|
+
return el
|
|
53
|
+
|
|
54
|
+
def _get_icon_svg(self):
|
|
55
|
+
icons = {
|
|
56
|
+
"info": '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>',
|
|
57
|
+
"success": '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
|
|
58
|
+
"warning": '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><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"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
|
|
59
|
+
"error": '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
|
|
60
|
+
}
|
|
61
|
+
return icons.get(self.variant)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""CronixUI Avatar Component"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from .core import create_el
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Avatar:
|
|
8
|
+
"""Avatar component for user images or initials."""
|
|
9
|
+
|
|
10
|
+
SIZES = ("sm", "md", "lg", "xl")
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
initials: Optional[str] = None,
|
|
15
|
+
image_url: Optional[str] = None,
|
|
16
|
+
size: str = "md",
|
|
17
|
+
):
|
|
18
|
+
self.initials = initials
|
|
19
|
+
self.image_url = image_url
|
|
20
|
+
self.size = size if size in self.SIZES else "md"
|
|
21
|
+
self.element = self._render()
|
|
22
|
+
|
|
23
|
+
def _render(self):
|
|
24
|
+
el = create_el("div", "cn-avatar")
|
|
25
|
+
if self.size != "md":
|
|
26
|
+
el.classList.add(f"cn-avatar-{self.size}")
|
|
27
|
+
|
|
28
|
+
if self.image_url:
|
|
29
|
+
img = create_el("img")
|
|
30
|
+
img.setAttribute("src", self.image_url)
|
|
31
|
+
img.setAttribute("alt", self.initials or "Avatar")
|
|
32
|
+
el.appendChild(img)
|
|
33
|
+
elif self.initials:
|
|
34
|
+
el.textContent = self.initials[:2].upper()
|
|
35
|
+
|
|
36
|
+
return el
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class AvatarGroup:
|
|
40
|
+
"""Group of overlapping avatars."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, *avatars: Avatar):
|
|
43
|
+
self.avatars = avatars
|
|
44
|
+
self.element = self._render()
|
|
45
|
+
|
|
46
|
+
def _render(self):
|
|
47
|
+
el = create_el("div", "cn-avatar-group")
|
|
48
|
+
for avatar in self.avatars:
|
|
49
|
+
el.appendChild(avatar.element)
|
|
50
|
+
return el
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""CronixUI Badge & Tag Components"""
|
|
2
|
+
|
|
3
|
+
from .core import create_el
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Badge:
|
|
7
|
+
"""Badge component for status indicators."""
|
|
8
|
+
|
|
9
|
+
VARIANTS = ("default", "accent", "success", "warning", "error", "info")
|
|
10
|
+
|
|
11
|
+
def __init__(self, text: str, variant: str = "default", solid: bool = False):
|
|
12
|
+
self.text = text
|
|
13
|
+
self.variant = variant if variant in self.VARIANTS else "default"
|
|
14
|
+
self.solid = solid
|
|
15
|
+
self.element = self._render()
|
|
16
|
+
|
|
17
|
+
def _render(self):
|
|
18
|
+
classes = f"cn-badge cn-badge-{self.variant}"
|
|
19
|
+
if self.solid:
|
|
20
|
+
classes += " cn-badge-solid"
|
|
21
|
+
el = create_el("span", classes)
|
|
22
|
+
el.textContent = self.text
|
|
23
|
+
return el
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Tag:
|
|
27
|
+
"""Tag component for labels."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, text: str, removable: bool = False, on_remove=None):
|
|
30
|
+
self.text = text
|
|
31
|
+
self.removable = removable
|
|
32
|
+
self.on_remove = on_remove
|
|
33
|
+
self.element = self._render()
|
|
34
|
+
|
|
35
|
+
def _render(self):
|
|
36
|
+
el = create_el("span", "cn-tag")
|
|
37
|
+
el.textContent = self.text
|
|
38
|
+
|
|
39
|
+
if self.removable:
|
|
40
|
+
remove_btn = create_el("span", "cn-tag-remove")
|
|
41
|
+
remove_btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 6L6 18M6 6l12 12"/></svg>'
|
|
42
|
+
if self.on_remove:
|
|
43
|
+
remove_btn.addEventListener("click", self.on_remove)
|
|
44
|
+
el.appendChild(remove_btn)
|
|
45
|
+
|
|
46
|
+
return el
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""CronixUI Button Component"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Callable
|
|
4
|
+
from .core import create_el
|
|
5
|
+
from .tokens import ACCENT, SURFACE_2, SURFACE_3, ERROR, SUCCESS, TEXT
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Button:
|
|
9
|
+
"""Button component with variants."""
|
|
10
|
+
|
|
11
|
+
VARIANTS = ("default", "primary", "ghost", "outline", "danger", "success")
|
|
12
|
+
SIZES = ("sm", "md", "lg")
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
text: str,
|
|
17
|
+
variant: str = "default",
|
|
18
|
+
size: str = "md",
|
|
19
|
+
icon: bool = False,
|
|
20
|
+
disabled: bool = False,
|
|
21
|
+
onclick: Optional[Callable] = None,
|
|
22
|
+
):
|
|
23
|
+
self.text = text
|
|
24
|
+
self.variant = variant if variant in self.VARIANTS else "default"
|
|
25
|
+
self.size = size if size in self.SIZES else "md"
|
|
26
|
+
self.icon = icon
|
|
27
|
+
self.disabled = disabled
|
|
28
|
+
self.onclick = onclick
|
|
29
|
+
self.element = self._render()
|
|
30
|
+
|
|
31
|
+
def _render(self):
|
|
32
|
+
el = create_el("button", f"cn-btn cn-btn-{self.variant}")
|
|
33
|
+
if self.size != "md":
|
|
34
|
+
el.classList.add(f"cn-btn-{self.size}")
|
|
35
|
+
if self.icon:
|
|
36
|
+
el.classList.add("cn-btn-icon")
|
|
37
|
+
if self.disabled:
|
|
38
|
+
el.setAttribute("disabled", "")
|
|
39
|
+
el.textContent = self.text
|
|
40
|
+
if self.onclick:
|
|
41
|
+
el.addEventListener("click", self.onclick)
|
|
42
|
+
return el
|
|
43
|
+
|
|
44
|
+
def disable(self):
|
|
45
|
+
self.disabled = True
|
|
46
|
+
self.element.setAttribute("disabled", "")
|
|
47
|
+
|
|
48
|
+
def enable(self):
|
|
49
|
+
self.disabled = False
|
|
50
|
+
self.element.removeAttribute("disabled")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ButtonGroup:
|
|
54
|
+
"""Button group component."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, *buttons: Button):
|
|
57
|
+
self.buttons = buttons
|
|
58
|
+
self.element = self._render()
|
|
59
|
+
|
|
60
|
+
def _render(self):
|
|
61
|
+
el = create_el("div", "cn-btn-group")
|
|
62
|
+
for btn in self.buttons:
|
|
63
|
+
el.appendChild(btn.element)
|
|
64
|
+
return el
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""CronixUI Card Component"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from .core import create_el
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Card:
|
|
8
|
+
"""Card container component."""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
title: Optional[str] = None,
|
|
13
|
+
subtitle: Optional[str] = None,
|
|
14
|
+
clickable: bool = False,
|
|
15
|
+
):
|
|
16
|
+
self.title = title
|
|
17
|
+
self.subtitle = subtitle
|
|
18
|
+
self.clickable = clickable
|
|
19
|
+
self.element = self._render()
|
|
20
|
+
|
|
21
|
+
def _render(self):
|
|
22
|
+
el = create_el("div", "cn-card")
|
|
23
|
+
if self.clickable:
|
|
24
|
+
el.classList.add("cn-card-clickable")
|
|
25
|
+
|
|
26
|
+
if self.title or self.subtitle:
|
|
27
|
+
header = create_el("div", "cn-card-header")
|
|
28
|
+
if self.title:
|
|
29
|
+
title_el = create_el("h3", "cn-card-title")
|
|
30
|
+
title_el.textContent = self.title
|
|
31
|
+
header.appendChild(title_el)
|
|
32
|
+
el.appendChild(header)
|
|
33
|
+
|
|
34
|
+
if self.subtitle:
|
|
35
|
+
subtitle_el = create_el("p", "cn-card-subtitle")
|
|
36
|
+
subtitle_el.textContent = self.subtitle
|
|
37
|
+
header.appendChild(subtitle_el)
|
|
38
|
+
|
|
39
|
+
return el
|
|
40
|
+
|
|
41
|
+
def set_body(self, content: str):
|
|
42
|
+
body = create_el("div", "cn-card-body")
|
|
43
|
+
body.innerHTML = content
|
|
44
|
+
self.element.appendChild(body)
|
|
45
|
+
|
|
46
|
+
def set_footer(self, content: str):
|
|
47
|
+
footer = create_el("div", "cn-card-footer")
|
|
48
|
+
footer.innerHTML = content
|
|
49
|
+
self.element.appendChild(footer)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CardIcon:
|
|
53
|
+
"""Card with icon."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, icon_svg: str):
|
|
56
|
+
self.icon_svg = icon_svg
|
|
57
|
+
self.element = self._render()
|
|
58
|
+
|
|
59
|
+
def _render(self):
|
|
60
|
+
el = create_el("div", "cn-card-icon")
|
|
61
|
+
el.innerHTML = self.icon_svg
|
|
62
|
+
return el
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""CronixUI Form Components"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Callable
|
|
4
|
+
from .core import create_el
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Input:
|
|
8
|
+
"""Input component."""
|
|
9
|
+
|
|
10
|
+
SIZES = ("sm", "md", "lg")
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
placeholder: str = "",
|
|
15
|
+
size: str = "md",
|
|
16
|
+
error: bool = False,
|
|
17
|
+
disabled: bool = False,
|
|
18
|
+
icon: Optional[str] = None,
|
|
19
|
+
):
|
|
20
|
+
self.placeholder = placeholder
|
|
21
|
+
self.size = size if size in self.SIZES else "md"
|
|
22
|
+
self.error = error
|
|
23
|
+
self.disabled = disabled
|
|
24
|
+
self.icon = icon
|
|
25
|
+
self.element = self._render()
|
|
26
|
+
|
|
27
|
+
def _render(self):
|
|
28
|
+
if self.icon:
|
|
29
|
+
wrapper = create_el("div", "cn-input-icon-wrapper")
|
|
30
|
+
input_el = create_el("input", "cn-input")
|
|
31
|
+
input_el.setAttribute("placeholder", self.placeholder)
|
|
32
|
+
if self.disabled:
|
|
33
|
+
input_el.setAttribute("disabled", "")
|
|
34
|
+
if self.error:
|
|
35
|
+
input_el.classList.add("cn-input-error")
|
|
36
|
+
|
|
37
|
+
icon_el = create_el("span", "cn-input-icon")
|
|
38
|
+
icon_el.innerHTML = self.icon
|
|
39
|
+
wrapper.appendChild(icon_el)
|
|
40
|
+
wrapper.appendChild(input_el)
|
|
41
|
+
return wrapper
|
|
42
|
+
|
|
43
|
+
el = create_el("input", "cn-input")
|
|
44
|
+
el.setAttribute("placeholder", self.placeholder)
|
|
45
|
+
if self.disabled:
|
|
46
|
+
el.setAttribute("disabled", "")
|
|
47
|
+
if self.error:
|
|
48
|
+
el.classList.add("cn-input-error")
|
|
49
|
+
if self.size != "md":
|
|
50
|
+
el.classList.add(f"cn-input-{self.size}")
|
|
51
|
+
return el
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Textarea:
|
|
55
|
+
"""Textarea component."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, placeholder: str = "", rows: int = 4):
|
|
58
|
+
self.placeholder = placeholder
|
|
59
|
+
self.rows = rows
|
|
60
|
+
self.element = self._render()
|
|
61
|
+
|
|
62
|
+
def _render(self):
|
|
63
|
+
el = create_el("textarea", "cn-input cn-textarea")
|
|
64
|
+
el.setAttribute("placeholder", self.placeholder)
|
|
65
|
+
el.setAttribute("rows", str(self.rows))
|
|
66
|
+
return el
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class FormField:
|
|
70
|
+
"""Form field with label and error."""
|
|
71
|
+
|
|
72
|
+
def __init__(self, label: str, input_el, error: str = None, help_text: str = None):
|
|
73
|
+
self.label = label
|
|
74
|
+
self.input = input_el
|
|
75
|
+
self.error = error
|
|
76
|
+
self.help_text = help_text
|
|
77
|
+
self.element = self._render()
|
|
78
|
+
|
|
79
|
+
def _render(self):
|
|
80
|
+
group = create_el("div", "cn-form-group")
|
|
81
|
+
|
|
82
|
+
label_el = create_el("label", "cn-form-label")
|
|
83
|
+
label_el.textContent = self.label
|
|
84
|
+
group.appendChild(label_el)
|
|
85
|
+
|
|
86
|
+
if hasattr(self.input, "element"):
|
|
87
|
+
group.appendChild(self.input.element)
|
|
88
|
+
else:
|
|
89
|
+
group.appendChild(self.input)
|
|
90
|
+
|
|
91
|
+
if self.error:
|
|
92
|
+
error_el = create_el("span", "cn-form-error")
|
|
93
|
+
error_el.textContent = self.error
|
|
94
|
+
group.appendChild(error_el)
|
|
95
|
+
|
|
96
|
+
if self.help_text:
|
|
97
|
+
help_el = create_el("span", "cn-form-help")
|
|
98
|
+
help_el.textContent = self.help_text
|
|
99
|
+
group.appendChild(help_el)
|
|
100
|
+
|
|
101
|
+
return group
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class Checkbox:
|
|
105
|
+
"""Checkbox component."""
|
|
106
|
+
|
|
107
|
+
def __init__(self, label: str, checked: bool = False, disabled: bool = False):
|
|
108
|
+
self.label = label
|
|
109
|
+
self.checked = checked
|
|
110
|
+
self.disabled = disabled
|
|
111
|
+
self.element = self._render()
|
|
112
|
+
|
|
113
|
+
def _render(self):
|
|
114
|
+
el = create_el("label", "cn-checkbox")
|
|
115
|
+
if self.disabled:
|
|
116
|
+
el.classList.add("disabled")
|
|
117
|
+
|
|
118
|
+
input_el = create_el("input")
|
|
119
|
+
input_el.setAttribute("type", "checkbox")
|
|
120
|
+
if self.checked:
|
|
121
|
+
input_el.setAttribute("checked", "")
|
|
122
|
+
if self.disabled:
|
|
123
|
+
input_el.setAttribute("disabled", "")
|
|
124
|
+
|
|
125
|
+
box = create_el("span", "cn-checkbox-box")
|
|
126
|
+
label_el = create_el("span", "cn-checkbox-label")
|
|
127
|
+
label_el.textContent = self.label
|
|
128
|
+
|
|
129
|
+
el.appendChild(input_el)
|
|
130
|
+
el.appendChild(box)
|
|
131
|
+
el.appendChild(label_el)
|
|
132
|
+
return el
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class Radio:
|
|
136
|
+
"""Radio button component."""
|
|
137
|
+
|
|
138
|
+
def __init__(self, name: str, options: list, selected: str = None):
|
|
139
|
+
self.name = name
|
|
140
|
+
self.options = options
|
|
141
|
+
self.selected = selected
|
|
142
|
+
self.element = self._render()
|
|
143
|
+
|
|
144
|
+
def _render(self):
|
|
145
|
+
container = create_el("div")
|
|
146
|
+
for option in self.options:
|
|
147
|
+
if isinstance(option, tuple):
|
|
148
|
+
value, label = option
|
|
149
|
+
else:
|
|
150
|
+
value = label = option
|
|
151
|
+
|
|
152
|
+
el = create_el("label", "cn-radio")
|
|
153
|
+
input_el = create_el("input")
|
|
154
|
+
input_el.setAttribute("type", "radio")
|
|
155
|
+
input_el.setAttribute("name", self.name)
|
|
156
|
+
input_el.setAttribute("value", value)
|
|
157
|
+
if value == self.selected:
|
|
158
|
+
input_el.setAttribute("checked", "")
|
|
159
|
+
|
|
160
|
+
box = create_el("span", "cn-radio-box")
|
|
161
|
+
label_el = create_el("span", "cn-radio-label")
|
|
162
|
+
label_el.textContent = label
|
|
163
|
+
|
|
164
|
+
el.appendChild(input_el)
|
|
165
|
+
el.appendChild(box)
|
|
166
|
+
el.appendChild(label_el)
|
|
167
|
+
container.appendChild(el)
|
|
168
|
+
|
|
169
|
+
return container
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class Select:
|
|
173
|
+
"""Select dropdown component."""
|
|
174
|
+
|
|
175
|
+
def __init__(self, options: list, placeholder: str = ""):
|
|
176
|
+
self.options = options
|
|
177
|
+
self.placeholder = placeholder
|
|
178
|
+
self.element = self._render()
|
|
179
|
+
|
|
180
|
+
def _render(self):
|
|
181
|
+
wrapper = create_el("div", "cn-select-wrapper")
|
|
182
|
+
select = create_el("select", "cn-select")
|
|
183
|
+
|
|
184
|
+
if self.placeholder:
|
|
185
|
+
placeholder_option = create_el("option")
|
|
186
|
+
placeholder_option.setAttribute("value", "")
|
|
187
|
+
placeholder_option.setAttribute("disabled", "")
|
|
188
|
+
placeholder_option.setAttribute("selected", "")
|
|
189
|
+
placeholder_option.textContent = self.placeholder
|
|
190
|
+
select.appendChild(placeholder_option)
|
|
191
|
+
|
|
192
|
+
for option in self.options:
|
|
193
|
+
if isinstance(option, tuple):
|
|
194
|
+
value, label = option
|
|
195
|
+
else:
|
|
196
|
+
value = label = option
|
|
197
|
+
|
|
198
|
+
opt = create_el("option")
|
|
199
|
+
opt.setAttribute("value", value)
|
|
200
|
+
opt.textContent = label
|
|
201
|
+
select.appendChild(opt)
|
|
202
|
+
|
|
203
|
+
wrapper.appendChild(select)
|
|
204
|
+
return wrapper
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class Slider:
|
|
208
|
+
"""Slider component."""
|
|
209
|
+
|
|
210
|
+
def __init__(self, min: float = 0, max: float = 100, value: float = 50):
|
|
211
|
+
self.min = min
|
|
212
|
+
self.max = max
|
|
213
|
+
self.value = value
|
|
214
|
+
self.element = self._render()
|
|
215
|
+
|
|
216
|
+
def _render(self):
|
|
217
|
+
el = create_el("input", "cn-slider")
|
|
218
|
+
el.setAttribute("type", "range")
|
|
219
|
+
el.setAttribute("min", str(self.min))
|
|
220
|
+
el.setAttribute("max", str(self.max))
|
|
221
|
+
el.setAttribute("value", str(self.value))
|
|
222
|
+
return el
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class FileInput:
|
|
226
|
+
"""File input component."""
|
|
227
|
+
|
|
228
|
+
def __init__(self, accept: str = "", multiple: bool = False):
|
|
229
|
+
self.accept = accept
|
|
230
|
+
self.multiple = multiple
|
|
231
|
+
self.element = self._render()
|
|
232
|
+
|
|
233
|
+
def _render(self):
|
|
234
|
+
wrapper = create_el("div", "cn-file-input")
|
|
235
|
+
|
|
236
|
+
input_el = create_el("input")
|
|
237
|
+
input_el.setAttribute("type", "file")
|
|
238
|
+
if self.accept:
|
|
239
|
+
input_el.setAttribute("accept", self.accept)
|
|
240
|
+
if self.multiple:
|
|
241
|
+
input_el.setAttribute("multiple", "")
|
|
242
|
+
|
|
243
|
+
label = create_el("div", "cn-file-input-label")
|
|
244
|
+
icon = create_el("div", "cn-file-input-icon")
|
|
245
|
+
icon.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>'
|
|
246
|
+
|
|
247
|
+
text = create_el("div", "cn-file-input-text")
|
|
248
|
+
text.innerHTML = "Drag and drop or <span>browse</span>"
|
|
249
|
+
|
|
250
|
+
label.appendChild(icon)
|
|
251
|
+
label.appendChild(text)
|
|
252
|
+
wrapper.appendChild(input_el)
|
|
253
|
+
wrapper.appendChild(label)
|
|
254
|
+
|
|
255
|
+
return wrapper
|