octo_ui 0.8.1 copy "octo_ui: ^0.8.1" to clipboard
octo_ui: ^0.8.1 copied to clipboard

Cross-platform Primer-inspired Flutter UI kit. Optimised for devtools, dashboards, and dense data-heavy interfaces.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show LogicalKeyboardKey;
// Octicons are re-exported by package:octo_ui/octo_ui.dart as `OctIcons`.
import 'package:octo_ui/octo_ui.dart';

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

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

  @override
  State<KitchenSinkApp> createState() => _KitchenSinkAppState();
}

class _KitchenSinkAppState extends State<KitchenSinkApp> {
  bool _dark = false;
  bool _highContrast = false;
  final OctoCommandPaletteController _paletteController =
      OctoCommandPaletteController();

  void _toggleDark() => setState(() => _dark = !_dark);

  void _toggleHighContrast() => setState(() => _highContrast = !_highContrast);

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

  List<OctoActionListItem> _paletteItems() => [
        OctoActionListItem(
          label: _dark ? 'Switch to light theme' : 'Switch to dark theme',
          leading: Icon(_dark ? OctIcons.sun_16 : OctIcons.moon_16),
          onPressed: _toggleDark,
        ),
        OctoActionListItem(
          label: _highContrast
              ? 'Switch to standard contrast'
              : 'Switch to high contrast',
          leading: Icon(_highContrast
              ? OctIcons.accessibility_16
              : OctIcons.accessibility_inset_16),
          onPressed: _toggleHighContrast,
        ),
        OctoActionListItem(
          label: 'New issue',
          description: 'Open the issue composer',
          leading: const Icon(OctIcons.plus_16),
          onPressed: () {},
        ),
        OctoActionListItem(
          label: 'Open pull requests',
          leading: const Icon(OctIcons.git_pull_request_16),
          onPressed: () {},
        ),
        OctoActionListItem(
          label: 'Repository settings',
          leading: const Icon(OctIcons.gear_16),
          onPressed: () {},
        ),
        OctoActionListItem(
          label: 'Delete repository',
          leading: const Icon(OctIcons.trash_16),
          variant: OctoActionListItemVariant.danger,
          onPressed: () {},
        ),
      ];

  @override
  Widget build(BuildContext context) {
    final variant = _highContrast
        ? OctoColorSchemeVariant.highContrast
        : OctoColorSchemeVariant.standard;
    final octo = _dark
        ? OctoThemeData.dark(variant: variant)
        : OctoThemeData.light(variant: variant);
    return OctoTheme(
      data: octo,
      child: MaterialApp(
        title: 'octo_ui kitchen sink',
        debugShowCheckedModeBanner: false,
        theme: octo.toMaterialTheme(),
        home: OctoCommandPalette(
          controller: _paletteController,
          items: _paletteItems(),
          // Cmd+K on macOS, Ctrl+K elsewhere — same activator covers both
          // because LogicalKeyboardKey.meta maps to the platform's command
          // key on macOS and the Windows / Super key on Linux. For desktops
          // a separate `control: true` activator could be added.
          openShortcut: const SingleActivator(
            LogicalKeyboardKey.keyK,
            meta: true,
          ),
          child: KitchenSinkPage(
            isDark: _dark,
            isHighContrast: _highContrast,
            onToggleTheme: _toggleDark,
            onToggleHighContrast: _toggleHighContrast,
            onOpenPalette: _paletteController.open,
          ),
        ),
      ),
    );
  }
}

class KitchenSinkPage extends StatefulWidget {
  final bool isDark;
  final bool isHighContrast;
  final VoidCallback onToggleTheme;
  final VoidCallback onToggleHighContrast;
  final VoidCallback onOpenPalette;

  const KitchenSinkPage({
    super.key,
    required this.isDark,
    required this.isHighContrast,
    required this.onToggleTheme,
    required this.onToggleHighContrast,
    required this.onOpenPalette,
  });

  @override
  State<KitchenSinkPage> createState() => _KitchenSinkPageState();
}

class _KitchenSinkPageState extends State<KitchenSinkPage> {
  final TextEditingController _emailController = TextEditingController();
  final OctoMenuController _menuController = OctoMenuController();
  bool _showError = false;
  String _lastAction = '';
  int _navIndex = 0;
  bool _notifications = true;
  bool? _terms = false;
  String _priority = 'medium';
  bool _showSkeleton = false;
  String? _dialogResult;
  String _segment = 'open';
  String? _droppedPriority = 'medium';

  int _paginationPage = 1;

  int _tabIndex = 0;

  int _sideNavIndex = 1;

