verdify_ui 0.1.0 copy "verdify_ui: ^0.1.0" to clipboard
verdify_ui: ^0.1.0 copied to clipboard

Verdify's Flutter design system: 36 token-driven, WCAG 2.2 AA widgets with light and dark themes, built on Material 3.

example/lib/main.dart

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'verdify_ui example',
      theme: verdifyLightTheme(),
      home: const _GalleryPage(),
    );
  }
}

class _GalleryPage extends StatelessWidget {
  const _GalleryPage();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Verdify UI Gallery')),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            VerdifyButton(
              onPressed: () {},
              child: const Text('Primary'),
            ),
            const SizedBox(height: 12),
            VerdifyButton(
              variant: VerdifyButtonVariant.secondary,
              onPressed: () {},
              child: const Text('Secondary'),
            ),
            const SizedBox(height: 12),
            VerdifyButton(
              variant: VerdifyButtonVariant.ghost,
              onPressed: () {},
              child: const Text('Ghost'),
            ),
            const SizedBox(height: 12),
            VerdifyButton(
              variant: VerdifyButtonVariant.destructive,
              onPressed: () {},
              child: const Text('Destructive'),
            ),
            const SizedBox(height: 24),
            const VerdifyTextField(label: 'Email'),
            const SizedBox(height: 24),
            const Text('Badges'),
            const SizedBox(height: 8),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                const VerdifyBadge(label: 'Neutral'),
                VerdifyBadge(
                  label: 'Verified',
                  status: VerdifyBadgeStatus.verified,
                ),
                VerdifyBadge(
                  label: 'Signal',
                  status: VerdifyBadgeStatus.signal,
                ),
                VerdifyBadge(
                  label: 'Caution',
                  status: VerdifyBadgeStatus.caution,
                ),
                VerdifyBadge(
                  label: 'Critical',
                  status: VerdifyBadgeStatus.critical,
                ),
                VerdifyBadge(
                  label: 'With icon',
                  status: VerdifyBadgeStatus.verified,
                  icon: const Icon(Icons.check),
                ),
              ],
            ),
            const SizedBox(height: 24),
            const SizedBox(height: 24),
            const Text('Cards'),
            const SizedBox(height: 8),
            VerdifyCard(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  VerdifyCardHeader(
                    title: const Text('Static Card'),
                    supporting: const Text('Non-interactive grouping.'),
                  ),
                  const VerdifyCardBody(
                      child: Text('Card body content goes here.')),
                  const VerdifyCardFooter(child: Text('Footer metadata')),
                ],
              ),
            ),
            const SizedBox(height: 12),
            VerdifyCard(
              onPressed: () {},
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  VerdifyCardHeader(title: const Text('Interactive Card')),
                  const VerdifyCardBody(child: Text('Tap the whole card.')),
                ],
              ),
            ),
            const SizedBox(height: 12),
            VerdifyCard(
              onPressed: () {},
              enabled: false,
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  VerdifyCardHeader(title: const Text('Disabled Card')),
                  const VerdifyCardBody(
                      child: Text('This card is unavailable.')),
                ],
              ),
            ),
            const SizedBox(height: 24),
            const Text('Checkboxes'),
            const SizedBox(height: 8),
            const _CheckboxGallery(),
            const SizedBox(height: 24),
            const Text('Tabs'),
            const SizedBox(height: 8),
            const _TabsGallery(),
            const SizedBox(height: 24),
            const Text('Radio (roving foundation)'),
            const SizedBox(height: 8),
            const _RadioGallery(),
            const SizedBox(height: 24),
            const Text('Popover (non-modal)'),
            const SizedBox(height: 8),
            const _PopoverGallery(),
            const SizedBox(height: 24),
            const Text('Toast (transient queue)'),
            const SizedBox(height: 8),
            const _ToastGallery(),
            const SizedBox(height: 24),
            const Text('VerifiedBadge (motion theatre exemplar)'),
            const SizedBox(height: 8),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: const [
                VerdifyVerifiedBadge(label: 'Email verified.'),
                VerdifyVerifiedBadge(
                  label: 'ID verified.',
                  size: VerifiedBadgeSize.sm,
                ),
                VerdifyVerifiedBadge(
                  labelHidden: true,
                  ariaLabel: 'Phone verified.',
                ),
                VerdifyVerifiedBadge(
                  labelHidden: true,
                  ariaLabel: 'Address verified.',
                  size: VerifiedBadgeSize.sm,
                ),
              ],
            ),
            const SizedBox(height: 24),
            VerdifyButton(
              onPressed: () => showVerdifyDialog<void>(
                context: context,
                title: 'Confirm',
                content: const Text('Proceed?'),
              ),
              child: const Text('Open dialog'),
            ),
            const SizedBox(height: 24),
            const Text('Menu', style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            const _MenuGallery(),
            const SizedBox(height: 24),
            const Text('Select', style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            const _SelectGallery(),
            const SizedBox(height: 24),
            const Text('Credential Cards',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            const _CredentialCardGallery(),
            const SizedBox(height: 24),
            const Text('Alert (inline status)',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            const _AlertGallery(),
            const SizedBox(height: 24),
            const Text('Spinner (unmeasured wait)',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            Wrap(
              spacing: 16,
              runSpacing: 12,
              crossAxisAlignment: WrapCrossAlignment.center,
              children: [
                VerdifySpinner(
                  size: VerdifySpinnerSize.sm,
                  accessibleLabel: 'Loading.',
                ),
                VerdifySpinner(accessibleLabel: 'Loading.'),
                VerdifySpinner(
                  size: VerdifySpinnerSize.lg,
                  accessibleLabel: 'Loading.',
                ),
                VerdifySpinner(label: 'Checking your ID.'),
              ],
            ),
            const SizedBox(height: 24),
            const Text('Progress (measured wait)',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            VerdifyProgress(
              label: 'Uploading',
              value: 0.6,
              valueText: '60%',
              description: 'Uploading your document.',
            ),
            const SizedBox(height: 12),
            VerdifyProgress(label: 'Loading'),
            const SizedBox(height: 12),
            VerdifyProgress(
              label: 'Upload',
              value: 0.4,
              error: 'Upload failed. Try again.',
            ),
            const SizedBox(height: 24),
            const Text('Skeleton (loading placeholder)',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            VerdifySkeletonGroup(
              children: [
                Row(
                  spacing: 12,
                  children: [
                    VerdifySkeleton.circle(diameter: 40),
                    Expanded(
                      child: VerdifySkeletonGroup(
                        children: [
                          VerdifySkeleton.text(width: 160),
                          VerdifySkeleton.text(width: 100),
                        ],
                      ),
                    ),
                  ],
                ),
                VerdifySkeleton.block(width: double.infinity, height: 120),
                VerdifySkeleton.text(width: 240),
                VerdifySkeleton.text(width: 180),
              ],
            ),
            const SizedBox(height: 24),
            const Text('Label', style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            Wrap(
              spacing: 16,
              runSpacing: 8,
              children: [
                const VerdifyLabel(label: 'Email address'),
                const VerdifyLabel(label: 'Password', required: true),
                const VerdifyLabel(label: 'Middle name', optional: true),
                const VerdifyLabel(label: 'Disabled field', disabled: true),
              ],
            ),
            const SizedBox(height: 24),
            const Text('Separator',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            const VerdifySeparator(),
            const SizedBox(height: 8),
            const VerdifySeparator(label: 'or'),
            const SizedBox(height: 8),
            const VerdifySeparator(decorative: true),
            const SizedBox(height: 8),
            const SizedBox(
              height: 40,
              child: Row(
                children: [
                  Text('Left'),
                  VerdifySeparator(
                      orientation: VerdifySeparatorOrientation.vertical),
                  Text('Right'),
                ],
              ),
            ),
            const SizedBox(height: 24),
            const Text('Agent Badges',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                const VerdifyAgentBadge(label: 'Agent'),
                const VerdifyAgentBadge(
                  label: 'Agent',
                  icon: Icon(Icons.smart_toy, size: 16),
                ),
                const VerdifyAgentBadge(
                  label: 'Agent — expiring',
                  status: VerdifyAgentBadgeStatus.caution,
                ),
                const VerdifyAgentBadge(
                  label: 'Agent — revoked',
                  status: VerdifyAgentBadgeStatus.critical,
                ),
                const VerdifyAgentBadge(
                  icon: Icon(Icons.smart_toy, size: 16),
                  semanticLabel: 'AI agent',
                ),
              ],
            ),
            const SizedBox(height: 24),
            const Text('Switch (Phase-3 assembly)',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            const _SwitchGallery(),
            const SizedBox(height: 24),
            const Text('Textarea (Phase-3 assembly)',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            const _TextareaGallery(),
            const SizedBox(height: 24),
            const Text('Tooltip (Phase-3 assembly)',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            const _TooltipGallery(),
            const SizedBox(height: 24),
            const Text('Sheet (Phase-3 assembly)',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            const _SheetGallery(),
            const SizedBox(height: 24),
            const Text('Accordion (Phase-3 assembly)',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            const _AccordionGallery(),
            const SizedBox(height: 24),
            const Text('Sidebar (Phase-3 assembly)',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            const _SidebarGallery(),
            const SizedBox(height: 24),
            const Text('Command palette (Phase-3 assembly)',
                style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            const _CommandPaletteGallery(),
          ],
        ),
      ),
    );
  }
}

/// Gallery section: opens the modal command palette via
/// [showVerdifyCommandPalette] with grouped commands, a disabled row, a recent
/// list, and a footer of key hints. Demonstrates the activedescendant model —
/// type to filter, arrow to move the active row (focus stays on the input),
/// Enter/tap to run.
class _CommandPaletteGallery extends StatelessWidget {
  const _CommandPaletteGallery();

  static const _items = <VerdifyCommandPaletteItem>[
    VerdifyCommandPaletteItem(
      id: 'new-doc',
      label: 'New document',
      secondary: 'Create a blank document',
      icon: Icon(Icons.add),
      shortcut: Text('⌘N'),
      group: 'Commands',
    ),
    VerdifyCommandPaletteItem(
      id: 'open',
      label: 'Open file',
      icon: Icon(Icons.folder_open),
      group: 'Commands',
    ),
    VerdifyCommandPaletteItem(
      id: 'archived',
      label: 'Archived (unavailable)',
      secondary: 'Restore from settings first',
      group: 'Commands',
      disabled: true,
    ),
    VerdifyCommandPaletteItem(
      id: 'settings',
      label: 'Go to settings',
      icon: Icon(Icons.settings),
      group: 'Navigation',
    ),
    VerdifyCommandPaletteItem(
      id: 'profile',
      label: 'Go to profile',
      icon: Icon(Icons.person),
      group: 'Navigation',
    ),
  ];

  static const _recent = <VerdifyCommandPaletteItem>[
    VerdifyCommandPaletteItem(id: 'r1', label: 'Recent dashboard'),
    VerdifyCommandPaletteItem(id: 'r2', label: 'Recent profile'),
  ];

  @override
  Widget build(BuildContext context) {
    return Builder(
      builder: (context) => VerdifyButton(
        onPressed: () => showVerdifyCommandPalette(
          context: context,
          inputLabel: 'Search commands',
          placeholder: 'Type a command…',
          items: _items,
          recent: _recent,
          footer: const Text('↑↓ Move   ⏎ Run   Esc Dismiss'),
          onRun: (item) {
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Ran: ${item.label}')),
            );
          },
        ),
        child: const Text('Open command palette (⌘K)'),
      ),
    );
  }
}

class _CheckboxGallery extends StatefulWidget {
  const _CheckboxGallery();

  @override
  State<_CheckboxGallery> createState() => _CheckboxGalleryState();
}

class _CheckboxGalleryState extends State<_CheckboxGallery> {
  bool _standalone = false;
  bool? _parent; // null = indeterminate
  bool _withDesc = false;
  bool _error = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        VerdifyCheckbox(
          label: 'Standalone',
          checked: _standalone,
          onChanged: (v) => setState(() => _standalone = v),
        ),
        const SizedBox(height: 8),
        VerdifyCheckbox(
          label: 'Parent (indeterminate capable)',
          variant: VerdifyCheckboxVariant.parent,
          checked: _parent,
          onChanged: (v) => setState(() => _parent = v),
        ),
        const SizedBox(height: 8),
        VerdifyCheckbox(
          label: 'With description',
          description: 'Receive weekly updates',
          checked: _withDesc,
          onChanged: (v) => setState(() => _withDesc = v),
        ),
        const SizedBox(height: 8),
        VerdifyCheckbox(
          label: 'Error state',
          error: 'You must agree to proceed',
          checked: _error,
          onChanged: (v) => setState(() => _error = v),
        ),
        const SizedBox(height: 8),
        VerdifyCheckbox(
          label: 'Disabled checked',
          checked: true,
          onChanged: null,
        ),
        const SizedBox(height: 8),
        VerdifyCheckbox(
          label: 'Size small',
          size: VerdifyCheckboxSize.sm,
          checked: false,
          onChanged: (_) {},
        ),
      ],
    );
  }
}

class _TabsGallery extends StatefulWidget {
  const _TabsGallery();

  @override
  State<_TabsGallery> createState() => _TabsGalleryState();
}

class _TabsGalleryState extends State<_TabsGallery> {
  int _underline = 0;
  int _pill = 1;

  static const _tabs = [
    VerdifyTab(label: 'Overview', icon: Icons.dashboard_outlined),
    VerdifyTab(label: 'Activity', count: 3),
    VerdifyTab(label: 'Settings'),
    VerdifyTab(label: 'Archived', disabled: true),
  ];

  static const _panels = [
    Text('Overview panel content.'),
    Text('Activity panel content.'),
    Text('Settings panel content.'),
    Text('Archived panel content.'),
  ];

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        VerdifyTabs(
          tabs: _tabs,
          panels: _panels,
          selected: _underline,
          onChanged: (i) => setState(() => _underline = i),
        ),
        const SizedBox(height: 16),
        VerdifyTabs(
          tabs: _tabs,
          panels: _panels,
          selected: _pill,
          variant: VerdifyTabsVariant.pill,
          activation: VerdifyTabsActivation.automatic,
          onChanged: (i) => setState(() => _pill = i),
        ),
      ],
    );
  }
}

class _RadioGallery extends StatefulWidget {
  const _RadioGallery();

  @override
  State<_RadioGallery> createState() => _RadioGalleryState();
}

class _RadioGalleryState extends State<_RadioGallery> {
  String? _fruit = 'banana';
  String? _plan = 'pro';
  String? _billing = 'monthly';

  static const _fruits = [
    VerdifyRadioOption(value: 'apple', label: 'Apple'),
    VerdifyRadioOption(value: 'banana', label: 'Banana'),
    VerdifyRadioOption(value: 'cherry', label: 'Cherry', disabled: true),
    VerdifyRadioOption(value: 'date', label: 'Date'),
  ];

  static const _plans = [
    VerdifyRadioOption(
      value: 'free',
      label: 'Free',
      description: 'For getting started — 1 identity.',
    ),
    VerdifyRadioOption(
      value: 'pro',
      label: 'Pro',
      description: 'For growing teams — unlimited profiles.',
    ),
  ];

  static const _billingOptions = [
    VerdifyRadioOption(value: 'monthly', label: 'Monthly'),
    VerdifyRadioOption(value: 'annual', label: 'Annual'),
  ];

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        VerdifyRadioGroup(
          label: 'Pick a fruit',
          options: _fruits,
          value: _fruit,
          onChanged: (v) => setState(() => _fruit = v),
        ),
        const SizedBox(height: 24),
        VerdifyRadioGroup(
          label: 'Plan',
          options: _plans,
          value: _plan,
          variant: VerdifyRadioVariant.withDescription,
          onChanged: (v) => setState(() => _plan = v),
        ),
        const SizedBox(height: 24),
        VerdifyRadioGroup(
          label: 'Billing',
          options: _billingOptions,
          value: _billing,
          variant: VerdifyRadioVariant.card,
          onChanged: (v) => setState(() => _billing = v),
        ),
      ],
    );
  }
}

