artisanal 0.1.0-dev+3
artisanal: ^0.1.0-dev+3 copied to clipboard
A full-stack terminal toolkit for Dart featuring Lip Gloss styling, Bubble Tea TUI architecture, and Ultraviolet rendering.
import 'dart:async';
import 'dart:io' as dartio;
import 'package:artisanal/artisanal.dart';
import 'package:artisanal/tui.dart';
// #region verbosity_usage
void demonstrateVerbosity(Console console) {
console.writeln('This is a normal message');
console.info('This is an info message (shown in normal/verbose/debug)');
// The following only show if verbosity is set appropriately
console.verbose('This is a verbose message (shown in verbose/debug)');
console.debug('This is a debug message (only shown in debug)');
}
// #endregion
// #region ansi_usage
void demonstrateAnsi() {
dartio.stdout.write(Ansi.cursorTo(0, 0));
dartio.stdout.write(Ansi.bold);
dartio.stdout.write('Top Left Bold');
dartio.stdout.write(Ansi.reset);
dartio.stdout.writeln();
}
// #endregion
// #region command_runner_usage
Future<void> main(List<String> args) async {
final runner = CommandRunner<void>('artisanal-demo', 'artisanal demo CLI')
..addCommand(DemoCommand())
..addCommand(UiTaskCommand())
..addCommand(UiTableCommand())
..addCommand(UiPromptsCommand())
..addCommand(UiProgressCommand())
..addCommand(UiComponentsCommand())
..addCommand(UiSecretCommand())
..addCommand(UiSelectCommand())
..addCommand(UiMultiSelectCommand())
..addCommand(UiSpinCommand())
..addCommand(UiSpinnerCommand())
..addCommand(UiPanelCommand())
..addCommand(UiTreeCommand())
..addCommand(UiSearchCommand())
..addCommand(UiPauseCommand())
..addCommand(UiChalkCommand())
..addCommand(UiValidatorsCommand())
..addCommand(UiExceptionCommand())
..addCommand(UiHorizontalTableCommand())
..addCommand(UiPasswordCommand())
..addCommand(UiBlockCommand())
..addCommand(UiColumnsCommand())
..addCommand(UiTerminalCommand())
..addCommand(UiAnticipateCommand())
..addCommand(UiTextareaCommand())
..addCommand(UiWizardCommand())
..addCommand(UiLinkCommand())
..addCommand(UiComponentSystemCommand())
..addCommand(UiAllCommand());
await runner.run(args);
}
// #endregion
// #region command_definition_usage
class DemoCommand extends Command<void> {
@override
String get name => 'demo';
@override
String get description =>
'Showcase basic output helpers (title/section/blocks/listing).';
@override
Future<void> run() async {
io.title('artisanal');
io.section('Messages');
io.info('Info message');
io.success('Success message');
io.warning('Warning message');
io.error('Error message');
io.note('Note message');
io.caution('Caution message');
io.section('Listing');
io.listing(['one', 'two', 'three']);
io.section('Two Column Detail');
io.twoColumnDetail('Driver', 'sqlite');
io.twoColumnDetail('Database', 'example.sqlite');
io.newLine();
io.section('Task');
await io.task(
'Run a sample task',
run: () async {
await Future<void>.delayed(const Duration(milliseconds: 40));
return TaskResult.success;
},
);
}
}
// #endregion
class UiTaskCommand extends Command<void> {
UiTaskCommand() {
argParser
..addFlag('fail', negatable: false, help: 'Return FAIL instead of DONE.')
..addFlag(
'skip',
negatable: false,
help: 'Return SKIPPED instead of DONE.',
);
}
@override
String get name => 'ui:task';
@override
String get description => 'Render a Laravel-like task line.';
@override
Future<void> run() async {
final fail = argResults?['fail'] == true;
final skip = argResults?['skip'] == true;
await io.task(
'Build something',
run: () async {
await Future<void>.delayed(const Duration(milliseconds: 80));
if (fail) return TaskResult.failure;
if (skip) return TaskResult.skipped;
return TaskResult.success;
},
);
}
}
class UiTableCommand extends Command<void> {
@override
String get name => 'ui:table';
@override
String get description => 'Render a simple ASCII table.';
@override
Future<void> run() async {
io.table(
headers: ['id', 'name', 'status'],
rows: [
[
1,
'create_users_table',
io.style.foreground(Colors.success).render('DONE'),
],
[
2,
'add_posts_table',
io.style.foreground(Colors.warning).render('PENDING'),
],
],
);
}
}
class UiPromptsCommand extends Command<void> {
UiPromptsCommand() {
argParser.addFlag(
'defaults',
negatable: false,
help: 'Use defaults so --no-interaction can run.',
);
}
@override
String get name => 'ui:prompts';
@override
String get description => 'Demonstrate confirm/ask/choice prompts.';
@override
Future<void> run() async {
final useDefaults = argResults?['defaults'] == true;
io.title('Prompts');
final confirmed = io.confirm('Continue?', defaultValue: true);
io.twoColumnDetail('confirm', confirmed.toString());
final name = io.ask(
'Your name',
defaultValue: useDefaults ? 'Anonymous' : null,
validator: (value) =>
value.trim().isEmpty ? 'Name cannot be empty' : null,
);
io.twoColumnDetail('ask', name);
final selected = io.choice(
'Pick a driver',
choices: const ['sqlite', 'postgres', 'mysql'],
defaultIndex: useDefaults ? 0 : null,
);
io.twoColumnDetail('choice', selected.toString());
}
}
class UiProgressCommand extends Command<void> {
UiProgressCommand() {
argParser.addOption(
'count',
defaultsTo: '25',
help: 'How many steps to run.',
);
}
@override
String get name => 'ui:progress';
@override
String get description => 'Render a simple progress bar.';
@override
Future<void> run() async {
final countRaw = argResults?['count'] as String? ?? '25';
final count = int.tryParse(countRaw) ?? 25;
for (final _ in io.progressIterate(
List<int>.filled(count, 0),
max: count,
)) {
await Future<void>.delayed(const Duration(milliseconds: 20));
}
}
}
/// Demonstrate the components facade (Laravel-style higher-level components).
class UiComponentsCommand extends Command<void> {
@override
String get name => 'ui:components';
@override
String get description =>
'Demonstrate the components facade (bulletList, definitionList, rule, etc).';
@override
Future<void> run() async {
io.title('Components Facade');
io.section('Bullet List');
io.components.bulletList([
'First item in the list',
'Second item in the list',
'Third item in the list',
]);
io.section('Definition List');
io.components.definitionList({
'Application Name': 'artisanal Demo',
'Version': '1.0.0',
'Environment': 'development',
'Debug Mode': 'enabled',
});
io.section('Horizontal Rule');
io.components.rule();
io.components.rule('Section Divider');
io.section('Line Separator');
io.components.line();
io.components.line(40);
io.section('Titled Messages');
io.components.info('Information', 'This is an informational message.');
io.components.success('Success', 'Operation completed successfully!');
io.components.warn('Warning', 'Please review before proceeding.');
io.components.error('Error', 'Something went wrong.');
io.section('Alert Box');
io.components.alert('This is an important alert message!');
io.section('Two Column Detail');
io.components.twoColumnDetail('Database', 'SQLite');
io.components.twoColumnDetail('Host', 'localhost');
io.components.twoColumnDetail('Port', '5432');
io.section('Task (via components)');
await io.components.task(
'Running migrations',
run: () async {
await Future<void>.delayed(const Duration(milliseconds: 50));
return TaskResult.success;
},
);
}
}
/// Demonstrate secret/password input (no echo).
class UiSecretCommand extends Command<void> {
UiSecretCommand() {
argParser.addFlag(
'defaults',
negatable: false,
help: 'Use fallback value for non-interactive mode.',
);
}
@override
String get name => 'ui:secret';
@override
String get description => 'Demonstrate secret/password input (no echo).';
@override
Future<void> run() async {
final useDefaults = argResults?['defaults'] == true;
io.title('Secret Input');
io.text('Characters will not be echoed as you type.');
io.newLine();
final password = await io.secret(
'Enter your password',
fallback: useDefaults ? '***hidden***' : null,
);
io.newLine();
io.success('Password received (${password.length} characters)');
io.twoColumnDetail('Length', '${password.length} chars');
}
}
/// Demonstrate interactive single-select with arrow-key navigation.
class UiSelectCommand extends Command<void> {
UiSelectCommand() {
argParser.addFlag(
'defaults',
negatable: false,
help: 'Use default selection for non-interactive mode.',
);
}
@override
String get name => 'ui:select';
@override
String get description =>
'Demonstrate interactive single-select with arrow-key navigation.';
@override
Future<void> run() async {
final useDefaults = argResults?['defaults'] == true;
io.title('Interactive Single Select');
io.text('Use arrow keys to navigate, Enter to select, q to cancel.');
io.newLine();
final databases = [
'SQLite (lightweight, file-based)',
'PostgreSQL (powerful, ACID-compliant)',
'MySQL (popular, widely deployed)',
'MongoDB (document-oriented, NoSQL)',
];
final selected = await io.selectChoice(
'Choose your database',
choices: databases,
defaultIndex: useDefaults ? 1 : 0,
);
io.newLine();
if (selected != null) {
io.success('Selected: $selected');
} else {
io.warning('Selection cancelled');
}
}
}
/// Demonstrate interactive multi-select with arrow-key navigation.
class UiMultiSelectCommand extends Command<void> {
UiMultiSelectCommand() {
argParser.addFlag(
'defaults',
negatable: false,
help: 'Use default selections for non-interactive mode.',
);
}
@override
String get name => 'ui:multiselect';
@override
String get description =>
'Demonstrate interactive multi-select with arrow-key navigation.';
@override
Future<void> run() async {
final useDefaults = argResults?['defaults'] == true;
io.title('Interactive Multi Select');
io.text('Use arrow keys to navigate, Space to toggle, Enter to confirm.');
io.newLine();
final features = [
'Authentication',
'Database ORM',
'REST API',
'GraphQL',
'WebSocket Support',
'Email Service',
'Queue System',
'Cache Layer',
];
final selected = await io.multiSelectChoice(
'Select features to enable',
choices: features,
defaultSelected: useDefaults ? [0, 1, 2] : [],
);
io.newLine();
if (selected.isEmpty) {
io.warning('No features selected');
} else {
io.success('Selected ${selected.length} feature(s):');
io.components.bulletList(selected);
}
}
}
/// Demonstrate the spin component (spinner with success/fail indicator).
class UiSpinCommand extends Command<void> {
UiSpinCommand() {
argParser.addFlag('fail', negatable: false, help: 'Simulate a failure.');
}
@override
String get name => 'ui:spin';
@override
String get description =>
'Demonstrate the spin component (processing indicator).';
@override
Future<void> run() async {
final shouldFail = argResults?['fail'] == true;
io.title('Spin Component');
io.text('Shows a processing indicator with success/fail status.');
io.newLine();
try {
final result = await io.components.spin(
'Connecting to database',
run: () async {
await Future<void>.delayed(const Duration(milliseconds: 500));
return 'Connection established';
},
);
io.twoColumnDetail('Result', result);
} catch (e) {
io.error('Connection failed');
}
try {
await io.components.spin(
'Running migrations',
run: () async {
await Future<void>.delayed(const Duration(milliseconds: 300));
if (shouldFail) {
throw Exception('Migration failed');
}
return null;
},
);
io.success('Migrations completed');
} catch (e) {
io.error('Migration error: $e');
}
try {
final count = await io.components.spin(
'Seeding database',
run: () async {
await Future<void>.delayed(const Duration(milliseconds: 400));
return 42;
},
);
io.twoColumnDetail('Records seeded', count.toString());
} catch (e) {
io.error('Seeding failed');
}
}
}
/// Demonstrate animated spinner.
class UiSpinnerCommand extends Command<void> {
UiSpinnerCommand() {
argParser.addOption(
'frames',
abbr: 'f',
defaultsTo: 'dots',
help: 'Spinner frame style (dots, line, circle, arc, arrows)',
);
}
@override
String get name => 'ui:spinner';
@override
String get description =>
'Demonstrate animated spinner with different styles.';
@override
Future<void> run() async {
final frameStyle = argResults?['frames'] as String? ?? 'dots';
final spinner = switch (frameStyle) {
'line' => Spinners.line,
'circle' => Spinners.circle,
'arc' => Spinners.arc,
'arrows' => Spinners.arrows,
_ => Spinners.miniDot,
};
io.title('Animated Spinner');
io.text('This demonstrates a real animated spinner.');
io.newLine();
final terminal = StdioTerminal(stdout: dartio.stdout, stdin: dartio.stdin);
final result = await runSpinnerTask(
message: 'Processing your request...',
spinner: spinner,
terminal: terminal,
task: () async {
await Future<void>.delayed(const Duration(seconds: 2));
return 'Completed successfully!';
},
);
io.success('Result: $result');
}
}
/// Demonstrate panels with box drawing.
class UiPanelCommand extends Command<void> {
UiPanelCommand() {
argParser.addOption(
'style',
abbr: 's',
defaultsTo: 'rounded',
help: 'Box style (rounded, single, double, heavy, ascii)',
);
}
@override
String get name => 'ui:panel';
@override
String get description => 'Demonstrate boxed panels with different styles.';
@override
Future<void> run() async {
final boxStyle = argResults?['style'] as String? ?? 'rounded';
final chars = switch (boxStyle) {
'single' => PanelBoxChars.single,
'double' => PanelBoxChars.double,
'heavy' => PanelBoxChars.heavy,
'ascii' => PanelBoxChars.ascii,
_ => PanelBoxChars.rounded,
};
final renderConfig = RenderConfig.fromRenderer(
defaultRenderer,
terminalWidth: io.terminalWidth,
);
io.title('Panels');
// Simple panel
PanelComponent(
content:
'This is a simple panel with some content.\nIt can have multiple lines.',
title: 'Info',
chars: chars,
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
// Success panel
PanelComponent(
content: 'Your operation completed successfully!',
title: 'Success',
chars: chars,
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
// Warning panel
PanelComponent(
content: 'Please review your configuration before proceeding.',
title: 'Warning',
titleAlign: PanelAlignment.center,
chars: chars,
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
// Columns demo
io.section('Multi-Column Layout');
ColumnsComponent(
items: [
'apple',
'banana',
'cherry',
'date',
'elderberry',
'fig',
'grape',
'honeydew',
'kiwi',
'lemon',
'mango',
'nectarine',
],
columnCount: 4,
renderConfig: renderConfig,
).writelnTo(io);
}
}
/// Demonstrate tree rendering.
class UiTreeCommand extends Command<void> {
@override
String get name => 'ui:tree';
@override
String get description => 'Demonstrate tree structure rendering.';
@override
Future<void> run() async {
io.title('Tree Structure');
final renderConfig = RenderConfig.fromRenderer(
defaultRenderer,
terminalWidth: io.terminalWidth,
);
TreeComponent(
data: {
'lib': {
'src': {
'io': ['console.dart', 'components.dart', 'prompts.dart'],
'output': ['progress_bar.dart', 'spinner.dart', 'table.dart'],
'runner': ['command.dart', 'command_runner.dart'],
'style': ['artisanal_style.dart', 'chalk.dart'],
},
'artisanal.dart': null,
},
'test': ['artisanal_io_test.dart', 'command_runner_test.dart'],
'example': ['main.dart'],
'pubspec.yaml': null,
'README.md': null,
},
renderConfig: renderConfig,
).writelnTo(io);
}
}
/// Demonstrate search prompt.
class UiSearchCommand extends Command<void> {
UiSearchCommand() {
argParser.addFlag(
'defaults',
negatable: false,
help: 'Use default for non-interactive mode.',
);
}
@override
String get name => 'ui:search';
@override
String get description => 'Demonstrate searchable selection prompt.';
@override
Future<void> run() async {
io.title('Search Prompt');
io.text('Type to filter, use arrow keys to navigate, Enter to select.');
io.newLine();
final packages = [
'flutter',
'dart',
'args',
'path',
'http',
'json_annotation',
'build_runner',
'test',
'mockito',
'provider',
'bloc',
'riverpod',
'get_it',
'dio',
'sqflite',
'shared_preferences',
'hive',
'drift',
];
final terminal = StdioTerminal(stdout: dartio.stdout, stdin: dartio.stdin);
final selected = await runSearchPrompt<String>(
SearchModel<String>(items: packages, title: 'Select a package'),
terminal,
);
io.newLine();
if (selected != null) {
io.success('Selected: $selected');
} else {
io.warning('Selection cancelled');
}
}
}
/// Demonstrate pause and countdown.
class UiPauseCommand extends Command<void> {
UiPauseCommand() {
argParser.addFlag(
'countdown',
abbr: 'c',
negatable: false,
help: 'Show countdown instead of pause.',
);
}
@override
String get name => 'ui:pause';
@override
String get description => 'Demonstrate pause and countdown.';
@override
Future<void> run() async {
final showCountdown = argResults?['countdown'] == true;
io.title('Pause & Countdown');
final terminal = StdioTerminal(stdout: dartio.stdout, stdin: dartio.stdin);
if (showCountdown) {
io.text('Starting countdown...');
io.newLine();
await Program(
CountdownModel(
duration: const Duration(seconds: 5),
message: 'Continuing in',
),
options: promptProgramOptions,
terminal: terminal,
).run();
io.success('Countdown complete!');
} else {
io.text('Press any key to continue after this message.');
io.newLine();
await Program(
PauseModel(message: 'Press any key to continue...'),
options: promptProgramOptions,
terminal: terminal,
).run();
io.success('You pressed a key!');
}
}
}
/// Demonstrate advanced chalk styling.
class UiChalkCommand extends Command<void> {
@override
String get name => 'ui:chalk';
@override
String get description => 'Demonstrate advanced color styling with Style.';
@override
Future<void> run() async {
final style = io.style;
io.title('Advanced Styling with Style');
io.section('Basic Colors');
io.writeln(
' ${style.foreground(Colors.red).render('Red')} ${style.foreground(Colors.green).render('Green')} ${style.foreground(Colors.blue).render('Blue')} ${style.foreground(Colors.yellow).render('Yellow')}',
);
io.writeln(
' ${style.foreground(Colors.magenta).render('Magenta')} ${style.foreground(Colors.cyan).render('Cyan')} ${style.foreground(Colors.white).render('White')} ${style.foreground(Colors.black).render('Black (on light bg)')}',
);
io.newLine();
io.section('Bright Colors');
io.writeln(
' ${style.foreground(Colors.brightRed).render('Bright Red')} ${style.foreground(Colors.brightGreen).render('Bright Green')} ${style.foreground(Colors.brightBlue).render('Bright Blue')}',
);
io.writeln(
' ${style.foreground(Colors.brightYellow).render('Bright Yellow')} ${style.foreground(Colors.brightMagenta).render('Bright Magenta')} ${style.foreground(Colors.brightCyan).render('Bright Cyan')}',
);
io.newLine();
io.section('Text Styles');
io.writeln(
' ${style.bold().render('Bold')} ${style.dim().render('Dim')} ${style.italic().render('Italic')} ${style.underline().render('Underline')}',
);
io.writeln(
' ${style.inverse().render('Inverse')} ${style.strikethrough().render('Strikethrough')}',
);
io.newLine();
io.section('Hex Colors');
io.writeln(' ${style.foreground(BasicColor('#ff6b6b')).render('Coral')}');
io.writeln(
' ${style.foreground(BasicColor('#4ecdc4')).render('Turquoise')}',
);
io.writeln(' ${style.foreground(BasicColor('#ffe66d')).render('Lemon')}');
io.newLine();
io.section('Semantic Styles');
io.writeln(
' ${style.foreground(Colors.success).render('Success message')}',
);
io.writeln(' ${style.foreground(Colors.error).render('Error message')}');
io.writeln(
' ${style.foreground(Colors.warning).render('Warning message')}',
);
io.writeln(' ${style.foreground(Colors.info).render('Info message')}');
io.writeln(' ${style.foreground(Colors.muted).render('Muted message')}');
io.writeln(
' ${style.bold().foreground(Colors.yellow).render('Highlighted text')}',
);
}
}
/// Demonstrate validators.
class UiValidatorsCommand extends Command<void> {
UiValidatorsCommand() {
argParser.addFlag(
'defaults',
negatable: false,
help: 'Use defaults for non-interactive mode.',
);
}
@override
String get name => 'ui:validators';
@override
String get description => 'Demonstrate built-in input validators.';
@override
Future<void> run() async {
io.title('Input Validators (powered by Acanthis)');
io.section('Email Validator');
try {
final email = io.ask(
'Enter your email',
defaultValue: 'test@example.com',
validator: Validators.combine([
Validators.required(),
Validators.email(),
]),
);
io.success('Valid email: $email');
} catch (e) {
io.error('Validation failed: $e');
}
io.section('Numeric Validator');
try {
final age = io.ask(
'Enter your age',
defaultValue: '25',
validator: Validators.combine([
Validators.required(),
Validators.integer(min: 0, max: 150),
]),
);
io.success('Valid age: $age');
} catch (e) {
io.error('Validation failed: $e');
}
io.section('URL Validator');
try {
final url = io.ask(
'Enter a URL',
defaultValue: 'https://example.com',
validator: Validators.url(),
);
io.success('Valid URL: $url');
} catch (e) {
io.error('Validation failed: $e');
}
io.section('UUID Validator');
try {
final uuid = io.ask(
'Enter a UUID',
defaultValue: '550e8400-e29b-41d4-a716-446655440000',
validator: Validators.uuid(),
);
io.success('Valid UUID: $uuid');
} catch (e) {
io.error('Validation failed: $e');
}
io.section('Using Acanthis Schema Directly');
io.components.comment('You can also use Acanthis schemas directly:');
io.writeln('');
io.writeln(' // Create schema');
io.writeln(' final schema = string().email().min(5).max(100);');
io.writeln('');
io.writeln(' // Use as validator');
io.writeln(' validator: schema.toValidator()');
io.writeln('');
io.writeln(' // Or parse directly');
io.writeln(' final result = schema.tryParse(value);');
io.writeln(' if (result.success) { ... }');
io.newLine();
io.section('Available Validators');
io.components.bulletList([
'Validators.required() - non-empty input',
'Validators.email() - valid email format',
'Validators.url() / uri() - valid URL/URI',
'Validators.uuid() - valid UUID format',
'Validators.jwt() / base64() - token formats',
'Validators.hexColor() - hex color codes',
'Validators.dateTime() - date-time strings',
'Validators.numeric() / integer() - number validation',
'Validators.positive() / negative() - sign validation',
'Validators.between(min, max) - range validation',
'Validators.minLength(n) / maxLength(n) - length constraints',
'Validators.pattern(regex) - custom regex',
'Validators.letters() / digits() / alphanumeric()',
'Validators.uppercase() / lowercase()',
'Validators.startsWith() / endsWith() / contains()',
'Validators.inList(values) / notIn(values)',
'Validators.ip() / port() - network validation',
'Validators.identifier() - valid identifier format',
'Validators.combine([...]) - chain validators',
'Validators.fromSchema(acanthisSchema) - use Acanthis directly',
]);
}
}
/// Demonstrate exception rendering.
class UiExceptionCommand extends Command<void> {
@override
String get name => 'ui:exception';
@override
String get description => 'Demonstrate pretty exception rendering.';
@override
Future<void> run() async {
io.title('Exception Rendering');
io.section('Simple Exception');
try {
throw FormatException('Invalid input format: expected JSON');
} catch (e, stack) {
io.components.renderException(e, stack);
}
io.section('Custom Exception');
try {
throw StateError('Cannot perform operation while loading');
} catch (e, stack) {
io.components.renderException(e, stack);
}
io.section('Using ExceptionComponent directly');
final renderConfig = RenderConfig.fromRenderer(
defaultRenderer,
terminalWidth: io.terminalWidth,
);
try {
throw ArgumentError.value('invalid', 'name', 'Name cannot be "invalid"');
} catch (e, stack) {
ExceptionComponent(
exception: e,
stackTrace: stack,
maxStackFrames: 5,
renderConfig: renderConfig,
).writelnTo(io);
}
}
}
/// Demonstrate horizontal table.
class UiHorizontalTableCommand extends Command<void> {
@override
String get name => 'ui:htable';
@override
String get description => 'Demonstrate horizontal table (row-as-headers).';
@override
Future<void> run() async {
io.title('Horizontal Table');
io.section('Application Info');
io.components.horizontalTable({
'Name': 'artisanal Demo',
'Version': '1.0.0',
'Environment': 'development',
'Debug': 'enabled',
'Dart SDK': dartio.Platform.version.split(' ').first,
});
io.section('Database Configuration');
io.components.horizontalTable({
'Driver': 'PostgreSQL',
'Host': 'localhost',
'Port': '5432',
'Database': 'myapp_dev',
'Username': 'postgres',
'SSL': 'disabled',
});
io.section('Using HorizontalTableComponent directly');
final renderConfig = RenderConfig.fromRenderer(
defaultRenderer,
terminalWidth: io.terminalWidth,
);
HorizontalTableComponent(
data: {
'Status': io.style.foreground(Colors.success).render('● Online'),
'Uptime': '3 days, 14 hours',
'Memory': '256 MB / 1 GB',
'CPU': '12%',
},
renderConfig: renderConfig,
).writelnTo(io);
}
}
/// Demonstrate password with confirmation.
class UiPasswordCommand extends Command<void> {
UiPasswordCommand() {
argParser.addFlag(
'confirm',
abbr: 'c',
negatable: false,
help: 'Require password confirmation.',
);
argParser.addFlag(
'defaults',
negatable: false,
help: 'Use defaults for non-interactive mode.',
);
}
@override
String get name => 'ui:password';
@override
String get description => 'Demonstrate password input with confirmation.';
@override
Future<void> run() async {
final confirm = argResults?['confirm'] == true;
io.title('Password Input');
io.text(
confirm
? 'Enter a password (will be asked to confirm)'
: 'Enter a password (no confirmation)',
);
io.newLine();
final terminal = StdioTerminal(stdout: dartio.stdout, stdin: dartio.stdin);
try {
final password = confirm
? await runPasswordConfirmPrompt(
PasswordConfirmModel(
prompt: 'Password',
confirmPrompt: 'Confirm password',
),
terminal,
)
: await runPasswordPrompt(
PasswordModel(prompt: 'Password'),
terminal,
);
if (password == null) {
io.warning('Password prompt cancelled');
return;
}
io.newLine();
io.success('Password set successfully!');
io.twoColumnDetail('Length', '${password.length} characters');
} catch (e) {
io.newLine();
io.error('$e');
}
}
}
/// Demonstrate styled blocks (Symfony-style).
class UiBlockCommand extends Command<void> {
UiBlockCommand() {
argParser.addFlag(
'large',
abbr: 'l',
negatable: false,
help: 'Show large block style.',
);
}
@override
String get name => 'ui:block';
@override
String get description => 'Demonstrate styled block output (Symfony-style).';
@override
Future<void> run() async {
final large = argResults?['large'] == true;
io.title('Styled Blocks');
final renderConfig = RenderConfig.fromRenderer(
defaultRenderer,
terminalWidth: io.terminalWidth,
);
io.section('Info Block');
StyledBlockComponent(
message: 'This is an informational message that provides context.',
blockStyle: BlockStyleType.info,
large: large,
renderConfig: renderConfig,
).writelnTo(io);
io.section('Success Block');
StyledBlockComponent(
message:
'Operation completed successfully!\nAll tasks finished without errors.',
blockStyle: BlockStyleType.success,
large: large,
renderConfig: renderConfig,
).writelnTo(io);
io.section('Warning Block');
StyledBlockComponent(
message: 'Please review the configuration before proceeding.',
blockStyle: BlockStyleType.warning,
large: large,
renderConfig: renderConfig,
).writelnTo(io);
io.section('Error Block');
StyledBlockComponent(
message: 'An error occurred during the operation.',
blockStyle: BlockStyleType.error,
large: large,
renderConfig: renderConfig,
).writelnTo(io);
io.section('Note Block');
StyledBlockComponent(
message: 'This is a note with additional information.',
blockStyle: BlockStyleType.note,
large: large,
renderConfig: renderConfig,
).writelnTo(io);
io.section('Comment Style');
CommentComponent(
text: [
'This is a comment block.',
'It displays text in a dimmed, code-comment style.',
'Useful for showing hints or secondary information.',
],
renderConfig: renderConfig,
).writelnTo(io);
}
}
/// Demonstrate multi-column layout.
class UiColumnsCommand extends Command<void> {
UiColumnsCommand() {
argParser.addOption(
'cols',
abbr: 'c',
defaultsTo: '4',
help: 'Number of columns.',
);
}
@override
String get name => 'ui:columns';
@override
String get description => 'Demonstrate multi-column layout.';
@override
Future<void> run() async {
final colCount = int.tryParse(argResults?['cols'] as String? ?? '4') ?? 4;
io.title('Multi-Column Layout');
final renderConfig = RenderConfig.fromRenderer(
defaultRenderer,
terminalWidth: io.terminalWidth,
);
io.section('Fruits ($colCount columns)');
ColumnsComponent(
items: [
'Apple',
'Banana',
'Cherry',
'Date',
'Elderberry',
'Fig',
'Grape',
'Honeydew',
'Kiwi',
'Lemon',
'Mango',
'Nectarine',
'Orange',
'Papaya',
'Quince',
'Raspberry',
],
columnCount: colCount,
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
io.section('Commands (auto columns)');
ColumnsComponent(
items: [
'make:model',
'make:controller',
'make:migration',
'make:seeder',
'db:migrate',
'db:seed',
'db:rollback',
'db:fresh',
'serve',
'build',
'test',
'lint',
'cache:clear',
'config:cache',
'route:list',
'queue:work',
],
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
io.section('Status Items');
ColumnsComponent(
items: [
'${io.style.foreground(Colors.success).render("●")} Online',
'${io.style.foreground(Colors.error).render("●")} Offline',
'${io.style.foreground(Colors.warning).render("●")} Degraded',
'${io.style.foreground(Colors.info).render("●")} Maintenance',
'${io.style.foreground(Colors.success).render("●")} Healthy',
'${io.style.foreground(Colors.error).render("●")} Critical',
],
columnCount: 3,
renderConfig: renderConfig,
).writelnTo(io);
}
}
/// Demonstrate terminal utilities.
class UiTerminalCommand extends Command<void> {
@override
String get name => 'ui:terminal';
@override
String get description => 'Demonstrate terminal utilities and info.';
@override
Future<void> run() async {
final terminal = StdioTerminal(stdout: dartio.stdout, stdin: dartio.stdin);
io.title('Terminal Utilities');
io.section('Terminal Information');
io.components.horizontalTable({
'Width': '${terminal.width} columns',
'Height': '${terminal.height} rows',
'Supports ANSI': terminal.supportsAnsi ? 'Yes' : 'No',
'Is Terminal': terminal.isTerminal ? 'Yes' : 'No',
});
io.section('Available Operations');
io.components.bulletList([
'terminal.hideCursor() / showCursor() - cursor visibility',
'terminal.cursorUp(n) / cursorDown(n) - move cursor vertically',
'terminal.cursorLeft(n) / cursorRight(n) - move cursor horizontally',
'terminal.cursorTo(row, col) - absolute positioning',
'terminal.saveCursor() / restoreCursor() - save/restore position',
'terminal.clearScreen() - clear entire screen',
'terminal.clearLine() - clear current line',
'terminal.clearPreviousLines(n) - clear n lines above',
'terminal.scrollUp(n) / scrollDown(n) - scroll viewport',
'terminal.enterAlternateScreen() / exitAlternateScreen()',
'terminal.bell() - ring terminal bell',
'terminal.setTitle(title) - set terminal window title',
'terminal.enableRawMode() - character-by-character input',
]);
io.section('Key Codes');
io.components.definitionList({
'KeyCode.enter': '10 (\\n)',
'KeyCode.escape': '27',
'KeyCode.space': '32',
'KeyCode.backspace': '127',
'KeyCode.tab': '9',
'KeyCode.ctrlC': '3',
'KeyCode.arrowUp/Down/Left/Right': '65/66/67/68 (after ESC[)',
});
io.section('Demo: Bell');
io.text('Ringing terminal bell...');
terminal.bell();
io.success('Bell rang! (you may have heard a beep)');
}
}
/// Run all UI demos in sequence.
class UiAllCommand extends Command<void> {
@override
String get name => 'ui:all';
@override
String get description => 'Run all UI component demos in sequence.';
@override
Future<void> run() async {
io.title('Complete artisanal Demo');
io.text('This demo showcases all available UI components.');
io.newLine();
// Basic output
io.section('1. Basic Output');
io.info('Info message');
io.success('Success message');
io.warning('Warning message');
io.error('Error message');
io.note('Note message');
io.caution('Caution message');
io.newLine();
// Listing
io.section('2. Listing');
io.listing(['First item', 'Second item', 'Third item']);
// Two column detail
io.section('3. Two Column Detail');
io.twoColumnDetail('Application', 'artisanal');
io.twoColumnDetail('Version', '1.0.0');
io.twoColumnDetail('Environment', 'development');
io.newLine();
// Table
io.section('4. Table');
io.table(
headers: ['ID', 'Name', 'Status'],
rows: [
['1', 'users', io.style.foreground(Colors.success).render('migrated')],
['2', 'posts', io.style.foreground(Colors.success).render('migrated')],
[
'3',
'comments',
io.style.foreground(Colors.warning).render('pending'),
],
],
);
// Horizontal table
io.section('5. Horizontal Table');
io.components.horizontalTable({
'Database': 'PostgreSQL',
'Host': 'localhost',
'Port': '5432',
});
// Components
io.section('6. Components');
io.writeln('Bullet List:');
io.components.bulletList(['Item A', 'Item B', 'Item C']);
io.writeln('Definition List:');
io.components.definitionList({'Key 1': 'Value 1', 'Key 2': 'Value 2'});
io.writeln('Rule:');
io.components.rule('Section');
io.writeln('Comment:');
io.components.comment('This is a comment');
io.newLine();
io.writeln('Alert:');
io.components.alert('Important alert message!');
// Panel
io.section('7. Panel');
final renderConfig = RenderConfig.fromRenderer(
defaultRenderer,
terminalWidth: io.terminalWidth,
);
PanelComponent(
content: 'This is a boxed panel with a title.',
title: 'Panel Title',
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
// Tree
io.section('8. Tree');
TreeComponent(
data: {
'src': {
'lib': ['main.dart'],
'test': ['main_test.dart'],
},
'pubspec.yaml': null,
},
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
// Columns
io.section('9. Columns');
ColumnsComponent(
items: ['one', 'two', 'three', 'four', 'five', 'six'],
columnCount: 3,
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
// Progress bar
io.section('10. Progress Bar');
for (final _ in io.progressIterate(List<int>.filled(20, 0), max: 20)) {
await Future<void>.delayed(const Duration(milliseconds: 30));
}
io.newLine();
// Task
io.section('11. Task');
await io.task(
'Running task',
run: () async {
await Future<void>.delayed(const Duration(milliseconds: 100));
return TaskResult.success;
},
);
// Spin
io.section('12. Spin Component');
await io.components.spin(
'Processing',
run: () async {
await Future<void>.delayed(const Duration(milliseconds: 200));
return null;
},
);
io.newLine();
// Colors
io.section('13. Colors');
final style = io.style;
io.writeln(
' ${style.foreground(Colors.red).render("Red")} ${style.foreground(Colors.green).render("Green")} ${style.foreground(Colors.blue).render("Blue")} ${style.foreground(Colors.yellow).render("Yellow")}',
);
io.writeln(
' ${style.bold().render("Bold")} ${style.italic().render("Italic")} ${style.underline().render("Underline")}',
);
io.writeln(
' ${style.foreground(BasicColor("#ff6b6b")).render("Coral")} ${style.foreground(BasicColor("#4ecdc4")).render("Turquoise")} ${style.foreground(BasicColor("#ff6432")).render("Orange")}',
);
io.newLine();
// Terminal info
io.section('14. Terminal Info');
final terminalInfo = StdioTerminal(stdout: dartio.stdout);
io.twoColumnDetail('Size', '${terminalInfo.width}x${terminalInfo.height}');
io.twoColumnDetail('ANSI', terminalInfo.supportsAnsi ? 'Yes' : 'No');
io.newLine();
// Summary
io.section('Summary');
io.success('All demos completed!');
io.newLine();
io.text('Run individual commands to see interactive demos:');
io.components.bulletList([
'ui:prompts - interactive prompts (confirm/ask/choice)',
'ui:secret - password input',
'ui:password - password with confirmation',
'ui:select - arrow-key selection',
'ui:multiselect - multi-select with checkboxes',
'ui:search - searchable selection',
'ui:spinner - animated spinner',
'ui:pause - press any key / countdown',
'ui:validators - input validation',
'ui:anticipate - autocomplete suggestions',
'ui:textarea - multi-line editor input',
'ui:wizard - multi-step wizard flow',
'ui:link - clickable terminal links',
]);
}
}
/// Demonstrate autocomplete/anticipate prompt.
class UiAnticipateCommand extends Command<void> {
@override
String get name => 'ui:anticipate';
@override
String get description => 'Demonstrate autocomplete input with suggestions.';
@override
Future<void> run() async {
io.title('Autocomplete / Anticipate');
io.text('Type to see matching suggestions. Use arrow keys to navigate.');
io.newLine();
final terminal = StdioTerminal(stdout: dartio.stdout, stdin: dartio.stdin);
// Country selection
final countries = [
'United States',
'United Kingdom',
'Canada',
'Australia',
'Germany',
'France',
'Japan',
'China',
'India',
'Brazil',
'Mexico',
'Spain',
'Italy',
'Netherlands',
'Sweden',
'Norway',
'Denmark',
'Finland',
'Switzerland',
'Austria',
];
final country = await runAnticipatePrompt(
AnticipateModel(
prompt: 'Select your country: ',
suggestions: countries,
defaultValue: 'United States',
),
terminal,
);
io.newLine();
if (country != null) {
io.success('Selected: $country');
} else {
io.warning('Selection cancelled');
}
io.newLine();
// Package selection
final packages = [
'flutter',
'dart',
'http',
'dio',
'provider',
'bloc',
'riverpod',
'get_it',
'injectable',
'freezed',
'json_serializable',
'equatable',
'dartz',
'rxdart',
'stream_transform',
];
final package = await runAnticipatePrompt(
AnticipateModel(prompt: 'Select a package: ', suggestions: packages),
terminal,
);
io.newLine();
if (package != null) {
io.success('Selected: $package');
} else {
io.warning('Selection cancelled');
}
}
}
/// Demonstrate textarea (multi-line editor input).
class UiTextareaCommand extends Command<void> {
@override
String get name => 'ui:textarea';
@override
String get description =>
'Demonstrate multi-line text input via external editor.';
@override
Future<void> run() async {
io.title('Textarea / Editor Input');
io.text('Multi-line input bubble (Ctrl+S to submit, Esc to cancel).');
io.newLine();
io.section('Simple Text Input');
try {
final model = TextAreaModel()
..value = 'This is the default content.\nYou can edit it.';
final text = await io.components.textArea(model);
if (text != null && text.isNotEmpty) {
io.success('Received ${text.split('\n').length} line(s):');
io.newLine();
for (final line in text.split('\n')) {
io.writeln(' $line');
}
} else {
io.warning('No content entered');
}
} catch (e) {
io.error('Editor not available: $e');
io.note('Set the \$EDITOR environment variable to use this feature.');
}
}
}
/// Demonstrate wizard (multi-step flow).
class UiWizardCommand extends Command<void> {
UiWizardCommand() {
argParser.addFlag(
'non-interactive',
abbr: 'n',
negatable: false,
help: 'Use defaults for all prompts.',
);
}
@override
String get name => 'ui:wizard';
@override
String get description => 'Demonstrate multi-step wizard flow.';
@override
Future<void> run() async {
final nonInteractive = argResults?['non-interactive'] == true;
io.title('Wizard / Multi-Step Flow');
if (nonInteractive || !io.interactive) {
io.note('Wizard prompt skipped in non-interactive mode.');
return;
}
final terminal = StdioTerminal(stdout: dartio.stdout, stdin: dartio.stdin);
final results = await runWizardPrompt(
WizardModel(
title: 'Create New Project',
steps: [
WizardStep.textInput(
key: 'name',
prompt: 'Project name',
defaultValue: 'my_project',
validate: (value) {
if (value.isEmpty) return 'Name is required';
if (!RegExp(r'^[a-z_][a-z0-9_]*$').hasMatch(value)) {
return 'Name must be a valid Dart identifier';
}
return null;
},
),
WizardStep.select(
key: 'template',
prompt: 'Project template',
options: ['console', 'package', 'server', 'flutter'],
defaultIndex: 0,
),
WizardStep.confirm(
key: 'git',
prompt: 'Initialize Git repository?',
defaultValue: true,
),
WizardStep.conditional(
step: WizardStep.textInput(
key: 'git_remote',
prompt: 'Git remote URL (optional)',
),
condition: (answers) => answers['git'] == true,
),
WizardStep.multiSelect(
key: 'features',
prompt: 'Select features to include',
options: ['Testing', 'CI/CD', 'Documentation', 'Linting', 'Docker'],
defaultSelected: [0, 3],
),
WizardStep.group(
key: 'author',
title: 'Author Information',
steps: [
WizardStep.textInput(
key: 'author_name',
prompt: 'Author name',
defaultValue: 'Anonymous',
),
WizardStep.textInput(key: 'author_email', prompt: 'Author email'),
],
),
],
),
terminal,
);
if (results == null) {
io.warning('Wizard cancelled');
return;
}
io.section('Wizard Results');
io.components.horizontalTable({
'Name': results['name'],
'Template': results['template'],
'Git': results['git'] == true ? 'Yes' : 'No',
'Git Remote': results['git_remote'] ?? '-',
'Features': (results['features'] as List?)?.join(', ') ?? '-',
'Author': results['author_name'] ?? '-',
'Email': results['author_email'] ?? '-',
});
}
}
/// Demonstrate clickable terminal links.
class UiLinkCommand extends Command<void> {
@override
String get name => 'ui:link';
@override
String get description =>
'Demonstrate clickable terminal hyperlinks (OSC 8).';
@override
Future<void> run() async {
io.title('Terminal Hyperlinks (OSC 8)');
io.text('Modern terminals support clickable links.');
io.newLine();
final renderConfig = RenderConfig.fromRenderer(
defaultRenderer,
terminalWidth: io.terminalWidth,
);
io.section('Link Support');
io.twoColumnDetail(
'OSC 8 Supported',
LinkComponent.isSupported ? 'Yes' : 'No',
);
io.newLine();
io.section('Basic Links');
io.write(' Visit ');
io.write(
LinkComponent(
url: 'https://dart.dev',
text: 'Dart',
renderConfig: renderConfig,
).render(),
);
io.writeln(' for more information.');
io.write(' Check out ');
io.write(
LinkComponent(
url: 'https://flutter.dev',
text: 'Flutter',
renderConfig: renderConfig,
).render(),
);
io.writeln(' for mobile development.');
io.write(' Read the ');
io.write(
LinkComponent(
url: 'https://pub.flutter-io.cn/packages/artisanal',
text: 'artisanal docs',
renderConfig: renderConfig,
).render(),
);
io.writeln('.');
io.newLine();
io.section('Styled Links');
io.writeln(
' ${LinkComponent(url: 'https://github.com', text: 'GitHub (underlined & blue)', styled: true, renderConfig: renderConfig).render()}',
);
io.newLine();
io.section('Using LinkComponent');
io.writeln(
' ${LinkComponent(url: 'https://google.com', text: 'Google', renderConfig: renderConfig).render()}',
);
io.writeln(
' ${LinkComponent(url: 'https://dart.dev/guides', text: 'Dart Guides', styled: true, renderConfig: renderConfig).render()}',
);
io.newLine();
io.section('Link Group (for footnotes)');
final links = LinkGroupComponent(prefix: 'ref');
io.writeln(
' Dart${links.add('https://dart.dev', text: '[1]')} is great for building ',
);
io.writeln(
' Flutter${links.add('https://flutter.dev', text: '[2]')} apps.',
);
io.newLine();
io.writeln(' References:');
links.writelnTo(io);
io.newLine();
io.note('Links may not be clickable in all terminals.');
io.text('Supported: iTerm2, Windows Terminal, VS Code, Hyper, WezTerm');
}
}
/// Demonstrate the new component system.
class UiComponentSystemCommand extends Command<void> {
@override
String get name => 'ui:system';
@override
String get description =>
'Demonstrate the structured component system (Flutter-like).';
@override
Future<void> run() async {
final renderConfig = RenderConfig.fromRenderer(
defaultRenderer,
terminalWidth: io.terminalWidth,
);
io.title('Component System Demo');
io.text('A structured way to build CLI UIs, similar to Flutter widgets.');
io.newLine();
// ─────────────────────────────────────────────────────────────────────────
io.section('Static Components');
// Text components
io.writeln('Text components:');
Text(' Plain text').writelnTo(io);
StyledText.info(
' Info styled text',
renderConfig: renderConfig,
).writelnTo(io);
StyledText.success(
' Success styled text',
renderConfig: renderConfig,
).writelnTo(io);
StyledText.warning(
' Warning styled text',
renderConfig: renderConfig,
).writelnTo(io);
StyledText.error(
' Error styled text',
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
// Rule component
io.writeln('Rule component:');
Rule(renderConfig: renderConfig).writelnTo(io);
Rule(text: 'Section', renderConfig: renderConfig).writelnTo(io);
io.newLine();
// Lists
io.writeln('List components:');
BulletList(
items: ['Apple', 'Banana', 'Cherry'],
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
NumberedList(
items: ['First', 'Second', 'Third'],
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
// Key-value
io.writeln('KeyValue component:');
KeyValue(
key: 'Name',
value: 'artisanal',
renderConfig: renderConfig,
).writelnTo(io);
KeyValue(
key: 'Version',
value: '1.0.0',
renderConfig: renderConfig,
).writelnTo(io);
KeyValue(
key: 'Author',
value: 'You',
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
// Box
io.writeln('Box component:');
Box(
content: 'This is a boxed message.\nIt can have multiple lines.',
title: 'Notice',
borderStyle: BorderStyle.rounded,
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
// Progress bar
io.writeln('ProgressBar component:');
ProgressBar(current: 7, total: 10).writelnTo(io);
ProgressBar(
current: 3,
total: 10,
fillChar: '▓',
emptyChar: '░',
).writelnTo(io);
io.newLine();
// ─────────────────────────────────────────────────────────────────────────
io.section('Composition');
io.writeln('Components can be composed together:');
io.newLine();
ColumnComponent(
children: [
StyledText.heading(' My Application', renderConfig: renderConfig),
Rule(char: '─', renderConfig: renderConfig),
BulletList(
items: ['Feature 1: Fast', 'Feature 2: Easy', 'Feature 3: Beautiful'],
indent: 4,
renderConfig: renderConfig,
),
],
).writelnTo(io);
io.newLine();
// Row composition
io.writeln('Row composition:');
RowComponent(
children: [
StyledText.success('✓ Pass', renderConfig: renderConfig),
Text(' | '),
StyledText.error('✗ Fail', renderConfig: renderConfig),
Text(' | '),
StyledText.warning('⚠ Warn', renderConfig: renderConfig),
],
).writelnTo(io);
io.newLine();
// ─────────────────────────────────────────────────────────────────────────
io.section('Output Components');
io.writeln('PanelComponent:');
PanelComponent(
content:
'This is a panel using the component system.\nIt supports titles and alignment.',
title: 'Panel Demo',
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
io.writeln('TaskComponent:');
TaskComponent(
description: 'Compiling assets',
status: TaskStatus.success,
renderConfig: renderConfig,
).writelnTo(io);
TaskComponent(
description: 'Running tests',
status: TaskStatus.failure,
renderConfig: renderConfig,
).writelnTo(io);
TaskComponent(
description: 'Deploying',
status: TaskStatus.skipped,
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
io.writeln('AlertComponent:');
AlertComponent(
message: 'This is informational',
type: AlertType.info,
renderConfig: renderConfig,
).writelnTo(io);
AlertComponent(
message: 'Operation succeeded',
type: AlertType.success,
renderConfig: renderConfig,
).writelnTo(io);
AlertComponent(
message: 'Be careful!',
type: AlertType.warning,
renderConfig: renderConfig,
).writelnTo(io);
AlertComponent(
message: 'Something went wrong',
type: AlertType.error,
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
io.writeln('TwoColumnDetailComponent:');
TwoColumnDetailComponent(
left: 'Name',
right: 'artisanal',
renderConfig: renderConfig,
).writelnTo(io);
TwoColumnDetailComponent(
left: 'Version',
right: '1.0.0',
renderConfig: renderConfig,
).writelnTo(io);
TwoColumnDetailComponent(
left: 'Status',
right: 'Active',
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
io.writeln('TreeComponent:');
TreeComponent(
data: {
'src': {
'lib': ['main.dart', 'utils.dart'],
'test': ['main_test.dart'],
},
'pubspec.yaml': null,
'README.md': null,
},
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
io.writeln('ColumnsComponent:');
ColumnsComponent(
items: [
'apple',
'banana',
'cherry',
'date',
'elderberry',
'fig',
'grape',
'honeydew',
],
columnCount: 4,
renderConfig: renderConfig,
).writelnTo(io);
io.newLine();
// ─────────────────────────────────────────────────────────────────────────
io.section('Interactive Components');
io.text('Interactive components return values from user input.');
io.newLine();
io.writeln('Available interactive components:');
io.components.bulletList([
'TextInput - text input with validation',
'Confirm - yes/no confirmation',
'SecretInputComponent - password input',
'Select<T> - single select with arrow keys',
'MultiSelect<T> - multi select with arrow keys',
'SpinnerComponent - async progress spinner',
]);
io.newLine();
// Demo interactive components
io.writeln('Demo: Select prompt');
final terminal = StdioTerminal(stdout: dartio.stdout, stdin: dartio.stdin);
final color = await runSelectPrompt<String>(
SelectModel<String>(
items: ['Red', 'Green', 'Blue', 'Yellow'],
title: 'Pick your favorite color',
),
terminal,
);
if (color != null) io.success('You selected: $color');
io.newLine();
// ─────────────────────────────────────────────────────────────────────────
io.section('Custom Components');
io.text('Create custom components by extending DisplayComponent:');
io.newLine();
io.writeln('''
class MyBanner extends DisplayComponent {
final String title;
MyBanner(this.title);
@override
String render() => Style().bold().foreground(Colors.yellow).render('★ \$title ★');
}
''');
// Demo custom component
_CustomBanner('artisanal', renderConfig: renderConfig).writelnTo(io);
}
}
/// Example custom component.
class _CustomBanner extends DisplayComponent {
const _CustomBanner(this.title, {this.renderConfig = const RenderConfig()});
final String title;
final RenderConfig renderConfig;
@override
String render() {
final stars = '★ ' * 3;
final style = renderConfig.configureStyle(Style());
return style.bold().foreground(Colors.yellow).render('$stars$title$stars');
}
}