feat: initial scaffold — swappable flow editor
Extracted from fai/studio's lib/pages/flow_editor.dart so the editor can be swapped out independently of the host. Public surface kept minimal — a single FlowEditorPage widget with three named parameters (initialFlowName, locale, onRun). The package brings its own design tokens, empty/error widgets, l10n table; no host-internal types leak through. Studio depends on this repo via pubspec.yaml git reference. Forks point Studio at a different URL and rebuild. See README.md for the swap recipe. Signed-off-by: F∆I Platform <platform@flemming.ai>
This commit is contained in:
commit
51f9a1d2b1
8 changed files with 1128 additions and 0 deletions
103
lib/src/widgets.dart
Normal file
103
lib/src/widgets.dart
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/// Minimal stand-ins for the FaiEmptyState + FaiErrorBox
|
||||
/// widgets Studio ships. The editor brings its own so the
|
||||
/// package compiles standalone — host-styled variants are
|
||||
/// a future plugin-API extension.
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'tokens.dart';
|
||||
|
||||
class FaiEmptyState extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String? hint;
|
||||
const FaiEmptyState({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
this.hint,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(FaiSpace.xl),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 40,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(height: FaiSpace.md),
|
||||
Text(
|
||||
title,
|
||||
style: theme.textTheme.titleMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (hint != null) ...[
|
||||
const SizedBox(height: FaiSpace.sm),
|
||||
Text(
|
||||
hint!,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FaiErrorBox extends StatelessWidget {
|
||||
final Object? error;
|
||||
final bool isError;
|
||||
final double? maxHeight;
|
||||
const FaiErrorBox({
|
||||
super.key,
|
||||
required this.error,
|
||||
this.isError = true,
|
||||
this.maxHeight,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final fg = isError ? theme.colorScheme.error : theme.colorScheme.onSurface;
|
||||
return Container(
|
||||
constraints: maxHeight == null
|
||||
? null
|
||||
: BoxConstraints(maxHeight: maxHeight!),
|
||||
padding: const EdgeInsets.all(FaiSpace.md),
|
||||
decoration: BoxDecoration(
|
||||
color: isError
|
||||
? theme.colorScheme.errorContainer.withValues(alpha: 0.4)
|
||||
: theme.colorScheme.surfaceContainer,
|
||||
borderRadius: BorderRadius.circular(FaiRadius.sm),
|
||||
border: Border.all(
|
||||
color: isError
|
||||
? theme.colorScheme.error.withValues(alpha: 0.4)
|
||||
: theme.colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: SelectableText(
|
||||
error?.toString() ?? '',
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 12,
|
||||
color: fg,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue