flutter icon indicating copy to clipboard operation
flutter copied to clipboard

Add Dropdown support to `FilterChip`

Open GregoryConrad opened this issue 2 years ago • 1 comments

Use case

This will allow a filter chip to make filter selections via a drop down menu, as showcased in the Material 3 Design specifications. This is mostly useful for desktop, but mobile in some situations too. DropdownButton is not customizable enough to support this use-case, and there is no simple way to open a drop down menu from a click (see here).

Proposal

Either the introduction of a new widget, perhaps DropdownFilterChip, modifications to the existing FilterChip, or a programmatic way to open up a dropdown menu from an arbitrary widget (preferred, seeing as this would be useful for various other use cases).

GregoryConrad avatar Jul 30 '22 21:07 GregoryConrad

My current workaround using flutter_hooks and font_awesome_flutter (neither of these packages are required for this workaround, but they make it easier):

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';

class DropdownFilterChip<T> extends HookWidget {
  const DropdownFilterChip({
    Key? key,
    required this.label,
    this.value,
    required this.items,
    required this.onChanged,
  }) : super(key: key);

  final Widget label;
  final T? value;
  final List<DropdownMenuItem<T>> items;
  final void Function(T? value) onChanged;

  @override
  Widget build(BuildContext context) {
    final dropdownButtonKey = useMemoized(() => GlobalKey());
    final focusNode = useFocusNode();
    return FilterChip(
      label: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          label,
          const SizedBox(width: 8),
          const FaIcon(FontAwesomeIcons.caretDown),
          Offstage(
            child: DropdownButton<T>(
              key: dropdownButtonKey,
              focusNode: focusNode,
              value: value,
              items: items,
              onChanged: (value) {
                onChanged(value);
                WidgetsBinding.instance.addPostFrameCallback((_) {
                  focusNode.unfocus();
                });
              },
            ),
          ),
        ],
      ),
      onSelected: (_) {
        dropdownButtonKey.currentContext?.visitChildElements((element) {
          if (element.widget is Semantics) {
            element.visitChildElements((element) {
              if (element.widget is Actions) {
                element.visitChildElements((element) {
                  Actions.invoke(element, const ActivateIntent());
                });
              }
            });
          }
        });
      },
    );
  }
}

Which can be used like so:

DropdownFilterChip<String>(
  label: Text(
    selectedCategories.value.isEmpty
        ? 'category'
        : selectedCategories.value.first,
  ),
  value: selectedCategories.value.isEmpty
      ? null
      : selectedCategories.value.first,
  items: [
    const DropdownMenuItem(value: null, child: Text('all')),
    for (final category in library.categories)
      DropdownMenuItem(value: category, child: Text(category)),
  ],
  onChanged: (category) {
    selectedCategories.value = {if (category != null) category};
  },
)

GregoryConrad avatar Jul 30 '22 22:07 GregoryConrad

any update on this ?

jesussmile avatar Jun 15 '23 11:06 jesussmile

This issue is assigned but has had no recent status updates. Please consider unassigning this issue if it is not going to be addressed in the near future. This allows people to have a clearer picture of what work is actually planned. Thanks!

flutter-triage-bot[bot] avatar Jul 08 '23 02:07 flutter-triage-bot[bot]

+1, this would be great

alexanderameye avatar Sep 06 '23 07:09 alexanderameye

any updates? so currently it is impossible to make such a design as specified in the m3 specs?

vec715 avatar Oct 23 '23 19:10 vec715

M3 has been marked done but this is still missing

debkanchan avatar Nov 16 '23 03:11 debkanchan

This feature would be a valuable addition if it were to be implemented. It appears that many individuals are searching for this particular missing feature: https://stackoverflow.com/q/76482263

wjkoh avatar Jan 28 '24 16:01 wjkoh

for this I combine FilterChip with MenuAnchor, to anyone who wanna use it here is the code:

class ChipMenu extends StatefulWidget {
  ChipMenu({required this.menuItemList, required this.initialSelectionIndex, this.onSelected, super.key});

  final List<(String, Widget?)> menuItemList;
  final int initialSelectionIndex;
  final Function(String)? onSelected;

  @override
  State<ChipMenu> createState() => _ChipMenuState();
}

class _ChipMenuState extends State<ChipMenu> {
  late String _selectedText;

  @override
  void initState() {
    super.initState();
    _selectedText = widget.menuItemList[widget.initialSelectionIndex].$1;
  }

  @override
  Widget build(BuildContext context) {
    return MenuAnchor(
      builder: (context, controller, child) {
        return FilterChip(
          label: Text(_selectedText),
          selected: true,
          onSelected: (_) {
            if (controller.isOpen) {
              controller.close();
            } else {
              controller.open();
            }
          },
        );
      },
      menuChildren: [
        for (final rec in widget.menuItemList)
          MenuItemButton(
            leadingIcon: rec.$2,
            child: Text(rec.$1),
            onPressed: () {
              setState(() {
                _selectedText = rec.$1;
              });
              widget.onSelected?.call(rec.$1);
            },
          ),
      ],
    );
  }
}

and the dartpad example

Mr-1311 avatar Mar 16 '24 23:03 Mr-1311

@Mr-1311 can you add a pointer too ?

jesussmile avatar Mar 17 '24 02:03 jesussmile

+1 for this addition

djnugent avatar Jul 17 '24 20:07 djnugent

Here is my solution to the problem:

https://dartpad.dev/?id=692d161174550822d13ce0c34d7eeca9

djnugent avatar Jul 17 '24 23:07 djnugent