seed_ui 0.6.7 copy "seed_ui: ^0.6.7" to clipboard
seed_ui: ^0.6.7 copied to clipboard

A themeable Flutter widget library — 30+ token-driven components plus context-free message, notification, modal and drawer APIs. No Material dependency.

example/lib/main.dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'
    hide ThemeData, Checkbox, Radio, RadioGroup, Switch, Tooltip, Drawer;
import 'package:seed_ui/seed_ui.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:go_router/go_router.dart';

import 'components/localization_demo.dart';
import 'components/feedback/alert_demo.dart';
import 'components/data_display/avatar_demo.dart';
import 'components/data_display/badge_demo.dart';
import 'components/general/button_demo.dart';
import 'components/data_display/card_demo.dart';
import 'components/data_display/collapse_demo.dart';
import 'components/data_display/countdown_demo.dart';
import 'components/data_display/tree_demo.dart';
import 'components/data_display/listy_demo.dart';
import 'components/data_display/reorderable_list_demo.dart';
import 'components/data_display/timeline_demo.dart';
import 'components/data_display/tour_demo.dart';
import 'components/data_display/popover_demo.dart';
import 'components/data_entry/checkbox_demo.dart';
import 'components/feedback/drawer_demo.dart';
import 'components/data_display/empty_demo.dart';
import 'components/navigation/dropdown_demo.dart';
import 'components/data_entry/input_demo.dart';
import 'components/data_entry/input_number_demo.dart';
import 'components/feedback/message_demo.dart';
import 'components/feedback/modal_demo.dart';
import 'components/feedback/notification_demo.dart';
import 'components/navigation/pagination_demo.dart';
import 'components/feedback/popconfirm_demo.dart';
import 'components/feedback/progress_demo.dart';
import 'components/data_entry/radio_demo.dart';
import 'components/feedback/result_demo.dart';
import 'components/feedback/spin_demo.dart';
import 'components/data_display/segmented_demo.dart';
import 'components/data_display/steps_demo.dart';
import 'components/data_entry/select_demo.dart';
import 'components/data_entry/switch_demo.dart';
import 'components/data_entry/upload_demo.dart';
import 'components/data_display/tabs_demo.dart';
import 'components/data_display/tag_demo.dart';
import 'components/data_display/tooltip_demo.dart';
import 'components/general/new_year_demo.dart';
import 'theme/new_year_theme.dart';

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

enum ThemeModeOption { light, dark, newYear, newYearNight }

class ThemeController extends InheritedWidget {
  const ThemeController({
    super.key,
    required this.mode,
    required this.setTheme,
    required super.child,
  });

  final ThemeModeOption mode;
  final ValueChanged<ThemeModeOption> setTheme;

  static ThemeController of(BuildContext context) {
    final result = context
        .dependOnInheritedWidgetOfExactType<ThemeController>();
    assert(result != null, 'No ThemeController found in context');
    return result!;
  }

  @override
  bool updateShouldNotify(ThemeController oldWidget) => mode != oldWidget.mode;
}

/// The language the gallery is in, so the kit's own words can be seen
/// changing. Beside [ThemeController] rather than folded into it: a language
/// and a theme are two unrelated choices.
class LocaleController extends InheritedWidget {
  const LocaleController({
    super.key,
    required this.locale,
    required this.setLocale,
    required super.child,
  });

  final Locale locale;
  final ValueChanged<Locale> setLocale;

  static LocaleController of(BuildContext context) {
    final result = context
        .dependOnInheritedWidgetOfExactType<LocaleController>();
    assert(result != null, 'No LocaleController found in context');
    return result!;
  }

  @override
  bool updateShouldNotify(LocaleController old) => locale != old.locale;
}

/// The languages the kit ships, with something readable to show for each.
const Map<String, String> demoLanguages = {
  'en': 'English',
  'ru': 'Русский',
  'tk': 'Türkmen',
  'de': 'Deutsch',
  'fr': 'Français',
  'es': 'Español',
  'zh': '中文',
  'ja': '日本語',
  'tr': 'Türkçe',
  'pt': 'Português',
  'ar': 'العربية',
  'he': 'עברית',
};

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

  @override
  State<DemoApp> createState() => _DemoAppState();
}

class _DemoAppState extends State<DemoApp> {
  ThemeModeOption _mode = ThemeModeOption.light;
  Locale _locale = const Locale('en');

  ThemeData _getTheme() {
    switch (_mode) {
      case ThemeModeOption.dark:
        return ThemeData.dark;
      case ThemeModeOption.newYear:
        return newYearTheme();
      case ThemeModeOption.newYearNight:
        return newYearTheme(dark: true);
      case ThemeModeOption.light:
        return ThemeData.light;
    }
  }

  late final GoRouter _router = GoRouter(
    navigatorKey: UiKit.navigatorKey,
    initialLocation: '/',
    routes: [
      ShellRoute(
        builder: (context, state, child) {
          return MainLayout(child: child);
        },
        routes: [
          GoRoute(path: '/', builder: (context, state) => const HomePage()),
          GoRoute(
            path: '/demo/:id',
            builder: (context, state) {
              final id = state.pathParameters['id'];
              final demo = demos.firstWhere(
                (d) => d.id == id,
                orElse: () => demos.first,
              );
              return DemoPage(demo: demo);
            },
          ),
        ],
      ),
    ],
  );