class _PopoverGallery extends StatelessWidget {
  const _PopoverGallery();

  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 16,
      runSpacing: 16,
      children: [
        // Content (default): a plain anchored note.
        VerdifyPopover(
          trigger: (context, isOpen, toggle) => VerdifyButton(
            variant: VerdifyButtonVariant.secondary,
            onPressed: toggle,
            child: Text(isOpen ? 'Hide note' : 'Show note'),
          ),
          builder: (context) => const VerdifyPopoverBody(
            child: Text('A short, non-modal note anchored to its trigger. The '
                'page behind stays live.'),
          ),
        ),
        // With-header: title + close control.
        VerdifyPopover(
          trigger: (context, isOpen, toggle) => VerdifyButton(
            variant: VerdifyButtonVariant.secondary,
            onPressed: toggle,
            child: const Text('Settings'),
          ),
          builder: (context) => const VerdifyPopoverHeader(
            title: VerdifyPopoverTitle(child: Text('Settings')),
            close: VerdifyPopoverClose(),
            body: VerdifyPopoverBody(
              child: Text('Tune how this credential is shared.'),
            ),
          ),
        ),
        // With-arrow, opening to the right.
        VerdifyPopover(
          side: PopoverSide.right,
          showArrow: true,
          trigger: (context, isOpen, toggle) => VerdifyButton(
            variant: VerdifyButtonVariant.secondary,
            onPressed: toggle,
            child: const Text('Details'),
          ),
          builder: (context) => const VerdifyPopoverBody(
            child: Text('Anchored with a pointer to its trigger.'),
          ),
        ),
      ],
    );
  }
}

