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,108 @@
1
+ import 'package:flutter/material.dart';
2
+ import '../tokens/colors.dart';
3
+ import '../tokens/spacing.dart';
4
+
5
+ class CnFooter extends StatelessWidget {
6
+ final List<Widget>? leading;
7
+ final List<Widget>? trailing;
8
+ final String? copyright;
9
+ final Color? backgroundColor;
10
+ final double height;
11
+ final bool bordered;
12
+
13
+ const CnFooter({
14
+ super.key,
15
+ this.leading,
16
+ this.trailing,
17
+ this.copyright,
18
+ this.backgroundColor,
19
+ this.height = 56,
20
+ this.bordered = true,
21
+ });
22
+
23
+ @override
24
+ Widget build(BuildContext context) {
25
+ return Container(
26
+ height: height,
27
+ decoration: BoxDecoration(
28
+ color: backgroundColor ?? CronixColors.surface,
29
+ border: bordered
30
+ ? const Border(
31
+ top: BorderSide(color: CronixColors.border),
32
+ )
33
+ : null,
34
+ ),
35
+ padding: const EdgeInsets.symmetric(horizontal: 16),
36
+ child: Row(
37
+ children: [
38
+ if (leading != null) ...leading!,
39
+ const Spacer(),
40
+ if (copyright != null)
41
+ Text(
42
+ copyright!,
43
+ style: const TextStyle(
44
+ color: CronixColors.textMuted,
45
+ fontSize: 12,
46
+ ),
47
+ ),
48
+ const Spacer(),
49
+ if (trailing != null) ...trailing!,
50
+ ],
51
+ ),
52
+ );
53
+ }
54
+ }
55
+
56
+ class CnFooterLinks extends StatelessWidget {
57
+ final List<FooterLink> links;
58
+ final String? divider;
59
+
60
+ const CnFooterLinks({
61
+ super.key,
62
+ required this.links,
63
+ this.divider,
64
+ });
65
+
66
+ @override
67
+ Widget build(BuildContext context) {
68
+ return Row(
69
+ children: links.asMap().entries.map((entry) {
70
+ final index = entry.key;
71
+ final link = entry.value;
72
+ return Row(
73
+ children: [
74
+ if (index > 0 && divider != null)
75
+ Padding(
76
+ padding: const EdgeInsets.symmetric(horizontal: 8),
77
+ child: Text(
78
+ divider!,
79
+ style: const TextStyle(color: CronixColors.textMuted),
80
+ ),
81
+ ),
82
+ InkWell(
83
+ onTap: link.onTap,
84
+ child: Text(
85
+ link.label,
86
+ style: TextStyle(
87
+ color: CronixColors.textSecondary,
88
+ fontSize: 12,
89
+ decoration: TextDecoration.underline,
90
+ ),
91
+ ),
92
+ ),
93
+ ],
94
+ );
95
+ }).toList(),
96
+ );
97
+ }
98
+ }
99
+
100
+ class FooterLink {
101
+ final String label;
102
+ final VoidCallback? onTap;
103
+
104
+ const FooterLink({
105
+ required this.label,
106
+ this.onTap,
107
+ });
108
+ }
@@ -0,0 +1,173 @@
1
+ import 'package:flutter/material.dart';
2
+ import '../tokens/colors.dart';
3
+ import '../tokens/spacing.dart';
4
+
5
+ class CnHeader extends StatelessWidget implements PreferredSizeWidget {
6
+ final String? title;
7
+ final List<Widget>? actions;
8
+ final Widget? leading;
9
+ final bool showBackButton;
10
+ final double height;
11
+ final Color? backgroundColor;
12
+ final bool borderless;
13
+ final Widget? flexibleSpace;
14
+
15
+ const CnHeader({
16
+ super.key,
17
+ this.title,
18
+ this.actions,
19
+ this.leading,
20
+ this.showBackButton = false,
21
+ this.height = 56,
22
+ this.backgroundColor,
23
+ this.borderless = false,
24
+ this.flexibleSpace,
25
+ });
26
+
27
+ @override
28
+ Size get preferredSize => Size.fromHeight(height);
29
+
30
+ @override
31
+ Widget build(BuildContext context) {
32
+ return Container(
33
+ decoration: BoxDecoration(
34
+ color: backgroundColor ?? CronixColors.background,
35
+ border: borderless ? null : const Border(
36
+ bottom: BorderSide(color: CronixColors.border),
37
+ ),
38
+ ),
39
+ child: AppBar(
40
+ backgroundColor: Colors.transparent,
41
+ elevation: 0,
42
+ scrolledUnderElevation: 0,
43
+ automaticallyImplyLeading: false,
44
+ leading: showBackButton
45
+ ? IconButton(
46
+ icon: const Icon(Icons.arrow_back, color: CronixColors.text),
47
+ onPressed: () => Navigator.of(context).pop(),
48
+ )
49
+ : leading,
50
+ title: title != null
51
+ ? Text(
52
+ title!,
53
+ style: const TextStyle(
54
+ color: CronixColors.text,
55
+ fontSize: 18,
56
+ fontWeight: FontWeight.w600,
57
+ ),
58
+ )
59
+ : null,
60
+ actions: actions?.map((action) => Padding(
61
+ padding: const EdgeInsets.only(right: 8),
62
+ child: action,
63
+ )).toList(),
64
+ flexibleSpace: flexibleSpace,
65
+ ),
66
+ );
67
+ }
68
+ }
69
+
70
+ class CnHeaderWithSearch extends StatefulWidget implements PreferredSizeWidget {
71
+ final String title;
72
+ final ValueChanged<String>? onSearch;
73
+ final String? searchPlaceholder;
74
+ final List<Widget>? actions;
75
+ final double height;
76
+
77
+ const CnHeaderWithSearch({
78
+ super.key,
79
+ required this.title,
80
+ this.onSearch,
81
+ this.searchPlaceholder,
82
+ this.actions,
83
+ this.height = 56,
84
+ });
85
+
86
+ @override
87
+ Size get preferredSize => Size.fromHeight(height * 2);
88
+
89
+ @override
90
+ State<CnHeaderWithSearch> createState() => _CnHeaderWithSearchState();
91
+ }
92
+
93
+ class _CnHeaderWithSearchState extends State<CnHeaderWithSearch> {
94
+ bool _isSearching = false;
95
+ final TextEditingController _searchController = TextEditingController();
96
+
97
+ @override
98
+ void dispose() {
99
+ _searchController.dispose();
100
+ super.dispose();
101
+ }
102
+
103
+ @override
104
+ Widget build(BuildContext context) {
105
+ return Container(
106
+ decoration: const BoxDecoration(
107
+ color: CronixColors.background,
108
+ border: Border(
109
+ bottom: BorderSide(color: CronixColors.border),
110
+ ),
111
+ ),
112
+ child: Column(
113
+ children: [
114
+ SizedBox(
115
+ height: widget.height,
116
+ child: Row(
117
+ children: [
118
+ Expanded(
119
+ child: _isSearching
120
+ ? TextField(
121
+ controller: _searchController,
122
+ autofocus: true,
123
+ onChanged: widget.onSearch,
124
+ style: const TextStyle(color: CronixColors.text),
125
+ decoration: InputDecoration(
126
+ hintText: widget.searchPlaceholder ?? 'Search...',
127
+ hintStyle: const TextStyle(color: CronixColors.textMuted),
128
+ filled: true,
129
+ fillColor: CronixColors.surface,
130
+ contentPadding: const EdgeInsets.symmetric(horizontal: 16),
131
+ border: OutlineInputBorder(
132
+ borderRadius: CronixRadius.radiusMD,
133
+ borderSide: BorderSide.none,
134
+ ),
135
+ prefixIcon: const Icon(Icons.search, color: CronixColors.textSecondary),
136
+ ),
137
+ )
138
+ : Padding(
139
+ padding: const EdgeInsets.only(left: 16),
140
+ child: Text(
141
+ widget.title,
142
+ style: const TextStyle(
143
+ color: CronixColors.text,
144
+ fontSize: 20,
145
+ fontWeight: FontWeight.w600,
146
+ ),
147
+ ),
148
+ ),
149
+ ),
150
+ IconButton(
151
+ icon: Icon(
152
+ _isSearching ? Icons.close : Icons.search,
153
+ color: CronixColors.text,
154
+ ),
155
+ onPressed: () {
156
+ setState(() {
157
+ _isSearching = !_isSearching;
158
+ if (!_isSearching) {
159
+ _searchController.clear();
160
+ widget.onSearch?.call('');
161
+ }
162
+ });
163
+ },
164
+ ),
165
+ if (widget.actions != null) ...widget.actions!,
166
+ ],
167
+ ),
168
+ ),
169
+ ],
170
+ ),
171
+ );
172
+ }
173
+ }
@@ -0,0 +1,142 @@
1
+ import 'package:flutter/material.dart';
2
+ import '../tokens/colors.dart';
3
+ import '../tokens/spacing.dart';
4
+
5
+ class CnInput extends StatefulWidget {
6
+ final String? label;
7
+ final String? placeholder;
8
+ final String? initialValue;
9
+ final TextEditingController? controller;
10
+ final bool obscureText;
11
+ final bool enabled;
12
+ final int maxLines;
13
+ final IconData? prefixIcon;
14
+ final IconData? suffixIcon;
15
+ final VoidCallback? onSuffixTap;
16
+ final ValueChanged<String>? onChanged;
17
+ final ValueChanged<String>? onSubmitted;
18
+ final String? Function(String?)? validator;
19
+ final TextInputType? keyboardType;
20
+ final FocusNode? focusNode;
21
+
22
+ const CnInput({
23
+ super.key,
24
+ this.label,
25
+ this.placeholder,
26
+ this.initialValue,
27
+ this.controller,
28
+ this.obscureText = false,
29
+ this.enabled = true,
30
+ this.maxLines = 1,
31
+ this.prefixIcon,
32
+ this.suffixIcon,
33
+ this.onSuffixTap,
34
+ this.onChanged,
35
+ this.onSubmitted,
36
+ this.validator,
37
+ this.keyboardType,
38
+ this.focusNode,
39
+ });
40
+
41
+ @override
42
+ State<CnInput> createState() => _CnInputState();
43
+ }
44
+
45
+ class _CnInputState extends State<CnInput> {
46
+ late TextEditingController _controller;
47
+ String? _errorText;
48
+
49
+ @override
50
+ void initState() {
51
+ super.initState();
52
+ _controller = widget.controller ??
53
+ TextEditingController(text: widget.initialValue);
54
+ }
55
+
56
+ @override
57
+ void dispose() {
58
+ if (widget.controller == null) {
59
+ _controller.dispose();
60
+ }
61
+ super.dispose();
62
+ }
63
+
64
+ @override
65
+ Widget build(BuildContext context) {
66
+ return Column(
67
+ crossAxisAlignment: CrossAxisAlignment.start,
68
+ mainAxisSize: MainAxisSize.min,
69
+ children: [
70
+ if (widget.label != null) ...[
71
+ Text(
72
+ widget.label!,
73
+ style: const TextStyle(
74
+ color: CronixColors.text,
75
+ fontSize: 14,
76
+ fontWeight: FontWeight.w500,
77
+ ),
78
+ ),
79
+ const SizedBox(height: 8),
80
+ ],
81
+ TextFormField(
82
+ controller: _controller,
83
+ obscureText: widget.obscureText,
84
+ enabled: widget.enabled,
85
+ maxLines: widget.maxLines,
86
+ keyboardType: widget.keyboardType,
87
+ focusNode: widget.focusNode,
88
+ onChanged: widget.onChanged,
89
+ onFieldSubmitted: widget.onSubmitted,
90
+ validator: (value) {
91
+ if (widget.validator != null) {
92
+ final error = widget.validator!(value);
93
+ setState(() => _errorText = error);
94
+ return error;
95
+ }
96
+ return null;
97
+ },
98
+ style: const TextStyle(color: CronixColors.text),
99
+ decoration: InputDecoration(
100
+ hintText: widget.placeholder,
101
+ hintStyle: const TextStyle(color: CronixColors.textMuted),
102
+ filled: true,
103
+ fillColor: widget.enabled ? CronixColors.surface : CronixColors.surfaceLight,
104
+ contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
105
+ border: OutlineInputBorder(
106
+ borderRadius: CronixRadius.radiusMD,
107
+ borderSide: const BorderSide(color: CronixColors.border),
108
+ ),
109
+ enabledBorder: OutlineInputBorder(
110
+ borderRadius: CronixRadius.radiusMD,
111
+ borderSide: const BorderSide(color: CronixColors.border),
112
+ ),
113
+ focusedBorder: OutlineInputBorder(
114
+ borderRadius: CronixRadius.radiusMD,
115
+ borderSide: const BorderSide(color: CronixColors.accent, width: 2),
116
+ ),
117
+ errorBorder: OutlineInputBorder(
118
+ borderRadius: CronixRadius.radiusMD,
119
+ borderSide: const BorderSide(color: CronixColors.error),
120
+ ),
121
+ prefixIcon: widget.prefixIcon != null
122
+ ? Icon(widget.prefixIcon, color: CronixColors.textSecondary, size: 20)
123
+ : null,
124
+ suffixIcon: widget.suffixIcon != null
125
+ ? IconButton(
126
+ icon: Icon(widget.suffixIcon, color: CronixColors.textSecondary, size: 20),
127
+ onPressed: widget.onSuffixTap,
128
+ )
129
+ : null,
130
+ ),
131
+ ),
132
+ if (_errorText != null) ...[
133
+ const SizedBox(height: 4),
134
+ Text(
135
+ _errorText!,
136
+ style: const TextStyle(color: CronixColors.error, fontSize: 12),
137
+ ),
138
+ ],
139
+ ],
140
+ );
141
+ }
142
+ }
@@ -0,0 +1,150 @@
1
+ import 'package:flutter/material.dart';
2
+ import '../tokens/colors.dart';
3
+ import '../tokens/spacing.dart';
4
+
5
+ class CnListItem {
6
+ final String title;
7
+ final String? subtitle;
8
+ final IconData? leadingIcon;
9
+ final IconData? trailingIcon;
10
+ final VoidCallback? onTap;
11
+ final Widget? trailing;
12
+ final Color? backgroundColor;
13
+
14
+ const CnListItem({
15
+ required this.title,
16
+ this.subtitle,
17
+ this.leadingIcon,
18
+ this.trailingIcon,
19
+ this.onTap,
20
+ this.trailing,
21
+ this.backgroundColor,
22
+ });
23
+ }
24
+
25
+ class CnList extends StatelessWidget {
26
+ final List<CnListItem> items;
27
+ final bool divided;
28
+ final bool bordered;
29
+ final double itemHeight;
30
+
31
+ const CnList({
32
+ super.key,
33
+ required this.items,
34
+ this.divided = true,
35
+ this.bordered = false,
36
+ this.itemHeight = 56,
37
+ });
38
+
39
+ @override
40
+ Widget build(BuildContext context) {
41
+ return Container(
42
+ decoration: BoxDecoration(
43
+ color: CronixColors.surface,
44
+ borderRadius: CronixRadius.radiusMD,
45
+ border: bordered ? Border.all(color: CronixColors.border) : null,
46
+ ),
47
+ child: ListView.separated(
48
+ shrinkWrap: true,
49
+ physics: const NeverScrollableScrollPhysics(),
50
+ itemCount: items.length,
51
+ separatorBuilder: (context, index) => divided
52
+ ? const Divider(color: CronixColors.border, height: 1)
53
+ : const SizedBox.shrink(),
54
+ itemBuilder: (context, index) {
55
+ final item = items[index];
56
+ return _buildItem(item);
57
+ },
58
+ ),
59
+ );
60
+ }
61
+
62
+ Widget _buildItem(CnListItem item) {
63
+ return InkWell(
64
+ onTap: item.onTap,
65
+ child: Container(
66
+ height: itemHeight,
67
+ padding: const EdgeInsets.symmetric(horizontal: 16),
68
+ color: item.backgroundColor,
69
+ child: Row(
70
+ children: [
71
+ if (item.leadingIcon != null) ...[
72
+ Icon(
73
+ item.leadingIcon,
74
+ size: 20,
75
+ color: CronixColors.textSecondary,
76
+ ),
77
+ const SizedBox(width: 12),
78
+ ],
79
+ Expanded(
80
+ child: Column(
81
+ mainAxisAlignment: MainAxisAlignment.center,
82
+ crossAxisAlignment: CrossAxisAlignment.start,
83
+ children: [
84
+ Text(
85
+ item.title,
86
+ style: const TextStyle(
87
+ color: CronixColors.text,
88
+ fontSize: 14,
89
+ ),
90
+ ),
91
+ if (item.subtitle != null) ...[
92
+ const SizedBox(height: 2),
93
+ Text(
94
+ item.subtitle!,
95
+ style: const TextStyle(
96
+ color: CronixColors.textSecondary,
97
+ fontSize: 12,
98
+ ),
99
+ ),
100
+ ],
101
+ ],
102
+ ),
103
+ ),
104
+ if (item.trailing != null) item.trailing!,
105
+ if (item.trailingIcon != null)
106
+ Icon(
107
+ item.trailingIcon,
108
+ size: 18,
109
+ color: CronixColors.textMuted,
110
+ ),
111
+ ],
112
+ ),
113
+ ),
114
+ );
115
+ }
116
+ }
117
+
118
+ class CnListBuilder<T> extends StatelessWidget {
119
+ final List<T> items;
120
+ final Widget Function(BuildContext context, T item, int index) itemBuilder;
121
+ final bool divided;
122
+ final bool bordered;
123
+
124
+ const CnListBuilder({
125
+ super.key,
126
+ required this.items,
127
+ required this.itemBuilder,
128
+ this.divided = true,
129
+ this.bordered = false,
130
+ });
131
+
132
+ @override
133
+ Widget build(BuildContext context) {
134
+ return Container(
135
+ decoration: BoxDecoration(
136
+ borderRadius: CronixRadius.radiusMD,
137
+ border: bordered ? Border.all(color: CronixColors.border) : null,
138
+ ),
139
+ child: ListView.separated(
140
+ shrinkWrap: true,
141
+ physics: const NeverScrollableScrollPhysics(),
142
+ itemCount: items.length,
143
+ separatorBuilder: (context, index) => divided
144
+ ? const Divider(color: CronixColors.border, height: 1)
145
+ : const SizedBox.shrink(),
146
+ itemBuilder: (context, index) => itemBuilder(context, items[index], index),
147
+ ),
148
+ );
149
+ }
150
+ }