neo_mobile_kit 0.2.4 copy "neo_mobile_kit: ^0.2.4" to clipboard
neo_mobile_kit: ^0.2.4 copied to clipboard

Neo-Brutalist Flutter UI components, theme tokens, layout helpers, backgrounds, navigation, and motion primitives.

example/lib/main.dart

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

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

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

  @override
  State<NeoMobileKitExample> createState() => _NeoMobileKitExampleState();
}

class _NeoMobileKitExampleState extends State<NeoMobileKitExample> {
  ThemeMode _themeMode = ThemeMode.light;
  NeoThemePreset _preset = NeoThemePreset.worldSkills;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: NeoTheme.buildTheme(Brightness.light, preset: _preset),
      darkTheme: NeoTheme.buildTheme(Brightness.dark, preset: _preset),
      themeMode: _themeMode,
      home: CatalogPage(
        themeMode: _themeMode,
        preset: _preset,
        onThemeModeChanged: (value) => setState(() => _themeMode = value),
        onPresetChanged: (value) => setState(() => _preset = value),
        onReset: () => setState(() {
          _themeMode = ThemeMode.light;
          _preset = NeoThemePreset.worldSkills;
        }),
      ),
    );
  }
}

class CatalogPage extends StatefulWidget {
  const CatalogPage({
    super.key,
    required this.themeMode,
    required this.preset,
    required this.onThemeModeChanged,
    required this.onPresetChanged,
    required this.onReset,
  });

  final ThemeMode themeMode;
  final NeoThemePreset preset;
  final ValueChanged<ThemeMode> onThemeModeChanged;
  final ValueChanged<NeoThemePreset> onPresetChanged;
  final VoidCallback onReset;

  @override
  State<CatalogPage> createState() => _CatalogPageState();
}

class _CatalogPageState extends State<CatalogPage> {
  final TextEditingController _searchController = TextEditingController();
  final GlobalKey _revealKey = GlobalKey();
  Set<String> _filters = {'widgets'};
  int _bottomIndex = 0;
  int _tabIndex = 0;

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

