brutalist_ui 0.0.1 copy "brutalist_ui: ^0.0.1" to clipboard
brutalist_ui: ^0.0.1 copied to clipboard

A neobrutalist design library for Flutter, built entirely on Flutter's core widget primitives.

example/lib/main.dart

import 'package:brutalist_ui/brutalist_ui.dart';

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

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

  @override
  State<GalleryApp> createState() => _GalleryAppState();
}

class _GalleryAppState extends State<GalleryApp> {
  bool _dark = false;

  @override
  Widget build(BuildContext context) {
    return NeoApp(
      title: 'brutalist_ui',
      theme: _dark ? const NeoThemeData.dark() : const NeoThemeData(),
      home: GalleryScreen(
        dark: _dark,
        onDarkChanged: (bool value) => setState(() => _dark = value),
      ),
    );
  }
}

class GalleryScreen extends StatefulWidget {
  const GalleryScreen({
    super.key,
    required this.dark,
    required this.onDarkChanged,
  });

  final bool dark;
  final ValueChanged<bool> onDarkChanged;

  @override
  State<GalleryScreen> createState() => _GalleryScreenState();
}

const List<NeoMenuEntry<String>> _fruitEntries = <NeoMenuEntry<String>>[
  NeoMenuEntry<String>(value: 'apple', label: 'Apple'),
  NeoMenuEntry<String>(value: 'banana', label: 'Banana'),
  NeoMenuEntry<String>(value: 'cherry', label: 'Cherry'),
  NeoMenuEntry<String>(
    value: 'durian',
    label: 'Durian (sold out)',
    enabled: false,
  ),
];

class _GalleryScreenState extends State<GalleryScreen> {
  final TextEditingController _nameController = TextEditingController();
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final Set<String> _tags = <String>{'flutter'};
  bool _subscribed = true;
  bool _notifications = false;
  double _volume = 0.4;
  String _plan = 'monthly';
  String? _fruit;
  int _navIndex = 0;

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

  void _toggleTag(String tag, bool selected) {
    setState(() {
      if (selected) {
        _tags.add(tag);
      } else {
        _tags.remove(tag);
      }
    });
  }

  Future<void> _confirmDelete() {
    return showNeoDialog<void>(
      context: context,
      builder: (BuildContext context) {
        return NeoDialog(
          title: const Text('DELETE PROJECT?'),
          content: const Text(
            'This cannot be undone. Everything in it goes away.',
          ),
          actions: <Widget>[
            NeoButton(
              variant: NeoVariant.neutral,
              size: NeoButtonSize.small,
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('CANCEL'),
            ),
            NeoButton(
              variant: NeoVariant.danger,
              size: NeoButtonSize.small,
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('DELETE'),
            ),
          ],
        );
      },
    );
  }