  @override
  Widget build(BuildContext context) {
    return ThemeController(
      mode: _mode,
      setTheme: (mode) => setState(() => _mode = mode),
      child: LocaleController(
        locale: _locale,
        setLocale: (l) => setState(() => _locale = l),
        // Above MaterialApp, as the kit's docs place it: overlays draw into
        // the navigator, above anything put inside `home`.
        child: ConfigProvider(
          theme: _getTheme(),
          child: MaterialApp.router(
            title: 'seed_ui',
            debugShowCheckedModeBanner: false,
            locale: _locale,
            // The kit's delegate is one more in the list, not a scheme of its
            // own. The global ones are Flutter's requirement: MaterialApp
            // refuses a non-English locale without them.
            localizationsDelegates: const [
              SeedLocalizations.delegate,
              GlobalMaterialLocalizations.delegate,
              GlobalWidgetsLocalizations.delegate,
              GlobalCupertinoLocalizations.delegate,
            ],
            supportedLocales: SeedLocalizations.supportedLocales,
            routerConfig: _router,
          ),
        ),
      ),
    );
  }
}

class MainLayout extends StatelessWidget {
  const MainLayout({super.key, required this.child});
  final Widget child;

  @override
  Widget build(BuildContext context) {
    final token = context.softToken;
    final themeController = ThemeController.of(context);
    final localeController = LocaleController.of(context);
    final themeLabel = switch (themeController.mode) {
      ThemeModeOption.light => 'Light ☀️',
      ThemeModeOption.dark => 'Dark 🌙',
      ThemeModeOption.newYear => 'New Year 🎄',
      ThemeModeOption.newYearNight => 'New Year night 🌙🎄',
    };
    final themeIcon = switch (themeController.mode) {
      ThemeModeOption.light => Icons.light_mode,
      ThemeModeOption.dark => Icons.dark_mode,
      ThemeModeOption.newYear => Icons.park,
      ThemeModeOption.newYearNight => Icons.nights_stay,
    };

    return Scaffold(
      backgroundColor: token.colorBgLayout,
      body: SafeArea(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Container(
              color: token.colorBgContainer,
              padding: EdgeInsets.symmetric(
                horizontal: token.sizeLG,
                vertical: token.sizeSM,
              ),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  MouseRegion(
                    cursor: SystemMouseCursors.click,
                    child: GestureDetector(
                      onTap: () => context.go('/'),
                      behavior: HitTestBehavior.opaque,
                      child: Row(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Image.asset(
                            'assets/images/logo.png',
                            width: 50,
                            height: 50,
                          ),
                          Text(
                            'seed_ui',
                            style: TextStyle(
                              fontSize: token.fontSizeXL,
                              color: token.colorText,
                              fontWeight: FontWeight.w600,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                  // Grouped, and allowed to shrink: the row is spaceBetween,
                  // so loose children would spread across it, and on a narrow
                  // screen two dropdowns side by side overflow.
                  Flexible(
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Dropdown(
                          trigger: const [DropdownTrigger.click],
                          menu: const [
                            DropdownItem(
                              key: ThemeModeOption.light,
                              label: Text('Light ☀️'),
                              icon: Icon(Icons.light_mode),
                            ),
                            DropdownItem(
                              key: ThemeModeOption.dark,
                              label: Text('Dark 🌙'),
                              icon: Icon(Icons.dark_mode),
                            ),
                            DropdownItem(
                              key: ThemeModeOption.newYear,
                              label: Text('New Year 🎄'),
                              icon: Icon(Icons.park),
                            ),
                            DropdownItem(
                              key: ThemeModeOption.newYearNight,
                              label: Text('New Year night 🌙🎄'),
                              icon: Icon(Icons.nights_stay),
                            ),
                          ],
                          onItemTap: (key) {
                            if (key is ThemeModeOption) {
                              themeController.setTheme(key);
                            }
                          },
                          child: Button(
                            onPressed: () {},
                            icon: Icon(themeIcon),
                            child: Text(themeLabel),
                          ),
                        ),
                        SizedBox(width: token.sizeSM),
                        Dropdown(
                          trigger: const [DropdownTrigger.click],
                          menu: [
                            for (final e in demoLanguages.entries)
                              DropdownItem(key: e.key, label: Text(e.value)),
                          ],
                          onItemTap: (key) {
                            if (key is String) {
                              localeController.setLocale(Locale(key));
                            }
                          },
                          child: Button(
                            onPressed: () {},
                            icon: const Icon(Icons.translate),
                            child: Text(
                              demoLanguages[localeController
                                      .locale
                                      .languageCode] ??
                                  'English',
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
            Divider(height: 1, color: token.colorBorderSecondary),
            Expanded(
              child: DefaultTextStyle(
                style: TextStyle(
                  fontSize: token.fontSize,
                  color: token.colorText,
                  fontFamily: token.fontFamily,
                  fontFamilyFallback: token.fontFamilyFallback,
                  decoration: TextDecoration.none,
                ),
                child: child,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class Demo {
  const Demo(this.id, this.title, this.builder);
  final String id;
  final String title;
  final WidgetBuilder builder;
}

final List<Demo> demos = [
  Demo('new_year', 'New Year Theme 🎄', (_) => const NewYearDemo()),
  Demo('localization', 'Localization 🌍', (_) => const LocalizationDemo()),
  Demo('avatar', 'Avatar', (_) => const AvatarDemo()),
  Demo('badge', 'Badge', (_) => const BadgeDemo()),
  Demo('countdown', 'Countdown', (_) => const CountdownDemo()),
  Demo('button', 'Button', (_) => const ButtonDemo()),
  Demo('message', 'message', (_) => const MessageDemo()),
  Demo('notification', 'notification', (_) => const NotificationDemo()),
  Demo('modal', 'Modal', (_) => const ModalDemo()),
  Demo('drawer', 'Drawer', (_) => const DrawerDemo()),
  Demo('popconfirm', 'Popconfirm', (_) => const PopconfirmDemo()),
  Demo('tooltip', 'Tooltip', (_) => const TooltipDemo()),
  Demo('popover', 'Popover', (_) => const PopoverDemo()),
  Demo('alert', 'Alert', (_) => const AlertDemo()),
  Demo('progress', 'Progress', (_) => const ProgressDemo()),
  Demo('result', 'Result', (_) => const ResultDemo()),
  Demo('spin', 'Spin', (_) => const SpinDemo()),
  Demo('input', 'Input', (_) => const InputDemo()),
  Demo('inputnumber', 'InputNumber', (_) => const InputNumberDemo()),
  Demo('segmented', 'Segmented', (_) => const SegmentedDemo()),
  Demo('tabs', 'Tabs', (_) => const TabsDemo()),
  Demo('card', 'Card', (_) => const CardDemo()),
  Demo('collapse', 'Collapse', (_) => const CollapseDemo()),
  Demo('tree', 'Tree', (_) => const TreeDemo()),
  Demo('sortablelist', 'SortableList', (_) => const ReorderableListDemo()),
  Demo('listy', 'Listy', (_) => const ListyDemo()),
  Demo('steps', 'Steps', (_) => const StepsDemo()),
  Demo('timeline', 'Timeline', (_) => const TimelineDemo()),
  Demo('tour', 'Tour', (_) => const TourDemo()),
  Demo('switch', 'Switch', (_) => const SwitchDemo()),
  Demo('upload', 'Upload', (_) => const UploadDemo()),
  Demo('select', 'Select', (_) => const SelectDemo()),
  Demo('dropdown', 'Dropdown', (_) => const DropdownDemo()),
  Demo('empty', 'Empty', (_) => const EmptyDemo()),
  Demo('pagination', 'Pagination', (_) => const PaginationDemo()),
  Demo('tag', 'Tag', (_) => const TagDemo()),
  Demo('checkbox', 'Checkbox', (_) => const CheckboxDemo()),
  Demo('radio', 'Radio', (_) => const RadioDemo()),
];

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

  @override
  Widget build(BuildContext context) {
    final token = context.softToken;
    return ListView.separated(
      padding: EdgeInsets.symmetric(horizontal: token.sizeLG),
      itemCount: demos.length,
      separatorBuilder: (_, __) => Divider(height: 1, color: token.colorSplit),
      itemBuilder: (context, i) {
        final demo = demos[i];
        return _DemoTile(
          title: demo.title,
          onTap: () => context.go('/demo/${demo.id}'),
        );
      },
    );
  }
}

class _DemoTile extends StatelessWidget {
  const _DemoTile({required this.title, required this.onTap});

  final String title;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    final token = context.softToken;
    return GestureDetector(
      behavior: HitTestBehavior.opaque,
      onTap: onTap,
      child: Padding(
        padding: EdgeInsets.symmetric(vertical: token.size),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(
              title,
              style: TextStyle(
                fontSize: token.fontSizeLG,
                color: token.colorText,
              ),
            ),
            Icon(Icons.chevron_right, color: token.colorTextTertiary),
          ],
        ),
      ),
    );
  }
}

class DemoPage extends StatelessWidget {
  const DemoPage({super.key, required this.demo});

  final Demo demo;

  @override
  Widget build(BuildContext context) {
    final token = context.softToken;
    return SingleChildScrollView(
      padding: EdgeInsets.all(token.sizeLG),
      child: Center(
        child: !kIsWeb
            ? ConstrainedBox(
                constraints: const BoxConstraints(maxWidth: 420),
                child: demo.builder(context),
              )
            : demo.builder(context),
      ),
    );
  }
}
4
likes
0
points
791
downloads

Documentation

Documentation

Publisher

verified publisherinterlib.dev

Weekly Downloads

A themeable Flutter widget library — 30+ token-driven components plus context-free message, notification, modal and drawer APIs. No Material dependency.

Repository (GitHub)
View/report issues

Topics

#ui #widget #design-system #components #theming

License

unknown (license)

Dependencies

flutter

More

Packages that depend on seed_ui