  @override
  Widget build(BuildContext context) {
    final palette = NeoPalette.of(context);
    final isDark = Theme.of(context).brightness == Brightness.dark;

    return NeoPageShell(
      background: NeoShellBackground.index(index: _bottomIndex),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          NeoAppBar(
            title: 'Neo Brutalist UI',
            subtitle: 'Component catalog',
            isDark: isDark,
            searchController: _searchController,
            onSearchChanged: (_) {},
            onThemeToggle: (value) {
              widget.onThemeModeChanged(value ? ThemeMode.dark : ThemeMode.light);
            },
            onMenuPressed: () => _openMenu(context),
            onSettingsPressed: () => _openSettings(context),
          ),
          const SizedBox(height: NeoSpacing.lg),
          NeoSectionHeader(
            eyebrow: 'CATALOG',
            title: 'Neo-Brutalist building blocks',
            subtitle:
                'Composable widgets, layouts, motion primitives, and visual layers for Flutter interfaces.',
            action: NeoIconButton(
              key: _revealKey,
              icon: Icons.bolt_rounded,
              onPressed: _runReveal,
            ),
          ),
          const SizedBox(height: NeoSpacing.lg),
          NeoTabs(
            currentIndex: _tabIndex,
            onChanged: (value) => setState(() => _tabIndex = value),
            items: const [
              NeoTabItem(label: 'Widgets', icon: Icons.widgets_rounded),
              NeoTabItem(label: 'Motion', icon: Icons.auto_awesome_rounded),
              NeoTabItem(label: 'Data', icon: Icons.table_chart_rounded),
            ],
          ),
          const SizedBox(height: NeoSpacing.lg),
          NeoEntranceGroup(
            sort: NeoStaggerSort.inOrder,
            preset: NeoEntrancePreset.fadeSlideUp,
            children: [
              _ThemeAndLayoutSection(palette: palette),
              _CoreWidgetsSection(
                filters: _filters,
                onFiltersChanged: (value) => setState(() => _filters = value),
              ),
              _DataWidgetsSection(tabIndex: _tabIndex),
              _MotionAndBackgroundSection(palette: palette),
              _PresentationSection(
                onOpenOnboarding: _openOnboarding,
                onOpenSplash: _openSplash,
              ),
              NeoBottomNav(
                currentIndex: _bottomIndex,
                onChanged: (value) => setState(() => _bottomIndex = value),
                items: const [
                  NeoBottomNavItem(label: 'Home', icon: Icons.home_rounded),
                  NeoBottomNavItem(label: 'Profile', icon: Icons.person_rounded),
                ],
              ),
            ],
          ),
        ],
      ),
    );
  }

  void _openMenu(BuildContext context) {
    showNeoSideMenu(
      context: context,
      title: 'SECTIONS',
      items: [
        NeoSideMenuItem(
          label: 'Widgets',
          icon: Icons.widgets_rounded,
          isSelected: _tabIndex == 0,
          onPressed: () => setState(() => _tabIndex = 0),
        ),
        NeoSideMenuItem(
          label: 'Motion',
          icon: Icons.auto_awesome_rounded,
          isSelected: _tabIndex == 1,
          onPressed: () => setState(() => _tabIndex = 1),
        ),
      ],
    );
  }

  void _openSettings(BuildContext context) {
    showModalBottomSheet<void>(
      context: context,
      backgroundColor: Colors.transparent,
      builder: (_) => SafeArea(
        child: NeoAppSettingsSheet(
          themeMode: widget.themeMode,
          preset: widget.preset,
          onThemeModeChanged: widget.onThemeModeChanged,
          onPresetChanged: widget.onPresetChanged,
          onReset: widget.onReset,
        ),
      ),
    );
  }

  void _runReveal() {
    showNeoOrbRevealTransition(
      context: context,
      originKey: _revealKey,
      color: NeoPalette.of(context).primary,
      onCovered: () {},
    );
  }

  void _openOnboarding() {
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        builder: (_) => NeoOnboardingPage(
          appName: 'Component Study',
          steps: const [
            NeoOnboardingStep(
              title: 'Compose surfaces',
              description: 'Use panels, inputs, navigation, and motion as independent pieces.',
              icon: Icons.dashboard_customize_rounded,
            ),
            NeoOnboardingStep(
              title: 'Control behavior',
              description: 'Keep state, routing, validation, and data in your own application code.',
              icon: Icons.tune_rounded,
            ),
          ],
          onFinish: () => Navigator.of(context).pop(),
        ),
      ),
    );
  }

  void _openSplash() {
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        builder: (_) => NeoSplashPage(
          replace: false,
          logoText: 'NEO',
          nextPageBuilder: (_) => const _SplashLandingPage(),
        ),
      ),
    );
  }
}

class _ThemeAndLayoutSection extends StatelessWidget {
  const _ThemeAndLayoutSection({required this.palette});

  final NeoPalette palette;

  @override
  Widget build(BuildContext context) {
    return NeoWindowPanel(
      title: 'theme_layout.dart',
      trailing: NeoPill(label: palette.isDark ? 'DARK' : 'LIGHT'),
      child: NeoSplitLayout(
        primary: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            NeoSummaryCard(
              title: 'Palette',
              value: palette.primaryStrong.toString(),
              helper: 'NeoPalette, NeoColors, and NeoTokens drive custom composition.',
              badge: 'TOKENS',
              icon: Icons.palette_rounded,
            ),
            const SizedBox(height: NeoSpacing.md),
            NeoStepper(
              currentStep: 1,
              steps: const [
                NeoStepItem(label: 'Theme', helper: 'Build ThemeData'),
                NeoStepItem(label: 'Layout', helper: 'Compose sections'),
                NeoStepItem(label: 'Motion', helper: 'Add animation'),
              ],
            ),
          ],
        ),
        secondary: const NeoPanel(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              NeoPill(label: 'SPACING'),
              SizedBox(height: NeoSpacing.sm),
              Text('NeoBreakpoints and NeoSpacing keep custom layouts aligned.'),
            ],
          ),
        ),
      ),
    );
  }
}