  Future<void> _showOptionsSheet() {
    return showNeoSheet<void>(
      context: context,
      builder: (BuildContext context) {
        return NeoBottomSheet(
          title: const Text('OPTIONS'),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              NeoListTile(title: const Text('Duplicate'), onPressed: () {}),
              const SizedBox(height: 10),
              NeoListTile(title: const Text('Archive'), onPressed: () {}),
              const SizedBox(height: 18),
              NeoButton(
                variant: NeoVariant.neutral,
                onPressed: () => Navigator.of(context).pop(),
                child: const Text('CLOSE'),
              ),
            ],
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    final NeoThemeData theme = NeoTheme.of(context);

    return NeoScaffold(
      bottomBar: NeoBottomNavBar(
        currentIndex: _navIndex,
        onSelected: (int index) => setState(() => _navIndex = index),
        items: const <NeoNavItem>[
          NeoNavItem(icon: NeoIcon(NeoGlyph.check), label: 'Browse'),
          NeoNavItem(icon: NeoIcon(NeoGlyph.plus), label: 'Create'),
          NeoNavItem(icon: NeoIcon(NeoGlyph.chevronRight), label: 'Profile'),
        ],
      ),
      appBar: NeoAppBar(
        title: const Text('NEOBRUTALIST'),
        actions: <Widget>[
          NeoSwitch(
            value: widget.dark,
            variant: NeoVariant.secondary,
            semanticLabel: 'Dark mode',
            onChanged: widget.onDarkChanged,
          ),
        ],
      ),
      floatingAction: NeoIconButton(
        variant: NeoVariant.danger,
        size: NeoButtonSize.large,
        semanticLabel: 'Delete project',
        onPressed: _confirmDelete,
        icon: const NeoIcon(NeoGlyph.close),
      ),
      body: ListView(
        padding: const EdgeInsets.fromLTRB(20, 20, 20, 110),
        children: <Widget>[
          Text('Built on widgets.dart', style: theme.typography.display),
          const SizedBox(height: 8),
          Text(
            'No Material. No Cupertino.',
            style: theme.typography.body.copyWith(
              color: theme.colors.mutedForeground,
            ),
          ),
          const _Section(
            title: 'BUTTONS',
            child: Wrap(
              spacing: 12,
              runSpacing: 12,
              children: <Widget>[
                _DemoButton(variant: NeoVariant.primary, label: 'PRIMARY'),
                _DemoButton(variant: NeoVariant.secondary, label: 'SECONDARY'),
                _DemoButton(variant: NeoVariant.accent, label: 'ACCENT'),
                _DemoButton(variant: NeoVariant.success, label: 'SUCCESS'),
                _DemoButton(variant: NeoVariant.warning, label: 'WARNING'),
                _DemoButton(variant: NeoVariant.danger, label: 'DANGER'),
                _DemoButton(variant: NeoVariant.neutral, label: 'NEUTRAL'),
                NeoButton(onPressed: null, child: Text('DISABLED')),
              ],
            ),
          ),
          _Section(
            title: 'SIZES',
            child: Wrap(
              spacing: 12,
              runSpacing: 12,
              crossAxisAlignment: WrapCrossAlignment.center,
              children: <Widget>[
                NeoButton(
                  size: NeoButtonSize.small,
                  onPressed: () {},
                  child: const Text('SMALL'),
                ),
                NeoButton(onPressed: () {}, child: const Text('MEDIUM')),
                NeoButton(
                  size: NeoButtonSize.large,
                  onPressed: () {},
                  child: const Text('LARGE'),
                ),
                NeoIconButton(
                  onPressed: () {},
                  icon: const NeoIcon(NeoGlyph.plus),
                ),
                NeoIconButton(
                  variant: NeoVariant.primary,
                  onPressed: () {},
                  icon: const NeoIcon(NeoGlyph.chevronRight),
                ),
              ],
            ),
          ),
          _Section(
            title: 'TEXT INPUT',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                NeoTextField(
                  controller: _nameController,
                  placeholder: 'Your name',
                  semanticLabel: 'Your name',
                ),
                const SizedBox(height: 12),
                const NeoTextField(placeholder: 'Disabled', enabled: false),
              ],
            ),
          ),
          _Section(
            title: 'SELECTION',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Row(
                  children: <Widget>[
                    NeoCheckbox(
                      value: _subscribed,
                      semanticLabel: 'Subscribe',
                      onChanged: (bool value) =>
                          setState(() => _subscribed = value),
                    ),
                    const SizedBox(width: 12),
                    const Text('Subscribe to the newsletter'),
                  ],
                ),
                const SizedBox(height: 16),
                Row(
                  children: <Widget>[
                    NeoSwitch(
                      value: _notifications,
                      semanticLabel: 'Notifications',
                      onChanged: (bool value) =>
                          setState(() => _notifications = value),
                    ),
                    const SizedBox(width: 12),
                    const Text('Push notifications'),
                  ],
                ),
                const SizedBox(height: 16),
                RadioGroup<String>(
                  groupValue: _plan,
                  onChanged: (String? value) => setState(() => _plan = value!),
                  child: const Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      _PlanOption(value: 'monthly', label: 'Monthly'),
                      SizedBox(height: 12),
                      _PlanOption(value: 'yearly', label: 'Yearly'),
                    ],
                  ),
                ),
              ],
            ),
          ),
          _Section(
            title: 'SLIDER & PROGRESS',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                NeoSlider(
                  value: _volume,
                  semanticLabel: 'Volume',
                  onChanged: (double value) => setState(() => _volume = value),
                ),
                const SizedBox(height: 20),
                NeoProgressBar(value: _volume, semanticLabel: 'Volume'),
                const SizedBox(height: 10),
                Text(
                  '${(_volume * 100).round()}%',
                  style: theme.typography.label,
                ),
              ],
            ),
          ),
          _Section(
            title: 'CHIPS & BADGES',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Wrap(
                  spacing: 10,
                  runSpacing: 10,
                  children: <Widget>[
                    for (final String tag in <String>[
                      'flutter',
                      'dart',
                      'design',
                      'brutal',
                    ])
                      NeoChip(
                        label: Text(tag.toUpperCase()),
                        selected: _tags.contains(tag),
                        onSelected: (bool selected) =>
                            _toggleTag(tag, selected),
                      ),
                  ],
                ),
                const SizedBox(height: 16),
                const Wrap(
                  spacing: 10,
                  runSpacing: 10,
                  children: <Widget>[
                    NeoBadge(child: Text('NEW')),
                    NeoBadge(
                      variant: NeoVariant.success,
                      child: Text('SHIPPED'),
                    ),
                    NeoBadge(variant: NeoVariant.danger, child: Text('BROKEN')),
                  ],
                ),
              ],
            ),
          ),
          _Section(
            title: 'DROPDOWN',
            child: NeoDropdown<String>(
              value: _fruit,
              entries: _fruitEntries,
              placeholder: 'Pick a fruit',
              semanticLabel: 'Fruit',
              onChanged: (String value) => setState(() => _fruit = value),
            ),
          ),
          _Section(
            title: 'FORM VALIDATION',
            child: Form(
              key: _formKey,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: <Widget>[
                  NeoTextFormField(
                    placeholder: 'you@example.com',
                    semanticLabel: 'Email',
                    validator: (String? value) => (value ?? '').contains('@')
                        ? null
                        : 'Enter a valid email',
                  ),
                  const SizedBox(height: 14),
                  NeoButton(
                    variant: NeoVariant.success,
                    onPressed: () => _formKey.currentState?.validate(),
                    child: const Text('SUBMIT'),
                  ),
                ],
              ),
            ),
          ),
          _Section(
            title: 'OVERLAYS & LOADING',
            child: Wrap(
              spacing: 16,
              runSpacing: 16,
              crossAxisAlignment: WrapCrossAlignment.center,
              children: <Widget>[
                NeoButton(
                  variant: NeoVariant.secondary,
                  onPressed: _showOptionsSheet,
                  child: const Text('BOTTOM SHEET'),
                ),
                NeoTooltip(
                  message: 'This is a tooltip',
                  child: NeoButton(
                    variant: NeoVariant.neutral,
                    onPressed: () {},
                    child: const Text('HOVER ME'),
                  ),
                ),
                const NeoActivityIndicator(),
                const NeoActivityIndicator(
                  size: 24,
                  variant: NeoVariant.danger,
                ),
              ],
            ),
          ),
          _Section(
            title: 'SURFACES',
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                NeoCard(
                  color: theme.colors.primary,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: <Widget>[
                      Text('A flat card', style: theme.typography.title),
                      const SizedBox(height: 6),
                      const Text('Hard border, hard shadow, no gradient.'),
                    ],
                  ),
                ),
                const NeoDivider(),
                NeoListTile(
                  leading: const NeoIcon(NeoGlyph.check),
                  title: const Text('Tappable tile'),
                  subtitle: const Text('It presses into the page'),
                  trailing: const NeoIcon(NeoGlyph.chevronRight),
                  onPressed: () {},
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class _Section extends StatelessWidget {
  const _Section({required this.title, required this.child});

  final String title;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    final NeoThemeData theme = NeoTheme.of(context);

    return Padding(
      padding: const EdgeInsets.only(top: 36),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          Text(
            title,
            style: theme.typography.label.copyWith(
              color: theme.colors.mutedForeground,
            ),
          ),
          const SizedBox(height: 14),
          child,
        ],
      ),
    );
  }
}

class _DemoButton extends StatelessWidget {
  const _DemoButton({required this.variant, required this.label});

  final NeoVariant variant;
  final String label;

  @override
  Widget build(BuildContext context) {
    return NeoButton(variant: variant, onPressed: () {}, child: Text(label));
  }
}

class _PlanOption extends StatelessWidget {
  const _PlanOption({required this.value, required this.label});

  final String value;
  final String label;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        NeoRadio<String>(value: value),
        const SizedBox(width: 12),
        Text(label),
      ],
    );
  }
}
0
likes
160
points
24
downloads
screenshot

Documentation

Documentation
API reference

Publisher

verified publisherdipendrasharma.com

Weekly Downloads

A neobrutalist design library for Flutter, built entirely on Flutter's core widget primitives.

Repository (GitHub)
View/report issues
Contributing

Topics

#ui #widget #design-system #neobrutalism

License

MIT (license)

Dependencies

flutter

More

Packages that depend on brutalist_ui