class _ToastGallery extends StatefulWidget {
  const _ToastGallery();

  @override
  State<_ToastGallery> createState() => _ToastGalleryState();
}

class _ToastGalleryState extends State<_ToastGallery> {
  final _controller = VerdifyToastController();

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

  @override
  Widget build(BuildContext context) {
    return VerdifyToastRegion(
      controller: _controller,
      child: Wrap(
        spacing: 8,
        runSpacing: 8,
        children: [
          for (final variant in VerdifyToastVariant.values)
            VerdifyButton(
              variant: VerdifyButtonVariant.secondary,
              onPressed: () => _controller.show(
                '${variant.name[0].toUpperCase()}'
                '${variant.name.substring(1)} notification.',
                variant: variant,
              ),
              child: Text(variant.name),
            ),
          VerdifyButton(
            variant: VerdifyButtonVariant.ghost,
            onPressed: _controller.dismissAll,
            child: const Text('Dismiss all'),
          ),
        ],
      ),
    );
  }
}

class _CredentialCardGallery extends StatefulWidget {
  const _CredentialCardGallery();

  @override
  State<_CredentialCardGallery> createState() => _CredentialCardGalleryState();
}

class _CredentialCardGalleryState extends State<_CredentialCardGallery> {
  bool _selected = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        // Email — default
        VerdifyCredentialCard(
          label: 'Email',
          identifier: 'jordan@example.com',
          identifierText: 'jordan@example.com',
          onRemove: () {},
        ),
        const SizedBox(height: 8),
        // Email — verified + primary badges
        VerdifyCredentialCard(
          label: 'Email',
          identifier: 'verified@example.com',
          identifierText: 'verified@example.com',
          onRemove: () {},
          status: const [
            CredentialCardStatus(
                kind: CredentialCardStatusKind.verified, label: 'Verified'),
            CredentialCardStatus(
                kind: CredentialCardStatusKind.primary, label: 'Primary'),
          ],
        ),
        const SizedBox(height: 8),
        // Passkey — with meta
        VerdifyCredentialCard(
          label: 'Passkey',
          identifier: 'MacBook Pro',
          identifierText: 'MacBook Pro',
          kind: CredentialKind.passkey,
          onRemove: () {},
          meta: 'Added 3 months ago',
        ),
        const SizedBox(height: 8),
        // Phone — selectable, stateful toggle
        VerdifyCredentialCard(
          label: 'Phone',
          identifier: '+1 (555) 123-4567',
          identifierText: 'phone +1 (555) 123-4567',
          kind: CredentialKind.phone,
          onRemove: () {},
          selectable: true,
          selected: _selected,
          onSelectedChange: (v) => setState(() => _selected = v),
        ),
        const SizedBox(height: 8),
        // Wallet — disabled remove
        VerdifyCredentialCard(
          label: 'Wallet',
          identifier: '0x7a25...4e2f',
          identifierText: '0x7a25...4e2f',
          kind: CredentialKind.wallet,
          onRemove: () {},
          removeDisabled: true,
          removeDisabledReason: 'Cannot remove the last sign-in credential',
        ),
        const SizedBox(height: 8),
        // Enterprise SSO — loading
        VerdifyCredentialCard(
          label: 'Enterprise SSO',
          identifier: 'acme-corp.okta.com',
          identifierText: 'acme-corp.okta.com',
          kind: CredentialKind.enterpriseSso,
          onRemove: () {},
          loading: true,
        ),
      ],
    );
  }
}

