Skip to content

Commit e3071db

Browse files
committed
feat: implement select theme
1 parent d14650f commit e3071db

5 files changed

Lines changed: 126 additions & 39 deletions

File tree

lib/src/application/components/select.dart

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import 'dart:async';
22
import 'dart:io';
33

44
import 'package:commander_ui/src/application/terminals/terminal.dart';
5+
import 'package:commander_ui/src/application/themes/default_select_theme.dart';
56
import 'package:commander_ui/src/application/utils/terminal_tools.dart';
67
import 'package:commander_ui/src/domains/models/component.dart';
8+
import 'package:commander_ui/src/domains/themes/select_theme.dart';
79
import 'package:commander_ui/src/io.dart';
810
import 'package:mansion/mansion.dart';
911

@@ -12,6 +14,7 @@ final class Select<T> with TerminalTools implements Component<Future<T>> {
1214
final _completer = Completer<T>();
1315

1416
final Terminal _terminal;
17+
SelectTheme _theme;
1518
late int _displayCount;
1619

1720
int _currentIndex = 0;
@@ -31,14 +34,15 @@ final class Select<T> with TerminalTools implements Component<Future<T>> {
3134
int displayCount = 5,
3235
T? defaultValue,
3336
String placeholder = '',
34-
String Function(T)? onDisplay}) {
35-
_message = message;
36-
_options = options;
37-
_displayCount = displayCount;
38-
_defaultValue = defaultValue;
39-
_placeholder = placeholder;
40-
_onDisplay = onDisplay;
41-
37+
String Function(T)? onDisplay,
38+
SelectTheme? theme})
39+
: _message = message,
40+
_options = options,
41+
_displayCount = displayCount,
42+
_defaultValue = defaultValue,
43+
_placeholder = placeholder,
44+
_onDisplay = onDisplay,
45+
_theme = theme ?? DefaultSelectTheme() {
4246
if (_defaultValue case T value) {
4347
_currentIndex = _options.indexOf(value);
4448
}
@@ -65,22 +69,18 @@ final class Select<T> with TerminalTools implements Component<Future<T>> {
6569
_currentIndex = _currentIndex - 1;
6670
_render();
6771
}
68-
} else if (key.controlChar == ControlCharacter.arrowDown ||
69-
key.char == 'j') {
72+
} else if (key.controlChar == ControlCharacter.arrowDown || key.char == 'j') {
7073
if (_currentIndex < _filteredOptions.length - 1) {
7174
_currentIndex = _currentIndex + 1;
7275
_render();
7376
}
74-
} else if ([ControlCharacter.ctrlJ, ControlCharacter.ctrlM]
75-
.contains(key.controlChar)) {
77+
} else if ([ControlCharacter.ctrlJ, ControlCharacter.ctrlM].contains(key.controlChar)) {
7678
_onSubmit();
7779
} else {
78-
if (RegExp(r'^[\p{L}\p{N}\p{P}\s\x7F]*$', unicode: true)
79-
.hasMatch(key.char)) {
80+
if (RegExp(r'^[\p{L}\p{N}\p{P}\s\x7F]*$', unicode: true).hasMatch(key.char)) {
8081
_currentIndex = 0;
8182

82-
if (key.controlChar == ControlCharacter.backspace &&
83-
_filter.isNotEmpty) {
83+
if (key.controlChar == ControlCharacter.backspace && _filter.isNotEmpty) {
8484
_filter = _filter.substring(0, _filter.length - 1);
8585
} else if (key.controlChar != ControlCharacter.backspace) {
8686
_filter = _filter + key.char;
@@ -97,9 +97,7 @@ final class Select<T> with TerminalTools implements Component<Future<T>> {
9797
List<T> _filterOptions() {
9898
return _options.where((item) {
9999
final value = _onDisplay?.call(item) ?? item.toString();
100-
return _options.isNotEmpty
101-
? value.toLowerCase().contains(_filter.toLowerCase())
102-
: true;
100+
return _options.isNotEmpty ? value.toLowerCase().contains(_filter.toLowerCase()) : true;
103101
}).toList();
104102
}
105103

@@ -110,32 +108,30 @@ final class Select<T> with TerminalTools implements Component<Future<T>> {
110108
final buffer = StringBuffer();
111109

112110
buffer.writeAnsiAll([
113-
SetStyles(Style.foreground(Color.yellow)),
114-
Print('?'),
111+
..._theme.askPrefixColor,
112+
Print(_theme.askPrefix),
115113
SetStyles.reset,
116114
Print(' $_message '),
117-
SetStyles(Style.foreground(Color.brightBlack)),
118-
Print(_filter.isEmpty ? _placeholder : _filter),
115+
..._filter.isEmpty ? _theme.placeholderColorMessage : _theme.filterColorMessage,
116+
_filter.isEmpty ? Print(_placeholder) : Print(_filter),
119117
SetStyles.reset,
120118
AsciiControl.lineFeed,
121119
]);
122120

123121
_filteredOptions.clear();
124122
_filteredOptions.addAll(_filterOptions());
125123

126-
int start = _currentIndex - _displayCount >= 0
127-
? _currentIndex - _displayCount + 1
128-
: 0;
124+
int start = _currentIndex - _displayCount >= 0 ? _currentIndex - _displayCount + 1 : 0;
129125

130126
for (final choice in _filteredOptions.skip(start).take(_displayCount)) {
131127
final isCurrent = _filteredOptions.indexOf(choice) == _currentIndex;
132128
if (isCurrent) {
133129
buffer.writeAnsiAll([
134-
SetStyles(Style.foreground(Color.brightGreen)),
135-
Print('❯'),
130+
..._theme.selectedIconColor,
131+
Print(_theme.selectedIcon),
136132
]);
137133
} else {
138-
buffer.writeAnsi(Print(' '));
134+
buffer.writeAnsi(Print(_theme.unselectedIcon));
139135
}
140136

141137
buffer.writeAnsiAll([
@@ -147,8 +143,8 @@ final class Select<T> with TerminalTools implements Component<Future<T>> {
147143

148144
buffer.writeAnsiAll([
149145
AsciiControl.lineFeed,
150-
SetStyles(Style.foreground(Color.brightBlack)),
151-
Print('(Type to filter, press ↑/↓ to navigate, enter to select)'),
146+
..._theme.helpColorMessage,
147+
Print(_theme.helpMessage),
152148
SetStyles.reset,
153149
AsciiControl.lineFeed,
154150
]);
@@ -171,13 +167,12 @@ final class Select<T> with TerminalTools implements Component<Future<T>> {
171167
showCursor();
172168

173169
buffer.writeAnsiAll([
174-
SetStyles(Style.foreground(Color.green)),
175-
Print('✔'),
170+
..._theme.successPrefixColor,
171+
Print(_theme.successPrefix),
176172
SetStyles.reset,
177173
Print(' $_message '),
178-
SetStyles(Style.foreground(Color.brightBlack)),
179-
Print(
180-
_onDisplay?.call(_selectedOption as T) ?? _selectedOption.toString()),
174+
..._theme.resultMessageColor,
175+
Print(_onDisplay?.call(_selectedOption as T) ?? _selectedOption.toString()),
181176
SetStyles.reset,
182177
AsciiControl.lineFeed,
183178
]);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import 'package:commander_ui/commander_ui.dart';
2+
import 'package:commander_ui/src/domains/themes/select_theme.dart';
3+
4+
final class DefaultSelectTheme implements SelectTheme {
5+
@override
6+
String askPrefix = '?';
7+
8+
@override
9+
String successPrefix = '✔';
10+
11+
@override
12+
String selectedIcon = '❯';
13+
14+
@override
15+
String unselectedIcon = ' ';
16+
17+
@override
18+
String helpMessage = '(Type to filter, press ↑/↓ to navigate, enter to select)';
19+
20+
@override
21+
List<Sequence> selectedIconColor = [SetStyles(Style.foreground(Color.brightGreen))];
22+
23+
@override
24+
List<Sequence> unselectedIconColor = [];
25+
26+
@override
27+
List<Sequence> placeholderColorMessage = [SetStyles(Style.foreground(Color.brightBlack))];
28+
29+
@override
30+
List<Sequence> filterColorMessage = [SetStyles(Style.foreground(Color.brightBlack))];
31+
32+
@override
33+
List<Sequence> helpColorMessage = [SetStyles(Style.foreground(Color.brightBlack))];
34+
35+
@override
36+
List<Sequence> currentLineColor = [SetStyles(Style.foreground(Color.white))];
37+
38+
@override
39+
List<Sequence> selectedLineColor = [SetStyles(Style.foreground(Color.white))];
40+
41+
@override
42+
List<Sequence> defaultLineColor = [SetStyles(Style.foreground(Color.brightBlack))];
43+
44+
@override
45+
List<Sequence> successPrefixColor = [SetStyles(Style.foreground(Color.green))];
46+
47+
@override
48+
List<Sequence> askPrefixColor = [SetStyles(Style.foreground(Color.yellow))];
49+
50+
@override
51+
List<Sequence> resultMessageColor = [SetStyles(Style.foreground(Color.brightBlack))];
52+
}

lib/src/commander.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:commander_ui/src/domains/models/commander_theme.dart';
1414
import 'package:commander_ui/src/domains/models/component_theme.dart';
1515
import 'package:commander_ui/src/domains/themes/ask_theme.dart';
1616
import 'package:commander_ui/src/domains/themes/checkbox_theme.dart';
17+
import 'package:commander_ui/src/domains/themes/select_theme.dart';
1718
import 'package:commander_ui/src/domains/themes/swap_theme.dart';
1819
import 'package:commander_ui/src/level.dart';
1920

@@ -78,14 +79,16 @@ class Commander with TerminalTools {
7879
required List<T> options,
7980
String placeholder = '',
8081
int displayCount = 5,
81-
String Function(T)? onDisplay}) =>
82+
String Function(T)? onDisplay,
83+
SelectTheme? theme}) =>
8284
Select<T>(_terminal,
8385
message: message,
8486
defaultValue: defaultValue,
8587
displayCount: displayCount,
8688
options: options,
8789
placeholder: placeholder,
88-
onDisplay: onDisplay)
90+
onDisplay: onDisplay,
91+
theme: theme ?? _componentTheme.selectTheme)
8992
.handle();
9093

9194
Future<List<T>> checkbox<T>(String message,
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import 'package:commander_ui/src/domains/themes/ask_theme.dart';
22
import 'package:commander_ui/src/domains/themes/checkbox_theme.dart';
3+
import 'package:commander_ui/src/domains/themes/select_theme.dart';
34
import 'package:commander_ui/src/domains/themes/swap_theme.dart';
45

56
final class ComponentTheme {
67
final AskTheme? askTheme;
78
final CheckboxTheme? checkboxTheme;
89
final SwapTheme? switchTheme;
10+
final SelectTheme? selectTheme;
911

10-
ComponentTheme({this.askTheme, this.checkboxTheme, this.switchTheme});
12+
ComponentTheme({this.askTheme, this.checkboxTheme, this.switchTheme, this.selectTheme});
1113
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import 'package:commander_ui/commander_ui.dart';
2+
3+
abstract interface class SelectTheme {
4+
String get askPrefix;
5+
6+
String get successPrefix;
7+
8+
String get selectedIcon;
9+
10+
String get unselectedIcon;
11+
12+
String get helpMessage;
13+
14+
List<Sequence> get selectedIconColor;
15+
16+
List<Sequence> get unselectedIconColor;
17+
18+
List<Sequence> get placeholderColorMessage;
19+
20+
List<Sequence> get helpColorMessage;
21+
22+
List<Sequence> get filterColorMessage;
23+
24+
List<Sequence> get currentLineColor;
25+
26+
List<Sequence> get selectedLineColor;
27+
28+
List<Sequence> get defaultLineColor;
29+
30+
List<Sequence> get successPrefixColor;
31+
32+
List<Sequence> get askPrefixColor;
33+
34+
List<Sequence> get resultMessageColor;
35+
}

0 commit comments

Comments
 (0)