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.
Files changed (129) hide show
  1. package/README.md +35 -5
  2. package/package.json +19 -5
  3. package/packages/go/cronixui/cronixui.go +784 -237
  4. package/packages/go/cronixui/go.mod +32 -0
  5. package/packages/go/cronixui/go.sum +666 -0
  6. package/packages/python/cronixui/__init__.py +59 -3
  7. package/packages/python/cronixui/alert.py +61 -0
  8. package/packages/python/cronixui/avatar.py +50 -0
  9. package/packages/python/cronixui/badge.py +46 -0
  10. package/packages/python/cronixui/button.py +64 -0
  11. package/packages/python/cronixui/card.py +62 -0
  12. package/packages/python/cronixui/form.py +255 -0
  13. package/packages/python/cronixui/layout.py +143 -0
  14. package/packages/python/cronixui/list.py +51 -0
  15. package/packages/python/cronixui/loading.py +36 -0
  16. package/packages/python/cronixui/progress.py +90 -0
  17. package/packages/python/cronixui/table.py +48 -0
  18. package/packages/python/cronixui/tooltip.py +28 -0
  19. package/packages/react/src/components/Accordion.tsx +82 -0
  20. package/packages/react/src/components/Alert.tsx +80 -0
  21. package/packages/react/src/components/Avatar.tsx +54 -0
  22. package/packages/react/src/components/Badge.tsx +32 -0
  23. package/packages/react/src/components/Breadcrumb.tsx +50 -0
  24. package/packages/react/src/components/Button.tsx +47 -0
  25. package/packages/react/src/components/Card.tsx +69 -0
  26. package/packages/react/src/components/Checkbox.tsx +30 -0
  27. package/packages/react/src/components/CommandPalette.tsx +131 -0
  28. package/packages/react/src/components/Container.tsx +26 -0
  29. package/packages/react/src/components/Dropdown.tsx +88 -0
  30. package/packages/react/src/components/FileInput.tsx +86 -0
  31. package/packages/react/src/components/Footer.tsx +36 -0
  32. package/packages/react/src/components/FormGroup.tsx +36 -0
  33. package/packages/react/src/components/Header.tsx +29 -0
  34. package/packages/react/src/components/Input.tsx +54 -0
  35. package/packages/react/src/components/List.tsx +55 -0
  36. package/packages/react/src/components/Modal.tsx +89 -0
  37. package/packages/react/src/components/Nav.tsx +63 -0
  38. package/packages/react/src/components/Pagination.tsx +107 -0
  39. package/packages/react/src/components/Progress.tsx +49 -0
  40. package/packages/react/src/components/Radio.tsx +64 -0
  41. package/packages/react/src/components/Search.tsx +95 -0
  42. package/packages/react/src/components/Select.tsx +41 -0
  43. package/packages/react/src/components/Sidebar.tsx +64 -0
  44. package/packages/react/src/components/Skeleton.tsx +39 -0
  45. package/packages/react/src/components/Slider.tsx +32 -0
  46. package/packages/react/src/components/Spinner.tsx +24 -0
  47. package/packages/react/src/components/Stack.tsx +69 -0
  48. package/packages/react/src/components/Stat.tsx +35 -0
  49. package/packages/react/src/components/Table.tsx +90 -0
  50. package/packages/react/src/components/Tabs.tsx +85 -0
  51. package/packages/react/src/components/Tag.tsx +30 -0
  52. package/packages/react/src/components/Textarea.tsx +21 -0
  53. package/packages/react/src/components/Toast.tsx +134 -0
  54. package/packages/react/src/components/Toggle.tsx +58 -0
  55. package/packages/react/src/components/Tooltip.tsx +31 -0
  56. package/packages/react/src/components/Typography.tsx +66 -0
  57. package/packages/react/src/index.ts +40 -0
  58. package/packages/react/src/styles.css +2039 -0
  59. package/packages/react/src/tokens/index.ts +94 -0
  60. package/packages/rust/cronixui/src/colors.rs +135 -0
  61. package/packages/rust/cronixui/src/components/accordion.rs +47 -0
  62. package/packages/rust/cronixui/src/components/alert.rs +95 -0
  63. package/packages/rust/cronixui/src/components/avatar.rs +85 -0
  64. package/packages/rust/cronixui/src/components/badge.rs +35 -0
  65. package/packages/rust/cronixui/src/components/breadcrumb.rs +58 -0
  66. package/packages/rust/cronixui/src/components/button.rs +70 -0
  67. package/packages/rust/cronixui/src/components/card.rs +259 -0
  68. package/packages/rust/cronixui/src/components/command_palette.rs +254 -0
  69. package/packages/rust/cronixui/src/components/dropdown.rs +179 -0
  70. package/packages/rust/cronixui/src/components/file_input.rs +74 -0
  71. package/packages/rust/cronixui/src/components/input.rs +21 -0
  72. package/packages/rust/cronixui/src/components/list.rs +38 -0
  73. package/packages/rust/cronixui/src/components/mod.rs +51 -0
  74. package/packages/rust/cronixui/src/{modal.rs → components/modal.rs} +15 -1
  75. package/packages/rust/cronixui/src/components/nav.rs +19 -0
  76. package/packages/rust/cronixui/src/{pagination.rs → components/pagination.rs} +14 -13
  77. package/packages/rust/cronixui/src/components/progress.rs +50 -0
  78. package/packages/rust/cronixui/src/components/search.rs +185 -0
  79. package/packages/rust/cronixui/src/components/skeleton.rs +63 -0
  80. package/packages/rust/cronixui/src/components/spinner.rs +21 -0
  81. package/packages/rust/cronixui/src/components/table.rs +56 -0
  82. package/packages/rust/cronixui/src/components/tabs.rs +43 -0
  83. package/packages/rust/cronixui/src/components/toast.rs +69 -0
  84. package/packages/rust/cronixui/src/{toggle.rs → components/toggle.rs} +7 -5
  85. package/packages/rust/cronixui/src/components/tooltip.rs +11 -0
  86. package/packages/rust/cronixui/src/lib.rs +111 -64
  87. package/packages/rust/cronixui/src/tokens.rs +97 -127
  88. package/packages/web/src/variables.css +81 -81
  89. package/packages/go/cronixui/tokens.go +0 -129
  90. package/packages/python/cronixui/pyproject.toml +0 -11
  91. package/packages/react/src/components/Accordion.jsx +0 -50
  92. package/packages/react/src/components/Alert.jsx +0 -62
  93. package/packages/react/src/components/Avatar.jsx +0 -34
  94. package/packages/react/src/components/Badge.jsx +0 -15
  95. package/packages/react/src/components/Breadcrumb.jsx +0 -27
  96. package/packages/react/src/components/Button.jsx +0 -21
  97. package/packages/react/src/components/Card.jsx +0 -23
  98. package/packages/react/src/components/Checkbox.jsx +0 -27
  99. package/packages/react/src/components/CommandPalette.jsx +0 -93
  100. package/packages/react/src/components/Dropdown.jsx +0 -48
  101. package/packages/react/src/components/FileInput.jsx +0 -44
  102. package/packages/react/src/components/Input.jsx +0 -22
  103. package/packages/react/src/components/List.jsx +0 -29
  104. package/packages/react/src/components/Modal.jsx +0 -65
  105. package/packages/react/src/components/Nav.jsx +0 -50
  106. package/packages/react/src/components/Pagination.jsx +0 -81
  107. package/packages/react/src/components/Progress.jsx +0 -23
  108. package/packages/react/src/components/Radio.jsx +0 -50
  109. package/packages/react/src/components/Search.jsx +0 -70
  110. package/packages/react/src/components/Select.jsx +0 -33
  111. package/packages/react/src/components/Skeleton.jsx +0 -15
  112. package/packages/react/src/components/Slider.jsx +0 -29
  113. package/packages/react/src/components/Spinner.jsx +0 -5
  114. package/packages/react/src/components/Stat.jsx +0 -19
  115. package/packages/react/src/components/Table.jsx +0 -48
  116. package/packages/react/src/components/Tabs.jsx +0 -65
  117. package/packages/react/src/components/Tag.jsx +0 -19
  118. package/packages/react/src/components/Textarea.jsx +0 -17
  119. package/packages/react/src/components/Toast.jsx +0 -78
  120. package/packages/react/src/components/Toggle.jsx +0 -34
  121. package/packages/react/src/components/Tooltip.jsx +0 -12
  122. package/packages/react/src/index.d.ts +0 -103
  123. package/packages/react/src/index.js +0 -33
  124. package/packages/rust/cronixui/src/accordion.rs +0 -49
  125. package/packages/rust/cronixui/src/command_palette.rs +0 -62
  126. package/packages/rust/cronixui/src/dropdown.rs +0 -31
  127. package/packages/rust/cronixui/src/search.rs +0 -49
  128. package/packages/rust/cronixui/src/tabs.rs +0 -23
  129. 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;