class _MenuGallery extends StatelessWidget {
  const _MenuGallery();

  @override
  Widget build(BuildContext context) {
    final messenger = ScaffoldMessenger.of(context);
    void fire(String command) => messenger.showSnackBar(
          SnackBar(content: Text('Ran: $command')),
        );
    return Align(
      alignment: Alignment.centerLeft,
      child: VerdifyMenu(
        triggerSemanticLabel: 'Credential actions',
        trigger: (context, isOpen, open) => VerdifyButton(
          variant: VerdifyButtonVariant.secondary,
          onPressed: open,
          child: const Text('Actions'),
        ),
        items: [
          VerdifyMenuGroup(
            label: 'Manage',
            items: [
              VerdifyMenuItem(
                label: 'Open',
                icon: const Icon(Icons.open_in_new),
                shortcut: 'Cmd+O',
                onSelected: () => fire('Open'),
              ),
              VerdifyMenuItem(
                label: 'Rename',
                icon: const Icon(Icons.edit_outlined),
                onSelected: () => fire('Rename'),
              ),
              const VerdifyMenuItem(
                label: 'Archive',
                icon: Icon(Icons.archive_outlined),
                disabled: true,
              ),
              VerdifyMenuSubmenu(
                label: 'Share',
                icon: const Icon(Icons.ios_share),
                items: [
                  VerdifyMenuItem(
                    label: 'Copy link',
                    onSelected: () => fire('Copy link'),
                  ),
                  VerdifyMenuItem(
                    label: 'Email',
                    onSelected: () => fire('Email'),
                  ),
                ],
              ),
            ],
          ),
          const VerdifyMenuSeparator(),
          VerdifyMenuItem(
            label: 'Revoke key',
            icon: const Icon(Icons.delete_outline),
            destructive: true,
            onSelected: () => fire('Revoke key'),
          ),
        ],
      ),
    );
  }
}

