cronixui 1.0.6 → 1.1.1

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 (169) hide show
  1. package/README.md +20 -5
  2. package/package.json +20 -5
  3. package/packages/flutter/lib/cronixui.dart +41 -0
  4. package/packages/flutter/lib/src/tokens/colors.dart +34 -0
  5. package/packages/flutter/lib/src/tokens/spacing.dart +54 -0
  6. package/packages/flutter/lib/src/tokens/theme.dart +174 -0
  7. package/packages/flutter/lib/src/widgets/cn_accordion.dart +254 -0
  8. package/packages/flutter/lib/src/widgets/cn_alert.dart +137 -0
  9. package/packages/flutter/lib/src/widgets/cn_avatar.dart +98 -0
  10. package/packages/flutter/lib/src/widgets/cn_badge.dart +80 -0
  11. package/packages/flutter/lib/src/widgets/cn_breadcrumb.dart +88 -0
  12. package/packages/flutter/lib/src/widgets/cn_button.dart +137 -0
  13. package/packages/flutter/lib/src/widgets/cn_card.dart +99 -0
  14. package/packages/flutter/lib/src/widgets/cn_checkbox.dart +77 -0
  15. package/packages/flutter/lib/src/widgets/cn_command_palette.dart +299 -0
  16. package/packages/flutter/lib/src/widgets/cn_container.dart +131 -0
  17. package/packages/flutter/lib/src/widgets/cn_dropdown.dart +149 -0
  18. package/packages/flutter/lib/src/widgets/cn_file_input.dart +113 -0
  19. package/packages/flutter/lib/src/widgets/cn_footer.dart +108 -0
  20. package/packages/flutter/lib/src/widgets/cn_header.dart +173 -0
  21. package/packages/flutter/lib/src/widgets/cn_input.dart +142 -0
  22. package/packages/flutter/lib/src/widgets/cn_list.dart +150 -0
  23. package/packages/flutter/lib/src/widgets/cn_modal.dart +213 -0
  24. package/packages/flutter/lib/src/widgets/cn_nav.dart +157 -0
  25. package/packages/flutter/lib/src/widgets/cn_pagination.dart +193 -0
  26. package/packages/flutter/lib/src/widgets/cn_progress.dart +146 -0
  27. package/packages/flutter/lib/src/widgets/cn_radio.dart +133 -0
  28. package/packages/flutter/lib/src/widgets/cn_search.dart +183 -0
  29. package/packages/flutter/lib/src/widgets/cn_select.dart +244 -0
  30. package/packages/flutter/lib/src/widgets/cn_sidebar.dart +207 -0
  31. package/packages/flutter/lib/src/widgets/cn_skeleton.dart +136 -0
  32. package/packages/flutter/lib/src/widgets/cn_slider.dart +141 -0
  33. package/packages/flutter/lib/src/widgets/cn_spinner.dart +85 -0
  34. package/packages/flutter/lib/src/widgets/cn_stat.dart +135 -0
  35. package/packages/flutter/lib/src/widgets/cn_table.dart +136 -0
  36. package/packages/flutter/lib/src/widgets/cn_tabs.dart +229 -0
  37. package/packages/flutter/lib/src/widgets/cn_tag.dart +185 -0
  38. package/packages/flutter/lib/src/widgets/cn_textarea.dart +143 -0
  39. package/packages/flutter/lib/src/widgets/cn_toast.dart +121 -0
  40. package/packages/flutter/lib/src/widgets/cn_toggle.dart +78 -0
  41. package/packages/flutter/lib/src/widgets/cn_tooltip.dart +118 -0
  42. package/packages/flutter/pubspec.yaml +20 -0
  43. package/packages/go/cronixui/cronixui.go +784 -237
  44. package/packages/go/cronixui/go.mod +32 -0
  45. package/packages/go/cronixui/go.sum +666 -0
  46. package/packages/python/cronixui/__init__.py +59 -3
  47. package/packages/python/cronixui/alert.py +61 -0
  48. package/packages/python/cronixui/avatar.py +50 -0
  49. package/packages/python/cronixui/badge.py +46 -0
  50. package/packages/python/cronixui/button.py +64 -0
  51. package/packages/python/cronixui/card.py +62 -0
  52. package/packages/python/cronixui/form.py +255 -0
  53. package/packages/python/cronixui/layout.py +143 -0
  54. package/packages/python/cronixui/list.py +51 -0
  55. package/packages/python/cronixui/loading.py +36 -0
  56. package/packages/python/cronixui/progress.py +90 -0
  57. package/packages/python/cronixui/table.py +48 -0
  58. package/packages/python/cronixui/tooltip.py +28 -0
  59. package/packages/react/src/components/Accordion.tsx +82 -0
  60. package/packages/react/src/components/Alert.tsx +80 -0
  61. package/packages/react/src/components/Avatar.tsx +54 -0
  62. package/packages/react/src/components/Badge.tsx +32 -0
  63. package/packages/react/src/components/Breadcrumb.tsx +50 -0
  64. package/packages/react/src/components/Button.tsx +47 -0
  65. package/packages/react/src/components/Card.tsx +69 -0
  66. package/packages/react/src/components/Checkbox.tsx +30 -0
  67. package/packages/react/src/components/CommandPalette.tsx +131 -0
  68. package/packages/react/src/components/Container.tsx +26 -0
  69. package/packages/react/src/components/Dropdown.tsx +88 -0
  70. package/packages/react/src/components/FileInput.tsx +86 -0
  71. package/packages/react/src/components/Footer.tsx +36 -0
  72. package/packages/react/src/components/FormGroup.tsx +36 -0
  73. package/packages/react/src/components/Header.tsx +29 -0
  74. package/packages/react/src/components/Input.tsx +54 -0
  75. package/packages/react/src/components/List.tsx +55 -0
  76. package/packages/react/src/components/Modal.tsx +89 -0
  77. package/packages/react/src/components/Nav.tsx +63 -0
  78. package/packages/react/src/components/Pagination.tsx +107 -0
  79. package/packages/react/src/components/Progress.tsx +49 -0
  80. package/packages/react/src/components/Radio.tsx +64 -0
  81. package/packages/react/src/components/Search.tsx +95 -0
  82. package/packages/react/src/components/Select.tsx +41 -0
  83. package/packages/react/src/components/Sidebar.tsx +64 -0
  84. package/packages/react/src/components/Skeleton.tsx +39 -0
  85. package/packages/react/src/components/Slider.tsx +32 -0
  86. package/packages/react/src/components/Spinner.tsx +24 -0
  87. package/packages/react/src/components/Stack.tsx +69 -0
  88. package/packages/react/src/components/Stat.tsx +35 -0
  89. package/packages/react/src/components/Table.tsx +90 -0
  90. package/packages/react/src/components/Tabs.tsx +85 -0
  91. package/packages/react/src/components/Tag.tsx +30 -0
  92. package/packages/react/src/components/Textarea.tsx +21 -0
  93. package/packages/react/src/components/Toast.tsx +134 -0
  94. package/packages/react/src/components/Toggle.tsx +58 -0
  95. package/packages/react/src/components/Tooltip.tsx +31 -0
  96. package/packages/react/src/components/Typography.tsx +66 -0
  97. package/packages/react/src/index.ts +40 -0
  98. package/packages/react/src/styles.css +2039 -0
  99. package/packages/react/src/tokens/index.ts +94 -0
  100. package/packages/rust/cronixui/src/colors.rs +135 -0
  101. package/packages/rust/cronixui/src/components/accordion.rs +47 -0
  102. package/packages/rust/cronixui/src/components/alert.rs +95 -0
  103. package/packages/rust/cronixui/src/components/avatar.rs +85 -0
  104. package/packages/rust/cronixui/src/components/badge.rs +35 -0
  105. package/packages/rust/cronixui/src/components/breadcrumb.rs +58 -0
  106. package/packages/rust/cronixui/src/components/button.rs +70 -0
  107. package/packages/rust/cronixui/src/components/card.rs +259 -0
  108. package/packages/rust/cronixui/src/components/command_palette.rs +254 -0
  109. package/packages/rust/cronixui/src/components/dropdown.rs +179 -0
  110. package/packages/rust/cronixui/src/components/file_input.rs +74 -0
  111. package/packages/rust/cronixui/src/components/input.rs +21 -0
  112. package/packages/rust/cronixui/src/components/list.rs +38 -0
  113. package/packages/rust/cronixui/src/components/mod.rs +51 -0
  114. package/packages/rust/cronixui/src/{modal.rs → components/modal.rs} +15 -1
  115. package/packages/rust/cronixui/src/components/nav.rs +19 -0
  116. package/packages/rust/cronixui/src/{pagination.rs → components/pagination.rs} +14 -13
  117. package/packages/rust/cronixui/src/components/progress.rs +50 -0
  118. package/packages/rust/cronixui/src/components/search.rs +185 -0
  119. package/packages/rust/cronixui/src/components/skeleton.rs +63 -0
  120. package/packages/rust/cronixui/src/components/spinner.rs +21 -0
  121. package/packages/rust/cronixui/src/components/table.rs +56 -0
  122. package/packages/rust/cronixui/src/components/tabs.rs +43 -0
  123. package/packages/rust/cronixui/src/components/toast.rs +69 -0
  124. package/packages/rust/cronixui/src/{toggle.rs → components/toggle.rs} +7 -5
  125. package/packages/rust/cronixui/src/components/tooltip.rs +11 -0
  126. package/packages/rust/cronixui/src/lib.rs +111 -64
  127. package/packages/rust/cronixui/src/tokens.rs +97 -127
  128. package/packages/web/src/variables.css +81 -81
  129. package/packages/go/cronixui/tokens.go +0 -129
  130. package/packages/python/cronixui/pyproject.toml +0 -11
  131. package/packages/react/src/components/Accordion.jsx +0 -50
  132. package/packages/react/src/components/Alert.jsx +0 -62
  133. package/packages/react/src/components/Avatar.jsx +0 -34
  134. package/packages/react/src/components/Badge.jsx +0 -15
  135. package/packages/react/src/components/Breadcrumb.jsx +0 -27
  136. package/packages/react/src/components/Button.jsx +0 -21
  137. package/packages/react/src/components/Card.jsx +0 -23
  138. package/packages/react/src/components/Checkbox.jsx +0 -27
  139. package/packages/react/src/components/CommandPalette.jsx +0 -93
  140. package/packages/react/src/components/Dropdown.jsx +0 -48
  141. package/packages/react/src/components/FileInput.jsx +0 -44
  142. package/packages/react/src/components/Input.jsx +0 -22
  143. package/packages/react/src/components/List.jsx +0 -29
  144. package/packages/react/src/components/Modal.jsx +0 -65
  145. package/packages/react/src/components/Nav.jsx +0 -50
  146. package/packages/react/src/components/Pagination.jsx +0 -81
  147. package/packages/react/src/components/Progress.jsx +0 -23
  148. package/packages/react/src/components/Radio.jsx +0 -50
  149. package/packages/react/src/components/Search.jsx +0 -70
  150. package/packages/react/src/components/Select.jsx +0 -33
  151. package/packages/react/src/components/Skeleton.jsx +0 -15
  152. package/packages/react/src/components/Slider.jsx +0 -29
  153. package/packages/react/src/components/Spinner.jsx +0 -5
  154. package/packages/react/src/components/Stat.jsx +0 -19
  155. package/packages/react/src/components/Table.jsx +0 -48
  156. package/packages/react/src/components/Tabs.jsx +0 -65
  157. package/packages/react/src/components/Tag.jsx +0 -19
  158. package/packages/react/src/components/Textarea.jsx +0 -17
  159. package/packages/react/src/components/Toast.jsx +0 -78
  160. package/packages/react/src/components/Toggle.jsx +0 -34
  161. package/packages/react/src/components/Tooltip.jsx +0 -12
  162. package/packages/react/src/index.d.ts +0 -103
  163. package/packages/react/src/index.js +0 -33
  164. package/packages/rust/cronixui/src/accordion.rs +0 -49
  165. package/packages/rust/cronixui/src/command_palette.rs +0 -62
  166. package/packages/rust/cronixui/src/dropdown.rs +0 -31
  167. package/packages/rust/cronixui/src/search.rs +0 -49
  168. package/packages/rust/cronixui/src/tabs.rs +0 -23
  169. package/packages/rust/cronixui/src/toast.rs +0 -70