  int? _sortColumn;
  OctoSortDirection _sortDirection = OctoSortDirection.none;
  List<_DemoPr> _prs = const [
    _DemoPr(42, 'Add tabs component', 'anna', 5, OctoStateLabelVariant.open),
    _DemoPr(43, 'Fix timeline rail', 'bob', 2, OctoStateLabelVariant.merged),
    _DemoPr(44, 'Cut 0.6 release', 'cara', 0, OctoStateLabelVariant.draft),
    _DemoPr(45, 'Switch chip dismiss', 'dee', 8, OctoStateLabelVariant.closed),
  ];

  void _sortPrs(int column, OctoSortDirection direction) {
    setState(() {
      _sortColumn = direction == OctoSortDirection.none ? null : column;
      _sortDirection = direction;
      if (direction == OctoSortDirection.none) return;
      final sorted = [..._prs];
      int cmp(_DemoPr a, _DemoPr b) {
        final byColumn = switch (column) {
          1 => a.title.compareTo(b.title),
          4 => a.comments.compareTo(b.comments),
          _ => 0,
        };
        return direction == OctoSortDirection.asc ? byColumn : -byColumn;
      }

      sorted.sort(cmp);
      _prs = sorted;
    });
  }

  final Set<String> _chips = {'frontend', 'flutter', 'p1'};

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

  void _record(String action) => setState(() => _lastAction = action);

