Tile

Select Menu Tile

A tile that, when triggered, displays a list of options for the user to pick from.

For desktop, a select group is generally recommended over this.

enum Notification { all, direct, nothing }

FSelectMenuTile<Notification>.fromMap(
  const {
    'All': Notification.all,
    'Direct Messages': Notification.direct,
    'None': Notification.nothing,
  },
  selectControl: const FMultiValueControl.managed(initial: {Notification.all}),
  autoHide: true,
  validator: (value) => value == null ? 'Select an item' : null,
  prefix: const Icon(FIcons.bell),
  title: const Text('Notifications'),
  detailsBuilder: (_, values, _) => Text(switch (values.firstOrNull) {
    Notification.all => 'All',
    Notification.direct => 'Direct Messages',
    _ => 'None',
  }),
);

CLI

To generate and customize this style:

dart run forui style create select-menu-tile

Usage

FSelectMenuTile(...)

FSelectMenuTile<Value>(
  title: const Text('Notifications'),
  menu: [
    FSelectTile(title: const Text('Option 1'), value: Value.option1),
  ],
  selectControl: FMultiValueControl.managed(initial: {Value.option1}),
  popoverControl: FPopoverControl.managed(),
  scrollController: ScrollController(),
  style: (style) => style.copyWith(...),
  cacheExtent: 100,
  maxHeight: 200,
  dragStartBehavior: DragStartBehavior.start,
  physics: const ClampingScrollPhysics(),
  divider: FItemDivider.indented,
  menuAnchor: Alignment.topRight,
  tileAnchor: Alignment.bottomRight,
  menuSpacing: FPortalSpacing.spacing(4),
  menuOverflow: FPortalOverflow.flip,
  menuOffset: Offset.zero,
  menuHideRegion: FPopoverHideRegion.excludeChild,
  menuOnTapHide: () {},
  menuGroupId: 'group-id',
  autoHide: true,
  label: const Text('Sidebar'),
  description: const Text('Select the items you want to display in the sidebar.'),
  autofocus: false,
  menuFocusNode: FocusScopeNode(),
  menuOnFocusChange: (focused) {},
  menuTraversalEdgeBehavior: TraversalEdgeBehavior.closedLoop,
  menuBarrierSemanticsLabel: 'Dismiss',
  menuBarrierSemanticsDismissible: true,
  semanticsLabel: 'Notifications',
  prefix: const Icon(FIcons.bell),
  subtitle: const Text('subtitle'),
  detailsBuilder: (context, values, child) => const Placeholder(),
  details: const Text('All'),
  suffix: const Icon(FIcons.chevronsUpDown),
  shortcuts: {SingleActivator(LogicalKeyboardKey.enter): ActivateIntent()},
  actions: {ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: (_) {})},
  errorBuilder: (context, error) => Text(error),
  onSaved: (values) {},
  onReset: () {},
  validator: (values) => values?.isEmpty ?? true ? 'Select an item' : null,
  forceErrorText: null,
  enabled: true,
  autovalidateMode: AutovalidateMode.disabled,
);

FSelectMenuTile.builder(...)

FSelectMenuTile<int>.builder(
  title: const Text('Applicable values'),
  menuBuilder: (context, index) => FSelectTile(title: Text('Tile $index'), value: index),
  count: 100,
  selectControl: FMultiValueControl.managed(initial: {1}),
  popoverControl: FPopoverControl.managed(),
  scrollController: ScrollController(),
  style: (style) => style.copyWith(...),
  cacheExtent: 100,
  maxHeight: 200,
  dragStartBehavior: DragStartBehavior.start,
  physics: const ClampingScrollPhysics(),
  divider: FItemDivider.indented,
  menuAnchor: Alignment.topRight,
  tileAnchor: Alignment.bottomRight,
  menuSpacing: FPortalSpacing.spacing(4),
  menuOverflow: FPortalOverflow.flip,
  menuOffset: Offset.zero,
  menuHideRegion: FPopoverHideRegion.excludeChild,
  menuOnTapHide: () {},
  menuGroupId: 'group-id',
  autoHide: true,
  label: const Text('Settings'),
  description: const Text('Select the items you want to display.'),
  autofocus: false,
  menuFocusNode: FocusScopeNode(),
  menuOnFocusChange: (focused) {},
  menuTraversalEdgeBehavior: TraversalEdgeBehavior.closedLoop,
  menuBarrierSemanticsLabel: 'Dismiss',
  menuBarrierSemanticsDismissible: true,
  semanticsLabel: 'Values',
  prefix: const Icon(FIcons.variable),
  subtitle: const Text('subtitle'),
  detailsBuilder: (context, values, child) => const Placeholder(),
  details: const Text('Details'),
  suffix: const Icon(FIcons.chevronsUpDown),
  shortcuts: {SingleActivator(LogicalKeyboardKey.enter): ActivateIntent()},
  actions: {ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: (_) {})},
  errorBuilder: (context, error) => Text(error),
  onSaved: (values) {},
  onReset: () {},
  validator: (values) => values?.isEmpty ?? true ? 'Select an item' : null,
  forceErrorText: null,
  enabled: true,
  autovalidateMode: AutovalidateMode.disabled,
);