class _SelectGallery extends StatefulWidget {
  const _SelectGallery();

  @override
  State<_SelectGallery> createState() => _SelectGalleryState();
}

class _SelectGalleryState extends State<_SelectGallery> {
  String? _region;
  String? _plan = 'pro';
  String? _invalid;

  static const _regions = [
    VerdifySelectOption(value: 'na', label: 'North America', group: 'Americas'),
    VerdifySelectOption(value: 'sa', label: 'South America', group: 'Americas'),
    VerdifySelectOption(value: 'eu', label: 'Europe', group: 'EMEA'),
    VerdifySelectOption(value: 'me', label: 'Middle East', group: 'EMEA'),
    VerdifySelectOption(value: 'ap', label: 'Asia Pacific', group: 'APAC'),
  ];

  static const _plans = [
    VerdifySelectOption(value: 'free', label: 'Free'),
    VerdifySelectOption(value: 'pro', label: 'Pro'),
    VerdifySelectOption(value: 'team', label: 'Team', disabled: true),
    VerdifySelectOption(value: 'enterprise', label: 'Enterprise'),
  ];

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        VerdifySelect(
          label: 'Region',
          placeholder: 'Choose a region',
          description: 'Where your data is stored.',
          options: _regions,
          value: _region,
          width: VerdifySelectWidth.full,
          onChanged: (v) => setState(() => _region = v),
        ),
        const SizedBox(height: 16),
        VerdifySelect(
          label: 'Plan',
          options: _plans,
          value: _plan,
          onChanged: (v) => setState(() => _plan = v),
        ),
        const SizedBox(height: 16),
        VerdifySelect(
          label: 'Required field',
          placeholder: 'Pick one',
          options: _plans,
          value: _invalid,
          error: _invalid == null ? 'This field is required.' : null,
          width: VerdifySelectWidth.full,
          onChanged: (v) => setState(() => _invalid = v),
        ),
        const SizedBox(height: 16),
        const VerdifySelect(
          label: 'Disabled',
          placeholder: 'Not available',
          options: _plans,
          enabled: false,
          onChanged: _noop,
        ),
      ],
    );
  }

  static void _noop(String _) {}
}

class _AlertGallery extends StatefulWidget {
  const _AlertGallery();

  @override
  State<_AlertGallery> createState() => _AlertGalleryState();
}