@@ -0,0 +1,32 @@
1
+ import * as React from 'react';
2
+
3
+ export interface SliderProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'type' | 'value' | 'onChange'> {
4
+ value?: number;
5
+ min?: number;
6
+ max?: number;
7
+ step?: number;
8
+ onChange?: (value: number) => void;
9
+ }
10
+
11
+ export const Slider = React.forwardRef<HTMLInputElement, SliderProps>(
12
+ ({ value = 0, min = 0, max = 100, step = 1, onChange, disabled = false, className = '', ...props }, ref) => {
13
+ return (
14
+ <input
15
+ ref={ref}
16
+ type="range"
17
+ className={`cn-slider ${className}`.trim()}
18
+ value={value}
19
+ min={min}
20
+ max={max}
21
+ step={step}
22
+ onChange={(e) => onChange?.(Number(e.target.value))}
23
+ disabled={disabled}
24
+ {...props}
25
+ />
26
+ );
27
+ }
28
+ );
29
+
30
+ Slider.displayName = 'Slider';
31
+
32
+ export default Slider;
@@ -0,0 +1,24 @@
1
+ import * as React from 'react';
2
+
3
+ export type SpinnerSize = 'sm' | 'md' | 'lg';
4
+
5
+ export interface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ size?: SpinnerSize;
7
+ }
8
+
9
+ export const Spinner: React.FC<SpinnerProps> = ({ size = 'md', className = '', ...props }) => {
10
+ const sizeClass = size !== 'md' ? `cn-spinner-${size}` : '';
11
+
12
+ return (
13
+ <div
14
+ className={`cn-spinner ${sizeClass} ${className}`.trim()}
15
+ role="status"
16
+ aria-label="Loading"
17
+ {...props}
18
+ />
19
+ );
20
+ };
21
+
22
+ Spinner.displayName = 'Spinner';
23
+
24
+ export default Spinner;
@@ -0,0 +1,69 @@
1
+ import * as React from 'react';
2
+
3
+ export interface StackProps extends React.HTMLAttributes<HTMLDivElement> {
4
+ spacing?: '1' | '2' | '4' | '6';
5
+ }
6
+
7
+ export const Stack: React.FC<StackProps> = ({
8
+ children,
9
+ spacing = '4',
10
+ className = '',
11
+ ...props
12
+ }) => {
13
+ return (
14
+ <div
15
+ className={`cn-stack cn-stack-${spacing} ${className}`.trim()}
16
+ {...props}
17
+ >
18
+ {children}
19
+ </div>
20
+ );
21
+ };
22
+
23
+ Stack.displayName = 'Stack';
24
+
25
+ export const HStack: React.FC<StackProps> = ({
26
+ children,
27
+ spacing = '4',
28
+ className = '',
29
+ ...props
30
+ }) => {
31
+ return (
32
+ <div
33
+ className={`cn-hstack cn-hstack-${spacing} ${className}`.trim()}
34
+ {...props}
35
+ >
36
+ {children}
37
+ </div>
38
+ );
39
+ };
40
+
41
+ HStack.displayName = 'HStack';
42
+
43
+ export interface DividerProps extends React.HTMLAttributes<HTMLHRElement> {
44
+ orientation?: 'horizontal' | 'vertical';
45
+ }
46
+
47
+ export const Divider: React.FC<DividerProps> = ({
48
+ className = '',
49
+ orientation = 'horizontal',
50
+ style,
51
+ ...props
52
+ }) => {
53
+ const dividerStyle: React.CSSProperties = orientation === 'vertical'
54
+ ? { width: '1px', height: 'auto', minHeight: '100%', borderRight: 'none', borderBottom: '1px solid var(--cn-border)', ...style }
55
+ : { height: '1px', border: 'none', backgroundColor: 'var(--cn-border)', ...style };
56
+
57
+ return (
58
+ <hr
59
+ className={`cn-divider ${className}`.trim()}
60
+ style={dividerStyle}
61
+ role="separator"
62
+ {...props}
63
+ />
64
+ );
65
+ };
66
+
67
+ Divider.displayName = 'Divider';
68
+
69
+ export default Stack;
@@ -0,0 +1,35 @@
1
+ import * as React from 'react';
2
+
3
+ export type StatDeltaType = 'up' | 'down';
4
+
5
+ export interface StatProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ value: React.ReactNode;
7
+ label?: string;
8
+ delta?: string;
9
+ deltaType?: StatDeltaType;
10
+ }
11
+
12
+ export const Stat: React.FC<StatProps> = ({
13
+ value,
14
+ label,
15
+ delta,
16
+ deltaType = 'up',
17
+ className = '',
18
+ ...props
19
+ }) => {
20
+ return (
21
+ <div className={`cn-stat ${className}`.trim()} {...props}>
22
+ <div className="cn-stat-value">{value}</div>
23
+ {label && <div className="cn-stat-label">{label}</div>}
24
+ {delta && (
25
+ <div className={`cn-stat-delta cn-stat-delta-${deltaType}`.trim()}>
26
+ {deltaType === 'up' ? '↑' : '↓'} {delta}
27
+ </div>
28
+ )}
29
+ </div>
30
+ );
31
+ };
32
+
33
+ Stat.displayName = 'Stat';
34
+
35
+ export default Stat;
@@ -0,0 +1,90 @@
1
+ import * as React from 'react';
2
+
3
+ export interface TableColumn<T = Record<string, unknown>> {
4
+ key: string;
5
+ header: string;
6
+ sortable?: boolean;
7
+ render?: (row: T) => React.ReactNode;
8
+ }
9
+
10
+ export interface TableProps<T = Record<string, unknown>> extends React.HTMLAttributes<HTMLDivElement> {
11
+ columns: TableColumn<T>[];
12
+ data: T[];
13
+ sortable?: boolean;
14
+ onSort?: (key: string) => void;
15
+ }
16
+
17
+ export function Table<T extends Record<string, unknown> = Record<string, unknown>>({
18
+ columns = [],
19
+ data = [],
20
+ sortable = false,
21
+ onSort,
22
+ className = '',
23
+ ...props
24
+ }: TableProps<T>): React.ReactElement {
25
+ const [sortState, setSortState] = React.useState<Record<string, 'none' | 'ascending' | 'descending'>>(
26
+ Object.fromEntries(columns.map((col) => [col.key, 'none']))
27
+ );
28
+
29
+ const handleSort = (column: TableColumn<T>) => {
30
+ if (!sortable || !column.sortable) return;
31
+
32
+ const next: Record<string, 'none' | 'ascending' | 'descending'> = {};
33
+ columns.forEach((col) => {
34
+ next[col.key] = 'none';
35
+ });
36
+
37
+ const current = sortState[column.key] ?? 'none';
38
+ if (current === 'none') {
39
+ next[column.key] = 'ascending';
40
+ } else if (current === 'ascending') {
41
+ next[column.key] = 'descending';
42
+ } else {
43
+ next[column.key] = 'none';
44
+ }
45
+
46
+ setSortState(next);
47
+ onSort?.(column.key);
48
+ };
49
+
50
+ return (
51
+ <div className={`cn-table-wrapper ${sortable ? 'cn-table-sortable' : ''} ${className}`.trim()} {...props}>
52
+ <table className="cn-table">
53
+ <thead>
54
+ <tr>
55
+ {columns.map((col, idx) => {
56
+ const sortDirection = sortState[col.key] ?? 'none';
57
+ const sortIndicator = sortDirection === 'ascending' ? ' ↑' : sortDirection === 'descending' ? ' ↓' : col.sortable ? ' ↕' : '';
58
+ return (
59
+ <th
60
+ key={idx}
61
+ onClick={() => handleSort(col)}
62
+ data-sort={col.key}
63
+ aria-sort={col.sortable ? sortDirection : undefined}
64
+ >
65
+ {col.header}
66
+ {sortIndicator}
67
+ </th>
68
+ );
69
+ })}
70
+ </tr>
71
+ </thead>
72
+ <tbody>
73
+ {data.map((row, idx) => (
74
+ <tr key={idx}>
75
+ {columns.map((col, colIdx) => (
76
+ <td key={colIdx}>
77
+ {col.render ? col.render(row) : (row as Record<string, unknown>)[col.key] as React.ReactNode}
78
+ </td>
79
+ ))}
80
+ </tr>
81
+ ))}
82
+ </tbody>
83
+ </table>
84
+ </div>
85
+ );
86
+ }
87
+
88
+ Table.displayName = 'Table';
89
+
90
+ export default Table;
@@ -0,0 +1,85 @@
1
+ import * as React from 'react';
2
+
3
+ export interface TabsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
4
+ defaultIndex?: number;
5
+ index?: number;
6
+ onChange?: (index: number) => void;
7
+ }
8
+
9
+ export interface TabProps extends React.HTMLAttributes<HTMLDivElement> {
10
+ children: React.ReactNode;
11
+ }
12
+
13
+ export interface TabPanelProps extends React.HTMLAttributes<HTMLDivElement> {
14
+ children: React.ReactNode;
15
+ }
16
+
17
+ export const Tabs: React.FC<TabsProps> & {
18
+ Tab: React.FC<TabProps>;
19
+ Panel: React.FC<TabPanelProps>;
20
+ } = Object.assign(
21
+ ({ defaultIndex = 0, index: controlledIndex, onChange, children, className = '', ...props }: TabsProps) => {
22
+ const [internalIndex, setInternalIndex] = React.useState(defaultIndex);
23
+ const activeIndex = controlledIndex !== undefined ? controlledIndex : internalIndex;
24
+
25
+ const handleTabClick = (idx: number) => {
26
+ if (onChange) {
27
+ onChange(idx);
28
+ } else {
29
+ setInternalIndex(idx);
30
+ }
31
+ };
32
+
33
+ const tabs: React.ReactElement<TabProps>[] = [];
34
+ const panels: React.ReactElement<TabPanelProps>[] = [];
35
+
36
+ React.Children.forEach(children, (child) => {
37
+ if (React.isValidElement(child)) {
38
+ if ((child.type as React.FC).displayName === 'Tab') {
39
+ tabs.push(child as React.ReactElement<TabProps>);
40
+ } else if ((child.type as React.FC).displayName === 'TabPanel') {
41
+ panels.push(child as React.ReactElement<TabPanelProps>);
42
+ }
43
+ }
44
+ });
45
+
46
+ return (
47
+ <div className={className} {...props}>
48
+ <div className="cn-tabs">
49
+ {tabs.map((tab, idx) => (
50
+ <div
51
+ key={idx}
52
+ className={`cn-tab ${activeIndex === idx ? 'cn-tab-active' : ''}`.trim()}
53
+ onClick={() => handleTabClick(idx)}
54
+ role="tab"
55
+ aria-selected={activeIndex === idx}
56
+ tabIndex={0}
57
+ >
58
+ {tab.props.children}
59
+ </div>
60
+ ))}
61
+ </div>
62
+ {panels.map((panel, idx) => (
63
+ <div
64
+ key={idx}
65
+ className={`cn-tab-panel ${activeIndex === idx ? 'cn-tab-panel-active' : ''}`.trim()}
66
+ role="tabpanel"
67
+ hidden={activeIndex !== idx}
68
+ >
69
+ {panel.props.children}
70
+ </div>
71
+ ))}
72
+ </div>
73
+ );
74
+ },
75
+ {
76
+ Tab: ({ children }: TabProps) => <>{children}</>,
77
+ Panel: ({ children }: TabPanelProps) => <>{children}</>,
78
+ }
79
+ );
80
+
81
+ Tabs.displayName = 'Tabs';
82
+ (Tabs.Tab as React.FC<TabProps> & { displayName?: string }).displayName = 'Tab';
83
+ (Tabs.Panel as React.FC<TabPanelProps> & { displayName?: string }).displayName = 'TabPanel';
84
+
85
+ export default Tabs;
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+
3
+ export interface TagProps extends React.HTMLAttributes<HTMLDivElement> {
4
+ onRemove?: () => void;
5
+ }
6
+
7
+ export const Tag: React.FC<TagProps> = ({
8
+ children,
9
+ onRemove,
10
+ className = '',
11
+ ...props
12
+ }) => {
13
+ return (
14
+ <div className={`cn-tag ${className}`.trim()} {...props}>
15
+ <span>{children}</span>
16
+ {onRemove && (
17
+ <button className="cn-tag-remove" onClick={onRemove} aria-label="Remove">
18
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
19
+ <line x1="18" y1="6" x2="6" y2="18" />
20
+ <line x1="6" y1="6" x2="18" y2="18" />
21
+ </svg>
22
+ </button>
23
+ )}
24
+ </div>
25
+ );
26
+ };
27
+
28
+ Tag.displayName = 'Tag';
29
+
30
+ export default Tag;
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+
3
+ export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
4
+ error?: boolean;
5
+ }
6
+
7
+ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
8
+ ({ error = false, className = '', ...props }, ref) => {
9
+ return (
10
+ <textarea
11
+ ref={ref}
12
+ className={`cn-input cn-textarea ${error ? 'cn-input-error' : ''} ${className}`.trim()}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+ );
18
+
19
+ Textarea.displayName = 'Textarea';
20
+
21
+ export default Textarea;
@@ -0,0 +1,134 @@
1
+ import * as React from 'react';
2
+
3
+ export type ToastType = 'success' | 'error' | 'warning' | 'info';
4
+
5
+ export interface ToastOptions {
6
+ id?: number;
7
+ title?: string;
8
+ message: string;
9
+ type?: ToastType;
10
+ duration?: number;
11
+ closable?: boolean;
12
+ }
13
+
14
+ export interface ToastProps {
15
+ toasts: ToastOptions[];
16
+ onRemove: (id: number) => void;
17
+ }
18
+
19
+ export interface UseToastReturn {
20
+ toasts: ToastOptions[];
21
+ toast: {
22
+ success: (message: string, title?: string) => number;
23
+ error: (message: string, title?: string) => number;
24
+ warning: (message: string, title?: string) => number;
25
+ info: (message: string, title?: string) => number;
26
+ show: (options: ToastOptions) => number;
27
+ remove: (id: number) => void;
28
+ };
29
+ removeToast: (id: number) => void;
30
+ }
31
+
32
+ const ToastItem: React.FC<{ toast: ToastOptions; onRemove: (id: number) => void }> = ({ toast, onRemove }) => {
33
+ const [isLeaving, setIsLeaving] = React.useState(false);
34
+
35
+ React.useEffect(() => {
36
+ if (toast.duration && toast.duration > 0) {
37
+ const timer = setTimeout(() => handleRemove(toast.id!), toast.duration);
38
+ return () => clearTimeout(timer);
39
+ }
40
+ }, [toast.id, toast.duration]);
41
+
42
+ const handleRemove = (id: number) => {
43
+ setIsLeaving(true);
44
+ setTimeout(() => onRemove(id), 200);
45
+ };
46
+
47
+ const icons: Record<ToastType, React.ReactNode> = {
48
+ success: <polyline points="20 6 9 17 4 12" />,
49
+ error: (
50
+ <>
51
+ <circle cx="12" cy="12" r="10" />
52
+ <line x1="15" y1="9" x2="9" y2="15" />
53
+ <line x1="9" y1="9" x2="15" y2="15" />
54
+ </>
55
+ ),
56
+ warning: (
57
+ <>
58
+ <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" />
59
+ <line x1="12" y1="9" x2="12" y2="13" />
60
+ <line x1="12" y1="17" x2="12.01" y2="17" />
61
+ </>
62
+ ),
63
+ info: (
64
+ <>
65
+ <circle cx="12" cy="12" r="10" />
66
+ <line x1="12" y1="16" x2="12" y2="12" />
67
+ <line x1="12" y1="8" x2="12.01" y2="8" />
68
+ </>
69
+ ),
70
+ };
71
+
72
+ return (
73
+ <div className={`cn-toast cn-toast-${toast.type} ${isLeaving ? 'cn-toast-leaving' : ''}`.trim()} role="alert">
74
+ <div className="cn-alert-icon">
75
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
76
+ {icons[toast.type || 'info']}
77
+ </svg>
78
+ </div>
79
+ <div className="cn-alert-content">
80
+ {toast.title && <div className="cn-alert-title">{toast.title}</div>}
81
+ {toast.message && <div className="cn-alert-message">{toast.message}</div>}
82
+ </div>
83
+ {toast.closable && (
84
+ <button className="cn-alert-close" onClick={() => handleRemove(toast.id!)} aria-label="Close">
85
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="16" height="16">
86
+ <line x1="18" y1="6" x2="6" y2="18" />
87
+ <line x1="6" y1="6" x2="18" y2="18" />
88
+ </svg>
89
+ </button>
90
+ )}
91
+ </div>
92
+ );
93
+ };
94
+
95
+ export const Toast: React.FC<ToastProps> = ({ toasts = [], onRemove }) => {
96
+ return (
97
+ <div className="cn-toast-container">
98
+ {toasts.map((toast) => (
99
+ <ToastItem key={toast.id} toast={toast} onRemove={onRemove} />
100
+ ))}
101
+ </div>
102
+ );
103
+ };
104
+
105
+ Toast.displayName = 'Toast';
106
+
107
+ export function useToast(): UseToastReturn {
108
+ const [toasts, setToasts] = React.useState<ToastOptions[]>([]);
109
+ const idRef = React.useRef(0);
110
+
111
+ const addToast = (options: ToastOptions): number => {
112
+ const id = ++idRef.current;
113
+ const toast: ToastOptions = { id, duration: 4000, closable: true, type: 'info', ...options };
114
+ setToasts((prev) => [...prev, toast]);
115
+ return id;
116
+ };
117
+
118
+ const removeToast = (id: number) => {
119
+ setToasts((prev) => prev.filter((t) => t.id !== id));
120
+ };
121
+
122
+ const toast = {
123
+ success: (message: string, title?: string) => addToast({ message, title, type: 'success' }),
124
+ error: (message: string, title?: string) => addToast({ message, title, type: 'error' }),
125
+ warning: (message: string, title?: string) => addToast({ message, title, type: 'warning' }),
126
+ info: (message: string, title?: string) => addToast({ message, title, type: 'info' }),
127
+ show: addToast,
128
+ remove: removeToast,
129
+ };
130
+
131
+ return { toasts, toast, removeToast };
132
+ }
133
+
134
+ export default Toast;
@@ -0,0 +1,58 @@
1
+ import * as React from 'react';
2
+
3
+ export type ToggleSize = 'sm' | 'md' | 'lg';
4
+
5
+ export interface ToggleProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ on?: boolean;
7
+ onChange?: (on: boolean) => void;
8
+ disabled?: boolean;
9
+ size?: ToggleSize;
10
+ label?: React.ReactNode;
11
+ }
12
+
13
+ export const Toggle: React.FC<ToggleProps> = ({
14
+ on: controlledOn,
15
+ onChange,
16
+ disabled = false,
17
+ size = 'md',
18
+ label,
19
+ className = '',
20
+ ...props
21
+ }) => {
22
+ const [internalOn, setInternalOn] = React.useState(false);
23
+ const isOn = controlledOn !== undefined ? controlledOn : internalOn;
24
+
25
+ const handleToggle = () => {
26
+ if (disabled) return;
27
+ if (onChange) {
28
+ onChange(!controlledOn);
29
+ } else {
30
+ setInternalOn(!internalOn);
31
+ }
32
+ };
33
+
34
+ const sizeClass = size === 'sm' ? 'cn-toggle-sm' : size === 'lg' ? 'cn-toggle-lg' : '';
35
+
36
+ return (
37
+ <label className={`cn-toggle-wrapper ${className}`.trim()} {...props}>
38
+ <div
39
+ className={`cn-toggle ${isOn ? 'on' : ''} ${sizeClass} ${disabled ? 'disabled' : ''}`.trim()}
40
+ onClick={handleToggle}
41
+ role="switch"
42
+ aria-checked={isOn}
43
+ tabIndex={0}
44
+ onKeyDown={(e) => {
45
+ if (e.key === 'Enter' || e.key === ' ') {
46
+ e.preventDefault();
47
+ handleToggle();
48
+ }
49
+ }}
50
+ />
51
+ {label && <span className="cn-toggle-label">{label}</span>}
52
+ </label>
53
+ );
54
+ };
55
+
56
+ Toggle.displayName = 'Toggle';
57
+
58
+ export default Toggle;
@@ -0,0 +1,31 @@
1
+ import * as React from 'react';
2
+
3
+ export type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';
4
+
5
+ export interface TooltipProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ content: React.ReactNode;
7
+ position?: TooltipPosition;
8
+ }
9
+
10
+ export const Tooltip: React.FC<TooltipProps> = ({
11
+ children,
12
+ content,
13
+ position = 'top',
14
+ className = '',
15
+ ...props
16
+ }) => {
17
+ const positionClass = position !== 'top' ? `cn-tooltip-${position}` : '';
18
+
19
+ return (
20
+ <div className={`cn-tooltip ${positionClass} ${className}`.trim()} {...props}>
21
+ {children}
22
+ <div className="cn-tooltip-content" role="tooltip">
23
+ {content}
24
+ </div>
25
+ </div>
26
+ );
27
+ };
28
+
29
+ Tooltip.displayName = 'Tooltip';
30
+
31
+ export default Tooltip;
@@ -0,0 +1,66 @@
1
+ import * as React from 'react';
2
+
3
+ export interface HeadingProps extends React.HTMLAttributes<HTMLHeadingElement> {
4
+ level?: 1 | 2 | 3 | 4 | 5 | 6;
5
+ }
6
+
7
+ export const H1: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({ className = '', ...props }) => (
8
+ <h1 className={`cn-h1 ${className}`.trim()} {...props} />
9
+ );
10
+ H1.displayName = 'H1';
11
+
12
+ export const H2: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({ className = '', ...props }) => (
13
+ <h2 className={`cn-h2 ${className}`.trim()} {...props} />
14
+ );
15
+ H2.displayName = 'H2';
16
+
17
+ export const H3: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({ className = '', ...props }) => (
18
+ <h3 className={`cn-h3 ${className}`.trim()} {...props} />
19
+ );
20
+ H3.displayName = 'H3';
21
+
22
+ export const H4: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({ className = '', ...props }) => (
23
+ <h4 className={`cn-h4 ${className}`.trim()} {...props} />
24
+ );
25
+ H4.displayName = 'H4';
26
+
27
+ export const H5: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({ className = '', ...props }) => (
28
+ <h5 className={`cn-h5 ${className}`.trim()} {...props} />
29
+ );
30
+ H5.displayName = 'H5';
31
+
32
+ export const H6: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({ className = '', ...props }) => (
33
+ <h6 className={`cn-h6 ${className}`.trim()} {...props} />
34
+ );
35
+ H6.displayName = 'H6';
36
+
37
+ export interface TextProps extends React.HTMLAttributes<HTMLParagraphElement> {
38
+ variant?: 'muted' | 'dim' | 'accent' | 'mono';
39
+ size?: 'xs' | 'sm' | 'base' | 'md' | 'lg' | 'xl';
40
+ }
41
+
42
+ export const Text: React.FC<TextProps> = ({
43
+ variant,
44
+ size,
45
+ className = '',
46
+ ...props
47
+ }) => {
48
+ const variantClass = variant ? `cn-text-${variant}` : '';
49
+ const sizeClass = size ? `cn-text-${size}` : '';
50
+
51
+ return (
52
+ <p className={`cn-text-base ${variantClass} ${sizeClass} ${className}`.trim()} {...props} />
53
+ );
54
+ };
55
+
56
+ Text.displayName = 'Text';
57
+
58
+ export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {}
59
+
60
+ export const Label: React.FC<LabelProps> = ({ className = '', ...props }) => (
61
+ <label className={`cn-label ${className}`.trim()} {...props} />
62
+ );
63
+
64
+ Label.displayName = 'Label';
65
+
66
+ export default H1;