Examples

No Auto Hide

enum Notification { all, direct, nothing }

FSelectMenuTile<Notification>(
  selectControl: const FMultiValueControl.managed(initial: {Notification.all}),
  autoHide: false,
  validator: (value) => value == null ? 'Select an item' : null,
  prefix: const Icon(FIcons.bell),
  title: const Text('Notifications'),
  detailsBuilder: (_, values, _) => Text(switch (values.firstOrNull) {
    Notification.all => 'All',
    Notification.direct => 'Direct Messages',
    _ => 'None',
  }),
  menu: const [
    FSelectTile(title: Text('All'), value: Notification.all),
    FSelectTile(title: Text('Direct Messages'), value: Notification.direct),
    FSelectTile(title: Text('None'), value: Notification.nothing),
  ],
);

Scrollable

enum Notification { all, direct, limitedTime, timeSensitive, selectedApps, nothing }

FSelectMenuTile<Notification>(
  selectControl: const FMultiValueControl.managed(initial: {Notification.all}),
  maxHeight: 150,
  validator: (value) => value == null ? 'Select an item' : null,
  prefix: const Icon(FIcons.bell),
  title: const Text('Notifications'),
  detailsBuilder: (_, values, _) => Text(switch (values.firstOrNull) {
    Notification.all => 'All',
    Notification.direct => 'Direct Messages',
    Notification.limitedTime => 'Limited Time',
    Notification.selectedApps => 'Selected Apps',
    Notification.timeSensitive => 'Time Sensitive',
    null || Notification.nothing => 'None',
  }),
  menu: const [
    FSelectTile(title: Text('All'), value: Notification.all),
    FSelectTile(title: Text('Direct Messages'), value: Notification.direct),
    FSelectTile(title: Text('Limited Time'), value: Notification.limitedTime),
    FSelectTile(title: Text('Selected Apps'), value: Notification.selectedApps),
    FSelectTile(title: Text('Time Sensitive'), value: Notification.timeSensitive),
    FSelectTile(title: Text('None'), value: Notification.nothing),
  ],
);

Lazy Scrollable

FSelectMenuTile<int>.builder(
  selectControl: const FMultiValueControl.managed(initial: {1}),
  prefix: const Icon(FIcons.variable),
  title: const Text('Applicable values'),
  maxHeight: 200,
  menuBuilder: (context, index) => FSelectTile(title: Text('Tile $index'), value: index),
);

Form

enum Notification { all, direct, nothing }

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

  @override
  State<SelectMenuTileForm> createState() => _SelectMenuTileFormState();
}

class _SelectMenuTileFormState extends State<SelectMenuTileForm> {
  final _key = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) => Form(
    key: _key,
    child: Column(
      mainAxisSize: MainAxisSize.min,
      spacing: 20,
      children: [
        FSelectMenuTile<Notification>(
          selectControl: const FMultiValueControl.managed(initial: {Notification.all}),
          validator: (value) => value == null ? 'Select an item' : null,
          prefix: const Icon(FIcons.bell),
          title: const Text('Notifications'),
          detailsBuilder: (_, values, _) => Text(switch (values.firstOrNull) {
            Notification.all => 'All',
            Notification.direct => 'Direct Messages',
            _ => 'None',
          }),
          menu: const [
            FSelectTile(title: Text('All'), value: Notification.all),
            FSelectTile(title: Text('Direct Messages'), value: Notification.direct),
            FSelectTile(title: Text('None'), value: Notification.nothing),
          ],
        ),
        FButton(
          child: const Text('Save'),
          onPress: () {
            if (!_key.currentState!.validate()) {
              // Handle errors here.
              return;
            }

            _key.currentState!.save();
            // Do something.
          },
        ),
      ],
    ),
  );
}

On this page