class _AlertGalleryState extends State<_AlertGallery> {
  bool _criticalDismissed = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        // Verified — body only
        const VerdifyAlert(
          variant: VerdifyAlertVariant.verified,
          body: Text('Your email address has been verified.'),
        ),
        const SizedBox(height: 8),
        // Signal — title + body
        const VerdifyAlert(
          variant: VerdifyAlertVariant.signal,
          title: Text('Heads up.'),
          body: Text('Your trial ends in 3 days. No action needed yet.'),
        ),
        const SizedBox(height: 8),
        // Caution — title + body + dismiss
        VerdifyAlert(
          variant: VerdifyAlertVariant.caution,
          title: const Text('Review required.'),
          body: const Text('Your proof document is pending manual review.'),
          onDismiss: () {},
        ),
        const SizedBox(height: 8),
        // Critical — title + body + action + dismiss (dismissible)
        if (!_criticalDismissed)
          VerdifyAlert(
            variant: VerdifyAlertVariant.critical,
            title: const Text('Verification failed.'),
            body: const Text(
                'The proof could not be validated. Check the document and try again.'),
            actions: [
              VerdifyButton(
                variant: VerdifyButtonVariant.secondary,
                onPressed: () {},
                child: const Text('Retry'),
              ),
            ],
            onDismiss: () => setState(() => _criticalDismissed = true),
          ),
        if (_criticalDismissed)
          VerdifyButton(
            variant: VerdifyButtonVariant.ghost,
            onPressed: () => setState(() => _criticalDismissed = false),
            child: const Text('Show critical alert again'),
          ),
        const SizedBox(height: 24),
        const Text('Breadcrumb'),
        const SizedBox(height: 8),
        VerdifyBreadcrumb(
          items: [
            VerdifyBreadcrumbItem(label: 'Home', onTap: () {}),
            VerdifyBreadcrumbItem(label: 'Billing', onTap: () {}),
          ],
          current: 'Invoice',
        ),
        const SizedBox(height: 8),
        VerdifyBreadcrumb(
          items: [
            VerdifyBreadcrumbItem(
              label: 'Home',
              icon: const Icon(Icons.home),
              onTap: () {},
            ),
            VerdifyBreadcrumbItem(label: 'Org', onTap: () {}),
            VerdifyBreadcrumbItem(label: 'Project', onTap: () {}),
          ],
          current: 'Settings',
          collapsed: true,
        ),
        const SizedBox(height: 24),
        const Text('Table'),
        const SizedBox(height: 8),
        VerdifyTable(
          columns: [
            VerdifyTableColumn(
              key: 'name',
              header: 'Name',
              sortable: true,
              onSort: (_) {},
            ),
            const VerdifyTableColumn(key: 'status', header: 'Status'),
            const VerdifyTableColumn(key: 'date', header: 'Date'),
          ],
          rows: [
            VerdifyTableRow(
              id: 'r1',
              cells: {
                'name': const Text('Alice Brown'),
                'status': const Text('Active'),
                'date': const Text('2026-06-01'),
              },
            ),
            VerdifyTableRow(
              id: 'r2',
              cells: {
                'name': const Text('Bob Clark'),
                'status': const Text('Pending'),
                'date': const Text('2026-06-02'),
              },
            ),
          ],
          caption: 'Users',
          sortedColumnKey: 'name',
          sortAscending: true,
        ),
        const SizedBox(height: 24),
        const Text('Data Grid'),
        const SizedBox(height: 8),
        const _DataGridGallery(),
        const SizedBox(height: 24),
        const Text('Avatar'),
        const SizedBox(height: 8),
        const Row(
          children: [
            VerdifyAvatar(
                displayName: 'Alice Brown', size: VerdifyAvatarSize.sm),
            SizedBox(width: 8),
            VerdifyAvatar(displayName: 'Alice Brown'),
            SizedBox(width: 8),
            VerdifyAvatar(
                displayName: 'Alice Brown', size: VerdifyAvatarSize.lg),
            SizedBox(width: 8),
            VerdifyAvatar(displayName: '', size: VerdifyAvatarSize.md),
            SizedBox(width: 8),
            VerdifyAvatar(
              displayName: 'Org',
              shape: VerdifyAvatarShape.rounded,
            ),
            SizedBox(width: 8),
            VerdifyAvatar(displayName: 'A B', showBorder: true),
            SizedBox(width: 8),
            VerdifyAvatar(displayName: 'Loading', loading: true),
          ],
        ),
        const SizedBox(height: 24),
        const Text('Pagination'),
        const SizedBox(height: 8),
        VerdifyPagination(
          currentPage: 3,
          totalPages: 10,
          onPageChanged: (_) {},
        ),
        const SizedBox(height: 8),
        VerdifyPagination(
          currentPage: 2,
          totalPages: 5,
          onPageChanged: (_) {},
          variant: VerdifyPaginationVariant.prevNext,
        ),
        const SizedBox(height: 24),
        const Text('IdentityChip'),
        const SizedBox(height: 8),
        const Wrap(
          spacing: 8,
          runSpacing: 8,
          children: [
            VerdifyIdentityChip(
              displayName: 'Jordan Rivera',
              name: 'Jordan Rivera',
            ),
            VerdifyIdentityChip(
              displayName: 'Atlas',
              name: 'Atlas',
              isAgent: true,
            ),
            VerdifyIdentityChip(
              displayName: 'Jordan Rivera',
              name: 'Jordan Rivera',
              showVerified: true,
              verifiedLabel: 'ID verified.',
            ),
          ],
        ),
        const SizedBox(height: 24),
        const Text('ConsentToggle'),
        const SizedBox(height: 8),
        VerdifyConsentToggle(
          scope: 'Share location data.',
          recipient: 'Verdify Maps',
          value: false,
          onChanged: (_) {},
        ),
        const SizedBox(height: 12),
        VerdifyConsentToggle(
          scope: 'Share location data.',
          recipient: 'Verdify Maps',
          value: true,
          onChanged: (_) {},
        ),
        const SizedBox(height: 12),
        VerdifyConsentToggle(
          scope: 'Allow Atlas to read your credentials.',
          recipient: 'Atlas (AI agent)',
          value: false,
          onChanged: (_) {},
        ),
        const SizedBox(height: 24),
        const Text('TrustScore'),
        const SizedBox(height: 8),
        const VerdifyTrustScore(
          score: 82,
          label: 'Trust score',
          scale: 'out of 100',
        ),
        const SizedBox(height: 12),
        const VerdifyTrustScore(
          score: 82,
          label: 'Trust score',
          scale: 'out of 100',
          variant: VerdifyTrustScoreVariant.meter,
          scoreMax: 100,
        ),
        const SizedBox(height: 12),
        const VerdifyTrustScore(
          score: 82,
          label: 'Trust score',
          variant: VerdifyTrustScoreVariant.compact,
        ),
      ],
    );
  }
}