  @override
  Widget build(BuildContext context) {
    final theme = OctoTheme.of(context);
    return Scaffold(
      backgroundColor: theme.colors.canvas.defaultColor,
      appBar: AppBar(
        backgroundColor: theme.colors.canvas.subtle,
        surfaceTintColor: theme.colors.canvas.subtle,
        elevation: 0,
        shape:
            Border(bottom: BorderSide(color: theme.colors.border.defaultColor)),
        title: const OctoText('octo_ui kitchen sink', kind: OctoTextKind.title),
        actions: [
          OctoTooltip(
            message: 'Open command palette (⌘K)',
            child: OctoIconButton(
              icon: OctIcons.search_16,
              onPressed: widget.onOpenPalette,
              variant: OctoButtonVariant.invisible,
              semanticLabel: 'Open command palette',
            ),
          ),
          SizedBox(width: theme.spacing.gap.sm),
          OctoIconButton(
            icon: widget.isHighContrast
                ? OctIcons.accessibility_16
                : OctIcons.accessibility_inset_16,
            onPressed: widget.onToggleHighContrast,
            variant: OctoButtonVariant.invisible,
            semanticLabel: widget.isHighContrast
                ? 'Switch to standard contrast'
                : 'Switch to high contrast',
          ),
          SizedBox(width: theme.spacing.gap.sm),
          OctoIconButton(
            icon: widget.isDark ? OctIcons.sun_16 : OctIcons.moon_16,
            onPressed: widget.onToggleTheme,
            variant: OctoButtonVariant.invisible,
            semanticLabel: widget.isDark
                ? 'Switch to light theme'
                : 'Switch to dark theme',
          ),
          SizedBox(width: theme.spacing.gap.md),
        ],
      ),
      body: Center(
        child: ConstrainedBox(
          constraints: const BoxConstraints(maxWidth: 720),
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(24),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                _Section(
                  title: 'Breadcrumbs',
                  child: OctoBreadcrumbs(
                    items: [
                      OctoBreadcrumbItem(
                        label: 'Autocrab',
                        onPressed: () => _record('Autocrab'),
                      ),
                      OctoBreadcrumbItem(
                        label: 'octo_ui',
                        onPressed: () => _record('octo_ui'),
                      ),
                      const OctoBreadcrumbItem(label: 'main'),
                    ],
                  ),
                ),
                _Section(
                  title: 'Avatars',
                  child: Wrap(
                    crossAxisAlignment: WrapCrossAlignment.center,
                    spacing: 12,
                    runSpacing: 12,
                    children: const [
                      OctoAvatar(
                        initials: 'X',
                        size: OctoAvatarSize.xs,
                        semanticLabel: 'X',
                      ),
                      OctoAvatar(
                        initials: 'S',
                        size: OctoAvatarSize.sm,
                        semanticLabel: 'S',
                      ),
                      OctoAvatar(initials: 'MA', semanticLabel: 'MA'),
                      OctoAvatar(
                        initials: 'JD',
                        size: OctoAvatarSize.lg,
                        semanticLabel: 'JD',
                      ),
                      OctoAvatar(
                        initials: 'XL',
                        size: OctoAvatarSize.xl,
                        semanticLabel: 'XL',
                      ),
                      OctoAvatar(
                        initials: 'OG',
                        shape: OctoAvatarShape.square,
                        size: OctoAvatarSize.lg,
                        semanticLabel: 'OG org',
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Underline nav — section tabs',
                  child: SingleChildScrollView(
                    scrollDirection: Axis.horizontal,
                    child: OctoUnderlineNav(
                      selectedIndex: _navIndex,
                      onChanged: (i) => setState(() => _navIndex = i),
                      items: const [
                        OctoUnderlineNavItem(
                          label: 'Code',
                          icon: Icon(OctIcons.code_16),
                        ),
                        OctoUnderlineNavItem(
                          label: 'Issues',
                          icon: Icon(OctIcons.bug_16),
                          trailing: OctoCounterLabel(12),
                        ),
                        OctoUnderlineNavItem(
                          label: 'Pull requests',
                          icon: Icon(OctIcons.git_pull_request_16),
                          trailing: OctoCounterLabel(3),
                        ),
                        OctoUnderlineNavItem(
                          label: 'Settings',
                          icon: Icon(OctIcons.gear_16),
                        ),
                      ],
                    ),
                  ),
                ),
                _Section(
                  title: 'Labels',
                  child: Wrap(
                    spacing: 8,
                    runSpacing: 8,
                    children: const [
                      OctoLabel('Bug'),
                      OctoLabel('Feature', variant: OctoLabelVariant.accent),
                      OctoLabel('Merged', variant: OctoLabelVariant.success),
                      OctoLabel('Review', variant: OctoLabelVariant.attention),
                      OctoLabel('Critical', variant: OctoLabelVariant.danger),
                    ],
                  ),
                ),
                _Section(
                  title: 'Counter labels',
                  child: Wrap(
                    crossAxisAlignment: WrapCrossAlignment.center,
                    spacing: 12,
                    runSpacing: 8,
                    children: const [
                      OctoText('Issues', kind: OctoTextKind.body),
                      OctoCounterLabel(12),
                      OctoText('Pull requests', kind: OctoTextKind.body),
                      OctoCounterLabel(
                        4,
                        variant: OctoCounterLabelVariant.primary,
                      ),
                      OctoText('Stars', kind: OctoTextKind.body),
                      OctoCounterLabel(
                        1248,
                        maxDisplayed: 999,
                        variant: OctoCounterLabelVariant.secondary,
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Buttons — variants',
                  child: Wrap(
                    spacing: 12,
                    runSpacing: 12,
                    children: [
                      OctoButton.label('Save',
                          onPressed: () {}, variant: OctoButtonVariant.primary),
                      OctoButton.label('Cancel', onPressed: () {}),
                      OctoButton.label('Delete',
                          onPressed: () {}, variant: OctoButtonVariant.danger),
                      OctoButton.label('Edit',
                          onPressed: () {},
                          variant: OctoButtonVariant.invisible),
                      OctoButton.label('Disabled', onPressed: null),
                    ],
                  ),
                ),
                _Section(
                  title: 'Buttons — sizes',
                  child: Wrap(
                    crossAxisAlignment: WrapCrossAlignment.center,
                    spacing: 12,
                    runSpacing: 12,
                    children: [
                      OctoButton.label('Small',
                          onPressed: () {}, size: OctoButtonSize.small),
                      OctoButton.label('Medium', onPressed: () {}),
                      OctoButton.label('Large',
                          onPressed: () {}, size: OctoButtonSize.large),
                    ],
                  ),
                ),
                _Section(
                  title: 'Icon buttons',
                  child: Wrap(
                    crossAxisAlignment: WrapCrossAlignment.center,
                    spacing: 12,
                    runSpacing: 12,
                    children: [
                      OctoIconButton(
                        icon: OctIcons.star_16,
                        onPressed: () {},
                        semanticLabel: 'Star',
                      ),
                      OctoIconButton(
                        icon: OctIcons.heart_16,
                        onPressed: () {},
                        variant: OctoButtonVariant.primary,
                        semanticLabel: 'Favorite',
                      ),
                      OctoIconButton(
                        icon: OctIcons.kebab_horizontal_16,
                        onPressed: () {},
                        variant: OctoButtonVariant.invisible,
                        semanticLabel: 'More',
                      ),
                      const OctoIconButton(
                        icon: OctIcons.lock_16,
                        onPressed: null,
                        semanticLabel: 'Locked',
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Flashes',
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: const [
                      OctoFlash(
                          message: 'A new release is available.',
                          icon: OctIcons.info_16),
                      SizedBox(height: 12),
                      OctoFlash(
                        message: 'Changes saved successfully.',
                        variant: OctoFlashVariant.success,
                        icon: OctIcons.check_circle_16,
                      ),
                      SizedBox(height: 12),
                      OctoFlash(
                        message: 'Review required before merge.',
                        variant: OctoFlashVariant.attention,
                        icon: OctIcons.alert_16,
                      ),
                      SizedBox(height: 12),
                      OctoFlash(
                        message: 'Build failed — see the logs for details.',
                        variant: OctoFlashVariant.danger,
                        icon: OctIcons.x_circle_16,
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Text field',
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: [
                      OctoTextField(
                        label: 'Email',
                        placeholder: 'you@example.com',
                        controller: _emailController,
                        helperText: 'Used for login only',
                        errorText: _showError ? 'Invalid email address' : null,
                      ),
                      SizedBox(height: theme.spacing.gap.md),
                      OctoButton.label(
                        _showError ? 'Hide error' : 'Show error',
                        onPressed: () =>
                            setState(() => _showError = !_showError),
                      ),
                      SizedBox(height: theme.spacing.gap.md),
                      const OctoTextField(
                        placeholder: 'disabled',
                        enabled: false,
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Segmented control — single-select filter',
                  child: Row(
                    children: [
                      OctoSegmentedControl<String>(
                        value: _segment,
                        onChanged: (v) => setState(() => _segment = v),
                        items: const [
                          OctoSegmentedControlItem(value: 'all', label: 'All'),
                          OctoSegmentedControlItem(
                              value: 'open', label: 'Open'),
                          OctoSegmentedControlItem(
                            value: 'closed',
                            label: 'Closed',
                            icon: Icon(OctIcons.check_circle_16),
                          ),
                        ],
                      ),
                      SizedBox(width: theme.spacing.gap.md),
                      OctoText(
                        'Viewing: $_segment',
                        kind: OctoTextKind.bodySmall,
                        color: theme.colors.fg.muted,
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Chips — labels, filters, recipients',
                  child: Wrap(
                    spacing: 8,
                    runSpacing: 8,
                    children: [
                      for (final tag in _chips)
                        OctoChip(
                          label: tag,
                          variant: tag == 'p1'
                              ? OctoChipVariant.danger
                              : OctoChipVariant.standard,
                          onDismiss: () => setState(() => _chips.remove(tag)),
                        ),
                      const OctoChip(
                          label: 'preview', variant: OctoChipVariant.accent),
                    ],
                  ),
                ),
                _Section(
                  title: 'Dropdown — single-select picker',
                  child: Row(
                    children: [
                      OctoDropdown<String>(
                        value: _droppedPriority,
                        onChanged: (v) => setState(() => _droppedPriority = v),
                        items: const [
                          OctoDropdownItem(value: 'low', label: 'Low'),
                          OctoDropdownItem(value: 'medium', label: 'Medium'),
                          OctoDropdownItem(value: 'high', label: 'High'),
                        ],
                      ),
                      SizedBox(width: theme.spacing.gap.md),
                      OctoText(
                        'Priority: ${_droppedPriority ?? "—"}',
                        kind: OctoTextKind.bodySmall,
                        color: theme.colors.fg.muted,
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Collapsible — accordion sections',
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      OctoCollapsible(
                        title: 'Repository details',
                        initiallyExpanded: true,
                        child: OctoText(
                          'Primer-inspired Flutter UI kit. Foundation '
                          'tokens, themed Material adapter, and a growing '
                          'set of components.',
                          kind: OctoTextKind.bodySmall,
                          color: theme.colors.fg.muted,
                        ),
                      ),
                      SizedBox(height: theme.spacing.gap.sm),
                      OctoCollapsible(
                        title: 'Roadmap',
                        child: OctoText(
                          'Toast / SnackBar, Tabs, Pagination, Table — see '
                          'CHANGELOG for the running list.',
                          kind: OctoTextKind.bodySmall,
                          color: theme.colors.fg.muted,
                        ),
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Toasts — transient overlay feedback',
                  child: Wrap(
                    spacing: 8,
                    runSpacing: 8,
                    children: [
                      OctoButton.label(
                        'Show success',
                        onPressed: () => OctoToast.show(
                          context,
                          message: 'Changes saved',
                          variant: OctoToastVariant.success,
                        ),
                      ),
                      OctoButton.label(
                        'Show warning',
                        variant: OctoButtonVariant.invisible,
                        onPressed: () => OctoToast.show(
                          context,
                          message: 'Heads up — 3 packages need an upgrade',
                          variant: OctoToastVariant.attention,
                        ),
                      ),
                      OctoButton.label(
                        'Show with action',
                        variant: OctoButtonVariant.invisible,
                        onPressed: () => OctoToast.show(
                          context,
                          message: 'Note archived',
                          action: OctoToastAction(
                            label: 'Undo',
                            onPressed: () {},
                          ),
                        ),
                      ),
                      OctoButton.label(
                        'Show danger',
                        variant: OctoButtonVariant.danger,
                        onPressed: () => OctoToast.show(
                          context,
                          message: 'Failed to publish workflow',
                          variant: OctoToastVariant.danger,
                          dismissible: true,
                        ),
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Data table — sortable tabular data',
                  child: OctoDataTable<_DemoPr>(
                    sortColumnIndex: _sortColumn,
                    sortDirection: _sortDirection,
                    onSortChanged: _sortPrs,
                    rows: _prs,
                    columns: [
                      OctoDataColumn<_DemoPr>(
                        label: '#',
                        text: (r) => '#${r.number}',
                      ),
                      // Title soaks up the leftover horizontal space;
                      // the other columns hug their content via
                      // IntrinsicColumnWidth.
                      OctoDataColumn<_DemoPr>(
                        label: 'Title',
                        text: (r) => r.title,
                        sortable: true,
                        flex: 1,
                      ),
                      OctoDataColumn<_DemoPr>(
                        label: 'Status',
                        cell: (_, r) => OctoStateLabel(
                          label: r.status.name,
                          variant: r.status,
                          emphasis: OctoStateLabelEmphasis.low,
                        ),
                      ),
                      OctoDataColumn<_DemoPr>(
                        label: 'Author',
                        text: (r) => r.author,
                      ),
                      OctoDataColumn<_DemoPr>(
                        label: 'Comments',
                        text: (r) => '${r.comments}',
                        alignment: OctoDataColumnAlignment.end,
                        sortable: true,
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Timeline — activity feed',
                  child: SizedBox(
                    width: 360,
                    child: OctoTimeline(
                      items: const [
                        OctoTimelineItem(
                          icon: OctIcons.git_commit_16,
                          title: 'Created branch feature/foo',
                          subtitle: '2 hours ago',
                        ),
                        OctoTimelineItem(
                          icon: OctIcons.comment_16,
                          title: 'Anna commented',
                          subtitle: '1 hour ago',
                          variant: OctoTimelineVariant.accent,
                          body: Text('LGTM — ship it.'),
                        ),
                        OctoTimelineItem(
                          icon: OctIcons.git_pull_request_16,
                          title: 'Opened pull request #42',
                          subtitle: '54 minutes ago',
                          variant: OctoTimelineVariant.success,
                        ),
                        OctoTimelineItem(
                          icon: OctIcons.x_circle_16,
                          title: 'Deploy to staging failed',
                          subtitle: 'just now',
                          variant: OctoTimelineVariant.danger,
                        ),
                      ],
                    ),
                  ),
                ),
                _Section(
                  title: 'Side nav — vertical sidebar navigation',
                  child: SizedBox(
                    width: 220,
                    child: OctoSideNav(
                      selectedIndex: _sideNavIndex,
                      onChanged: (i) => setState(() => _sideNavIndex = i),
                      items: const [
                        OctoSideNavItem(
                          label: 'Repositories',
                          icon: Icon(OctIcons.repo_16),
                        ),
                        OctoSideNavItem(
                          label: 'Projects',
                          icon: Icon(OctIcons.project_16),
                          trailing: OctoCounterLabel(7),
                        ),
                        OctoSideNavItem(
                          label: 'Discussions',
                          icon: Icon(OctIcons.comment_discussion_16),
                        ),
                        OctoSideNavItem(
                          label: 'Insights',
                          icon: Icon(OctIcons.graph_16),
                        ),
                      ],
                    ),
                  ),
                ),
                _Section(
                  title: 'Tabs — content-switching tab group',
                  child: OctoTabs(
                    selectedIndex: _tabIndex,
                    onTabChanged: (i) => setState(() => _tabIndex = i),
                    tabs: const [
                      OctoUnderlineNavItem(label: 'Overview'),
                      OctoUnderlineNavItem(
                        label: 'Issues',
                        trailing: OctoCounterLabel(12),
                      ),
                      OctoUnderlineNavItem(
                        label: 'Pull requests',
                        trailing: OctoCounterLabel(3),
                      ),
                    ],
                    children: [
                      Padding(
                        padding: EdgeInsets.symmetric(
                          vertical: theme.spacing.gap.md,
                        ),
                        child: OctoText(
                          'Repository overview — README, recent commits, '
                          'contributors.',
                          kind: OctoTextKind.bodySmall,
                          color: theme.colors.fg.muted,
                        ),
                      ),
                      Padding(
                        padding: EdgeInsets.symmetric(
                          vertical: theme.spacing.gap.md,
                        ),
                        child: OctoText(
                          '12 open issues, sorted by recent activity.',
                          kind: OctoTextKind.bodySmall,
                          color: theme.colors.fg.muted,
                        ),
                      ),
                      Padding(
                        padding: EdgeInsets.symmetric(
                          vertical: theme.spacing.gap.md,
                        ),
                        child: OctoText(
                          '3 pull requests awaiting review.',
                          kind: OctoTextKind.bodySmall,
                          color: theme.colors.fg.muted,
                        ),
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Pagination — paged navigator',
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      OctoPagination(
                        currentPage: _paginationPage,
                        pageCount: 20,
                        onPageChanged: (p) =>
                            setState(() => _paginationPage = p),
                      ),
                      SizedBox(height: theme.spacing.gap.sm),
                      OctoText(
                        'Page $_paginationPage of 20',
                        kind: OctoTextKind.bodySmall,
                        color: theme.colors.fg.muted,
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'State labels — PR / issue lifecycle pills',
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      const Wrap(
                        spacing: 8,
                        runSpacing: 8,
                        children: [
                          OctoStateLabel(
                            label: 'Open',
                            variant: OctoStateLabelVariant.open,
                          ),
                          OctoStateLabel(
                            label: 'Merged',
                            variant: OctoStateLabelVariant.merged,
                          ),
                          OctoStateLabel(
                            label: 'Closed',
                            variant: OctoStateLabelVariant.closed,
                          ),
                          OctoStateLabel(
                            label: 'Draft',
                            variant: OctoStateLabelVariant.draft,
                          ),
                          OctoStateLabel(
                            label: 'Stale',
                            variant: OctoStateLabelVariant.attention,
                          ),
                        ],
                      ),
                      SizedBox(height: theme.spacing.gap.sm),
                      const Wrap(
                        spacing: 8,
                        runSpacing: 8,
                        children: [
                          OctoStateLabel(
                            label: 'Open',
                            variant: OctoStateLabelVariant.open,
                            emphasis: OctoStateLabelEmphasis.low,
                          ),
                          OctoStateLabel(
                            label: 'Merged',
                            variant: OctoStateLabelVariant.merged,
                            emphasis: OctoStateLabelEmphasis.low,
                          ),
                          OctoStateLabel(
                            label: 'Closed',
                            variant: OctoStateLabelVariant.closed,
                            emphasis: OctoStateLabelEmphasis.low,
                          ),
                          OctoStateLabel(
                            label: 'Draft',
                            variant: OctoStateLabelVariant.draft,
                            emphasis: OctoStateLabelEmphasis.low,
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Spinners — small / medium / large',
                  child: Row(
                    children: [
                      const OctoSpinner(size: OctoSpinnerSize.small),
                      SizedBox(width: theme.spacing.gap.md),
                      const OctoSpinner(),
                      SizedBox(width: theme.spacing.gap.md),
                      const OctoSpinner(size: OctoSpinnerSize.large),
                      SizedBox(width: theme.spacing.gap.md),
                      OctoText(
                        'Fetching repositories…',
                        kind: OctoTextKind.bodySmall,
                        color: theme.colors.fg.muted,
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Progress bars — determinate + indeterminate',
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      const OctoText('accent · 25%',
                          kind: OctoTextKind.bodySmall),
                      const SizedBox(height: 6),
                      const OctoProgressBar(value: 0.25),
                      const SizedBox(height: 12),
                      const OctoText('success · 75%',
                          kind: OctoTextKind.bodySmall),
                      const SizedBox(height: 6),
                      const OctoProgressBar(
                        value: 0.75,
                        variant: OctoProgressBarVariant.success,
                      ),
                      const SizedBox(height: 12),
                      const OctoText('danger · small · 40%',
                          kind: OctoTextKind.bodySmall),
                      const SizedBox(height: 6),
                      const OctoProgressBar(
                        value: 0.4,
                        variant: OctoProgressBarVariant.danger,
                        size: OctoProgressBarSize.small,
                      ),
                      const SizedBox(height: 12),
                      const OctoText('indeterminate',
                          kind: OctoTextKind.bodySmall),
                      const SizedBox(height: 6),
                      const OctoProgressBar(),
                    ],
                  ),
                ),
                _Section(
                  title: 'Dividers — subtle / muted / strong + vertical',
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      const OctoText('Subtle', kind: OctoTextKind.bodySmall),
                      const SizedBox(height: 6),
                      const OctoDivider(emphasis: OctoDividerEmphasis.subtle),
                      const SizedBox(height: 12),
                      const OctoText('Muted', kind: OctoTextKind.bodySmall),
                      const SizedBox(height: 6),
                      const OctoDivider(),
                      const SizedBox(height: 12),
                      const OctoText('Strong + indent',
                          kind: OctoTextKind.bodySmall),
                      const SizedBox(height: 6),
                      const OctoDivider(
                        emphasis: OctoDividerEmphasis.strong,
                        indent: 32,
                        endIndent: 32,
                      ),
                      const SizedBox(height: 16),
                      SizedBox(
                        height: 28,
                        child: Row(
                          crossAxisAlignment: CrossAxisAlignment.stretch,
                          children: [
                            const Padding(
                              padding: EdgeInsets.symmetric(horizontal: 12),
                              child: Center(
                                child: OctoText('Inline A',
                                    kind: OctoTextKind.bodySmall),
                              ),
                            ),
                            const OctoDivider.vertical(),
                            const Padding(
                              padding: EdgeInsets.symmetric(horizontal: 12),
                              child: Center(
                                child: OctoText('Inline B',
                                    kind: OctoTextKind.bodySmall),
                              ),
                            ),
                            const OctoDivider.vertical(),
                            const Padding(
                              padding: EdgeInsets.symmetric(horizontal: 12),
                              child: Center(
                                child: OctoText('Inline C',
                                    kind: OctoTextKind.bodySmall),
                              ),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Form controls',
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          OctoSwitch(
                            value: _notifications,
                            onChanged: (v) =>
                                setState(() => _notifications = v),
                            semanticLabel: 'Notifications',
                          ),
                          SizedBox(width: theme.spacing.gap.md),
                          OctoText(
                            _notifications
                                ? 'Notifications: enabled'
                                : 'Notifications: muted',
                            kind: OctoTextKind.body,
                          ),
                        ],
                      ),
                      SizedBox(height: theme.spacing.gap.md),
                      Row(
                        children: [
                          OctoCheckbox(
                            value: _terms,
                            tristate: true,
                            onChanged: (v) => setState(() => _terms = v),
                            semanticLabel: 'I accept the terms',
                          ),
                          SizedBox(width: theme.spacing.gap.md),
                          const OctoText(
                            'I accept the terms (tap cycles tri-state)',
                            kind: OctoTextKind.body,
                          ),
                        ],
                      ),
                      SizedBox(height: theme.spacing.gap.md),
                      OctoText(
                        'Priority',
                        kind: OctoTextKind.label,
                        color: theme.colors.fg.muted,
                      ),
                      SizedBox(height: theme.spacing.gap.sm),
                      Row(
                        children: [
                          for (final p in const ['low', 'medium', 'high']) ...[
                            OctoRadio<String>(
                              value: p,
                              groupValue: _priority,
                              onChanged: (v) => setState(() => _priority = v!),
                            ),
                            SizedBox(width: theme.spacing.gap.sm),
                            OctoText(p, kind: OctoTextKind.body),
                            SizedBox(width: theme.spacing.gap.lg),
                          ],
                        ],
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Tooltips — hover or long-press',
                  child: Wrap(
                    spacing: 12,
                    runSpacing: 12,
                    children: [
                      OctoTooltip(
                        message: 'Save changes (⌘S)',
                        child: OctoButton.label(
                          'Save',
                          onPressed: () => _record('Save tapped'),
                          variant: OctoButtonVariant.primary,
                        ),
                      ),
                      OctoTooltip(
                        message: 'Watch this repository',
                        child: OctoIconButton(
                          icon: OctIcons.eye_16,
                          onPressed: () => _record('Watch tapped'),
                          semanticLabel: 'Watch',
                        ),
                      ),
                      OctoTooltip(
                        message: 'Pinned for later',
                        child: OctoIconButton(
                          icon: OctIcons.pin_16,
                          onPressed: () => _record('Pin tapped'),
                          variant: OctoButtonVariant.invisible,
                          semanticLabel: 'Pin',
                        ),
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Menu — controller-driven popover',
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      OctoMenu(
                        controller: _menuController,
                        items: [
                          OctoActionListItem(
                            label: 'New issue',
                            leading: const Icon(OctIcons.plus_16),
                            onPressed: () => _record('New issue'),
                          ),
                          OctoActionListItem(
                            label: 'New pull request',
                            leading: const Icon(OctIcons.git_pull_request_16),
                            onPressed: () => _record('New PR'),
                          ),
                          OctoActionListItem(
                            label: 'Settings',
                            leading: const Icon(OctIcons.gear_16),
                            onPressed: () => _record('Settings'),
                          ),
                          OctoActionListItem(
                            label: 'Delete repository',
                            leading: const Icon(OctIcons.trash_16),
                            variant: OctoActionListItemVariant.danger,
                            onPressed: () => _record('Delete'),
                          ),
                        ],
                        child: OctoButton.label(
                          'More actions',
                          onPressed: _menuController.toggle,
                          trailingIcon: const Icon(OctIcons.chevron_down_16),
                        ),
                      ),
                      SizedBox(width: theme.spacing.gap.md),
                      Flexible(
                        child: OctoText(
                          _lastAction.isEmpty
                              ? 'Pick something from the menu…'
                              : 'Last action: $_lastAction',
                          kind: OctoTextKind.bodySmall,
                          color: theme.colors.fg.muted,
                        ),
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Action list — inline (e.g. drawer / sidebar)',
                  child: SizedBox(
                    width: 320,
                    child: OctoActionList(
                      items: [
                        OctoActionListItem(
                          label: 'Code',
                          leading: const Icon(OctIcons.code_16),
                          trailing: const OctoCounterLabel(42),
                          onPressed: () => _record('Code'),
                          selected: true,
                        ),
                        OctoActionListItem(
                          label: 'Issues',
                          leading: const Icon(OctIcons.bug_16),
                          trailing: const OctoCounterLabel(7),
                          onPressed: () => _record('Issues'),
                        ),
                        OctoActionListItem(
                          label: 'Pull requests',
                          leading: const Icon(OctIcons.git_pull_request_16),
                          trailing: const OctoCounterLabel(3),
                          onPressed: () => _record('PRs'),
                        ),
                        OctoActionListItem(
                          label: 'Settings',
                          leading: const Icon(OctIcons.gear_16),
                          description: 'Members, integrations, secrets',
                          onPressed: () => _record('Settings'),
                        ),
                      ],
                    ),
                  ),
                ),
                _Section(
                  title: 'Skeleton — loading placeholders',
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          OctoButton.label(
                            _showSkeleton ? 'Hide loading' : 'Show loading',
                            onPressed: () =>
                                setState(() => _showSkeleton = !_showSkeleton),
                          ),
                        ],
                      ),
                      SizedBox(height: theme.spacing.gap.md),
                      if (_showSkeleton)
                        const SizedBox(
                          width: 320,
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.stretch,
                            children: [
                              Row(
                                children: [
                                  OctoSkeletonAvatar(),
                                  SizedBox(width: 12),
                                  Expanded(child: OctoSkeletonText(lines: 2)),
                                ],
                              ),
                              SizedBox(height: 16),
                              OctoSkeleton(height: 32),
                            ],
                          ),
                        )
                      else
                        Row(
                          children: [
                            const OctoAvatar(
                                initials: 'JD', semanticLabel: 'JD'),
                            SizedBox(width: theme.spacing.gap.md),
                            const Expanded(
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  OctoText(
                                    'Jane Doe',
                                    kind: OctoTextKind.bodyEmphasis,
                                  ),
                                  OctoText(
                                    'Opened PR #42 a moment ago.',
                                    kind: OctoTextKind.bodySmall,
                                  ),
                                ],
                              ),
                            ),
                          ],
                        ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Dialog — confirm destructive action',
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      OctoButton.label(
                        'Delete repository…',
                        leadingIcon: const Icon(OctIcons.trash_16),
                        variant: OctoButtonVariant.danger,
                        onPressed: () async {
                          final ctx = context;
                          final result = await OctoDialog.show<String>(
                            context: ctx,
                            title: const OctoDialogTitle('Delete repository?'),
                            content: const OctoText(
                              'This action cannot be undone. The repository '
                              'and all of its history will be permanently '
                              'removed.',
                            ),
                            actions: [
                              Builder(
                                builder: (dCtx) => OctoButton.label(
                                  'Cancel',
                                  onPressed: () =>
                                      Navigator.pop(dCtx, 'cancel'),
                                ),
                              ),
                              Builder(
                                builder: (dCtx) => OctoButton.label(
                                  'Delete',
                                  onPressed: () =>
                                      Navigator.pop(dCtx, 'delete'),
                                  variant: OctoButtonVariant.danger,
                                ),
                              ),
                            ],
                          );
                          setState(() => _dialogResult = result ?? 'dismissed');
                        },
                      ),
                      SizedBox(height: theme.spacing.gap.sm),
                      OctoText(
                        _dialogResult == null
                            ? 'No result yet.'
                            : 'Last result: $_dialogResult',
                        kind: OctoTextKind.bodySmall,
                        color: theme.colors.fg.muted,
                      ),
                    ],
                  ),
                ),
                _Section(
                  title: 'Command palette — ⌘K from anywhere',
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      OctoButton.label(
                        'Open command palette',
                        leadingIcon: const Icon(OctIcons.search_16),
                        onPressed: widget.onOpenPalette,
                        variant: OctoButtonVariant.primary,
                      ),
                      SizedBox(width: theme.spacing.gap.md),
                      Flexible(
                        child: OctoText(
                          'Or press ⌘K from anywhere on the page.',
                          kind: OctoTextKind.bodySmall,
                          color: theme.colors.fg.muted,
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class _Section extends StatelessWidget {
  final String title;
  final Widget child;

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

  @override
  Widget build(BuildContext context) {
    final theme = OctoTheme.of(context);
    return Padding(
      padding: EdgeInsets.only(bottom: theme.spacing.gap.xl),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          OctoText(title, kind: OctoTextKind.heading),
          SizedBox(height: theme.spacing.gap.md),
          Container(
            padding: EdgeInsets.all(theme.spacing.gap.lg),
            decoration: BoxDecoration(
              color: theme.colors.canvas.subtle,
              border: Border.all(color: theme.colors.border.defaultColor),
              borderRadius:
                  BorderRadius.all(Radius.circular(theme.radii.medium)),
            ),
            child: child,
          ),
        ],
      ),
    );
  }
}

class _DemoPr {
  final int number;
  final String title;
  final String author;
  final int comments;
  final OctoStateLabelVariant status;
  const _DemoPr(
    this.number,
    this.title,
    this.author,
    this.comments,
    this.status,
  );
}
1
likes
0
points
220
downloads

Publisher

verified publishermavoryl.com

Weekly Downloads

Cross-platform Primer-inspired Flutter UI kit. Optimised for devtools, dashboards, and dense data-heavy interfaces.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, flutter_octicons, meta

More

Packages that depend on octo_ui