fifty_ui 0.7.0 copy "fifty_ui: ^0.7.0" to clipboard
fifty_ui: ^0.7.0 copied to clipboard

FDL v2 styled Flutter components for the fifty.dev ecosystem. Buttons, cards, inputs, and more.

example/lib/main.dart

import 'package:fifty_theme/fifty_theme.dart';
import 'package:fifty_tokens/fifty_tokens.dart';
import 'package:fifty_ui/fifty_ui.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const FiftyUIExampleApp());
}

/// Example gallery app demonstrating all fifty_ui components.
class FiftyUIExampleApp extends StatelessWidget {
  const FiftyUIExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fifty UI Gallery',
      theme: FiftyTheme.dark(),
      debugShowCheckedModeBanner: false,
      home: const GalleryHome(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('FIFTY UI GALLERY'),
      ),
      body: ListView(
        padding: EdgeInsets.all(FiftySpacing.lg),
        children: [
          _buildSection(
            context,
            title: 'BUTTONS',
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const ButtonsPage()),
            ),
          ),
          _buildSection(
            context,
            title: 'INPUTS',
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const InputsPage()),
            ),
          ),
          _buildSection(
            context,
            title: 'CONTROLS',
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const ControlsPage()),
            ),
          ),
          _buildSection(
            context,
            title: 'DISPLAY',
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const DisplayPage()),
            ),
          ),
          _buildSection(
            context,
            title: 'FEEDBACK',
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const FeedbackPage()),
            ),
          ),
          _buildSection(
            context,
            title: 'LAYOUT',
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const LayoutPage()),
            ),
          ),
          _buildSection(
            context,
            title: 'NAVIGATION',
            onTap: () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const NavigationPage()),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildSection(
    BuildContext context, {
    required String title,
    required VoidCallback onTap,
  }) {
    return Padding(
      padding: EdgeInsets.only(bottom: FiftySpacing.md),
      child: FiftyCard(
        onTap: onTap,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(
              title,
              style: TextStyle(
                fontFamily: FiftyTypography.fontFamily,
                fontSize: FiftyTypography.bodyLarge,
                fontWeight: FiftyTypography.medium,
              ),
            ),
            const Icon(Icons.arrow_forward_ios, size: 16),
          ],
        ),
      ),
    );
  }
}

// ============================================================================
// BUTTONS PAGE
// ============================================================================

class ButtonsPage extends StatefulWidget {
  const ButtonsPage({super.key});

  @override
  State<ButtonsPage> createState() => _ButtonsPageState();
}