class _TextareaGallery extends StatelessWidget {
  const _TextareaGallery();

  @override
  Widget build(BuildContext context) {
    return const Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        VerdifyTextarea(label: 'Notes'),
        SizedBox(height: 12),
        VerdifyTextarea(
          label: 'Bio',
          description: 'A short biography.',
          placeholder: 'Tell us about yourself.',
        ),
        SizedBox(height: 12),
        VerdifyTextarea(
          label: 'Tweet',
          maxLength: 280,
          showCounter: true,
        ),
        SizedBox(height: 12),
        VerdifyTextarea(
          label: 'Reason',
          errorText: 'This field is required.',
        ),
        SizedBox(height: 12),
        VerdifyTextarea(
          label: 'Disabled notes',
          enabled: false,
        ),
        SizedBox(height: 12),
        VerdifyTextarea(
          label: 'Read-only notes',
          readOnly: true,
        ),
        SizedBox(height: 12),
        VerdifyTextarea(
          label: 'Auto-grow',
          resize: VerdifyTextareaResize.autoGrow,
        ),
      ],
    );
  }
}

class _SwitchGallery extends StatefulWidget {
  const _SwitchGallery();

  @override
  State<_SwitchGallery> createState() => _SwitchGalleryState();
}

class _SwitchGalleryState extends State<_SwitchGallery> {
  bool _notifications = false;
  bool _darkMode = true;
  bool _sync = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        VerdifySwitch(
          label: 'Notifications',
          value: _notifications,
          onChanged: (v) => setState(() => _notifications = v),
        ),
        const SizedBox(height: 8),
        VerdifySwitch(
          label: 'Dark mode',
          description: 'Applies a dark colour scheme.',
          value: _darkMode,
          onChanged: (v) => setState(() => _darkMode = v),
        ),
        const SizedBox(height: 8),
        VerdifySwitch(
          label: 'Sync',
          value: _sync,
          onChanged: (v) => setState(() => _sync = v),
          labelPlacement: VerdifySwitchLabelPlacement.after,
        ),
        const SizedBox(height: 8),
        VerdifySwitch(
          label: 'Small switch',
          value: false,
          onChanged: (v) => setState(() {}),
          size: VerdifySwitchSize.sm,
        ),
        const SizedBox(height: 8),
        VerdifySwitch(
          label: 'Disabled (off)',
          value: false,
          onChanged: null,
        ),
        const SizedBox(height: 8),
        VerdifySwitch(
          label: 'Disabled (on)',
          value: true,
          onChanged: null,
        ),
      ],
    );
  }
}

/// Gallery section: VerdifySheet rows.
class _SheetGallery extends StatelessWidget {
  const _SheetGallery();

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // Default: inline-end, md, with footer.
        VerdifyButton(
          onPressed: () => showVerdifySheet(
            context: context,
            title: 'Edit profile',
            body: const Text('Profile fields go here.'),
            footer: VerdifyButton(
              onPressed: () {},
              child: const Text('Save'),
            ),
          ),
          child: const Text('Open sheet (inline-end, md)'),
        ),
        const SizedBox(height: 8),
        // inline-start, sm.
        VerdifyButton(
          variant: VerdifyButtonVariant.secondary,
          onPressed: () => showVerdifySheet(
            context: context,
            title: 'Navigation',
            side: VerdifySheetSide.inlineStart,
            size: VerdifySheetSize.sm,
            body: const Text('Nav links here.'),
          ),
          child: const Text('Open sheet (inline-start, sm)'),
        ),
        const SizedBox(height: 8),
        // block-end (bottom).
        VerdifyButton(
          variant: VerdifyButtonVariant.secondary,
          onPressed: () => showVerdifySheet(
            context: context,
            title: 'Filters',
            side: VerdifySheetSide.blockEnd,
            body: const Text('Filter controls here.'),
          ),
          child: const Text('Open sheet (block-end)'),
        ),
      ],
    );
  }
}

/// Gallery section: VerdifyTooltip rows.
class _TooltipGallery extends StatefulWidget {
  const _TooltipGallery();

  @override
  State<_TooltipGallery> createState() => _TooltipGalleryState();
}

class _TooltipGalleryState extends State<_TooltipGallery> {
  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // label variant — tooltip IS the accessible name for an icon-only button.
        VerdifyTooltip(
          content: 'Copy key',
          variant: VerdifyTooltipVariant.label,
          child: VerdifyButton(
            onPressed: () {},
            semanticLabel: 'Copy key',
            child: const Icon(Icons.copy, size: 18),
          ),
        ),
        const SizedBox(height: 8),
        // description variant — tooltip supplements a labeled button.
        VerdifyTooltip(
          content: 'Rotates every 90 days.',
          variant: VerdifyTooltipVariant.description,
          child: VerdifyButton(
            onPressed: () {},
            child: const Text('API Key'),
          ),
        ),
        const SizedBox(height: 8),
        // side: bottom — opens below trigger.
        VerdifyTooltip(
          content: 'Opens below',
          side: AnchoredSide.bottom,
          child: VerdifyButton(
            onPressed: () {},
            variant: VerdifyButtonVariant.secondary,
            child: const Text('Below'),
          ),
        ),
      ],
    );
  }
}

// ── Accordion gallery ─────────────────────────────────────────────────────────

