cronixui 1.1.0 → 1.1.2

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 (42) hide show
  1. package/README.md +16 -27
  2. package/package.json +2 -1
  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
@@ -0,0 +1,136 @@
1
+ import 'package:flutter/material.dart';
2
+ import '../tokens/colors.dart';
3
+ import '../tokens/spacing.dart';
4
+
5
+ class CnTableColumn<T> {
6
+ final String label;
7
+ final double? width;
8
+ final bool sortable;
9
+ final String? Function(T item)? valueBuilder;
10
+ final Widget Function(T item)? cellBuilder;
11
+ final TextAlign alignment;
12
+
13
+ const CnTableColumn({
14
+ required this.label,
15
+ this.width,
16
+ this.sortable = false,
17
+ this.valueBuilder,
18
+ this.cellBuilder,
19
+ this.alignment = TextAlign.left,
20
+ });
21
+ }
22
+
23
+ class CnTable<T> extends StatefulWidget {
24
+ final List<CnTableColumn<T>> columns;
25
+ final List<T> data;
26
+ final void Function(T item)? onRowTap;
27
+ final bool striped;
28
+ final bool bordered;
29
+ final String? emptyMessage;
30
+ final Widget? emptyWidget;
31
+
32
+ const CnTable({
33
+ super.key,
34
+ required this.columns,
35
+ required this.data,
36
+ this.onRowTap,
37
+ this.striped = false,
38
+ this.bordered = false,
39
+ this.emptyMessage,
40
+ this.emptyWidget,
41
+ });
42
+
43
+ @override
44
+ State<CnTable<T>> createState() => _CnTableState<T>();
45
+ }
46
+
47
+ class _CnTableState<T> extends State<CnTable<T>> {
48
+ int? _sortColumnIndex;
49
+ bool _sortAscending = true;
50
+
51
+ void _sort(int columnIndex) {
52
+ setState(() {
53
+ if (_sortColumnIndex == columnIndex) {
54
+ _sortAscending = !_sortAscending;
55
+ } else {
56
+ _sortColumnIndex = columnIndex;
57
+ _sortAscending = true;
58
+ }
59
+ });
60
+ }
61
+
62
+ @override
63
+ Widget build(BuildContext context) {
64
+ if (widget.data.isEmpty) {
65
+ return widget.emptyWidget ??
66
+ Center(
67
+ child: Padding(
68
+ padding: const EdgeInsets.all(32),
69
+ child: Text(
70
+ widget.emptyMessage ?? 'No data available',
71
+ style: const TextStyle(color: CronixColors.textMuted),
72
+ ),
73
+ ),
74
+ );
75
+ }
76
+
77
+ return Container(
78
+ decoration: BoxDecoration(
79
+ border: widget.bordered
80
+ ? Border.all(color: CronixColors.border)
81
+ : null,
82
+ borderRadius: CronixRadius.radiusMD,
83
+ ),
84
+ child: SingleChildScrollView(
85
+ scrollDirection: Axis.horizontal,
86
+ child: DataTable(
87
+ headingRowColor: WidgetStateProperty.all(CronixColors.surfaceLight),
88
+ dataRowColor: WidgetStateProperty.resolveWith((states) {
89
+ if (widget.striped) {
90
+ final index = states.contains(WidgetState.selected) ? -1 : 0;
91
+ return index % 2 == 0
92
+ ? CronixColors.surface
93
+ : CronixColors.surfaceLight;
94
+ }
95
+ return Colors.transparent;
96
+ }),
97
+ dividerThickness: widget.bordered ? 1 : 0,
98
+ columns: widget.columns.map((col) {
99
+ return DataColumn(
100
+ label: Text(
101
+ col.label,
102
+ style: const TextStyle(
103
+ color: CronixColors.text,
104
+ fontWeight: FontWeight.w600,
105
+ ),
106
+ ),
107
+ onSort: col.sortable ? (_) => _sort(widget.columns.indexOf(col)) : null,
108
+ );
109
+ }).toList(),
110
+ rows: widget.data.map((item) {
111
+ return DataRow(
112
+ onSelectChanged: widget.onRowTap != null
113
+ ? (_) => widget.onRowTap!(item)
114
+ : null,
115
+ cells: widget.columns.map((col) {
116
+ Widget cell;
117
+ if (col.cellBuilder != null) {
118
+ cell = col.cellBuilder!(item);
119
+ } else if (col.valueBuilder != null) {
120
+ cell = Text(
121
+ col.valueBuilder!(item) ?? '',
122
+ style: const TextStyle(color: CronixColors.text),
123
+ textAlign: col.alignment,
124
+ );
125
+ } else {
126
+ cell = const SizedBox.shrink();
127
+ }
128
+ return DataCell(cell);
129
+ }).toList(),
130
+ );
131
+ }).toList(),
132
+ ),
133
+ ),
134
+ );
135
+ }
136
+ }
@@ -0,0 +1,229 @@
1
+ import 'package:flutter/material.dart';
2
+ import '../tokens/colors.dart';
3
+ import '../tokens/spacing.dart';
4
+
5
+ class CnTabs extends StatefulWidget {
6
+ final List<String> tabs;
7
+ final int initialIndex;
8
+ final ValueChanged<int>? onChanged;
9
+ final bool fullWidth;
10
+ final Color? indicatorColor;
11
+ final Color? backgroundColor;
12
+
13
+ const CnTabs({
14
+ super.key,
15
+ required this.tabs,
16
+ this.initialIndex = 0,
17
+ this.onChanged,
18
+ this.fullWidth = false,
19
+ this.indicatorColor,
20
+ this.backgroundColor,
21
+ });
22
+
23
+ @override
24
+ State<CnTabs> createState() => _CnTabsState();
25
+ }
26
+
27
+ class _CnTabsState extends State<CnTabs> with SingleTickerProviderStateMixin {
28
+ late TabController _controller;
29
+
30
+ @override
31
+ void initState() {
32
+ super.initState();
33
+ _controller = TabController(
34
+ length: widget.tabs.length,
35
+ vsync: this,
36
+ initialIndex: widget.initialIndex,
37
+ );
38
+ _controller.addListener(_handleTabChange);
39
+ }
40
+
41
+ void _handleTabChange() {
42
+ if (!_controller.indexIsChanging) {
43
+ widget.onChanged?.call(_controller.index);
44
+ }
45
+ }
46
+
47
+ @override
48
+ void didUpdateWidget(CnTabs oldWidget) {
49
+ super.didUpdateWidget(oldWidget);
50
+ if (oldWidget.tabs.length != widget.tabs.length) {
51
+ _controller.removeListener(_handleTabChange);
52
+ _controller.dispose();
53
+ _controller = TabController(
54
+ length: widget.tabs.length,
55
+ vsync: this,
56
+ initialIndex: _controller.index.clamp(0, widget.tabs.length - 1),
57
+ );
58
+ _controller.addListener(_handleTabChange);
59
+ }
60
+ }
61
+
62
+ @override
63
+ void dispose() {
64
+ _controller.removeListener(_handleTabChange);
65
+ _controller.dispose();
66
+ super.dispose();
67
+ }
68
+
69
+ @override
70
+ Widget build(BuildContext context) {
71
+ return Container(
72
+ color: widget.backgroundColor ?? Colors.transparent,
73
+ child: TabBar(
74
+ controller: _controller,
75
+ isScrollable: !widget.fullWidth,
76
+ indicatorSize: TabBarIndicatorSize.label,
77
+ indicator: UnderlineTabIndicator(
78
+ borderSide: BorderSide(
79
+ color: widget.indicatorColor ?? CronixColors.accent,
80
+ width: 2,
81
+ ),
82
+ ),
83
+ indicatorWeight: 2,
84
+ labelColor: CronixColors.text,
85
+ unselectedLabelColor: CronixColors.textSecondary,
86
+ labelStyle: const TextStyle(
87
+ fontSize: 14,
88
+ fontWeight: FontWeight.w500,
89
+ ),
90
+ unselectedLabelStyle: const TextStyle(
91
+ fontSize: 14,
92
+ fontWeight: FontWeight.w400,
93
+ ),
94
+ tabs: widget.tabs.map((tab) => Tab(text: tab)).toList(),
95
+ ),
96
+ );
97
+ }
98
+ }
99
+
100
+ class CnTabView extends StatefulWidget {
101
+ final List<Widget> children;
102
+ final int initialIndex;
103
+
104
+ const CnTabView({
105
+ super.key,
106
+ required this.children,
107
+ this.initialIndex = 0,
108
+ });
109
+
110
+ @override
111
+ State<CnTabView> createState() => _CnTabViewState();
112
+ }
113
+
114
+ class _CnTabViewState extends State<CnTabView> with SingleTickerProviderStateMixin {
115
+ late TabController _controller;
116
+
117
+ @override
118
+ void initState() {
119
+ super.initState();
120
+ _controller = TabController(
121
+ length: widget.children.length,
122
+ vsync: this,
123
+ initialIndex: widget.initialIndex,
124
+ );
125
+ }
126
+
127
+ @override
128
+ void dispose() {
129
+ _controller.dispose();
130
+ super.dispose();
131
+ }
132
+
133
+ @override
134
+ Widget build(BuildContext context) {
135
+ return TabBarView(
136
+ controller: _controller,
137
+ children: widget.children,
138
+ );
139
+ }
140
+ }
141
+
142
+ class CnTabsWithContent extends StatefulWidget {
143
+ final List<TabData> tabs;
144
+ final int initialIndex;
145
+ final ValueChanged<int>? onChanged;
146
+
147
+ const CnTabsWithContent({
148
+ super.key,
149
+ required this.tabs,
150
+ this.initialIndex = 0,
151
+ this.onChanged,
152
+ });
153
+
154
+ @override
155
+ State<CnTabsWithContent> createState() => _CnTabsWithContentState();
156
+ }
157
+
158
+ class _CnTabsWithContentState extends State<CnTabsWithContent>
159
+ with SingleTickerProviderStateMixin {
160
+ late TabController _controller;
161
+
162
+ @override
163
+ void initState() {
164
+ super.initState();
165
+ _controller = TabController(
166
+ length: widget.tabs.length,
167
+ vsync: this,
168
+ initialIndex: widget.initialIndex,
169
+ );
170
+ _controller.addListener(() {
171
+ if (!_controller.indexIsChanging) {
172
+ widget.onChanged?.call(_controller.index);
173
+ }
174
+ });
175
+ }
176
+
177
+ @override
178
+ void dispose() {
179
+ _controller.dispose();
180
+ super.dispose();
181
+ }
182
+
183
+ @override
184
+ Widget build(BuildContext context) {
185
+ return Column(
186
+ children: [
187
+ Container(
188
+ decoration: const BoxDecoration(
189
+ border: Border(
190
+ bottom: BorderSide(color: CronixColors.border),
191
+ ),
192
+ ),
193
+ child: TabBar(
194
+ controller: _controller,
195
+ indicator: UnderlineTabIndicator(
196
+ borderSide: BorderSide(
197
+ color: CronixColors.accent,
198
+ width: 2,
199
+ ),
200
+ ),
201
+ labelColor: CronixColors.text,
202
+ unselectedLabelColor: CronixColors.textSecondary,
203
+ labelStyle: const TextStyle(
204
+ fontSize: 14,
205
+ fontWeight: FontWeight.w500,
206
+ ),
207
+ tabs: widget.tabs.map((tab) => Tab(text: tab.label)).toList(),
208
+ ),
209
+ ),
210
+ Expanded(
211
+ child: TabBarView(
212
+ controller: _controller,
213
+ children: widget.tabs.map((tab) => tab.content).toList(),
214
+ ),
215
+ ),
216
+ ],
217
+ );
218
+ }
219
+ }
220
+
221
+ class TabData {
222
+ final String label;
223
+ final Widget content;
224
+
225
+ const TabData({
226
+ required this.label,
227
+ required this.content,
228
+ });
229
+ }
@@ -0,0 +1,185 @@
1
+ import 'package:flutter/material.dart';
2
+ import '../tokens/colors.dart';
3
+ import '../tokens/spacing.dart';
4
+
5
+ class CnTag extends StatelessWidget {
6
+ final String label;
7
+ final IconData? icon;
8
+ final VoidCallback? onRemove;
9
+ final VoidCallback? onTap;
10
+ final Color? backgroundColor;
11
+ final Color? textColor;
12
+ final double fontSize;
13
+ final bool outlined;
14
+
15
+ const CnTag({
16
+ super.key,
17
+ required this.label,
18
+ this.icon,
19
+ this.onRemove,
20
+ this.onTap,
21
+ this.backgroundColor,
22
+ this.textColor,
23
+ this.fontSize = 12,
24
+ this.outlined = false,
25
+ });
26
+
27
+ @override
28
+ Widget build(BuildContext context) {
29
+ Widget content = Container(
30
+ padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
31
+ decoration: BoxDecoration(
32
+ color: outlined ? null : (backgroundColor ?? CronixColors.surfaceLight),
33
+ borderRadius: CronixRadius.radiusFull,
34
+ border: outlined
35
+ ? Border.all(color: backgroundColor ?? CronixColors.border)
36
+ : null,
37
+ ),
38
+ child: Row(
39
+ mainAxisSize: MainAxisSize.min,
40
+ children: [
41
+ if (icon != null) ...[
42
+ Icon(icon, size: fontSize, color: textColor ?? CronixColors.text),
43
+ const SizedBox(width: 4),
44
+ ],
45
+ Text(
46
+ label,
47
+ style: TextStyle(
48
+ color: textColor ?? CronixColors.text,
49
+ fontSize: fontSize,
50
+ fontWeight: FontWeight.w500,
51
+ ),
52
+ ),
53
+ if (onRemove != null) ...[
54
+ const SizedBox(width: 6),
55
+ GestureDetector(
56
+ onTap: onRemove,
57
+ child: Icon(
58
+ Icons.close,
59
+ size: fontSize + 2,
60
+ color: textColor ?? CronixColors.textSecondary,
61
+ ),
62
+ ),
63
+ ],
64
+ ],
65
+ ),
66
+ );
67
+
68
+ if (onTap != null) {
69
+ return InkWell(
70
+ onTap: onTap,
71
+ borderRadius: CronixRadius.radiusFull,
72
+ child: content,
73
+ );
74
+ }
75
+
76
+ return content;
77
+ }
78
+ }
79
+
80
+ class CnTagGroup extends StatelessWidget {
81
+ final List<CnTag> tags;
82
+ final WrapAlignment alignment;
83
+ final double spacing;
84
+ final double runSpacing;
85
+
86
+ const CnTagGroup({
87
+ super.key,
88
+ required this.tags,
89
+ this.alignment = WrapAlignment.start,
90
+ this.spacing = 8,
91
+ this.runSpacing = 8,
92
+ });
93
+
94
+ @override
95
+ Widget build(BuildContext context) {
96
+ return Wrap(
97
+ alignment: alignment,
98
+ spacing: spacing,
99
+ runSpacing: runSpacing,
100
+ children: tags,
101
+ );
102
+ }
103
+ }
104
+
105
+ class CnTagInput extends StatefulWidget {
106
+ final List<String> initialTags;
107
+ final ValueChanged<List<String>>? onChanged;
108
+ final String? placeholder;
109
+ final int? maxTags;
110
+
111
+ const CnTagInput({
112
+ super.key,
113
+ this.initialTags = const [],
114
+ this.onChanged,
115
+ this.placeholder,
116
+ this.maxTags,
117
+ });
118
+
119
+ @override
120
+ State<CnTagInput> createState() => _CnTagInputState();
121
+ }
122
+
123
+ class _CnTagInputState extends State<CnTagInput> {
124
+ late List<String> _tags;
125
+ final TextEditingController _controller = TextEditingController();
126
+
127
+ @override
128
+ void initState() {
129
+ super.initState();
130
+ _tags = List.from(widget.initialTags);
131
+ }
132
+
133
+ void _addTag(String tag) {
134
+ if (tag.isEmpty || _tags.contains(tag)) return;
135
+ if (widget.maxTags != null && _tags.length >= widget.maxTags!) return;
136
+
137
+ setState(() => _tags.add(tag));
138
+ _controller.clear();
139
+ widget.onChanged?.call(_tags);
140
+ }
141
+
142
+ void _removeTag(String tag) {
143
+ setState(() => _tags.remove(tag));
144
+ widget.onChanged?.call(_tags);
145
+ }
146
+
147
+ @override
148
+ Widget build(BuildContext context) {
149
+ return Container(
150
+ padding: const EdgeInsets.all(8),
151
+ decoration: BoxDecoration(
152
+ color: CronixColors.surface,
153
+ borderRadius: CronixRadius.radiusMD,
154
+ border: Border.all(color: CronixColors.border),
155
+ ),
156
+ child: Wrap(
157
+ spacing: 8,
158
+ runSpacing: 8,
159
+ children: [
160
+ ..._tags.map((tag) => CnTag(
161
+ label: tag,
162
+ onRemove: () => _removeTag(tag),
163
+ )),
164
+ if (widget.maxTags == null || _tags.length < widget.maxTags!)
165
+ SizedBox(
166
+ width: 120,
167
+ height: 28,
168
+ child: TextField(
169
+ controller: _controller,
170
+ onSubmitted: _addTag,
171
+ style: const TextStyle(color: CronixColors.text, fontSize: 12),
172
+ decoration: InputDecoration(
173
+ hintText: widget.placeholder ?? 'Add tag...',
174
+ hintStyle: const TextStyle(color: CronixColors.textMuted),
175
+ border: InputBorder.none,
176
+ contentPadding: EdgeInsets.zero,
177
+ isDense: true,
178
+ ),
179
+ ),
180
+ ),
181
+ ],
182
+ ),
183
+ );
184
+ }
185
+ }
@@ -0,0 +1,143 @@
1
+ import 'package:flutter/material.dart';
2
+ import '../tokens/colors.dart';
3
+ import '../tokens/spacing.dart';
4
+
5
+ class CnTextarea extends StatefulWidget {
6
+ final String? label;
7
+ final String? placeholder;
8
+ final String? initialValue;
9
+ final TextEditingController? controller;
10
+ final bool enabled;
11
+ final int minLines;
12
+ final int maxLines;
13
+ final int? maxLength;
14
+ final ValueChanged<String>? onChanged;
15
+ final String? Function(String?)? validator;
16
+
17
+ const CnTextarea({
18
+ super.key,
19
+ this.label,
20
+ this.placeholder,
21
+ this.initialValue,
22
+ this.controller,
23
+ this.enabled = true,
24
+ this.minLines = 3,
25
+ this.maxLines = 5,
26
+ this.maxLength,
27
+ this.onChanged,
28
+ this.validator,
29
+ });
30
+
31
+ @override
32
+ State<CnTextarea> createState() => _CnTextareaState();
33
+ }
34
+
35
+ class _CnTextareaState extends State<CnTextarea> {
36
+ late TextEditingController _controller;
37
+ String? _errorText;
38
+ int _currentLength = 0;
39
+
40
+ @override
41
+ void initState() {
42
+ super.initState();
43
+ _controller = widget.controller ??
44
+ TextEditingController(text: widget.initialValue);
45
+ _currentLength = _controller.text.length;
46
+ _controller.addListener(_updateLength);
47
+ }
48
+
49
+ void _updateLength() {
50
+ setState(() => _currentLength = _controller.text.length);
51
+ }
52
+
53
+ @override
54
+ void dispose() {
55
+ _controller.removeListener(_updateLength);
56
+ if (widget.controller == null) {
57
+ _controller.dispose();
58
+ }
59
+ super.dispose();
60
+ }
61
+
62
+ @override
63
+ Widget build(BuildContext context) {
64
+ return Column(
65
+ crossAxisAlignment: CrossAxisAlignment.start,
66
+ mainAxisSize: MainAxisSize.min,
67
+ children: [
68
+ if (widget.label != null) ...[
69
+ Text(
70
+ widget.label!,
71
+ style: const TextStyle(
72
+ color: CronixColors.text,
73
+ fontSize: 14,
74
+ fontWeight: FontWeight.w500,
75
+ ),
76
+ ),
77
+ const SizedBox(height: 8),
78
+ ],
79
+ TextFormField(
80
+ controller: _controller,
81
+ enabled: widget.enabled,
82
+ minLines: widget.minLines,
83
+ maxLines: widget.maxLines,
84
+ maxLength: widget.maxLength,
85
+ onChanged: widget.onChanged,
86
+ validator: (value) {
87
+ if (widget.validator != null) {
88
+ final error = widget.validator!(value);
89
+ setState(() => _errorText = error);
90
+ return error;
91
+ }
92
+ return null;
93
+ },
94
+ style: const TextStyle(color: CronixColors.text),
95
+ decoration: InputDecoration(
96
+ hintText: widget.placeholder,
97
+ hintStyle: const TextStyle(color: CronixColors.textMuted),
98
+ filled: true,
99
+ fillColor: widget.enabled ? CronixColors.surface : CronixColors.surfaceLight,
100
+ contentPadding: const EdgeInsets.all(12),
101
+ border: OutlineInputBorder(
102
+ borderRadius: CronixRadius.radiusMD,
103
+ borderSide: const BorderSide(color: CronixColors.border),
104
+ ),
105
+ enabledBorder: OutlineInputBorder(
106
+ borderRadius: CronixRadius.radiusMD,
107
+ borderSide: const BorderSide(color: CronixColors.border),
108
+ ),
109
+ focusedBorder: OutlineInputBorder(
110
+ borderRadius: CronixRadius.radiusMD,
111
+ borderSide: const BorderSide(color: CronixColors.accent, width: 2),
112
+ ),
113
+ errorBorder: OutlineInputBorder(
114
+ borderRadius: CronixRadius.radiusMD,
115
+ borderSide: const BorderSide(color: CronixColors.error),
116
+ ),
117
+ counterText: '',
118
+ ),
119
+ ),
120
+ if (widget.maxLength != null)
121
+ Align(
122
+ alignment: Alignment.centerRight,
123
+ child: Text(
124
+ '$_currentLength/${widget.maxLength}',
125
+ style: TextStyle(
126
+ color: _currentLength >= widget.maxLength!
127
+ ? CronixColors.error
128
+ : CronixColors.textMuted,
129
+ fontSize: 12,
130
+ ),
131
+ ),
132
+ ),
133
+ if (_errorText != null) ...[
134
+ const SizedBox(height: 4),
135
+ Text(
136
+ _errorText!,
137
+ style: const TextStyle(color: CronixColors.error, fontSize: 12),
138
+ ),
139
+ ],
140
+ ],
141
+ );
142
+ }
143
+ }