class _CoreWidgetsSection extends StatelessWidget {
  const _CoreWidgetsSection({
    required this.filters,
    required this.onFiltersChanged,
  });

  final Set<String> filters;
  final ValueChanged<Set<String>> onFiltersChanged;

  @override
  Widget build(BuildContext context) {
    return NeoWindowPanel(
      title: 'core_widgets.dart',
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          NeoInfoBanner(
            variant: NeoInfoBannerVariant.info,
            title: 'Composable components',
            message: 'Each widget renders presentation only; behavior comes from callbacks and external state.',
            trailing: const NeoPill(label: 'API'),
          ),
          const SizedBox(height: NeoSpacing.md),
          NeoFilterChips<String>(
            selectedValues: filters,
            onChanged: onFiltersChanged,
            items: const [
              NeoFilterChipItem(value: 'widgets', label: 'Widgets', icon: Icons.widgets_rounded),
              NeoFilterChipItem(value: 'forms', label: 'Forms', icon: Icons.edit_rounded),
              NeoFilterChipItem(value: 'feedback', label: 'Feedback', icon: Icons.info_rounded),
            ],
          ),
          const SizedBox(height: NeoSpacing.md),
          NeoAccordion(
            title: 'Form controls',
            subtitle: 'Inputs, buttons, search, and press feedback.',
            initiallyExpanded: true,
            child: Column(
              children: [
                const NeoInputField(
                  label: 'Label',
                  hint: 'Type something',
                  icon: Icons.edit_rounded,
                ),
                const SizedBox(height: NeoSpacing.md),
                NeoSearchBox(controller: TextEditingController(), hintText: 'Search locally'),
                const SizedBox(height: NeoSpacing.md),
                NeoPressMotion(
                  onTap: () {},
                  child: NeoButton(
                    label: 'Primary action',
                    icon: Icons.arrow_forward_rounded,
                    onPressed: () {},
                  ),
                ),
                const SizedBox(height: NeoSpacing.md),
                NeoButton(
                  label: 'Secondary action',
                  variant: NeoButtonVariant.secondary,
                  onPressed: () {},
                ),
              ],
            ),
          ),
          const SizedBox(height: NeoSpacing.md),
          NeoActionTile(
            title: 'Open details',
            subtitle: 'Action tile with custom trailing content.',
            trailing: const NeoPill(label: 'GO'),
            onTap: () {},
          ),
          const SizedBox(height: NeoSpacing.md),
          NeoCompactListItem(
            title: 'Compact row',
            subtitle: 'Dense content with badges and metadata.',
            meta: '12m',
            badges: const [NeoPill(label: 'NEW')],
            selected: true,
          ),
          const SizedBox(height: NeoSpacing.md),
          NeoListItem(
            title: 'Readable row',
            subtitle: 'A larger list item with icon and pill label.',
            icon: Icons.article_rounded,
            pillLabel: 'DOC',
          ),
          const SizedBox(height: NeoSpacing.md),
          const NeoDetailRow(
            label: 'Status',
            value: 'Ready',
            helper: 'Displayed with NeoDetailRow.',
            emphasis: true,
          ),
          const SizedBox(height: NeoSpacing.md),
          NeoBottomSheetCard(
            title: 'Inline sheet shell',
            subtitle: 'The same surface can be placed in a modal or inline.',
            primaryActionLabel: 'Accept',
            secondaryActionLabel: 'Cancel',
            onPrimaryPressed: () {},
            onSecondaryPressed: () {},
            child: const Text('Custom content belongs here.'),
          ),
          const SizedBox(height: NeoSpacing.md),
          NeoEmptyState(
            icon: Icons.inbox_rounded,
            title: 'Nothing selected',
            message: 'Use this surface when a collection or filter has no visible data.',
            buttonLabel: 'Create item',
            onPressed: () {},
          ),
          const SizedBox(height: NeoSpacing.md),
          NeoOrbSubmitButton<String>(
            label: 'Submit with orb',
            onSubmit: () async => 'ok',
            onSuccess: (_) {},
          ),
          const SizedBox(height: NeoSpacing.md),
          const Center(
            child: NeoOrbSubmitSpinner(color: Colors.black, size: 28),
          ),
        ],
      ),
    );
  }
}

