From 999a4f61c6b65de391f3ebbe49aa1404d896cb64 Mon Sep 17 00:00:00 2001 From: "Anaz S. Aji" Date: Mon, 27 Jul 2026 09:23:06 +0700 Subject: [PATCH 1/2] docs(example): borderless/gradient card demo + searchable icon catalog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Widget Gallery — CofluiCard section now demos: - Default (border + shadow) - Borderless (borderless: true — flat, embeddable) - Gradient (gradient: CofluiGradients.accent) New 'Icons' nav tab — searchable catalog of the 111 Material icon names supported by IconResolver. Tap any icon card → copies a ready-to-paste JSON snippet ('{"type":"icon","props":{"icon":""}}') to the clipboard. Paste into Playground to render instantly. --- example/lib/main.dart | 7 + example/lib/samples/icon_catalog.dart | 37 ++++ example/lib/screens/icon_catalog_screen.dart | 180 ++++++++++++++++++ .../lib/screens/widget_gallery_screen.dart | 21 +- 4 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 example/lib/samples/icon_catalog.dart create mode 100644 example/lib/screens/icon_catalog_screen.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 82fc868..9b8a71d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -5,6 +5,7 @@ import 'screens/detail_page_screen.dart'; import 'screens/dynamic_components_screen.dart'; import 'screens/dynamic_dashboard_screen.dart'; import 'screens/dynamic_form_screen.dart'; +import 'screens/icon_catalog_screen.dart'; import 'screens/playground_screen.dart'; import 'screens/reference_screen.dart'; import 'screens/responsive_screen.dart'; @@ -82,6 +83,11 @@ class _ShellState extends State<_Shell> { icon: Icons.dashboard_outlined, selectedIcon: Icons.dashboard, ), + ( + label: 'Icons', + icon: Icons.grid_view_outlined, + selectedIcon: Icons.grid_view, + ), ( label: 'Playground', icon: Icons.code_outlined, @@ -106,6 +112,7 @@ class _ShellState extends State<_Shell> { DynamicFormScreen(), DynamicDashboardScreen(), PlaygroundScreen(), + IconCatalogScreen(), ReferenceScreen(), ResponsiveScreen(), ]; diff --git a/example/lib/samples/icon_catalog.dart b/example/lib/samples/icon_catalog.dart new file mode 100644 index 0000000..d4ae904 --- /dev/null +++ b/example/lib/samples/icon_catalog.dart @@ -0,0 +1,37 @@ +// Katalog nama ikon Material yang didukung IconResolver. +// +// Daftar ini disinkronkan dengan IconResolver.map di lib/. Setiap entri bisa +// di-copy sebagai snippet JSON siap tempel ke Playground atau detail_page. +const List cofluiIconCatalog = [ + // Actions + 'add', 'edit', 'create', 'delete', 'remove', 'reset', 'refresh', + 'check', 'check_circle', 'done', 'close', 'clear', 'cancel', + 'save', 'send', 'search', 'filter', 'sort', + // Navigation + 'arrow_back', 'arrow_forward', 'arrow_upward', 'arrow_downward', + 'chevron_right', 'chevron_left', 'expand_more', 'expand_less', + 'home', 'menu', 'dashboard', 'more_vert', 'more_horiz', + // People + 'person', 'people', 'group', 'account', 'account_circle', + 'admin', 'badge', 'verified_user', + // Content + 'description', 'article', 'document', 'folder', 'file_copy', + 'image', 'photo', 'picture', 'picture_as_pdf', 'pdf', + 'attachment', 'attach_file', 'download', 'upload', + 'cloud_upload', 'cloud_download', 'link', + // Communication + 'email', 'mail', 'phone', 'call', 'message', 'chat', + 'notifications', 'bell', 'share', 'favorite', 'heart', 'star', + // Commerce + 'shopping_cart', 'cart', 'payment', 'receipt', 'money', 'currency', + 'account_balance', 'payments', + // Status + 'info', 'warning', 'error', 'success', 'pending', + 'clock', 'time', 'schedule', 'calendar', 'calendar_today', 'event', + 'history', 'visibility', 'visibility_off', 'lock', 'unlock', 'key', + 'fingerprint', 'verified', 'task', 'task_alt', + // Misc + 'copy', 'print', 'help', 'help_outline', 'lightbulb', 'flag', + 'tag', 'label', 'location', 'location_on', 'place', + 'gavel', 'shield', 'security', +]; diff --git a/example/lib/screens/icon_catalog_screen.dart b/example/lib/screens/icon_catalog_screen.dart new file mode 100644 index 0000000..6d53a00 --- /dev/null +++ b/example/lib/screens/icon_catalog_screen.dart @@ -0,0 +1,180 @@ +import 'package:coflui/coflui.dart'; +import 'package:flutter/material.dart'; + +import '../samples/icon_catalog.dart'; +import '../util/clipboard_util.dart'; + +/// A searchable catalog of icon names supported by [IconResolver]. +/// +/// Tap any icon card → copies a JSON snippet ready to paste into the +/// Playground or detail page. Search bar filters by name. +/// +/// This screen is a **live reference** for which Material icon names can be +/// used in `props.icon` (detail_row), `props.leading` / `props.trailing` +/// (list_tile), and `props.icon` (chip / button). +class IconCatalogScreen extends StatefulWidget { + const IconCatalogScreen({super.key}); + + @override + State createState() => _IconCatalogScreenState(); +} + +class _IconCatalogScreenState extends State { + String _query = ''; + String? _lastCopied; + + @override + Widget build(BuildContext context) { + final filtered = cofluiIconCatalog + .where((n) => n.contains(_query.toLowerCase())) + .toList(); + + return Scaffold( + appBar: AppBar(title: const Text('Icon Catalog')), + body: Column( + children: [ + // Search bar + Padding( + padding: const EdgeInsets.all(12), + child: TextField( + decoration: InputDecoration( + hintText: 'Search icons (e.g. check, arrow, person)…', + prefixIcon: const Icon(Icons.search, size: 20), + isDense: true, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + onChanged: (q) => setState(() => _query = q), + ), + ), + // Helper text + Padding( + padding: const EdgeInsets.fromLTRB(12, 0, 12, 8), + child: Align( + alignment: Alignment.centerLeft, + child: CofluiText( + 'Tap an icon to copy a JSON snippet → paste into Playground. ' + '${filtered.length} of ${cofluiIconCatalog.length} icons.', + style: TextStyle( + fontSize: 11, + color: CofluiColors.onSurfaceVariant, + ), + ), + ), + ), + // Grid + Expanded( + child: GridView.builder( + padding: const EdgeInsets.fromLTRB(12, 0, 12, 16), + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 110, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + childAspectRatio: 0.85, + ), + itemCount: filtered.length, + itemBuilder: (_, i) { + final name = filtered[i]; + final icon = IconResolver.resolve(name) ?? Icons.help_outline; + final isCopied = _lastCopied == name; + return _IconCard( + name: name, + icon: icon, + isCopied: isCopied, + onTap: () => _copy(name), + ); + }, + ), + ), + ], + ), + ); + } + + /// Copies a ready-to-paste JSON snippet using this icon name. + Future _copy(String name) async { + final snippet = '''{ + "type": "icon", + "props": { "icon": "$name", "size": 24, "color": "#088ECE" } +}'''; + final ok = await copyToClipboard(snippet); + if (!mounted) return; + setState(() => _lastCopied = name); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(ok + ? 'Copied: icon "$name" — paste into Playground' + : 'Copy failed — select manually'), + duration: const Duration(seconds: 2), + ), + ); + } +} + +class _IconCard extends StatelessWidget { + final String name; + final IconData icon; + final bool isCopied; + final VoidCallback onTap; + + const _IconCard({ + required this.name, + required this.icon, + required this.isCopied, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Material( + color: isCopied + ? CofluiColors.accentGreen.withValues(alpha: 0.15) + : CofluiColors.surface, + borderRadius: BorderRadius.circular(10), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(10), + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: isCopied + ? CofluiColors.accentGreen + : CofluiColors.border, + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + icon, + size: 28, + color: isCopied + ? CofluiColors.accentGreen + : CofluiColors.onSurface, + ), + const SizedBox(height: 6), + Text( + name, + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 10, + fontFamily: 'monospace', + color: CofluiColors.onSurfaceVariant, + ), + ), + if (isCopied) ...[ + const SizedBox(height: 2), + Icon(Icons.check, size: 12, color: CofluiColors.accentGreen), + ], + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/screens/widget_gallery_screen.dart b/example/lib/screens/widget_gallery_screen.dart index ba43e74..ccbc7eb 100644 --- a/example/lib/screens/widget_gallery_screen.dart +++ b/example/lib/screens/widget_gallery_screen.dart @@ -99,10 +99,25 @@ class WidgetGalleryScreen extends StatelessWidget { // ── Card ────────────────────────────────────────── const _SectionTitle('CofluiCard'), const CofluiCard( - title: 'Card Title', + title: 'Default Card', child: CofluiText( - 'Card content goes here. This is a styled surface with a ' - 'title, soft shadow, and rounded corners.', + 'Default style — white surface, soft border, subtle shadow.', + ), + ), + const SizedBox(height: 10), + const CofluiCard( + title: 'Borderless Card', + borderless: true, + child: CofluiText( + 'borderless: true — no border, no shadow. Flat, embeddable.', + ), + ), + const SizedBox(height: 10), + CofluiCard( + gradient: CofluiGradients.accent, + child: const CofluiText( + 'gradient: CofluiGradients.accent — brand gradient background.', + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), ), ), From f50418d58b2e223c44d660c9292bed0dac894171 Mon Sep 17 00:00:00 2001 From: "Anaz S. Aji" Date: Mon, 27 Jul 2026 09:42:16 +0700 Subject: [PATCH 2/2] docs(example): Guide screen + Showcase hub + nav reorg + borderless fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Navigation reorganized from 9 scattered tabs → 6 logical tabs by learning path: 1. Guide (NEW) — tutorials, JSON conventions, clone patterns 2. Gallery — native widget showcase (incl. borderless/gradient card demos) 3. Showcase (NEW) — hub for dynamic-UI demos (Detail/Components/Form/Dashboard) with internal TabBar 4. Playground — live JSON → UI editor 5. Icons — searchable catalog (tap to copy) 6. Reference — JSON schema reference Guide screen: 7 sections with copy-paste code blocks covering native widgets, dynamic UI, component types, props vs style convention, list/repeat, icons (any source), and clone-ready detail page pattern. Playground 'Halaman Detail' template + detail_page_json now use props.borderless: true (fixes the gotcha where borderless was placed at component top-level instead of inside props). --- example/lib/main.dart | 65 ++-- example/lib/samples/detail_page_json.dart | 1 + example/lib/samples/playground_templates.dart | 1 + example/lib/screens/guide_screen.dart | 340 ++++++++++++++++++ example/lib/screens/showcase_screen.dart | 44 +++ 5 files changed, 410 insertions(+), 41 deletions(-) create mode 100644 example/lib/screens/guide_screen.dart create mode 100644 example/lib/screens/showcase_screen.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 9b8a71d..1ad28fa 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,22 +1,25 @@ import 'package:coflui/coflui.dart'; import 'package:flutter/material.dart'; -import 'screens/detail_page_screen.dart'; -import 'screens/dynamic_components_screen.dart'; -import 'screens/dynamic_dashboard_screen.dart'; -import 'screens/dynamic_form_screen.dart'; +import 'screens/guide_screen.dart'; import 'screens/icon_catalog_screen.dart'; import 'screens/playground_screen.dart'; import 'screens/reference_screen.dart'; -import 'screens/responsive_screen.dart'; +import 'screens/showcase_screen.dart'; import 'screens/widget_gallery_screen.dart'; /// Coflui example app entry point. /// -/// A simple shell with a NavigationRail (desktop/tablet) or BottomNavigationBar -/// (mobile) — itself demonstrating responsive layout. +/// Navigation is organized into **6 tabs by learning path**: +/// 1. **Guide** — start here (tutorials, JSON conventions, clone patterns) +/// 2. **Gallery** — native widget showcase +/// 3. **Showcase** — full dynamic-UI pages (Detail, Components, Form, Dashboard) +/// 4. **Playground** — live JSON → UI editor +/// 5. **Icons** — searchable icon catalog (tap to copy) +/// 6. **Reference** — JSON schema reference +/// +/// The Responsive demo lives inside Gallery. void main() { - // Register the dynamic UI engine's default builders once at app boot. DynamicUIBootstrap.registerDefaults(); runApp(const CofluiExampleApp()); } @@ -46,7 +49,6 @@ class CofluiExampleApp extends StatelessWidget { } } -/// Responsive shell: NavigationRail on tablet/desktop, BottomNav on mobile. class _Shell extends StatefulWidget { const _Shell(); @@ -58,68 +60,49 @@ class _ShellState extends State<_Shell> { int _index = 0; static const _destinations = [ + ( + label: 'Guide', + icon: Icons.school_outlined, + selectedIcon: Icons.school, + ), ( label: 'Gallery', icon: Icons.widgets_outlined, selectedIcon: Icons.widgets, ), ( - label: 'Detail', - icon: Icons.receipt_long_outlined, - selectedIcon: Icons.receipt_long, - ), - ( - label: 'Components', - icon: Icons.extension_outlined, - selectedIcon: Icons.extension, + label: 'Showcase', + icon: Icons.view_carousel_outlined, + selectedIcon: Icons.view_carousel, ), ( - label: 'Form', - icon: Icons.description_outlined, - selectedIcon: Icons.description, - ), - ( - label: 'Dashboard', - icon: Icons.dashboard_outlined, - selectedIcon: Icons.dashboard, + label: 'Playground', + icon: Icons.code_outlined, + selectedIcon: Icons.code, ), ( label: 'Icons', icon: Icons.grid_view_outlined, selectedIcon: Icons.grid_view, ), - ( - label: 'Playground', - icon: Icons.code_outlined, - selectedIcon: Icons.code, - ), ( label: 'Reference', icon: Icons.menu_book_outlined, selectedIcon: Icons.menu_book, ), - ( - label: 'Responsive', - icon: Icons.devices_outlined, - selectedIcon: Icons.devices, - ), ]; static const _screens = [ + GuideScreen(), WidgetGalleryScreen(), - DetailPageScreen(), - DynamicComponentsScreen(), - DynamicFormScreen(), - DynamicDashboardScreen(), + ShowcaseScreen(), PlaygroundScreen(), IconCatalogScreen(), ReferenceScreen(), - ResponsiveScreen(), ]; @override Widget build(BuildContext context) { - // Use Coflui's own breakpoint to switch nav style. final useRail = !CofluiBreakpoints.isMobile(context); if (useRail) { diff --git a/example/lib/samples/detail_page_json.dart b/example/lib/samples/detail_page_json.dart index 729c8e5..c6a427e 100644 --- a/example/lib/samples/detail_page_json.dart +++ b/example/lib/samples/detail_page_json.dart @@ -62,6 +62,7 @@ List> buildDetailJson(Map d) { { 'id': 'detail_card', 'type': 'card', + 'props': {'borderless': true}, 'style': {'padding': 16}, 'children': [ { diff --git a/example/lib/samples/playground_templates.dart b/example/lib/samples/playground_templates.dart index 66d5249..61b15e0 100644 --- a/example/lib/samples/playground_templates.dart +++ b/example/lib/samples/playground_templates.dart @@ -138,6 +138,7 @@ const playgroundTemplates = [ { "id": "detail_card", "type": "card", + "props": { "borderless": true }, "style": { "padding": 16 }, "children": [ { "id": "dr1", "type": "detail_row", "props": { "icon": "domain", "label": "Company", "value": "PT Pura Barutama" } }, diff --git a/example/lib/screens/guide_screen.dart b/example/lib/screens/guide_screen.dart new file mode 100644 index 0000000..d2e9b08 --- /dev/null +++ b/example/lib/screens/guide_screen.dart @@ -0,0 +1,340 @@ +import '../util/clipboard_util.dart' show copyToClipboard; +import 'package:coflui/coflui.dart'; +import 'package:flutter/material.dart'; + +/// Getting Started — the FIRST screen users see. +/// +/// A concise, copy-paste-ready tutorial covering: +/// 1. What Coflui is +/// 2. Native widgets (hand-written) +/// 3. Dynamic UI (JSON-driven) +/// 4. JSON conventions (props vs style) +/// 5. Clone-ready patterns +/// +/// Tap any code block to copy it. +class GuideScreen extends StatelessWidget { + const GuideScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Guide — Getting Started')), + body: ListView( + padding: const EdgeInsets.all(16), + children: [ + const _Intro(), + const SizedBox(height: 16), + const _Section( + icon: Icons.widgets, + title: '1. Native Widgets', + body: 'Coflui provides styled wrappers over Flutter widgets. ' + 'Import once, use everywhere. No JSON needed.', + code: '''import 'package:coflui/coflui.dart'; + +CofluiCard( + title: 'My Card', + child: Column( + children: [ + CofluiText('Hello, Coflui!'), + CofluiButton( + label: 'Tap me', + icon: Icons.send, + onPressed: () {}, + ), + ], + ), +)''', + ), + SizedBox(height: 16), + _Section( + icon: Icons.code, + title: '2. Dynamic UI (JSON-driven)', + body: 'Render an entire UI tree from JSON. Register builders once, ' + 'then feed JSON. Great for server-driven UI, forms, and ' + 'detail pages.', + code: '''DynamicUIBootstrap.registerDefaults(); + +final ctrl = CofluiFormController() + ..loadFromJson([ + {'id': 't', 'type': 'text', 'label': 'Hello!'}, + { + 'id': 'btn', 'type': 'button', + 'label': 'Submit', + 'props': {'icon': 'check', 'action': 'submit'}, + }, + ]); + +DynamicUIWidget(ctrl.components.first, controller: ctrl)''', + ), + SizedBox(height: 16), + _Section( + icon: Icons.category, + title: '3. Component Types', + body: 'Every type maps to a builder. Use aliases freely ' + '(e.g. "tile" = "list_tile").', + code: '''// Containers (nestable): +"column", "row", "card", "section", "grid" + +// Display: +"text", "heading", "divider", "chip" + +// Input: +"textfield", "dropdown", "switch", "datepicker", "checkbox" + +// Action: +"button" + +// Media: +"icon", "image", "gradient_bar" + +// Content: +"list_tile", "detail_row" + +// Composite: +"list" // repeat a child template over items[]''', + ), + SizedBox(height: 16), + _Section( + icon: Icons.tune, + title: '4. props vs style (IMPORTANT)', + body: 'A common gotcha: component-specific config goes in `props`, ' + 'visual styling goes in `style`. They are NOT interchangeable.', + code: '''{ + "type": "card", + "props": { + "borderless": true, // ← config specific to card + "gradient": "accent", + "maxWidth": 480 + }, + "style": { + "padding": 16, // ← generic visual style + "bgColor": "#FFFFFF", + "radius": 14, + "elevation": 6 + }, + "children": [...] +}''', + ), + SizedBox(height: 16), + _Section( + icon: Icons.repeat, + title: '5. list — Repeat Over Arrays', + body: 'The `list` component clones its first child for each item. ' + 'Use {field} placeholders to bind item data. Perfect for ' + 'approvers, attachments, line items.', + code: '''{ + "type": "list", + "props": { + "items": [ + {"name": "Andi", "role": "Manager"}, + {"name": "Maya", "role": "Finance"} + ], + "direction": "vertical", + "spacing": 8 + }, + "children": [ + { + "type": "list_tile", + "props": { + "title": "{name}", + "subtitle": "{role}", + "leading": "person" + } + } + ] +}''', + ), + SizedBox(height: 16), + _Section( + icon: Icons.image, + title: '6. Icons — Any Source', + body: 'Icon props accept Material names, code-points, SVG assets, ' + 'PNG assets, or network URLs. Auto-detected.', + code: '''// Material icon name (see Icons tab): +{"icon": "home", "size": 24} + +// Hex code-point: +{"icon": "0xe318"} + +// SVG asset: +{"icon": "assets/logo.svg"} + +// Network image (disk-cached): +{"icon": "https://example.com/logo.png"}''', + ), + SizedBox(height: 16), + _Section( + icon: Icons.content_copy, + title: '7. Clone-Ready Detail Page', + body: 'A full detail page is just a JSON template + a data map. ' + 'Keep the template, swap the data — the whole page re-renders. ' + 'See the "Detail" tab for a live example.', + code: '''// In your app: +final json = buildDetailJson(apiResponse); +final ctrl = CofluiFormController()..loadFromJson(json); +// → render ctrl.components via DynamicUIWidget''', + ), + SizedBox(height: 32), + Padding( + padding: EdgeInsets.all(16), + child: CofluiText( + '💡 Tip: Open the Playground tab to experiment with JSON in ' + 'real-time. Load a template, tweak, and hit Render. Copy JSON ' + 'snippets from the Detail and Icons tabs.', + style: TextStyle( + fontSize: 12, + fontStyle: FontStyle.italic, + color: CofluiColors.onSurfaceVariant, + ), + ), + ), + SizedBox(height: 24), + ], + ), + ); + } +} + +class _Intro extends StatelessWidget { + const _Intro(); + + @override + Widget build(BuildContext context) { + return CofluiCard( + gradient: CofluiGradients.accent, + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Coflui', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + SizedBox(height: 4), + Text( + 'A Flutter UI package combining a JSON-driven dynamic UI engine ' + 'with a set of native widgets. State-management agnostic, ' + 'responsive across mobile / tablet / desktop.', + style: TextStyle(fontSize: 13, color: Colors.white70, height: 1.5), + ), + ], + ), + ); + } +} + +class _Section extends StatelessWidget { + final IconData icon; + final String title; + final String body; + final String code; + + const _Section({ + required this.icon, + required this.title, + required this.body, + required this.code, + }); + + @override + Widget build(BuildContext context) { + return CofluiCard( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(icon, size: 20, color: CofluiColors.accentBlue), + const SizedBox(width: 8), + Text( + title, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + body, + style: TextStyle( + fontSize: 13, + color: CofluiColors.onSurfaceVariant, + height: 1.5, + ), + ), + const SizedBox(height: 12), + _CodeBlock(code: code), + ], + ), + ); + } +} + +class _CodeBlock extends StatefulWidget { + final String code; + const _CodeBlock({required this.code}); + + @override + State<_CodeBlock> createState() => _CodeBlockState(); +} + +class _CodeBlockState extends State<_CodeBlock> { + bool _copied = false; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () async { + await copyToClipboard(widget.code); + if (!mounted) return; + setState(() => _copied = true); + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Copied to clipboard'), + duration: Duration(milliseconds: 1200), + ), + ); + Future.delayed(const Duration(seconds: 2), + () => mounted ? setState(() => _copied = false) : null); + }, + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFF1E1E2E), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: SelectableText( + widget.code, + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 12, + color: Color(0xFFCDD6F4), + height: 1.5, + ), + ), + ), + const SizedBox(width: 8), + Icon( + _copied ? Icons.check : Icons.copy, + size: 16, + color: _copied + ? CofluiColors.accentGreen + : const Color(0xFF6C7086), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/screens/showcase_screen.dart b/example/lib/screens/showcase_screen.dart new file mode 100644 index 0000000..8e9eb31 --- /dev/null +++ b/example/lib/screens/showcase_screen.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; + +import 'detail_page_screen.dart'; +import 'dynamic_components_screen.dart'; +import 'dynamic_dashboard_screen.dart'; +import 'dynamic_form_screen.dart'; + +/// A hub for all full-page dynamic-UI demos. +/// +/// Instead of cluttering the main nav with 4 separate tabs, this screen +/// uses an internal TabBar to switch between the dynamic demos: +/// Detail Page, Components, Form, and Dashboard. +class ShowcaseScreen extends StatelessWidget { + const ShowcaseScreen({super.key}); + + static const _tabs = [ + ('Detail', DetailPageScreen()), + ('Components', DynamicComponentsScreen()), + ('Form', DynamicFormScreen()), + ('Dashboard', DynamicDashboardScreen()), + ]; + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: _tabs.length, + child: Scaffold( + appBar: AppBar( + title: const Text('Showcase — Dynamic UI'), + bottom: TabBar( + isScrollable: true, + tabAlignment: TabAlignment.start, + tabs: [ + for (final t in _tabs) Tab(text: t.$1), + ], + ), + ), + body: TabBarView( + children: [for (final t in _tabs) t.$2], + ), + ), + ); + } +}