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.
Files changed (76) hide show
  1. package/README.md +1 -1
  2. package/package.json +71 -71
  3. package/packages/flutter/.qwen/settings.json +7 -0
  4. package/packages/flutter/pubspec.yaml +20 -20
  5. package/packages/go/cronixui/cronixui.go +926 -926
  6. package/packages/python/README.md +142 -0
  7. package/packages/python/cronixui/__init__.py +15 -6
  8. package/packages/python/cronixui/__pycache__/__init__.cpython-314.pyc +0 -0
  9. package/packages/python/cronixui/__pycache__/accordion.cpython-314.pyc +0 -0
  10. package/packages/python/cronixui/__pycache__/alert.cpython-314.pyc +0 -0
  11. package/packages/python/cronixui/__pycache__/avatar.cpython-314.pyc +0 -0
  12. package/packages/python/cronixui/__pycache__/badge.cpython-314.pyc +0 -0
  13. package/packages/python/cronixui/__pycache__/button.cpython-314.pyc +0 -0
  14. package/packages/python/cronixui/__pycache__/card.cpython-314.pyc +0 -0
  15. package/packages/python/cronixui/__pycache__/command_palette.cpython-314.pyc +0 -0
  16. package/packages/python/cronixui/__pycache__/core.cpython-314.pyc +0 -0
  17. package/packages/python/cronixui/__pycache__/dropdown.cpython-314.pyc +0 -0
  18. package/packages/python/cronixui/__pycache__/form.cpython-314.pyc +0 -0
  19. package/packages/python/cronixui/__pycache__/layout.cpython-314.pyc +0 -0
  20. package/packages/python/cronixui/__pycache__/list.cpython-314.pyc +0 -0
  21. package/packages/python/cronixui/__pycache__/loading.cpython-314.pyc +0 -0
  22. package/packages/python/cronixui/__pycache__/modal.cpython-314.pyc +0 -0
  23. package/packages/python/cronixui/__pycache__/nav.cpython-314.pyc +0 -0
  24. package/packages/python/cronixui/__pycache__/pagination.cpython-314.pyc +0 -0
  25. package/packages/python/cronixui/__pycache__/progress.cpython-314.pyc +0 -0
  26. package/packages/python/cronixui/__pycache__/search.cpython-314.pyc +0 -0
  27. package/packages/python/cronixui/__pycache__/table.cpython-314.pyc +0 -0
  28. package/packages/python/cronixui/__pycache__/tabs.cpython-314.pyc +0 -0
  29. package/packages/python/cronixui/__pycache__/toast.cpython-314.pyc +0 -0
  30. package/packages/python/cronixui/__pycache__/toggle.cpython-314.pyc +0 -0
  31. package/packages/python/cronixui/__pycache__/tokens.cpython-314.pyc +0 -0
  32. package/packages/python/cronixui/__pycache__/tooltip.cpython-314.pyc +0 -0
  33. package/packages/python/cronixui/alert.py +119 -36
  34. package/packages/python/cronixui/avatar.py +129 -22
  35. package/packages/python/cronixui/badge.py +161 -24
  36. package/packages/python/cronixui/button.py +96 -27
  37. package/packages/python/cronixui/card.py +206 -33
  38. package/packages/python/cronixui/core.py +212 -23
  39. package/packages/python/cronixui/form.py +552 -141
  40. package/packages/python/cronixui/layout.py +358 -96
  41. package/packages/python/cronixui/list.py +140 -37
  42. package/packages/python/cronixui/loading.py +107 -17
  43. package/packages/python/cronixui/progress.py +189 -47
  44. package/packages/python/cronixui/table.py +118 -31
  45. package/packages/python/cronixui/tooltip.py +117 -15
  46. package/packages/react/src/components/Accordion.tsx +82 -82
  47. package/packages/react/src/components/Button.tsx +47 -47
  48. package/packages/react/src/components/Card.tsx +69 -69
  49. package/packages/react/src/components/CommandPalette.tsx +131 -131
  50. package/packages/react/src/components/Dropdown.tsx +88 -88
  51. package/packages/react/src/components/FileInput.tsx +86 -86
  52. package/packages/react/src/components/FormGroup.tsx +36 -36
  53. package/packages/react/src/components/List.tsx +55 -55
  54. package/packages/react/src/components/Pagination.tsx +107 -107
  55. package/packages/react/src/components/Progress.tsx +49 -49
  56. package/packages/react/src/components/Search.tsx +95 -95
  57. package/packages/react/src/components/Sidebar.tsx +64 -64
  58. package/packages/react/src/components/Stack.tsx +69 -69
  59. package/packages/react/src/components/Table.tsx +90 -90
  60. package/packages/react/src/components/Toast.tsx +134 -134
  61. package/packages/react/src/components/Typography.tsx +66 -66
  62. package/packages/react/src/index.ts +40 -40
  63. package/packages/react/src/styles.css +2039 -2039
  64. package/packages/rust/cronixui/src/components/avatar.rs +85 -85
  65. package/packages/rust/cronixui/src/components/breadcrumb.rs +58 -58
  66. package/packages/rust/cronixui/src/components/card.rs +259 -259
  67. package/packages/rust/cronixui/src/components/command_palette.rs +254 -254
  68. package/packages/rust/cronixui/src/components/dropdown.rs +179 -179
  69. package/packages/rust/cronixui/src/components/file_input.rs +74 -74
  70. package/packages/rust/cronixui/src/components/mod.rs +51 -51
  71. package/packages/rust/cronixui/src/components/search.rs +185 -185
  72. package/packages/rust/cronixui/src/components/skeleton.rs +63 -63
  73. package/packages/rust/cronixui/src/components/table.rs +56 -56
  74. package/packages/rust/cronixui/src/lib.rs +128 -128
  75. package/packages/web/dist/cronixui.css +97 -93
  76. package/packages/web/dist/cronixui.min.css +1 -1
@@ -1,48 +1,135 @@
1
- """CronixUI Table Component"""
1
+ """CronixUI Table Component.
2
2
 
3
- from typing import List, Optional
4
- from .core import create_el
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.element = self._render()
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
- def _render(self):
22
- wrapper = create_el("div", "cn-table-wrapper")
23
- table = create_el("table", "cn-table")
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
- 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
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("&", "&amp;")
131
+ .replace("<", "&lt;")
132
+ .replace(">", "&gt;")
133
+ .replace('"', "&quot;")
134
+ .replace("'", "&#x27;")
135
+ )
@@ -1,28 +1,130 @@
1
- """CronixUI Tooltip Component"""
1
+ """CronixUI Tooltip Component.
2
2
 
3
- from .core import create_el
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 if position in self.POSITIONS else "top"
14
- self.element = self._render()
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
- def _render(self):
17
- el = create_el("div", "cn-tooltip")
101
+ Args:
102
+ element_html: HTML string of the element to wrap
18
103
 
19
- tooltip_content = create_el("div", "cn-tooltip-content")
20
- tooltip_content.textContent = self.content
104
+ Returns:
105
+ HTML string with the element wrapped in a tooltip wrapper
21
106
 
22
- el.appendChild(tooltip_content)
23
- return el
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
- def wrap(self, element):
26
- """Wrap an element with tooltip."""
27
- self.element.insertBefore(element, self.element.firstChild)
28
- return self.element
121
+ @staticmethod
122
+ def _esc(text: str) -> str:
123
+ """Escape HTML special characters."""
124
+ return (
125
+ text.replace("&", "&amp;")
126
+ .replace("<", "&lt;")
127
+ .replace(">", "&gt;")
128
+ .replace('"', "&quot;")
129
+ .replace("'", "&#x27;")
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;