class _DataWidgetsSection extends StatelessWidget {
  const _DataWidgetsSection({required this.tabIndex});

  final int tabIndex;

  @override
  Widget build(BuildContext context) {
    return NeoWindowPanel(
      title: 'data_feedback.dart',
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          NeoMetricStrip(
            items: const [
              NeoMetricItem(label: 'Widgets', value: '42', helper: 'Documented'),
              NeoMetricItem(label: 'Groups', value: '8', helper: 'Catalog sections'),
              NeoMetricItem(label: 'Tab', value: '0', helper: 'Local state'),
            ],
          ),
          const SizedBox(height: NeoSpacing.md),
          NeoDataTableLite(
            columns: const [
              NeoDataTableColumn(label: 'Component', flex: 2),
              NeoDataTableColumn(label: 'Layer'),
              NeoDataTableColumn(label: 'State'),
            ],
            rows: [
              const NeoDataTableRow(cells: ['NeoPanel', 'Surface', 'Stable'], highlight: true),
              NeoDataTableRow(cells: ['NeoTabs', 'Navigation', 'Index $tabIndex']),
            ],
          ),
          const SizedBox(height: NeoSpacing.md),
          const NeoProgressBadge(
            label: 'Documentation',
            progress: .86,
            value: '86%',
          ),
          const SizedBox(height: NeoSpacing.md),
          NeoStatusCluster(
            items: const [
              NeoStatusItem(label: 'Stable', tone: NeoStatusTone.success),
              NeoStatusItem(label: 'Review', tone: NeoStatusTone.warning),
              NeoStatusItem(label: 'Neutral', tone: NeoStatusTone.neutral),
            ],
          ),
          const SizedBox(height: NeoSpacing.md),
          const NeoShapeLoader(label: 'Loading state'),
          const SizedBox(height: NeoSpacing.md),
          NeoTimeline(
            highlightedIndex: 1,
            entries: const [
              NeoTimelineEntry(title: 'Install', subtitle: 'Add the package.'),
              NeoTimelineEntry(title: 'Compose', subtitle: 'Place widgets in your layout.'),
              NeoTimelineEntry(title: 'Customize', subtitle: 'Tune colors and motion.'),
            ],
          ),
          const SizedBox(height: NeoSpacing.md),
          const NeoStatCard(
            label: 'Coverage',
            value: 'All',
            helper: 'Current public widgets are represented.',
            icon: Icons.verified_rounded,
          ),
        ],
      ),
    );
  }
}

class _MotionAndBackgroundSection extends StatelessWidget {
  const _MotionAndBackgroundSection({required this.palette});

  final NeoPalette palette;