class _ButtonsPageState extends State<ButtonsPage> {
  bool _loading = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('BUTTONS')),
      body: ListView(
        padding: EdgeInsets.all(FiftySpacing.lg),
        children: [
          const _SectionTitle('Button Variants'),
          Wrap(
            spacing: FiftySpacing.md,
            runSpacing: FiftySpacing.md,
            children: [
              FiftyButton(
                label: 'Primary',
                onPressed: () {},
                variant: FiftyButtonVariant.primary,
              ),
              FiftyButton(
                label: 'Secondary',
                onPressed: () {},
                variant: FiftyButtonVariant.secondary,
              ),
              FiftyButton(
                label: 'Ghost',
                onPressed: () {},
                variant: FiftyButtonVariant.ghost,
              ),
              FiftyButton(
                label: 'Danger',
                onPressed: () {},
                variant: FiftyButtonVariant.danger,
              ),
              FiftyButton(
                label: 'Outline',
                onPressed: () {},
                variant: FiftyButtonVariant.outline,
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Button Sizes'),
          Wrap(
            spacing: FiftySpacing.md,
            runSpacing: FiftySpacing.md,
            crossAxisAlignment: WrapCrossAlignment.center,
            children: [
              FiftyButton(
                label: 'Small',
                onPressed: () {},
                size: FiftyButtonSize.small,
              ),
              FiftyButton(
                label: 'Medium',
                onPressed: () {},
                size: FiftyButtonSize.medium,
              ),
              FiftyButton(
                label: 'Large',
                onPressed: () {},
                size: FiftyButtonSize.large,
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Button States'),
          Wrap(
            spacing: FiftySpacing.md,
            runSpacing: FiftySpacing.md,
            children: [
              FiftyButton(
                label: 'With Icon',
                onPressed: () {},
                icon: Icons.rocket_launch,
              ),
              const FiftyButton(
                label: 'Disabled',
                onPressed: null,
              ),
              FiftyButton(
                label: 'Loading',
                onPressed: () {},
                loading: _loading,
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.md),
          FiftyButton(
            label: _loading ? 'Stop Loading' : 'Start Loading',
            onPressed: () => setState(() => _loading = !_loading),
            variant: FiftyButtonVariant.secondary,
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Trailing Icons'),
          Wrap(
            spacing: FiftySpacing.md,
            runSpacing: FiftySpacing.md,
            children: [
              FiftyButton(
                label: 'Get Started',
                onPressed: () {},
                trailingIcon: Icons.arrow_forward,
              ),
              FiftyButton(
                label: 'Download',
                onPressed: () {},
                variant: FiftyButtonVariant.secondary,
                icon: Icons.download,
              ),
              FiftyButton(
                label: 'Learn More',
                onPressed: () {},
                variant: FiftyButtonVariant.outline,
                trailingIcon: Icons.arrow_forward,
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Icon Buttons'),
          Wrap(
            spacing: FiftySpacing.md,
            runSpacing: FiftySpacing.md,
            children: [
              FiftyIconButton(
                icon: Icons.settings,
                tooltip: 'Settings',
                onPressed: () {},
                variant: FiftyIconButtonVariant.primary,
              ),
              FiftyIconButton(
                icon: Icons.edit,
                tooltip: 'Edit',
                onPressed: () {},
                variant: FiftyIconButtonVariant.secondary,
              ),
              FiftyIconButton(
                icon: Icons.delete,
                tooltip: 'Delete',
                onPressed: () {},
                variant: FiftyIconButtonVariant.ghost,
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('FDL v2 Effects'),
          Text(
            'Hover over buttons to see effects',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          Wrap(
            spacing: FiftySpacing.md,
            runSpacing: FiftySpacing.md,
            children: [
              FiftyButton(
                label: 'GLITCH EFFECT',
                onPressed: () {},
                isGlitch: true,
              ),
              FiftyButton(
                label: 'SECONDARY',
                onPressed: () {},
                variant: FiftyButtonVariant.secondary,
              ),
              FiftyButton(
                label: 'GLITCH + DANGER',
                onPressed: () {},
                isGlitch: true,
                variant: FiftyButtonVariant.danger,
              ),
            ],
          ),
        ],
      ),
    );
  }
}

// ============================================================================
// INPUTS PAGE
// ============================================================================

class InputsPage extends StatefulWidget {
  const InputsPage({super.key});

  @override
  State<InputsPage> createState() => _InputsPageState();
}

class _InputsPageState extends State<InputsPage> {
  final _emailController = TextEditingController();
  String? _emailError;

  // Form component states
  bool _switchValue = false;
  bool _notificationsEnabled = true;
  bool _darkMode = true;
  double _sliderValue = 50;
  double _volumeValue = 75;
  String? _selectedLanguage;
  String? _selectedPriority;

  // Checkbox states
  bool _checkbox1 = false;
  bool _checkbox2 = true;

  // Radio state
  String? _radioValue = 'option1';

  // Radio card state
  int _displayMode = 1;

  final _languages = [
    const FiftyDropdownItem(value: 'dart', label: 'Dart', icon: Icons.code),
    const FiftyDropdownItem(value: 'kotlin', label: 'Kotlin', icon: Icons.android),
    const FiftyDropdownItem(value: 'swift', label: 'Swift', icon: Icons.apple),
    const FiftyDropdownItem(value: 'rust', label: 'Rust', icon: Icons.memory),
  ];

  final _priorities = [
    const FiftyDropdownItem(value: 'p0', label: 'P0 - Critical'),
    const FiftyDropdownItem(value: 'p1', label: 'P1 - High'),
    const FiftyDropdownItem(value: 'p2', label: 'P2 - Medium'),
    const FiftyDropdownItem(value: 'p3', label: 'P3 - Low'),
  ];

  @override
  void dispose() {
    _emailController.dispose();
    super.dispose();
  }

  void _validateEmail() {
    final email = _emailController.text;
    setState(() {
      if (email.isEmpty) {
        _emailError = 'Email is required';
      } else if (!email.contains('@')) {
        _emailError = 'Please enter a valid email';
      } else {
        _emailError = null;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('INPUTS')),
      body: ListView(
        padding: EdgeInsets.all(FiftySpacing.lg),
        children: [
          const _SectionTitle('Text Field'),
          FiftyTextField(
            controller: _emailController,
            label: 'Email',
            hint: 'Enter your email address',
            prefix: const Icon(Icons.email),
            errorText: _emailError,
            onChanged: (_) => _validateEmail(),
          ),
          SizedBox(height: FiftySpacing.lg),
          const FiftyTextField(
            label: 'Password',
            hint: 'Enter your password',
            prefix: Icon(Icons.lock),
            obscureText: true,
          ),
          SizedBox(height: FiftySpacing.lg),
          const FiftyTextField(
            label: 'Disabled',
            hint: 'This field is disabled',
            enabled: false,
          ),
          SizedBox(height: FiftySpacing.lg),
          const FiftyTextField(
            label: 'Multiline',
            hint: 'Enter a longer message...',
            maxLines: 4,
          ),
          SizedBox(height: FiftySpacing.lg),
          const FiftyTextField(
            label: 'Terminal Style',
            hint: 'Enter command',
            terminalStyle: true,
          ),
          SizedBox(height: FiftySpacing.lg),
          const FiftyTextField(
            hint: 'Search...',
            prefix: Icon(Icons.search),
            shape: FiftyTextFieldShape.rounded,
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Terminal Styles'),
          Text(
            'Advanced styling for terminal-like inputs',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          const FiftyTextField(
            label: 'Bottom Border Only',
            hint: 'Enter value',
            borderStyle: FiftyBorderStyle.bottom,
          ),
          SizedBox(height: FiftySpacing.lg),
          const FiftyTextField(
            label: 'No Border',
            hint: 'Enter text',
            borderStyle: FiftyBorderStyle.none,
          ),
          SizedBox(height: FiftySpacing.lg),
          const FiftyTextField(
            label: 'Chevron Prefix',
            hint: 'command',
            borderStyle: FiftyBorderStyle.bottom,
            prefixStyle: FiftyPrefixStyle.chevron,
          ),
          SizedBox(height: FiftySpacing.lg),
          const FiftyTextField(
            label: 'Comment Prefix',
            hint: 'your comment',
            prefixStyle: FiftyPrefixStyle.comment,
          ),
          SizedBox(height: FiftySpacing.lg),
          const FiftyTextField(
            label: 'Block Cursor',
            hint: 'Focus to see block cursor',
            borderStyle: FiftyBorderStyle.bottom,
            prefixStyle: FiftyPrefixStyle.chevron,
            cursorStyle: FiftyCursorStyle.block,
          ),
          SizedBox(height: FiftySpacing.lg),
          const FiftyTextField(
            label: 'Underscore Cursor',
            hint: 'Focus to see underscore cursor',
            cursorStyle: FiftyCursorStyle.underscore,
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Checkboxes'),
          Text(
            'Selection controls with kinetic animation',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          FiftyCheckbox(
            value: _checkbox1,
            onChanged: (value) => setState(() => _checkbox1 = value),
            label: 'Accept terms and conditions',
          ),
          SizedBox(height: FiftySpacing.md),
          FiftyCheckbox(
            value: _checkbox2,
            onChanged: (value) => setState(() => _checkbox2 = value),
            label: 'Subscribe to newsletter',
          ),
          SizedBox(height: FiftySpacing.md),
          const FiftyCheckbox(
            value: false,
            onChanged: null,
            label: 'Disabled checkbox',
            enabled: false,
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Radio Buttons'),
          Text(
            'Mutually exclusive selection with animated dot',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          FiftyRadio<String>(
            value: 'option1',
            groupValue: _radioValue,
            onChanged: (value) => setState(() => _radioValue = value),
            label: 'Option 1',
          ),
          SizedBox(height: FiftySpacing.md),
          FiftyRadio<String>(
            value: 'option2',
            groupValue: _radioValue,
            onChanged: (value) => setState(() => _radioValue = value),
            label: 'Option 2',
          ),
          SizedBox(height: FiftySpacing.md),
          FiftyRadio<String>(
            value: 'option3',
            groupValue: _radioValue,
            onChanged: (value) => setState(() => _radioValue = value),
            label: 'Option 3',
          ),
          SizedBox(height: FiftySpacing.md),
          const FiftyRadio<String>(
            value: 'disabled',
            groupValue: null,
            onChanged: null,
            label: 'Disabled radio',
            enabled: false,
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Radio Cards'),
          Text(
            'Card-style radio selection for theme modes',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          Row(
            children: [
              Expanded(
                child: FiftyRadioCard<int>(
                  value: 0,
                  groupValue: _displayMode,
                  onChanged: (v) => setState(() => _displayMode = v ?? 0),
                  icon: Icons.light_mode,
                  label: 'Light',
                ),
              ),
              SizedBox(width: FiftySpacing.md),
              Expanded(
                child: FiftyRadioCard<int>(
                  value: 1,
                  groupValue: _displayMode,
                  onChanged: (v) => setState(() => _displayMode = v ?? 1),
                  icon: Icons.dark_mode,
                  label: 'Dark',
                ),
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Switches'),
          Text(
            'Kinetic toggle switches with snap animation',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          FiftySwitch(
            value: _switchValue,
            onChanged: (value) => setState(() => _switchValue = value),
            label: 'Basic Switch',
          ),
          SizedBox(height: FiftySpacing.md),
          FiftySwitch(
            value: _notificationsEnabled,
            onChanged: (value) => setState(() => _notificationsEnabled = value),
            label: 'Enable Notifications',
          ),
          SizedBox(height: FiftySpacing.md),
          FiftySwitch(
            value: _darkMode,
            onChanged: (value) => setState(() => _darkMode = value),
            label: 'Dark Mode',
          ),
          SizedBox(height: FiftySpacing.md),
          Text(
            'Switch Sizes',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.sm),
          Wrap(
            spacing: FiftySpacing.lg,
            runSpacing: FiftySpacing.md,
            crossAxisAlignment: WrapCrossAlignment.center,
            children: [
              FiftySwitch(
                value: _switchValue,
                onChanged: (value) => setState(() => _switchValue = value),
                size: FiftySwitchSize.small,
              ),
              FiftySwitch(
                value: _switchValue,
                onChanged: (value) => setState(() => _switchValue = value),
                size: FiftySwitchSize.medium,
              ),
              FiftySwitch(
                value: _switchValue,
                onChanged: (value) => setState(() => _switchValue = value),
                size: FiftySwitchSize.large,
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.md),
          const FiftySwitch(
            value: false,
            onChanged: null,
            label: 'Disabled Switch',
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Sliders'),
          Text(
            'Brutalist range selectors with square thumb',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          FiftySlider(
            value: _sliderValue,
            min: 0,
            max: 100,
            onChanged: (value) => setState(() => _sliderValue = value),
            label: 'Value',
            showLabel: true,
          ),
          SizedBox(height: FiftySpacing.lg),
          FiftySlider(
            value: _volumeValue,
            min: 0,
            max: 100,
            onChanged: (value) => setState(() => _volumeValue = value),
            label: 'Volume',
            showLabel: true,
            labelBuilder: (value) => '${value.round()}%',
          ),
          SizedBox(height: FiftySpacing.lg),
          FiftySlider(
            value: _sliderValue,
            min: 0,
            max: 100,
            divisions: 10,
            onChanged: (value) => setState(() => _sliderValue = value),
            label: 'With Divisions (steps of 10)',
          ),
          SizedBox(height: FiftySpacing.lg),
          const FiftySlider(
            value: 50,
            min: 0,
            max: 100,
            onChanged: null,
            label: 'Disabled',
            enabled: false,
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Dropdowns'),
          Text(
            'Terminal-styled dropdown selectors',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          FiftyDropdown<String>(
            items: _languages,
            value: _selectedLanguage,
            onChanged: (value) => setState(() => _selectedLanguage = value),
            label: 'Language',
            hint: 'Select a language',
          ),
          SizedBox(height: FiftySpacing.lg),
          FiftyDropdown<String>(
            items: _priorities,
            value: _selectedPriority,
            onChanged: (value) => setState(() => _selectedPriority = value),
            label: 'Priority',
            hint: 'Select priority level',
          ),
          SizedBox(height: FiftySpacing.lg),
          FiftyDropdown<String>(
            items: _languages,
            value: 'dart',
            onChanged: null,
            label: 'Disabled',
            enabled: false,
          ),
          SizedBox(height: FiftySpacing.xl),
        ],
      ),
    );
  }
}

// ============================================================================
// CONTROLS PAGE
// ============================================================================

class ControlsPage extends StatefulWidget {
  const ControlsPage({super.key});

  @override
  State<ControlsPage> createState() => _ControlsPageState();
}

class _ControlsPageState extends State<ControlsPage> {
  String _period = 'daily';
  String _viewMode = 'grid';
  String _expandedValue = 'all';
  String _themeMode = 'dark';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('CONTROLS')),
      body: ListView(
        padding: EdgeInsets.all(FiftySpacing.lg),
        children: [
          const _SectionTitle('Segmented Control'),
          Text(
            'Pill-style segments with animated selection',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          FiftySegmentedControl<String>(
            segments: const [
              FiftySegment(value: 'daily', label: 'Daily'),
              FiftySegment(value: 'weekly', label: 'Weekly'),
              FiftySegment(value: 'monthly', label: 'Monthly'),
            ],
            selected: _period,
            onChanged: (value) => setState(() => _period = value),
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Variants'),
          Text(
            'Primary (cream/burgundy) and Secondary (slate/cream)',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          Row(
            children: [
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Primary',
                      style: TextStyle(
                        fontFamily: FiftyTypography.fontFamily,
                        fontSize: FiftyTypography.bodySmall,
                        color: FiftyColors.slateGrey,
                      ),
                    ),
                    SizedBox(height: FiftySpacing.sm),
                    FiftySegmentedControl<String>(
                      segments: const [
                        FiftySegment(value: 'daily', label: 'Daily'),
                        FiftySegment(value: 'weekly', label: 'Weekly'),
                      ],
                      selected: _period,
                      onChanged: (value) => setState(() => _period = value),
                      variant: FiftySegmentedControlVariant.primary,
                    ),
                  ],
                ),
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.lg),
          Row(
            children: [
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Secondary',
                      style: TextStyle(
                        fontFamily: FiftyTypography.fontFamily,
                        fontSize: FiftyTypography.bodySmall,
                        color: FiftyColors.slateGrey,
                      ),
                    ),
                    SizedBox(height: FiftySpacing.sm),
                    FiftySegmentedControl<String>(
                      segments: const [
                        FiftySegment(value: 'light', label: 'Light', icon: Icons.light_mode),
                        FiftySegment(value: 'dark', label: 'Dark', icon: Icons.dark_mode),
                        FiftySegment(value: 'system', label: 'System', icon: Icons.settings_suggest),
                      ],
                      selected: _themeMode,
                      onChanged: (value) => setState(() => _themeMode = value),
                      variant: FiftySegmentedControlVariant.secondary,
                    ),
                  ],
                ),
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('With Icons'),
          Text(
            'Segments with leading icons',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          FiftySegmentedControl<String>(
            segments: const [
              FiftySegment(value: 'grid', label: 'Grid', icon: Icons.grid_view),
              FiftySegment(value: 'list', label: 'List', icon: Icons.list),
            ],
            selected: _viewMode,
            onChanged: (value) => setState(() => _viewMode = value),
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Expanded Mode'),
          Text(
            'Segments expand to fill available width',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          FiftySegmentedControl<String>(
            segments: const [
              FiftySegment(value: 'all', label: 'All'),
              FiftySegment(value: 'active', label: 'Active'),
              FiftySegment(value: 'completed', label: 'Completed'),
            ],
            selected: _expandedValue,
            onChanged: (value) => setState(() => _expandedValue = value),
            expanded: true,
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Disabled State'),
          Text(
            'Non-interactive segmented control',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          FiftySegmentedControl<String>(
            segments: const [
              FiftySegment(value: 'on', label: 'On'),
              FiftySegment(value: 'off', label: 'Off'),
            ],
            selected: 'on',
            onChanged: (value) {},
            enabled: false,
          ),
        ],
      ),
    );
  }
}

// ============================================================================
// DISPLAY PAGE
// ============================================================================

class DisplayPage extends StatefulWidget {
  const DisplayPage({super.key});

  @override
  State<DisplayPage> createState() => _DisplayPageState();
}

class _DisplayPageState extends State<DisplayPage> {
  double _progress = 0.65;
  bool _selectedCard = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('DISPLAY')),
      body: ListView(
        padding: EdgeInsets.all(FiftySpacing.lg),
        children: [
          const _SectionTitle('Cards'),
          FiftyCard(
            onTap: () => setState(() => _selectedCard = !_selectedCard),
            selected: _selectedCard,
            child: const Text('Tap to select this card'),
          ),
          SizedBox(height: FiftySpacing.md),
          FiftyCard(
            hasTexture: true,
            hoverScale: 1.05,
            onTap: () {},
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'HALFTONE TEXTURE',
                  style: TextStyle(
                    fontFamily: FiftyTypography.fontFamily,
                    fontSize: FiftyTypography.bodyLarge,
                    fontWeight: FiftyTypography.medium,
                  ),
                ),
                SizedBox(height: FiftySpacing.xs),
                Text(
                  'Card with halftone overlay and 5% hover scale',
                  style: TextStyle(
                    fontFamily: FiftyTypography.fontFamily,
                    fontSize: FiftyTypography.bodySmall,
                    color: FiftyColors.slateGrey,
                  ),
                ),
              ],
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          FiftyCard(
            hasTexture: true,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'NON-INTERACTIVE TEXTURE',
                  style: TextStyle(
                    fontFamily: FiftyTypography.fontFamily,
                    fontSize: FiftyTypography.bodyLarge,
                    fontWeight: FiftyTypography.medium,
                  ),
                ),
                SizedBox(height: FiftySpacing.xs),
                Text(
                  'Static card with halftone texture (no hover)',
                  style: TextStyle(
                    fontFamily: FiftyTypography.fontFamily,
                    fontSize: FiftyTypography.bodySmall,
                    color: FiftyColors.slateGrey,
                  ),
                ),
              ],
            ),
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Stat Cards'),
          Text(
            'Metric display with trend indicators',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          Row(
            children: [
              const Expanded(
                child: FiftyStatCard(
                  label: 'Total Views',
                  value: '45.2k',
                  icon: Icons.visibility,
                  trend: FiftyStatTrend.up,
                  trendValue: '12%',
                ),
              ),
              SizedBox(width: FiftySpacing.md),
              const Expanded(
                child: FiftyStatCard(
                  label: 'Revenue',
                  value: '\$12.5k',
                  icon: Icons.account_balance_wallet,
                  highlight: true,
                ),
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Progress Card'),
          Text(
            'Progress metrics with gradient bar',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          const FiftyProgressCard(
            icon: Icons.trending_up,
            title: 'Weekly Goal',
            progress: 0.75,
            subtitle: '12 sales remaining to reach target',
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('List Tiles'),
          Text(
            'Transaction-style list items',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          FiftyCard(
            padding: EdgeInsets.zero,
            child: Column(
              children: [
                FiftyListTile(
                  leadingIcon: Icons.subscriptions,
                  leadingIconColor: Colors.blue,
                  leadingIconBackgroundColor: Colors.blue.withValues(alpha: 0.15),
                  title: 'Subscription',
                  subtitle: 'Adobe Creative Cloud',
                  trailingText: '-\$54.00',
                  trailingSubtext: 'Today',
                  showDivider: true,
                ),
                FiftyListTile(
                  leadingIcon: Icons.arrow_downward,
                  leadingIconColor: FiftyColors.hunterGreen,
                  leadingIconBackgroundColor: FiftyColors.hunterGreen.withValues(alpha: 0.15),
                  title: 'Deposit',
                  subtitle: 'Freelance Work',
                  trailingText: '+\$850.00',
                  trailingTextColor: FiftyColors.hunterGreen,
                  trailingSubtext: 'Yesterday',
                ),
              ],
            ),
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Chips'),
          Wrap(
            spacing: FiftySpacing.sm,
            runSpacing: FiftySpacing.sm,
            children: [
              const FiftyChip(label: 'Default'),
              const FiftyChip(
                label: 'Success',
                variant: FiftyChipVariant.success,
                selected: true,
              ),
              const FiftyChip(
                label: 'Warning',
                variant: FiftyChipVariant.warning,
              ),
              FiftyChip(
                label: 'Deletable',
                onDeleted: () {},
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Badges'),
          Wrap(
            spacing: FiftySpacing.sm,
            runSpacing: FiftySpacing.sm,
            children: [
              FiftyBadge(label: 'Primary'),
              FiftyBadge(label: 'Live', variant: FiftyBadgeVariant.success, showGlow: true),
              FiftyBadge(label: 'Warning', variant: FiftyBadgeVariant.warning),
              FiftyBadge(label: 'Error', variant: FiftyBadgeVariant.error),
              FiftyBadge(label: 'Neutral', variant: FiftyBadgeVariant.neutral),
            ],
          ),
          SizedBox(height: FiftySpacing.md),
          Text(
            'Factory Constructors',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.sm),
          Wrap(
            spacing: FiftySpacing.sm,
            runSpacing: FiftySpacing.sm,
            children: [
              FiftyBadge.tech('FLUTTER'),
              FiftyBadge.tech('DART'),
              FiftyBadge.status('ONLINE'),
              FiftyBadge.status('CONNECTED'),
              FiftyBadge.ai('IGRIS'),
              FiftyBadge.ai('AGENT'),
            ],
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Avatars'),
          Wrap(
            spacing: FiftySpacing.md,
            runSpacing: FiftySpacing.md,
            children: [
              FiftyAvatar(fallbackText: 'JD', size: 32),
              FiftyAvatar(fallbackText: 'AB', size: 40),
              FiftyAvatar(fallbackText: 'XY', size: 48, borderColor: FiftyColors.burgundy),
            ],
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Progress Bar'),
          FiftyProgressBar(
            value: _progress,
            label: 'Uploading',
            showPercentage: true,
          ),
          SizedBox(height: FiftySpacing.md),
          Slider(
            value: _progress,
            onChanged: (value) => setState(() => _progress = value),
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Loading Indicator'),
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // FDL-compliant text-based loading indicators
              FiftyLoadingIndicator(size: FiftyLoadingSize.small),
              SizedBox(height: FiftySpacing.md),
              FiftyLoadingIndicator(size: FiftyLoadingSize.medium),
              SizedBox(height: FiftySpacing.md),
              FiftyLoadingIndicator(
                size: FiftyLoadingSize.large,
                color: FiftyColors.hunterGreen,
              ),
              SizedBox(height: FiftySpacing.md),
              // Custom loading text
              FiftyLoadingIndicator(text: 'PROCESSING'),
              SizedBox(height: FiftySpacing.md),
              // Static style (no animation)
              FiftyLoadingIndicator(
                text: 'UPLOADING',
                style: FiftyLoadingStyle.static,
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.md),
          Text(
            'Sequence Mode',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.sm),
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // Default sequence (uses built-in messages)
              FiftyLoadingIndicator(
                style: FiftyLoadingStyle.sequence,
              ),
              SizedBox(height: FiftySpacing.md),
              // Custom sequence list
              FiftyLoadingIndicator(
                style: FiftyLoadingStyle.sequence,
                color: FiftyColors.hunterGreen,
                sequences: [
                  '> BOOTING SYSTEM...',
                  '> LOADING MODULES...',
                  '> CONNECTING TO API...',
                  '> READY',
                ],
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Code Block'),
          Text(
            'Terminal-style code display with syntax highlighting',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          const FiftyCodeBlock(
            code: '''void main() {
  print('Hello, Fifty!');

  // Initialize the app
  final app = FiftyApp();
  app.run();
}''',
            language: 'dart',
          ),
          SizedBox(height: FiftySpacing.lg),
          Text(
            'Without Line Numbers',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.sm),
          const FiftyCodeBlock(
            code: 'final greeting = "Welcome to Fifty UI";',
            language: 'dart',
            showLineNumbers: false,
          ),
          SizedBox(height: FiftySpacing.lg),
          Text(
            'Without Copy Button',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.sm),
          const FiftyCodeBlock(
            code: 'npm install fifty-ui',
            language: 'plain',
            copyButton: false,
          ),
          SizedBox(height: FiftySpacing.lg),
          Text(
            'With Max Height (Scrollable)',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.sm),
          const FiftyCodeBlock(
            code: '''class FiftyWidget extends StatelessWidget {
  const FiftyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.black,
        borderRadius: BorderRadius.circular(8),
      ),
      child: const Text('Fifty UI'),
    );
  }
}

// More code to demonstrate scrolling...
class AnotherWidget extends StatefulWidget {
  const AnotherWidget({super.key});

  @override
  State<AnotherWidget> createState() => _AnotherWidgetState();
}''',
            language: 'dart',
            maxHeight: 150,
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Data Slate'),
          const FiftyDataSlate(
            title: 'System Status',
            data: {
              'CPU': '45%',
              'Memory': '8.2 GB / 16 GB',
              'Disk': '234 GB free',
              'Network': 'Connected',
              'Uptime': '72h 14m 32s',
            },
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Dividers'),
          const FiftyDivider(),
          SizedBox(height: FiftySpacing.md),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('Left'),
              SizedBox(width: FiftySpacing.md),
              SizedBox(height: 20, child: FiftyDivider(vertical: true)),
              SizedBox(width: FiftySpacing.md),
              Text('Right'),
            ],
          ),
        ],
      ),
    );
  }
}

// ============================================================================
// FEEDBACK PAGE
// ============================================================================

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('FEEDBACK')),
      body: ListView(
        padding: EdgeInsets.all(FiftySpacing.lg),
        children: [
          const _SectionTitle('Snackbars'),
          Wrap(
            spacing: FiftySpacing.md,
            runSpacing: FiftySpacing.md,
            children: [
              FiftyButton(
                label: 'Info',
                variant: FiftyButtonVariant.secondary,
                onPressed: () => FiftySnackbar.show(
                  context,
                  message: 'This is an info message',
                  variant: FiftySnackbarVariant.info,
                ),
              ),
              FiftyButton(
                label: 'Success',
                variant: FiftyButtonVariant.secondary,
                onPressed: () => FiftySnackbar.show(
                  context,
                  message: 'Operation completed successfully!',
                  variant: FiftySnackbarVariant.success,
                ),
              ),
              FiftyButton(
                label: 'Warning',
                variant: FiftyButtonVariant.secondary,
                onPressed: () => FiftySnackbar.show(
                  context,
                  message: 'Please review your changes',
                  variant: FiftySnackbarVariant.warning,
                ),
              ),
              FiftyButton(
                label: 'Error',
                variant: FiftyButtonVariant.secondary,
                onPressed: () => FiftySnackbar.show(
                  context,
                  message: 'An error occurred',
                  variant: FiftySnackbarVariant.error,
                ),
              ),
              FiftyButton(
                label: 'With Action',
                variant: FiftyButtonVariant.secondary,
                onPressed: () => FiftySnackbar.show(
                  context,
                  message: 'File deleted',
                  variant: FiftySnackbarVariant.info,
                  actionLabel: 'Undo',
                  onAction: () {},
                ),
              ),
            ],
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Dialogs'),
          FiftyButton(
            label: 'Show Dialog',
            onPressed: () => showFiftyDialog(
              context: context,
              builder: (context) => FiftyDialog(
                title: 'Confirm Action',
                content: const Text(
                  'Are you sure you want to proceed with this action? '
                  'This cannot be undone.',
                ),
                actions: [
                  FiftyButton(
                    label: 'Cancel',
                    variant: FiftyButtonVariant.ghost,
                    onPressed: () => Navigator.pop(context),
                  ),
                  FiftyButton(
                    label: 'Confirm',
                    onPressed: () {
                      Navigator.pop(context);
                      FiftySnackbar.show(
                        context,
                        message: 'Action confirmed!',
                        variant: FiftySnackbarVariant.success,
                      );
                    },
                  ),
                ],
              ),
            ),
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Tooltips'),
          Wrap(
            spacing: FiftySpacing.md,
            runSpacing: FiftySpacing.md,
            children: [
              FiftyTooltip(
                message: 'This is a tooltip',
                child: FiftyButton(
                  label: 'Hover Me',
                  variant: FiftyButtonVariant.secondary,
                  onPressed: () {},
                ),
              ),
              const FiftyTooltip(
                message: 'Settings configuration',
                child: FiftyIconButton(
                  icon: Icons.settings,
                  tooltip: 'Settings',
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

// ============================================================================
// LAYOUT PAGE
// ============================================================================

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('LAYOUT')),
      body: ListView(
        padding: EdgeInsets.all(FiftySpacing.lg),
        children: [
          const _SectionTitle('Hero Text'),
          Text(
            'Monument headers with Manrope font',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          const FiftyHero(
            text: 'Display Size',
            size: FiftyHeroSize.display,
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('H1 Size'),
          SizedBox(height: FiftySpacing.md),
          const FiftyHero(
            text: 'H1 Headline',
            size: FiftyHeroSize.h1,
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('H2 Size'),
          SizedBox(height: FiftySpacing.md),
          const FiftyHero(
            text: 'H2 Section Header',
            size: FiftyHeroSize.h2,
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('With Glitch Effect'),
          Text(
            'Kinetic glitch animation on mount',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          const FiftyHero(
            text: 'Glitch Hero',
            size: FiftyHeroSize.h1,
            glitchOnMount: true,
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('With Gradient'),
          Text(
            'Custom gradient fill for dramatic effect',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          FiftyHero(
            text: 'Gradient Text',
            size: FiftyHeroSize.h1,
            gradient: LinearGradient(
              colors: [FiftyColors.burgundy, FiftyColors.hunterGreen],
            ),
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Hero Section'),
          Text(
            'Title with subtitle combo',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          const FiftyHeroSection(
            title: 'Fifty UI',
            subtitle: 'Fifty Design Language v2',
            glitchOnMount: true,
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Left Aligned Section'),
          SizedBox(height: FiftySpacing.md),
          const FiftyHeroSection(
            title: 'Welcome',
            subtitle: 'Build beautiful interfaces with confidence',
            titleSize: FiftyHeroSize.h1,
            crossAxisAlignment: CrossAxisAlignment.start,
          ),
        ],
      ),
    );
  }
}

// ============================================================================
// NAVIGATION PAGE
// ============================================================================

class NavigationPage extends StatefulWidget {
  const NavigationPage({super.key});

  @override
  State<NavigationPage> createState() => _NavigationPageState();
}

class _NavigationPageState extends State<NavigationPage> {
  int _pillIndex = 0;
  int _standardIndex = 1;
  int _textOnlyIndex = 2;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('NAVIGATION')),
      body: ListView(
        padding: EdgeInsets.all(FiftySpacing.lg),
        children: [
          const _SectionTitle('NavBar - Pill Style'),
          Text(
            'Dynamic Island inspired with glassmorphism',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          Container(
            height: 120,
            decoration: BoxDecoration(
              color: FiftyColors.surfaceDark,
              borderRadius: FiftyRadii.lgRadius,
            ),
            child: Stack(
              alignment: Alignment.bottomCenter,
              children: [
                Positioned(
                  bottom: FiftySpacing.lg,
                  left: 0,
                  right: 0,
                  child: FiftyNavBar(
                    items: const [
                      FiftyNavItem(label: 'Home', icon: Icons.home),
                      FiftyNavItem(label: 'Search', icon: Icons.search),
                      FiftyNavItem(label: 'Profile', icon: Icons.person),
                    ],
                    selectedIndex: _pillIndex,
                    onItemSelected: (index) => setState(() => _pillIndex = index),
                    style: FiftyNavBarStyle.pill,
                  ),
                ),
              ],
            ),
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('NavBar - Standard Style'),
          Text(
            'Rectangular with standard border radius',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          Container(
            height: 120,
            decoration: BoxDecoration(
              color: FiftyColors.surfaceDark,
              borderRadius: FiftyRadii.lgRadius,
            ),
            child: Stack(
              alignment: Alignment.bottomCenter,
              children: [
                Positioned(
                  bottom: FiftySpacing.lg,
                  left: 0,
                  right: 0,
                  child: FiftyNavBar(
                    items: const [
                      FiftyNavItem(label: 'Dashboard', icon: Icons.dashboard),
                      FiftyNavItem(label: 'Analytics', icon: Icons.analytics),
                      FiftyNavItem(label: 'Reports', icon: Icons.description),
                      FiftyNavItem(label: 'Settings', icon: Icons.settings),
                    ],
                    selectedIndex: _standardIndex,
                    onItemSelected: (index) => setState(() => _standardIndex = index),
                    style: FiftyNavBarStyle.standard,
                  ),
                ),
              ],
            ),
          ),
          SizedBox(height: FiftySpacing.xl),
          const _SectionTitle('Text-Only (No Icons)'),
          Text(
            'Label-only navigation items',
            style: TextStyle(
              fontFamily: FiftyTypography.fontFamily,
              fontSize: FiftyTypography.bodySmall,
              color: FiftyColors.slateGrey,
            ),
          ),
          SizedBox(height: FiftySpacing.md),
          Container(
            height: 100,
            decoration: BoxDecoration(
              color: FiftyColors.surfaceDark,
              borderRadius: FiftyRadii.lgRadius,
            ),
            child: Stack(
              alignment: Alignment.bottomCenter,
              children: [
                Positioned(
                  bottom: FiftySpacing.lg,
                  left: 0,
                  right: 0,
                  child: FiftyNavBar(
                    items: const [
                      FiftyNavItem(label: 'Today'),
                      FiftyNavItem(label: 'Week'),
                      FiftyNavItem(label: 'Month'),
                      FiftyNavItem(label: 'Year'),
                    ],
                    selectedIndex: _textOnlyIndex,
                    onItemSelected: (index) => setState(() => _textOnlyIndex = index),
                    style: FiftyNavBarStyle.pill,
                    height: 44,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// ============================================================================
// HELPERS
// ============================================================================

class _SectionTitle extends StatelessWidget {
  const _SectionTitle(this.title);

  final String title;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.only(bottom: FiftySpacing.md),
      child: Text(
        title,
        style: TextStyle(
          fontFamily: FiftyTypography.fontFamily,
          fontSize: FiftyTypography.bodySmall,
          fontWeight: FiftyTypography.medium,
          color: FiftyColors.slateGrey,
          letterSpacing: 1,
        ),
      ),
    );
  }
}
0
likes
150
points
409
downloads
screenshot

Documentation

API reference

Publisher

verified publisherfifty.dev

Weekly Downloads

FDL v2 styled Flutter components for the fifty.dev ecosystem. Buttons, cards, inputs, and more.

Homepage
Repository (GitHub)
View/report issues

Topics

#flutter #ui #components #design-system #widget

License

MIT (license)

Dependencies

fifty_theme, fifty_tokens, flutter, flutter_animate

More

Packages that depend on fifty_ui