flux_ai_ui 1.0.0 copy "flux_ai_ui: ^1.0.0" to clipboard
flux_ai_ui: ^1.0.0 copied to clipboard

Open Flutter primitives for AI-native mobile interfaces.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flux_ai_ui/flux_ai_ui.dart';

void main() => runApp(const FluxAiDemoApp());

class FluxAiDemoApp extends StatelessWidget {
  const FluxAiDemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flux AI UI',
      theme: FluxAiTheme.materialLight(),
      home: Uri.base.queryParameters['preview'] == 'all'
          ? const AllComponentsPreviewScreen()
          : const ComponentIndexScreen(),
    );
  }
}

class ComponentIndexScreen extends StatelessWidget {
  const ComponentIndexScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final tokens = FluxAiTheme.of(context);
    return Scaffold(
      body: SafeArea(
        child: CustomScrollView(
          slivers: [
            SliverToBoxAdapter(
              child: Padding(
                padding: const EdgeInsets.fromLTRB(20, 18, 20, 14),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Flux AI UI',
                      style: Theme.of(context).textTheme.headlineLarge,
                    ),
                    const SizedBox(height: 8),
                    Text(
                      'Open Flutter primitives for agentic mobile interfaces.',
                      style: TextStyle(color: tokens.mutedInk, fontSize: 16),
                    ),
                    const SizedBox(height: 16),
                    Wrap(
                      spacing: 8,
                      runSpacing: 8,
                      children: const [
                        FluxPill(label: '17 components', icon: Icons.widgets),
                        FluxPill(
                          label: 'iOS + Android',
                          icon: Icons.phone_iphone,
                        ),
                        FluxPill(label: 'MIT', icon: Icons.lock_open),
                      ],
                    ),
                  ],
                ),
              ),
            ),
            SliverPadding(
              padding: const EdgeInsets.fromLTRB(16, 0, 16, 24),
              sliver: SliverLayoutBuilder(
                builder: (context, constraints) {
                  final crossAxisCount = constraints.crossAxisExtent >= 720
                      ? 2
                      : 1;
                  return SliverGrid.builder(
                    itemCount: _sections.length,
                    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: crossAxisCount,
                      crossAxisSpacing: 12,
                      mainAxisSpacing: 12,
                      mainAxisExtent: 154,
                    ),
                    itemBuilder: (context, index) {
                      final section = _sections[index];
                      return _ComponentCard(
                        index: index,
                        section: section,
                        onTap: () {
                          Navigator.of(context).push(
                            MaterialPageRoute<void>(
                              builder: (_) => _ComponentDetailScreen(
                                index: index,
                                section: section,
                              ),
                            ),
                          );
                        },
                      );
                    },
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class AllComponentsPreviewScreen extends StatelessWidget {
  const AllComponentsPreviewScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final tokens = FluxAiTheme.of(context);
    return Scaffold(
      backgroundColor: tokens.canvas,
      body: SafeArea(
        child: CustomScrollView(
          slivers: [
            SliverToBoxAdapter(
              child: Padding(
                padding: const EdgeInsets.fromLTRB(28, 24, 28, 16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Flux AI UI',
                      style: Theme.of(context).textTheme.displaySmall,
                    ),
                    const SizedBox(height: 8),
                    Text(
                      'AI-native Flutter components rendered from the live example app.',
                      style: TextStyle(color: tokens.mutedInk, fontSize: 18),
                    ),
                    const SizedBox(height: 16),
                    Wrap(
                      spacing: 8,
                      runSpacing: 8,
                      children: const [
                        FluxPill(label: '17 components', icon: Icons.widgets),
                        FluxPill(label: 'Live Flutter Web', icon: Icons.public),
                        FluxPill(label: 'Themeable', icon: Icons.palette),
                      ],
                    ),
                  ],
                ),
              ),
            ),
            SliverPadding(
              padding: const EdgeInsets.fromLTRB(20, 0, 20, 28),
              sliver: SliverLayoutBuilder(
                builder: (context, constraints) {
                  final width = constraints.crossAxisExtent;
                  final crossAxisCount = width >= 1320
                      ? 3
                      : width >= 860
                      ? 2
                      : 1;
                  return SliverGrid.builder(
                    itemCount: _sections.length,
                    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: crossAxisCount,
                      crossAxisSpacing: 14,
                      mainAxisSpacing: 14,
                      mainAxisExtent: 348,
                    ),
                    itemBuilder: (context, index) {
                      final section = _sections[index];
                      return _PreviewComponentTile(
                        index: index,
                        section: section,
                      );
                    },
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _PreviewComponentTile extends StatelessWidget {
  const _PreviewComponentTile({required this.index, required this.section});

  final int index;
  final _DemoSection section;

  @override
  Widget build(BuildContext context) {
    final tokens = FluxAiTheme.of(context);
    return FluxPanel(
      padding: const EdgeInsets.all(14),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Container(
                width: 36,
                height: 36,
                decoration: BoxDecoration(
                  color: tokens.accent.withValues(alpha: .12),
                  borderRadius: BorderRadius.circular(10),
                ),
                child: Icon(section.icon, color: tokens.accent, size: 19),
              ),
              const SizedBox(width: 10),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      section.title,
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    Text(
                      'Component ${(index + 1).toString().padLeft(2, '0')}',
                      style: TextStyle(
                        color: tokens.mutedInk,
                        fontSize: 12,
                        fontWeight: FontWeight.w700,
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
          const SizedBox(height: 10),
          Text(
            section.description,
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(color: tokens.mutedInk, height: 1.25),
          ),
          const SizedBox(height: 12),
          Expanded(
            child: ClipRect(child: _PreviewComponentBody(section: section)),
          ),
        ],
      ),
    );
  }
}

class _PreviewComponentBody extends StatelessWidget {
  const _PreviewComponentBody({required this.section});

  final _DemoSection section;

  @override
  Widget build(BuildContext context) {
    if (section.title == 'Prompt Bar') {
      return OverflowBox(
        alignment: Alignment.bottomCenter,
        minHeight: 0,
        maxHeight: 384,
        child: SizedBox(height: 384, child: section.child),
      );
    }

    return SingleChildScrollView(
      physics: const NeverScrollableScrollPhysics(),
      child: section.child,
    );
  }
}

class _ComponentCard extends StatelessWidget {
  const _ComponentCard({
    required this.index,
    required this.section,
    required this.onTap,
  });

  final int index;
  final _DemoSection section;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    final tokens = FluxAiTheme.of(context);
    return Material(
      color: Colors.transparent,
      child: InkWell(
        borderRadius: BorderRadius.circular(tokens.radius),
        onTap: onTap,
        child: FluxPanel(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                children: [
                  Container(
                    width: 38,
                    height: 38,
                    decoration: BoxDecoration(
                      color: tokens.accent.withValues(alpha: .12),
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Icon(section.icon, color: tokens.accent, size: 20),
                  ),
                  const Spacer(),
                  Text(
                    (index + 1).toString().padLeft(2, '0'),
                    style: TextStyle(
                      color: tokens.mutedInk,
                      fontWeight: FontWeight.w800,
                    ),
                  ),
                ],
              ),
              const Spacer(),
              Text(
                section.title,
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: Theme.of(context).textTheme.titleMedium,
              ),
              const SizedBox(height: 6),
              Text(
                section.description,
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(color: tokens.mutedInk, height: 1.25),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _ComponentDetailScreen extends StatelessWidget {
  const _ComponentDetailScreen({required this.index, required this.section});

  final int index;
  final _DemoSection section;

  @override
  Widget build(BuildContext context) {
    final tokens = FluxAiTheme.of(context);
    return Scaffold(
      appBar: AppBar(
        title: Text(section.title),
        backgroundColor: tokens.canvas,
        surfaceTintColor: tokens.canvas,
      ),
      body: SafeArea(
        child: ListView(
          padding: const EdgeInsets.fromLTRB(16, 6, 16, 28),
          children: [
            Row(
              children: [
                Container(
                  width: 46,
                  height: 46,
                  decoration: BoxDecoration(
                    color: tokens.accent.withValues(alpha: .12),
                    borderRadius: BorderRadius.circular(14),
                  ),
                  child: Icon(section.icon, color: tokens.accent),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Component ${(index + 1).toString().padLeft(2, '0')}',
                        style: TextStyle(
                          color: tokens.mutedInk,
                          fontWeight: FontWeight.w800,
                        ),
                      ),
                      Text(
                        section.title,
                        style: Theme.of(context).textTheme.headlineSmall,
                      ),
                    ],
                  ),
                ),
              ],
            ),
            const SizedBox(height: 10),
            Text(
              section.description,
              style: TextStyle(color: tokens.mutedInk, fontSize: 16),
            ),
            const SizedBox(height: 18),
            section.child,
            const SizedBox(height: 18),
            FluxPanel(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('Usage', style: Theme.of(context).textTheme.titleMedium),
                  const SizedBox(height: 8),
                  Text(
                    section.usage,
                    style: TextStyle(color: tokens.mutedInk, height: 1.35),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _DemoSection {
  const _DemoSection({
    required this.title,
    required this.description,
    required this.icon,
    required this.child,
    required this.usage,
  });

  final String title;
  final String description;
  final IconData icon;
  final Widget child;
  final String usage;
}

final _sections = <_DemoSection>[
  const _DemoSection(
    title: 'Loading State',
    description: 'Pixel-grid loader with shimmer and elapsed time.',
    icon: Icons.hourglass_empty,
    usage: 'Use FluxLoadingState when an agent is actively producing a result.',
    child: FluxLoadingState(),
  ),
  const _DemoSection(
    title: 'Thinking',
    description:
        'Expandable-style traces for steps, reasoning, search, and coding.',
    icon: Icons.psychology_alt_outlined,
    usage:
        'Use FluxThinkingTrace to show visible agent progress and tool work.',
    child: FluxThinkingTrace(
      steps: [
        FluxTraceStep(
          title: 'Read signals',
          detail: 'Inspecting sources and constraints.',
          duration: '2s',
        ),
        FluxTraceStep(
          title: 'Search catalog',
          detail: 'Looking for related supply records.',
          icon: Icons.search,
          duration: '3s',
        ),
        FluxTraceStep(
          title: 'Prepare patch',
          detail: 'Writing a small focused change.',
          icon: Icons.code,
          state: FluxTaskState.running,
        ),
      ],
    ),
  ),
  const _DemoSection(
    title: 'Streaming Text',
    description:
        'Streamed answer with inline sources, actions, and follow-ups.',
    icon: Icons.notes,
    usage:
        'Use FluxStreamingText for generated answers that need sources and next actions.',
    child: FluxStreamingText(
      text:
          'Pistachio is your fastest-growing flavor — sales are up 23% this month and margins beat vanilla by 8 points.',
      sources: [
        FluxSource(title: 'Scoop Data', subtitle: 'scoopdata.io'),
        FluxSource(title: 'Trends Index', subtitle: 'trends.google.com'),
        FluxSource(title: 'Market Basket', subtitle: 'marketbasket.io'),
      ],
      followUps: [
        'Which flavors sell best in winter',
        'Compare gelato and soft serve margins',
      ],
    ),
  ),
  const _DemoSection(
    title: 'Approval Card',
    description: 'Human-in-the-loop questions before the agent acts.',
    icon: Icons.fact_check_outlined,
    usage:
        'Use FluxApprovalCard before irreversible, costly, or ambiguous agent actions.',
    child: FluxApprovalCard(
      question: 'How many flavors should we launch?',
      options: ['Three (core line)', 'Five (full case)', 'Just one hero'],
    ),
  ),
  const _DemoSection(
    title: 'Tool Chips',
    description: 'Code edits and tool calls as compact chips.',
    icon: Icons.terminal,
    usage:
        'Use FluxToolChips to summarize tools, messages, patches, and external calls.',
    child: FluxToolChips(
      tools: [
        FluxChipData('4 tool calls', icon: Icons.terminal, selected: true),
        FluxChipData('2 messages', icon: Icons.chat_bubble_outline),
        FluxChipData('1 patch', icon: Icons.edit_note),
      ],
    ),
  ),
  const _DemoSection(
    title: 'Task Rows',
    description:
        'Live agent task status for running, failed, and completed work.',
    icon: Icons.task_alt,
    usage:
        'Use FluxTaskRows for multi-step jobs, background runs, and task queues.',
    child: FluxTaskRows(
      tasks: [
        FluxTaskItem(
          title: 'Verified vendor records',
          subtitle: '12 suppliers completed',
          state: FluxTaskState.completed,
        ),
        FluxTaskItem(
          title: 'Build reorder task list',
          subtitle: '7 SKUs scored',
          state: FluxTaskState.running,
          progress: .68,
        ),
        FluxTaskItem(
          title: 'Draft supplier emails',
          subtitle: '2 messages',
          state: FluxTaskState.completed,
        ),
      ],
    ),
  ),
  const _DemoSection(
    title: 'Chat',
    description: 'Tabbed chat panel with reasoning replies and a composer.',
    icon: Icons.chat_bubble_outline,
    usage:
        'Use FluxChatPanel as a compact chat surface inside a task or workspace.',
    child: FluxChatPanel(),
  ),
  const _DemoSection(
    title: 'Prompt Bar',
    description:
        'Composer with @ sources, commands, model picker, and send action.',
    icon: Icons.keyboard_command_key,
    usage:
        'Use FluxPromptBar as the primary input for agentic mobile workflows.',
    child: FluxPromptBar(),
  ),
  const _DemoSection(
    title: 'Recommendation Card',
    description: 'Agent suggestion with confidence and actions.',
    icon: Icons.recommend_outlined,
    usage:
        'Use FluxRecommendationCard when the agent proposes a decision and confidence.',
    child: FluxRecommendationCard(),
  ),
  const _DemoSection(
    title: 'Context Cards',
    description: 'Retrieved knowledge chunks with their sources.',
    icon: Icons.article_outlined,
    usage:
        'Use FluxContextCards to explain where an answer or decision came from.',
    child: FluxContextCards(
      sources: [
        FluxSource(
          title: 'Vendor onboarding rule',
          subtitle:
              'Cold-chain certification must be verified before a new dairy can be added.',
        ),
        FluxSource(
          title: 'Seasonal demand row',
          subtitle:
              'Q4 velocity table: pistachio +18%, vanilla +6%, rocky road -11%.',
        ),
      ],
    ),
  ),
  const _DemoSection(
    title: 'Diff Table',
    description: 'AI-proposed edits sweeping through tabular data.',
    icon: Icons.difference_outlined,
    usage:
        'Use FluxDiffTable to preview AI-proposed row additions before applying table edits.',
    child: FluxDiffTable(),
  ),
  const _DemoSection(
    title: 'Records Table',
    description: 'CRM-style grid with tags, sorting, and relationship status.',
    icon: Icons.table_chart_outlined,
    usage:
        'Use FluxRecordsTable for compact CRM-style records and entity lists.',
    child: FluxRecordsTable(),
  ),
  const _DemoSection(
    title: 'Filter Table',
    description: 'Status chips that reorganize live data.',
    icon: Icons.filter_alt_outlined,
    usage: 'Use FluxFilterTable to communicate active status filters.',
    child: FluxFilterTable(),
  ),
  const _DemoSection(
    title: 'Search',
    description: 'Command search with live filtering and an empty state.',
    icon: Icons.search,
    usage: 'Use FluxSearchPanel for command palettes and source/action lookup.',
    child: FluxSearchPanel(
      commands: [
        'Forecast summer demand',
        'Find waffle cone suppliers',
        'Compare seasonal flavors',
        'Draft flavor launch plan',
        'Check cold-chain status',
      ],
    ),
  ),
  const _DemoSection(
    title: 'Insight Cards',
    description: 'Paged agent insights with scrub-ready live charts.',
    icon: Icons.insights_outlined,
    usage:
        'Use FluxInsightCards to present AI-discovered changes and opportunities.',
    child: FluxInsightCards(),
  ),
  const _DemoSection(
    title: 'Code Block',
    description: 'Agent-written code streaming in line by line.',
    icon: Icons.code,
    usage:
        'Use FluxCodeBlock when generated code needs readable mobile presentation.',
    child: FluxCodeBlock(
      filename: 'churn.ts',
      code:
          'export async function churnBatch() {\n'
          '  const flavor = await getFlavor("pistachio");\n'
          '  const base = await dairy.fetch({ flavor });\n'
          '  await freezer.store(base, { temp: "-14C" });\n'
          '  return base.gallons;\n'
          '}',
    ),
  ),
  const _DemoSection(
    title: 'Fine-tune Card',
    description: 'Agent adjusts design properties in an inspector.',
    icon: Icons.tune,
    usage: 'Use FluxFineTuneCard for editable AI parameters and live previews.',
    child: FluxFineTuneCard(),
  ),
];
0
likes
160
points
--
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Open Flutter primitives for AI-native mobile interfaces.

Repository (GitHub)
View/report issues
Contributing

Topics

#flutter #ui #ai #components

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flux_ai_ui