geniuslink_design_system 2.8.1
geniuslink_design_system: ^2.8.1 copied to clipboard
GeniusLink Design System for Flutter — a themeable, MVC widget kit (BrowserStyleTabBar, EditableTable with typed columns, ReadableTable, Tree, NavigationSidebar) with light/dark + LTR/RTL support.
// ============================================================
// GeniusLink Design System — component examples app.
// A launcher with ONE example screen per component, plus focused sub-demos:
// • All Components — the ERP Console: Tree + BrowserStyleTabBar + EditableTable
// • BrowserStyleTabBar — the tab-strip showcase (pin, drag-reorder, overflow, thumbnails)
// • EditableTable — the Excel-style data-entry grid
// • EditableTable Combo — ComboBoxColumn cells powered by AutoSuggestionsBox
// • AutoSuggestionsBox — the typed auto-suggest field (static/grouped/async/hybrid)
// • ReadableTable — the read-only display grid
// • ReadableTable Filter — the per-column filter system
// • FilterEditingView — the grouped AND/OR visual filter builder
// • Tree — the chart-of-accounts tree
// • NavigationSidebar — the app nav rail (expanded / collapsed / drawer)
//
// Every screen registers its own ThemeExtension; each component reads it via
// <Component>ThemeData.of(context). All screens run live in Light/Dark and
// EN / AR (RTL). The top-level MaterialApp registers the global localization
// delegates so the Material date/time pickers localize in AR.
//
// Run: cd geniuslink_design_system_flutter/example && flutter pub get && flutter run -d chrome
// ============================================================
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:geniuslink_design_system/geniuslink_browser_tabs.dart';
import 'browser_tabs_demo.dart';
import 'editable_table_demo.dart';
import 'editable_table/combo_demo.dart';
import 'auto_suggestions_box_demo.dart';
import 'readable_table_demo.dart';
import 'readable_table/filter_demo.dart';
import 'readable_table/filter_editing_demo.dart';
import 'tree_demo.dart';
import 'navigation_sidebar_demo.dart';
import 'erp_console.dart';
import 'shell_kit.dart';
void main() => runApp(const ExampleApp());
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GeniusLink · Component Examples',
debugShowCheckedModeBanner: false,
// Registered app-wide so the Material date/time pickers (and any other
// localized widgets) read correctly in AR as well as EN.
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
theme: ThemeData(
brightness: Brightness.light,
useMaterial3: true,
fontFamily: BrowserStyleTabBarThemeData.bodyFont,
scaffoldBackgroundColor: BrowserStyleTabBarThemeData.light.bg,
extensions: const [BrowserStyleTabBarThemeData.light],
),
supportedLocales: const [Locale('en'), Locale('ar')],
home: const LauncherScreen(),
);
}
}
// ════════════════════════════════════════════════════════════
// LAUNCHER
// ════════════════════════════════════════════════════════════
class LauncherScreen extends StatelessWidget {
const LauncherScreen({super.key});
@override
Widget build(BuildContext context) {
final s = BrowserStyleTabBarThemeData.of(context);
return Scaffold(
backgroundColor: s.bg,
body: SafeArea(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 1040),
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 48),
children: [
Text('GENIUSLINK DESIGN SYSTEM',
style: TextStyle(
fontFamily: BrowserStyleTabBarThemeData.bodyFont, fontSize: 11, fontWeight: FontWeight.w700, letterSpacing: 1.6, color: BrowserStyleTabBarThemeData.accent)),
const SizedBox(height: 12),
Text('Component Examples',
style: TextStyle(fontFamily: BrowserStyleTabBarThemeData.displayFont, fontSize: 36, fontWeight: FontWeight.w800, letterSpacing: -0.8, color: s.fg1)),
const SizedBox(height: 10),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 620),
child: Text(
'One example screen per component — BrowserStyleTabBar, EditableTable, '
'ReadableTable and the Tree — plus focused demos for the combo auto-suggest '
'editor and the ReadableTable filter system, and an all-in-one console that '
'runs them together. Open any to try it live in Light / Dark and EN / AR (RTL).',
style: TextStyle(fontFamily: BrowserStyleTabBarThemeData.bodyFont, fontSize: 14.5, height: 1.55, color: s.fg3),
),
),
const SizedBox(height: 32),
LayoutBuilder(builder: (context, c) {
final cols = c.maxWidth > 760 ? 3 : (c.maxWidth > 460 ? 2 : 1);
return GridView.count(
crossAxisCount: cols,
crossAxisSpacing: 18,
mainAxisSpacing: 18,
childAspectRatio: 0.92,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: [
_DemoCard(
title: 'All Components — ERP Console',
subtitle: 'Tree navigator + BrowserStyleTabBar + EditableTable working together in one console · Light/Dark · EN/AR (RTL)',
accent: const Color(0xFF4A7CFF),
preview: const _ErpConsoleThumb(),
onTap: () => _open(context, const ErpConsole()),
),
_DemoCard(
title: 'BrowserStyleTabBar',
subtitle: 'Tab strip · pin, drag-reorder, unsaved-close guard, overflow menu, live hover thumbnails · state-preserving pages',
accent: BrowserStyleTabBarThemeData.accent,
preview: const _ErpThumb(),
onTap: () => _open(context, const _GalleryRoute()),
),
_DemoCard(
title: 'EditableTable',
subtitle: 'Excel-style data-entry grid · typed generic rows · resize & reorder columns · copy as TSV · sort · undo',
accent: BrowserStyleTabBarThemeData.success,
preview: const _EditableTableThumb(),
onTap: () => _open(context, const EditableTableDemo()),
),
_DemoCard(
title: 'EditableTable — Combo cells',
subtitle: 'ComboBoxColumn powered by the native AutoSuggestionsBox · type to filter, ↑ ↓ to move, Enter / click to pick, or free text',
accent: BrowserStyleTabBarThemeData.success,
preview: const _ComboThumb(),
onTap: () => _open(context, const ComboDemo()),
),
_DemoCard(
title: 'AutoSuggestionsBox',
subtitle: 'Typed auto-suggest field · static / grouped / async / hybrid sources · match highlighting · keyboard nav · free text',
accent: const Color(0xFF4A7CFF),
preview: const _SuggestThumb(),
onTap: () => _open(context, const AutoSuggestionsBoxDemo()),
),
_DemoCard(
title: 'ReadableTable',
subtitle: 'Read-only display grid · typed column kinds · resize / reorder · multi-select & copy · keyboard nav · scroll-on-focus',
accent: const Color(0xFF4A7CFF),
preview: const _ReadableTableThumb(),
onTap: () => _open(context, const ReadableTableDemo()),
),
_DemoCard(
title: 'ReadableTable — Filter system',
subtitle: 'Per-column filters · operators by type · live visible/total readout · combine predicates · clear all',
accent: const Color(0xFF4A7CFF),
preview: const _FilterThumb(),
onTap: () => _open(context, const ReadableFilterDemo()),
),
_DemoCard(
title: 'FilterEditingView',
subtitle: 'Visual filter builder · grouped AND / OR conditions · per-column operators · nested groups · add / remove rules',
accent: const Color(0xFF4A7CFF),
preview: const _FilterEditingThumb(),
onTap: () => _open(context, const FilterEditingDemo()),
),
_DemoCard(
title: 'Tree',
subtitle: 'Chart-of-accounts tree · single & multi-select · add / remove nodes · code/EN/AR search · roll-up balances',
accent: const Color(0xFF4A7CFF),
preview: const _TreeThumb(),
onTap: () => _open(context, const TreeDemo()),
),
_DemoCard(
title: 'NavigationSidebar',
subtitle: 'App nav rail · expanded tree + collapsed rail with flyouts + mobile drawer · badges · responsive · Light/Dark · LTR/RTL',
accent: const Color(0xFF4A7CFF),
preview: const _NavSidebarThumb(),
onTap: () => _open(context, const NavigationSidebarDemo()),
),
],
);
}),
],
),
),
),
),
);
}
void _open(BuildContext context, Widget screen) {
Navigator.of(context).push(MaterialPageRoute(builder: (_) => _BackScaffold(child: screen)));
}
}
class _DemoCard extends StatefulWidget {
final String title, subtitle;
final Color accent;
final Widget preview;
final VoidCallback onTap;
const _DemoCard({required this.title, required this.subtitle, required this.accent, required this.preview, required this.onTap});
@override
State<_DemoCard> createState() => _DemoCardState();
}
class _DemoCardState extends State<_DemoCard> {
bool _h = false;
@override
Widget build(BuildContext context) {
final s = BrowserStyleTabBarThemeData.of(context);
return MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) => setState(() => _h = true),
onExit: (_) => setState(() => _h = false),
child: GestureDetector(
onTap: widget.onTap,
child: AnimatedContainer(
duration: BrowserStyleTabBarThemeData.durBase,
transform: _h ? (Matrix4.identity()..translate(0.0, -3.0)) : Matrix4.identity(),
decoration: BoxDecoration(
color: s.surface,
border: Border.all(color: _h ? widget.accent.withOpacity(0.5) : s.border),
borderRadius: BorderRadius.circular(BrowserStyleTabBarThemeData.radiusXl),
boxShadow: _h ? BrowserStyleTabBarThemeData.cardShadow : null,
),
clipBehavior: Clip.antiAlias,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: widget.preview),
Padding(
padding: const EdgeInsets.fromLTRB(16, 14, 16, 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Expanded(
child: Text(widget.title,
style: TextStyle(fontFamily: BrowserStyleTabBarThemeData.displayFont, fontSize: 16, fontWeight: FontWeight.w700, color: s.fg1)),
),
Icon(Icons.arrow_outward, size: 16, color: _h ? widget.accent : s.fg3),
]),
const SizedBox(height: 6),
Text(widget.subtitle, style: TextStyle(fontFamily: BrowserStyleTabBarThemeData.bodyFont, fontSize: 12.5, height: 1.45, color: s.fg3)),
],
),
),
],
),
),
),
);
}
}
// ── small static thumbnails for the launcher cards ──
class _MiniTabs extends StatelessWidget {
final Color strip, active, text;
final List<String> labels;
final int activeIndex;
const _MiniTabs({required this.strip, required this.active, required this.text, required this.labels, this.activeIndex = 0});
@override
Widget build(BuildContext context) {
return Container(
color: strip,
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
for (int i = 0; i < labels.length; i++) ...[
if (i > 0) const SizedBox(width: 3),
Container(
height: 24,
padding: const EdgeInsets.symmetric(horizontal: 9),
alignment: Alignment.center,
decoration: BoxDecoration(
color: i == activeIndex ? active : Colors.transparent,
borderRadius: const BorderRadius.vertical(top: Radius.circular(6)),
),
child: Text(labels[i], style: TextStyle(fontFamily: BrowserStyleTabBarThemeData.bodyFont, fontSize: 9.5, fontWeight: FontWeight.w600, color: text)),
),
],
const Spacer(),
Icon(Icons.add, size: 13, color: text),
],
),
);
}
}
class _ErpThumb extends StatelessWidget {
const _ErpThumb();
@override
Widget build(BuildContext context) {
return Container(
color: const Color(0xFFF7F8FA),
child: Column(children: [
const _MiniTabs(strip: Color(0xFFF7F8FA), active: Colors.white, text: Color(0xFF64748B), labels: ['Ledger', 'Journal', 'Store'], activeIndex: 1),
Expanded(child: Container(color: Colors.white, margin: const EdgeInsets.fromLTRB(8, 0, 8, 8), padding: const EdgeInsets.all(10), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Container(width: 80, height: 8, color: const Color(0xFF0F172A)),
const SizedBox(height: 8),
for (int i = 0; i < 3; i++) Padding(padding: const EdgeInsets.only(bottom: 6), child: Row(children: [Container(width: 30, height: 6, color: const Color(0xFFE2E8F0)), const SizedBox(width: 8), Expanded(child: Container(height: 6, color: const Color(0xFFEEF1F7)))])),
]))),
]),
);
}
}
class _EditableTableThumb extends StatelessWidget {
const _EditableTableThumb();
@override
Widget build(BuildContext context) {
const line = Color(0xFFE2E8F0);
return Container(
color: const Color(0xFFF7F8FA),
padding: const EdgeInsets.all(12),
child: Container(
decoration: BoxDecoration(color: Colors.white, border: Border.all(color: const Color(0xFFC2C6D6)), borderRadius: BorderRadius.circular(5)),
clipBehavior: Clip.antiAlias,
child: Column(children: [
Container(height: 16, color: const Color(0xFFF7F8FA), child: Row(children: [
for (int i = 0; i < 4; i++) Expanded(child: Container(margin: const EdgeInsets.symmetric(horizontal: 5), height: 4, color: const Color(0xFFBDC1C6))),
])),
Container(height: 1, color: const Color(0xFFC2C6D6)),
for (int r = 0; r < 3; r++)
Expanded(child: Row(children: [
for (int cIdx = 0; cIdx < 4; cIdx++)
Expanded(child: Container(
margin: const EdgeInsets.all(0.5),
decoration: BoxDecoration(
color: r == 1 && cIdx == 2 ? const Color(0x1A4A7CFF) : Colors.white,
border: r == 1 && cIdx == 2 ? Border.all(color: const Color(0xFF4A7CFF), width: 1.5) : Border.all(color: line, width: 0.5),
),
child: Center(child: Container(height: 4, margin: const EdgeInsets.symmetric(horizontal: 6), color: const Color(0xFFEEF1F7))),
)),
])),
Container(height: 14, decoration: const BoxDecoration(color: Color(0xFFF7F8FA), border: Border(top: BorderSide(color: Color(0xFFC2C6D6), width: 1.5)))),
]),
),
);
}
}
class _ReadableTableThumb extends StatelessWidget {
const _ReadableTableThumb();
@override
Widget build(BuildContext context) {
const accent = Color(0xFF4A7CFF);
const line = Color(0xFFE2E8F0);
const selFill = Color(0x1A4A7CFF);
return Container(
color: const Color(0xFFF7F8FA),
padding: const EdgeInsets.all(12),
child: Container(
decoration: BoxDecoration(color: Colors.white, border: Border.all(color: const Color(0xFFC2C6D6)), borderRadius: BorderRadius.circular(5)),
clipBehavior: Clip.antiAlias,
child: Column(children: [
// header with a sorted column arrow
Container(height: 16, color: const Color(0xFFF7F8FA), child: Row(children: [
for (int i = 0; i < 4; i++)
Expanded(child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 5),
child: Row(children: [
Expanded(child: Container(height: 4, color: i == 3 ? accent : const Color(0xFFBDC1C6))),
if (i == 3) const Icon(Icons.arrow_downward_rounded, size: 7, color: accent),
]),
)),
])),
Container(height: 1, color: const Color(0xFFC2C6D6)),
// rows — two selected (accent fill + left bar)
for (int r = 0; r < 4; r++)
Expanded(child: Container(
decoration: BoxDecoration(
color: (r == 1 || r == 2) ? selFill : Colors.white,
border: Border(
bottom: const BorderSide(color: line, width: 0.5),
left: (r == 1 || r == 2) ? const BorderSide(color: accent, width: 2) : BorderSide.none,
),
),
child: Row(children: [
for (int cIdx = 0; cIdx < 4; cIdx++)
Expanded(child: Center(child: cIdx == 1
? Container(height: 5, margin: const EdgeInsets.symmetric(horizontal: 6), decoration: BoxDecoration(color: const Color(0xFFD7DCE6), borderRadius: BorderRadius.circular(2)))
: (cIdx == 2
? Container(width: 18, height: 7, decoration: BoxDecoration(color: accent.withOpacity(0.18), borderRadius: BorderRadius.circular(999)))
: Container(height: 4, margin: const EdgeInsets.symmetric(horizontal: 6), color: const Color(0xFFEEF1F7))))),
]),
)),
]),
),
);
}
}
class _ErpConsoleThumb extends StatelessWidget {
const _ErpConsoleThumb();
@override
Widget build(BuildContext context) {
const accent = Color(0xFF4A7CFF);
const border = Color(0xFFE2E8F0);
Widget treeRow(int indent, bool folder, {bool sel = false}) => Container(
height: 8,
margin: const EdgeInsets.only(bottom: 4),
child: Row(children: [
SizedBox(width: 5.0 * indent),
Container(width: 5, height: 5, decoration: BoxDecoration(color: folder ? accent.withOpacity(0.8) : const Color(0xFFBDC1C6), borderRadius: BorderRadius.circular(folder ? 1.5 : 2.5))),
const SizedBox(width: 4),
Expanded(child: Container(height: 3, decoration: BoxDecoration(color: sel ? accent.withOpacity(0.5) : const Color(0xFFD7DCE6), borderRadius: BorderRadius.circular(2)))),
]),
);
return Container(
color: const Color(0xFFF7F8FA),
padding: const EdgeInsets.all(10),
child: Container(
decoration: BoxDecoration(color: Colors.white, border: Border.all(color: const Color(0xFFC2C6D6)), borderRadius: BorderRadius.circular(6)),
clipBehavior: Clip.antiAlias,
child: Column(children: [
// top bar
Container(height: 14, color: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 6), child: Row(children: [
Container(width: 7, height: 7, decoration: BoxDecoration(color: accent, borderRadius: BorderRadius.circular(2))),
const Spacer(),
Container(width: 16, height: 6, decoration: BoxDecoration(color: const Color(0xFFEEF1F7), borderRadius: BorderRadius.circular(3))),
const SizedBox(width: 4),
Container(width: 16, height: 6, decoration: BoxDecoration(color: accent.withOpacity(0.15), borderRadius: BorderRadius.circular(3))),
])),
Container(height: 1, color: border),
Expanded(child: Row(children: [
// tree sidebar
Container(width: 58, color: const Color(0xFFF7F8FA), padding: const EdgeInsets.all(7), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
treeRow(0, true),
treeRow(1, false, sel: true),
treeRow(1, false),
treeRow(0, true),
treeRow(1, false),
treeRow(0, true),
])),
Container(width: 1, color: border),
// workspace
Expanded(child: Container(color: const Color(0xFFF7F8FA), padding: const EdgeInsets.all(7), child: Column(children: [
// tabs
Row(children: [
Container(width: 26, height: 9, decoration: BoxDecoration(color: Colors.white, borderRadius: const BorderRadius.vertical(top: Radius.circular(3)), border: Border.all(color: border))),
const SizedBox(width: 2),
Container(width: 22, height: 9, decoration: BoxDecoration(color: const Color(0xFFEDEFF3), borderRadius: const BorderRadius.vertical(top: Radius.circular(3)))),
]),
const SizedBox(height: 5),
// KPI strip
Row(children: [
for (int i = 0; i < 3; i++) ...[
if (i > 0) const SizedBox(width: 4),
Expanded(child: Container(height: 16, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(3), border: Border.all(color: border)))),
],
]),
const SizedBox(height: 5),
// table
Expanded(child: Container(decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(3), border: Border.all(color: border)), child: Column(children: [
Container(height: 7, decoration: const BoxDecoration(color: Color(0xFFF1F3F8), borderRadius: BorderRadius.vertical(top: Radius.circular(3)))),
for (int i = 0; i < 4; i++) Expanded(child: Container(decoration: BoxDecoration(border: Border(bottom: BorderSide(color: border, width: 0.5))))),
]))),
]))),
])),
]),
),
);
}
}
class _TreeThumb extends StatelessWidget {
const _TreeThumb();
@override
Widget build(BuildContext context) {
const folder = Color(0xFF4A7CFF);
const guide = Color(0xFFD7DCE6);
Widget row(int indent, bool isFolder, {bool sel = false}) => Container(
height: 13,
margin: const EdgeInsets.symmetric(vertical: 1.5),
decoration: BoxDecoration(
color: sel ? const Color(0x1A4A7CFF) : Colors.transparent,
borderRadius: BorderRadius.circular(3),
),
child: Row(children: [
SizedBox(width: 8.0 * indent),
if (indent > 0)
Container(width: 1, height: 13, color: guide, margin: const EdgeInsets.only(right: 5)),
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: isFolder ? folder.withOpacity(0.85) : const Color(0xFFBDC1C6),
borderRadius: BorderRadius.circular(isFolder ? 2 : 4),
),
),
const SizedBox(width: 6),
Expanded(child: Container(height: 4, color: isFolder ? const Color(0xFFBDC1C6) : const Color(0xFFD7DCE6))),
const SizedBox(width: 8),
]),
);
return Container(
color: const Color(0xFFF7F8FA),
padding: const EdgeInsets.all(12),
child: Container(
decoration: BoxDecoration(color: Colors.white, border: Border.all(color: const Color(0xFFC2C6D6)), borderRadius: BorderRadius.circular(5)),
clipBehavior: Clip.antiAlias,
child: Column(children: [
Container(height: 16, color: const Color(0xFFF7F8FA), padding: const EdgeInsets.symmetric(horizontal: 6), child: Row(children: [
Container(width: 8, height: 8, decoration: BoxDecoration(color: const Color(0xFFBDC1C6), borderRadius: BorderRadius.circular(2))),
const SizedBox(width: 5),
Expanded(child: Container(height: 5, decoration: BoxDecoration(color: const Color(0xFFEEF1F7), borderRadius: BorderRadius.circular(3)))),
])),
Container(height: 1, color: const Color(0xFFC2C6D6)),
Expanded(child: Padding(
padding: const EdgeInsets.fromLTRB(8, 6, 8, 6),
child: Column(mainAxisAlignment: MainAxisAlignment.start, children: [
row(0, true),
row(1, true),
row(2, false),
row(2, false, sel: true),
row(1, false),
row(0, false),
]),
)),
]),
),
);
}
}
class _NavSidebarThumb extends StatelessWidget {
const _NavSidebarThumb();
@override
Widget build(BuildContext context) {
const accent = Color(0xFF4A7CFF);
const border = Color(0xFFE2E8F0);
Widget eyebrow() => Container(width: 22, height: 3, margin: const EdgeInsets.only(bottom: 5, top: 3), color: const Color(0xFFC2C6D6));
Widget navRow({bool sel = false, bool box = false, double indent = 0}) => Container(
height: 11,
margin: const EdgeInsets.only(bottom: 4),
padding: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
color: sel ? accent : Colors.transparent,
borderRadius: BorderRadius.circular(4),
),
child: Row(children: [
SizedBox(width: 6.0 * indent),
Container(
width: 7,
height: 7,
decoration: BoxDecoration(
color: sel ? Colors.white : (box ? Colors.transparent : const Color(0xFFBDC1C6)),
border: box ? Border.all(color: const Color(0xFFC2C6D6)) : null,
borderRadius: BorderRadius.circular(box ? 2 : 3.5),
),
),
const SizedBox(width: 5),
Expanded(child: Container(height: 3, color: sel ? Colors.white : const Color(0xFFD7DCE6))),
]),
);
return Container(
color: const Color(0xFFF7F8FA),
child: Column(children: [
// app bar
Container(height: 16, color: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 7), child: Row(children: [
Container(width: 8, height: 8, decoration: BoxDecoration(color: accent, borderRadius: BorderRadius.circular(2.5))),
const SizedBox(width: 6),
Expanded(child: Container(height: 6, decoration: BoxDecoration(color: const Color(0xFFEEF1F7), borderRadius: BorderRadius.circular(3)))),
const SizedBox(width: 6),
Container(width: 8, height: 8, decoration: const BoxDecoration(color: Color(0x294A7CFF), shape: BoxShape.circle)),
])),
Container(height: 1, color: border),
Expanded(child: Row(children: [
// sidebar
Container(width: 78, color: Colors.white, padding: const EdgeInsets.fromLTRB(8, 9, 8, 8), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
eyebrow(),
navRow(),
navRow(),
eyebrow(),
navRow(),
navRow(indent: 1, sel: true),
navRow(indent: 1, box: true),
navRow(),
])),
Container(width: 1, color: border),
// page
Expanded(child: Container(color: const Color(0xFFF7F8FA), padding: const EdgeInsets.all(9), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Container(width: 40, height: 8, decoration: BoxDecoration(color: const Color(0xFF0F172A), borderRadius: BorderRadius.circular(2))),
const SizedBox(height: 9),
Row(children: [
for (int i = 0; i < 3; i++) ...[
if (i > 0) const SizedBox(width: 6),
Expanded(child: Container(height: 26, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4), border: Border.all(color: border)))),
],
]),
const SizedBox(height: 6),
Expanded(child: Container(decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4), border: Border.all(color: border)))),
]))),
])),
]),
);
}
}
// (removed unused launcher thumbnails: _FigmaThumb, _ChromeThumb, _DocsThumb, _Box)
// ── AutoSuggestionsBox: a search field with an open suggestions overlay ──
class _SuggestThumb extends StatelessWidget {
const _SuggestThumb();
@override
Widget build(BuildContext context) {
const accent = Color(0xFF4A7CFF);
const line = Color(0xFFE2E8F0);
Widget row({bool first = false}) => Container(
height: 13,
padding: const EdgeInsets.symmetric(horizontal: 8),
alignment: Alignment.centerLeft,
decoration: BoxDecoration(
color: first ? const Color(0xFFEEF3FF) : Colors.white,
border: Border(left: BorderSide(color: first ? accent : Colors.transparent, width: 2)),
),
child: Row(children: [
Container(width: 5, height: 5, decoration: const BoxDecoration(color: Color(0xFFD7DCE6), shape: BoxShape.circle)),
const SizedBox(width: 6),
Container(width: 14, height: 4, color: const Color(0xFFD7DCE6)),
Container(width: 9, height: 4, color: accent),
Container(width: 7, height: 4, color: const Color(0xFFD7DCE6)),
]),
);
return Container(
color: const Color(0xFFF7F8FA),
padding: const EdgeInsets.fromLTRB(16, 16, 16, 12),
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
// the field
Container(
height: 22,
padding: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(color: Colors.white, border: Border.all(color: accent, width: 1.4), borderRadius: BorderRadius.circular(5)),
child: Row(children: [
const Icon(Icons.search_rounded, size: 11, color: accent),
const SizedBox(width: 6),
Container(width: 30, height: 4, color: const Color(0xFF0F172A)),
Container(width: 1.5, height: 11, margin: const EdgeInsets.only(left: 1), color: accent),
]),
),
const SizedBox(height: 4),
// overlay
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: line),
borderRadius: BorderRadius.circular(6),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.10), blurRadius: 8, offset: const Offset(0, 3))],
),
clipBehavior: Clip.antiAlias,
child: Column(children: [row(first: true), Container(height: 0.5, color: line), row(), Container(height: 0.5, color: line), row()]),
),
]),
);
}
}
// ── EditableTable combo cell: a cell in edit mode with an auto-suggest overlay ──
class _ComboThumb extends StatelessWidget {
const _ComboThumb();
@override
Widget build(BuildContext context) {
const accent = Color(0xFF4A7CFF);
const line = Color(0xFFE2E8F0);
Widget sugg(String pre, String hit, String post, {bool first = false}) => Container(
height: 12,
padding: const EdgeInsets.symmetric(horizontal: 6),
color: first ? const Color(0xFFEEF3FF) : Colors.white,
alignment: Alignment.centerLeft,
child: Row(children: [
Container(width: 10, height: 4, color: const Color(0xFFD7DCE6)),
Container(width: 6, height: 4, color: accent),
Container(width: 8, height: 4, color: const Color(0xFFD7DCE6)),
]),
);
return Container(
color: const Color(0xFFF7F8FA),
padding: const EdgeInsets.all(12),
child: Container(
decoration: BoxDecoration(color: Colors.white, border: Border.all(color: const Color(0xFFC2C6D6)), borderRadius: BorderRadius.circular(5)),
clipBehavior: Clip.antiAlias,
child: Column(children: [
// header
Container(height: 14, color: const Color(0xFFF7F8FA), child: Row(children: [
for (int i = 0; i < 3; i++) Expanded(child: Container(margin: const EdgeInsets.symmetric(horizontal: 5), height: 4, color: const Color(0xFFBDC1C6))),
])),
Container(height: 1, color: const Color(0xFFC2C6D6)),
// a normal row
SizedBox(height: 13, child: Row(children: [
for (int i = 0; i < 3; i++) Expanded(child: Container(decoration: const BoxDecoration(border: Border(right: BorderSide(color: line, width: 0.5))), child: Center(child: Container(height: 4, margin: const EdgeInsets.symmetric(horizontal: 6), color: const Color(0xFFEEF1F7))))),
])),
// the editing row: middle cell is active + overlay
Expanded(child: Stack(clipBehavior: Clip.none, children: [
Row(children: [
Expanded(child: Container(decoration: const BoxDecoration(border: Border(right: BorderSide(color: line, width: 0.5))))),
Expanded(child: Container(decoration: BoxDecoration(color: const Color(0x1A4A7CFF), border: Border.all(color: accent, width: 1.5)), child: Center(child: Container(height: 4, margin: const EdgeInsets.symmetric(horizontal: 5), color: accent)))),
Expanded(child: Container(decoration: const BoxDecoration(border: Border(left: BorderSide(color: line, width: 0.5))))),
]),
// suggestions overlay hanging under the active cell
Positioned(
left: 0, right: 0, top: 14,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: Container(
decoration: BoxDecoration(color: Colors.white, border: Border.all(color: accent.withOpacity(0.45)), borderRadius: BorderRadius.circular(4), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.12), blurRadius: 6, offset: const Offset(0, 2))]),
clipBehavior: Clip.antiAlias,
child: Column(mainAxisSize: MainAxisSize.min, children: [sugg('', '', '', first: true), Container(height: 0.5, color: line), sugg('', '', '')]),
),
),
),
])),
]),
),
);
}
}
// ── ReadableTable filter system: a filter chip bar above a filtered grid ──
class _FilterThumb extends StatelessWidget {
const _FilterThumb();
@override
Widget build(BuildContext context) {
const accent = Color(0xFF4A7CFF);
const line = Color(0xFFE2E8F0);
Widget chip({bool active = false}) => Container(
height: 11,
padding: const EdgeInsets.symmetric(horizontal: 5),
decoration: BoxDecoration(
color: active ? accent.withOpacity(0.14) : Colors.white,
border: Border.all(color: active ? accent.withOpacity(0.5) : const Color(0xFFC2C6D6)),
borderRadius: BorderRadius.circular(999),
),
child: Row(children: [
Container(width: 4, height: 4, decoration: BoxDecoration(color: active ? accent : const Color(0xFFBDC1C6), shape: BoxShape.circle)),
const SizedBox(width: 3),
Container(width: active ? 16 : 12, height: 3, color: active ? accent : const Color(0xFFC2C6D6)),
]),
);
return Container(
color: const Color(0xFFF7F8FA),
padding: const EdgeInsets.all(12),
child: Container(
decoration: BoxDecoration(color: Colors.white, border: Border.all(color: const Color(0xFFC2C6D6)), borderRadius: BorderRadius.circular(5)),
clipBehavior: Clip.antiAlias,
child: Column(children: [
// filter bar
Container(
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 6),
decoration: const BoxDecoration(color: Color(0xFFF7F8FA), border: Border(bottom: BorderSide(color: Color(0xFFC2C6D6)))),
child: Row(children: [
const Icon(Icons.filter_alt_outlined, size: 10, color: accent),
const SizedBox(width: 5),
chip(active: true),
const SizedBox(width: 4),
chip(),
const Spacer(),
Container(width: 14, height: 7, decoration: BoxDecoration(color: const Color(0xFFEEF1F7), borderRadius: BorderRadius.circular(3))),
]),
),
// header
Container(height: 12, color: Colors.white, child: Row(children: [
for (int i = 0; i < 4; i++) Expanded(child: Container(margin: const EdgeInsets.symmetric(horizontal: 5), height: 3, color: const Color(0xFFBDC1C6))),
])),
Container(height: 1, color: line),
// filtered rows (fewer, with one highlighted as match)
for (int r = 0; r < 3; r++)
Expanded(child: Container(
decoration: BoxDecoration(border: Border(bottom: const BorderSide(color: line, width: 0.5)), color: r == 0 ? accent.withOpacity(0.05) : Colors.white),
child: Row(children: [
for (int cIdx = 0; cIdx < 4; cIdx++)
Expanded(child: Center(child: Container(height: 3, margin: const EdgeInsets.symmetric(horizontal: 6), color: cIdx == 1 ? const Color(0xFFD7DCE6) : const Color(0xFFEEF1F7)))),
]),
)),
]),
),
);
}
}
// ── FilterEditingView: the grouped condition builder card ──
class _FilterEditingThumb extends StatelessWidget {
const _FilterEditingThumb();
@override
Widget build(BuildContext context) {
const accent = Color(0xFF4A7CFF);
const line = Color(0xFFE2E8F0);
Widget pill(Color c, double w) => Container(width: w, height: 7, decoration: BoxDecoration(color: c, borderRadius: BorderRadius.circular(3)));
Widget condition() => Container(
margin: const EdgeInsets.only(bottom: 5),
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 5),
decoration: BoxDecoration(color: Colors.white, border: Border.all(color: line), borderRadius: BorderRadius.circular(4)),
child: Row(children: [
pill(const Color(0xFFD7DCE6), 22),
const SizedBox(width: 4),
pill(accent.withOpacity(0.18), 16),
const SizedBox(width: 4),
Expanded(child: pill(const Color(0xFFEEF1F7), 0)),
const SizedBox(width: 4),
const Icon(Icons.close, size: 8, color: Color(0xFFBDC1C6)),
]),
);
return Container(
color: const Color(0xFFF7F8FA),
padding: const EdgeInsets.all(12),
child: Container(
decoration: BoxDecoration(color: Colors.white, border: Border.all(color: const Color(0xFFC2C6D6)), borderRadius: BorderRadius.circular(6)),
clipBehavior: Clip.antiAlias,
child: Column(children: [
// title bar
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 7),
decoration: const BoxDecoration(border: Border(bottom: BorderSide(color: line))),
child: Row(children: [
const Icon(Icons.tune, size: 11, color: accent),
const SizedBox(width: 5),
pill(const Color(0xFF0F172A), 34),
const Spacer(),
Container(width: 16, height: 9, decoration: BoxDecoration(color: accent.withOpacity(0.14), borderRadius: BorderRadius.circular(3))),
]),
),
// grouped conditions (AND/OR group with nested rows)
Expanded(child: Container(
padding: const EdgeInsets.all(8),
child: Container(
padding: const EdgeInsets.fromLTRB(7, 7, 7, 3),
decoration: BoxDecoration(
color: const Color(0xFFF7F8FA),
border: Border.all(color: accent.withOpacity(0.35)),
borderRadius: BorderRadius.circular(5),
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
Container(padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2), decoration: BoxDecoration(color: accent, borderRadius: BorderRadius.circular(3)), child: const SizedBox(width: 14, height: 4)),
const SizedBox(width: 5),
pill(const Color(0xFFC2C6D6), 18),
]),
const SizedBox(height: 6),
condition(),
condition(),
]),
),
)),
]),
),
);
}
}
// ── wraps a pushed demo with a floating "back to launcher" button ──
class _BackScaffold extends StatelessWidget {
final Widget child;
const _BackScaffold({required this.child});
@override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned.fill(child: child),
Positioned(
left: 16,
bottom: 16,
child: SafeArea(
child: Material(
color: Colors.black.withOpacity(0.62),
borderRadius: BorderRadius.circular(999),
child: InkWell(
borderRadius: BorderRadius.circular(999),
onTap: () => Navigator.of(context).maybePop(),
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 14, vertical: 10),
child: Row(mainAxisSize: MainAxisSize.min, children: [
Icon(Icons.arrow_back, size: 16, color: Colors.white),
SizedBox(width: 7),
Text('Demos', style: TextStyle(fontFamily: BrowserStyleTabBarThemeData.bodyFont, fontSize: 13, fontWeight: FontWeight.w600, color: Colors.white)),
]),
),
),
),
),
),
],
);
}
}
// ── the documentation gallery, self-contained with a theme toggle ──
class _GalleryRoute extends StatefulWidget {
const _GalleryRoute();
@override
State<_GalleryRoute> createState() => _GalleryRouteState();
}
class _GalleryRouteState extends State<_GalleryRoute> {
bool _light = true;
@override
Widget build(BuildContext context) {
return themed(
brightness: _light ? Brightness.light : Brightness.dark,
ext: _light ? BrowserStyleTabBarThemeData.light : BrowserStyleTabBarThemeData.dark,
child: BrowserTabsDemo(light: _light, onToggleTheme: (v) => setState(() => _light = v)),
);
}
}