  @override
  Widget build(BuildContext context) {
    return NeoWindowPanel(
      title: 'motion_backgrounds.dart',
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          SizedBox(
            height: 180,
            child: Stack(
              children: [
                Positioned.fill(
                  child: NeoOrbBackground(specs: buildAuthOrbs(palette)),
                ),
                const Center(child: NeoPill(label: 'NeoOrbBackground')),
              ],
            ),
          ),
          const SizedBox(height: NeoSpacing.md),
          SizedBox(
            height: 150,
            child: Stack(
              children: [
                Positioned.fill(
                  child: NeoMoonBackground(
                    specs: [
                      NeoMoonSpec(
                        leftFactor: .15,
                        topFactor: .2,
                        size: 90,
                        color: NeoColors.moon,
                        shadowColor: palette.shadowSoft,
                        pageColor: palette.page,
                        borderColor: palette.outline,
                        xTravel: 8,
                        yTravel: 10,
                      ),
                    ],
                  ),
                ),
                const Center(child: NeoPill(label: 'NeoMoonBackground')),
              ],
            ),
          ),
          const SizedBox(height: NeoSpacing.md),
          const SizedBox(height: 160, child: NeoProfileMotionBanner()),
          const SizedBox(height: NeoSpacing.md),
          NeoFadeSlide(child: NeoPanel(child: Text('NeoFadeSlide'))),
          const SizedBox(height: NeoSpacing.md),
          NeoPop(child: NeoPanel(child: Text('NeoPop'))),
          const SizedBox(height: NeoSpacing.md),
          NeoPanelDrop(child: NeoPanel(child: Text('NeoPanelDrop'))),
          const SizedBox(height: NeoSpacing.md),
          NeoScrollReveal(
            preset: NeoEntrancePreset.neoPop,
            child: NeoPanel(child: Text('NeoScrollReveal')),
          ),
          const SizedBox(height: NeoSpacing.md),
          Text(
            'Motion token duration: ${NeoMotionTokens.normal.inMilliseconds}ms',
            style: Theme.of(context).textTheme.labelLarge,
          ),
        ],
      ),
    );
  }
}

class _PresentationSection extends StatelessWidget {
  const _PresentationSection({
    required this.onOpenOnboarding,
    required this.onOpenSplash,
  });

  final VoidCallback onOpenOnboarding;
  final VoidCallback onOpenSplash;

  @override
  Widget build(BuildContext context) {
    return NeoWindowPanel(
      title: 'presentation.dart',
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          NeoOnboardingWizard(
            steps: const [
              NeoOnboardingWizardStep(
                id: 'one',
                title: 'Choose primitives',
                description: 'Use only the components that fit your interface.',
                icon: Icons.widgets_rounded,
                content: Text('Arbitrary widget content can live inside each step.'),
              ),
              NeoOnboardingWizardStep(
                id: 'two',
                title: 'Connect behavior',
                description: 'Callbacks expose navigation without owning it.',
                icon: Icons.route_rounded,
                content: Text('The host application decides what happens next.'),
              ),
            ],
            onComplete: () {},
          ),
          const SizedBox(height: NeoSpacing.md),
          NeoButton(
            label: 'Open NeoOnboardingPage',
            icon: Icons.play_arrow_rounded,
            onPressed: onOpenOnboarding,
          ),
          const SizedBox(height: NeoSpacing.md),
          NeoButton(
            label: 'Open NeoSplashPage',
            icon: Icons.auto_awesome_rounded,
            variant: NeoButtonVariant.secondary,
            onPressed: onOpenSplash,
          ),
        ],
      ),
    );
  }
}

class _SplashLandingPage extends StatelessWidget {
  const _SplashLandingPage();

  @override
  Widget build(BuildContext context) {
    return NeoPageShell(
      alignment: Alignment.center,
      child: NeoPanel(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const NeoPill(label: 'SPLASH COMPLETE'),
            const SizedBox(height: NeoSpacing.md),
            NeoButton(
              label: 'Close',
              onPressed: () => Navigator.of(context).pop(),
            ),
          ],
        ),
      ),
    );
  }
}
4
likes
160
points
51
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Neo-Brutalist Flutter UI components, theme tokens, layout helpers, backgrounds, navigation, and motion primitives.

Repository (GitHub)
View/report issues

Topics

#flutter #widget #ui #neobrutalism #design-system

License

MIT (license)

Dependencies

flutter

More

Packages that depend on neo_mobile_kit