class _AccordionGallery extends StatelessWidget {
  const _AccordionGallery();

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        const Text('Single mode'),
        const SizedBox(height: 8),
        VerdifyAccordion(
          items: [
            VerdifyAccordionItem(
              title: 'What is Verdify?',
              panel: const Text('A decentralised identity platform.'),
            ),
            VerdifyAccordionItem(
              title: 'How does it work?',
              panel: const Text('Via proofs and verifiable credentials.'),
            ),
            VerdifyAccordionItem(
              title: 'Coming soon',
              panel: const Text('More features.'),
              disabled: true,
            ),
          ],
        ),
        const SizedBox(height: 24),
        const Text('Multiple mode'),
        const SizedBox(height: 8),
        VerdifyAccordion(
          mode: VerdifyAccordionMode.multiple,
          items: [
            VerdifyAccordionItem(
              title: 'Credentials',
              panel: const Text('Tamper-evident proofs.'),
              initiallyOpen: true,
            ),
            VerdifyAccordionItem(
              title: 'Identity',
              panel: const Text('One identity, many profiles.'),
              initiallyOpen: true,
            ),
          ],
        ),
      ],
    );
  }
}

// ── Sidebar gallery ───────────────────────────────────────────────────────────

class _SidebarGallery extends StatefulWidget {
  const _SidebarGallery();

  @override
  State<_SidebarGallery> createState() => _SidebarGalleryState();
}

class _SidebarGalleryState extends State<_SidebarGallery> {
  int _current = 0;
  bool _collapsed = false;

  static const _items = [
    VerdifySidebarItem(icon: Icons.home, label: 'Home'),
    VerdifySidebarItem(icon: Icons.search, label: 'Search'),
    VerdifySidebarItem(icon: Icons.person, label: 'Profile', disabled: true),
    VerdifySidebarItem(icon: Icons.settings, label: 'Settings'),
  ];

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        VerdifySidebar(
          items: _items,
          currentIndex: _current,
          onItemTap: (i) => setState(() => _current = i),
          collapsed: _collapsed,
          collapsible: true,
          onCollapseToggle: (v) => setState(() => _collapsed = v),
          header: const Text('Verdify'),
          footer: const Text('user@example.com'),
        ),
        const SizedBox(width: 16),
        Padding(
          padding: const EdgeInsets.all(16),
          child: Text('Page: ${_items[_current].label}'),
        ),
      ],
    );
  }
}

// ── Data Grid gallery ───────────────────────────────────────────────────────

class _DataGridGallery extends StatefulWidget {
  const _DataGridGallery();

  @override
  State<_DataGridGallery> createState() => _DataGridGalleryState();
}

class _DataGridGalleryState extends State<_DataGridGallery> {
  Set<String> _selected = {'sk_live_1'};
  String? _sortedKey = 'status';
  bool _ascending = true;

  static const _rows = [
    VerdifyDataGridRow(
      id: 'sk_live_1',
      rowIndex: 4210,
      cells: {
        'key': VerdifyDataGridCell(text: 'sk_live_1'),
        'status': VerdifyDataGridCell(
          text: 'Verified',
          status: VerdifyDataGridCellStatus.verified,
        ),
      },
    ),
    VerdifyDataGridRow(
      id: 'sk_live_2',
      rowIndex: 4211,
      cells: {
        'key': VerdifyDataGridCell(text: 'sk_live_2'),
        'status': VerdifyDataGridCell(
          text: 'Expired',
          status: VerdifyDataGridCellStatus.critical,
        ),
      },
    ),
    VerdifyDataGridRow(
      id: 'sk_live_3',
      rowIndex: 4212,
      cells: {
        'key': VerdifyDataGridCell(text: 'sk_live_3'),
        'status': VerdifyDataGridCell(
          text: 'Pending',
          status: VerdifyDataGridCellStatus.caution,
        ),
      },
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 280,
      child: VerdifyDataGrid(
        label: 'API keys',
        rowCount: 12000,
        colCount: 3,
        columns: [
          const VerdifyDataGridColumn(
            key: 'key',
            header: 'Key',
            mono: true,
            flex: 2,
          ),
          VerdifyDataGridColumn(
            key: 'status',
            header: 'Status',
            sortable: true,
            onSort: (asc) => setState(() {
              _sortedKey = 'status';
              _ascending = asc;
            }),
          ),
        ],
        rows: _rows,
        selection: VerdifyDataGridSelection.multiple,
        selectedIds: _selected,
        onSelectionChanged: (s) => setState(() => _selected = s),
        sortedColumnKey: _sortedKey,
        sortAscending: _ascending,
        announcement: '${_selected.length} selected.',
        onSelectAll: () =>
            setState(() => _selected = _rows.map((r) => r.id).toSet()),
        bulkActions: [
          VerdifyDataGridBulkAction(label: 'Export', onPressed: () {}),
          VerdifyDataGridBulkAction(
            label: 'Revoke',
            destructive: true,
            onPressed: () => setState(() => _selected = {}),
          ),
        ],
      ),
    );
  }
}
0
likes
150
points
23
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Verdify's Flutter design system: 36 token-driven, WCAG 2.2 AA widgets with light and dark themes, built on Material 3.

Repository (GitHub)
View/report issues

Topics

#design-system #ui #widget #accessibility #material-design

License

unknown (license)

Dependencies

flutter

More

Packages that depend on verdify_ui