Skip to content
Permalink
main
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
// Copyright 2021 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
const rowDivider = SizedBox(width: 20);
const colDivider = SizedBox(height: 10);
const tinySpacing = 3.0;
const smallSpacing = 10.0;
const double cardWidth = 115;
const double widthConstraint = 450;
class FirstComponentList extends StatelessWidget {
const FirstComponentList({
super.key,
required this.showNavBottomBar,
required this.scaffoldKey,
required this.showSecondList,
});
final bool showNavBottomBar;
final GlobalKey<ScaffoldState> scaffoldKey;
final bool showSecondList;
@override
Widget build(BuildContext context) {
List<Widget> children = [
const Actions(),
colDivider,
const Communication(),
colDivider,
const Containment(),
if (!showSecondList) ...[
colDivider,
Navigation(scaffoldKey: scaffoldKey),
colDivider,
const Selection(),
colDivider,
const TextInputs()
],
];
List<double?> heights = List.filled(children.length, null);
// Fully traverse this list before moving on.
return FocusTraversalGroup(
child: CustomScrollView(
slivers: [
SliverPadding(
padding: showSecondList
? const EdgeInsetsDirectional.only(end: smallSpacing)
: EdgeInsets.zero,
sliver: SliverList(
delegate: BuildSlivers(
heights: heights,
builder: (context, index) {
return _CacheHeight(
heights: heights,
index: index,
child: children[index],
);
},
),
),
),
],
),
);
}
}
class SecondComponentList extends StatelessWidget {
const SecondComponentList({
super.key,
required this.scaffoldKey,
});
final GlobalKey<ScaffoldState> scaffoldKey;
@override
Widget build(BuildContext context) {
List<Widget> children = [
Navigation(scaffoldKey: scaffoldKey),
colDivider,
const Selection(),
colDivider,
const TextInputs(),
];
List<double?> heights = List.filled(children.length, null);
// Fully traverse this list before moving on.
return FocusTraversalGroup(
child: CustomScrollView(
slivers: [
SliverPadding(
padding: const EdgeInsetsDirectional.only(end: smallSpacing),
sliver: SliverList(
delegate: BuildSlivers(
heights: heights,
builder: (context, index) {
return _CacheHeight(
heights: heights,
index: index,
child: children[index],
);
},
),
),
),
],
),
);
}
}
// If the content of a CustomScrollView does not change, then it's
// safe to cache the heights of each item as they are laid out. The
// sum of the cached heights are returned by an override of
// `SliverChildDelegate.estimateMaxScrollOffset`. The default version
// of this method bases its estimate on the average height of the
// visible items. The override ensures that the scrollbar thumb's
// size, which depends on the max scroll offset, will shrink smoothly
// as the contents of the list are exposed for the first time, and
// then remain fixed.
class _CacheHeight extends SingleChildRenderObjectWidget {
const _CacheHeight({
super.child,
required this.heights,
required this.index,
});
final List<double?> heights;
final int index;
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderCacheHeight(
heights: heights,
index: index,
);
}
@override
void updateRenderObject(
BuildContext context, _RenderCacheHeight renderObject) {
renderObject
..heights = heights
..index = index;
}
}
class _RenderCacheHeight extends RenderProxyBox {
_RenderCacheHeight({
required List<double?> heights,
required int index,
}) : _heights = heights,
_index = index,
super();
List<double?> _heights;
List<double?> get heights => _heights;
set heights(List<double?> value) {
if (value == _heights) {
return;
}
_heights = value;
markNeedsLayout();
}
int _index;
int get index => _index;
set index(int value) {
if (value == index) {
return;
}
_index = value;
markNeedsLayout();
}
@override
void performLayout() {
super.performLayout();
heights[index] = size.height;
}
}
// The heights information is used to override the `estimateMaxScrollOffset` and
// provide a more accurate estimation for the max scroll offset.
class BuildSlivers extends SliverChildBuilderDelegate {
BuildSlivers({
required NullableIndexedWidgetBuilder builder,
required this.heights,
}) : super(builder, childCount: heights.length);
final List<double?> heights;
@override
double? estimateMaxScrollOffset(int firstIndex, int lastIndex,
double leadingScrollOffset, double trailingScrollOffset) {
return heights.reduce((sum, height) => (sum ?? 0) + (height ?? 0))!;
}
}
class Actions extends StatelessWidget {
const Actions({super.key});
@override
Widget build(BuildContext context) {
return const ComponentGroupDecoration(label: 'Actions', children: <Widget>[
Buttons(),
FloatingActionButtons(),
IconToggleButtons(),
SegmentedButtons(),
]);
}
}
class Communication extends StatelessWidget {
const Communication({super.key});
@override
Widget build(BuildContext context) {
return const ComponentGroupDecoration(label: 'Communication', children: [
NavigationBars(
selectedIndex: 1,
isExampleBar: true,
isBadgeExample: true,
),
ProgressIndicators(),
SnackBarSection(),
]);
}
}
class Containment extends StatelessWidget {
const Containment({super.key});
@override
Widget build(BuildContext context) {
return const ComponentGroupDecoration(label: 'Containment', children: [
BottomSheetSection(),
Cards(),
Dialogs(),
Dividers(),
// TODO: Add Lists, https://github.com/flutter/flutter/issues/114006
// TODO: Add Side sheets, https://github.com/flutter/flutter/issues/119328
]);
}
}
class Navigation extends StatelessWidget {
const Navigation({super.key, required this.scaffoldKey});
final GlobalKey<ScaffoldState> scaffoldKey;
@override
Widget build(BuildContext context) {
return ComponentGroupDecoration(label: 'Navigation', children: [
const BottomAppBars(),
const NavigationBars(
selectedIndex: 0,
isExampleBar: true,
),
NavigationDrawers(scaffoldKey: scaffoldKey),
const NavigationRails(),
const Tabs(),
const SearchAnchors(),
const TopAppBars(),
]);
}
}
class Selection extends StatelessWidget {
const Selection({super.key});
@override
Widget build(BuildContext context) {
return const ComponentGroupDecoration(label: 'Selection', children: [
Checkboxes(),
Chips(),
DatePickers(),
Menus(),
Radios(),
Sliders(),
Switches(),
TimePickers(),
]);
}
}
class TextInputs extends StatelessWidget {
const TextInputs({super.key});
@override
Widget build(BuildContext context) {
return const ComponentGroupDecoration(
label: 'Text inputs',
children: [TextFields()],
);
}
}
class Buttons extends StatefulWidget {
const Buttons({super.key});
@override
State<Buttons> createState() => _ButtonsState();
}
class _ButtonsState extends State<Buttons> {
@override
Widget build(BuildContext context) {
return const ComponentDecoration(
label: 'Common buttons',
tooltipMessage:
'Use ElevatedButton, FilledButton, FilledButton.tonal, OutlinedButton, or TextButton',
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
ButtonsWithoutIcon(isDisabled: false),
ButtonsWithIcon(),
ButtonsWithoutIcon(isDisabled: true),
],
),
),
);
}
}
class ButtonsWithoutIcon extends StatelessWidget {
final bool isDisabled;
const ButtonsWithoutIcon({super.key, required this.isDisabled});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 5.0),
child: IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ElevatedButton(
onPressed: isDisabled ? null : () {},
child: const Text('Elevated'),
),
colDivider,
FilledButton(
onPressed: isDisabled ? null : () {},
child: const Text('Filled'),
),
colDivider,
FilledButton.tonal(
onPressed: isDisabled ? null : () {},
child: const Text('Filled tonal'),
),
colDivider,
OutlinedButton(
onPressed: isDisabled ? null : () {},
child: const Text('Outlined'),
),
colDivider,
TextButton(
onPressed: isDisabled ? null : () {},
child: const Text('Text'),
),
],
),
),
);
}
}
class ButtonsWithIcon extends StatelessWidget {
const ButtonsWithIcon({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('Icon'),
),
colDivider,
FilledButton.icon(
onPressed: () {},
label: const Text('Icon'),
icon: const Icon(Icons.add),
),
colDivider,
FilledButton.tonalIcon(
onPressed: () {},
label: const Text('Icon'),
icon: const Icon(Icons.add),
),
colDivider,
OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('Icon'),
),
colDivider,
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('Icon'),
)
],
),
),
);
}
}
class FloatingActionButtons extends StatelessWidget {
const FloatingActionButtons({super.key});
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Floating action buttons',
tooltipMessage:
'Use FloatingActionButton or FloatingActionButton.extended',
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
runSpacing: smallSpacing,
spacing: smallSpacing,
children: [
FloatingActionButton.small(
onPressed: () {},
tooltip: 'Small',
child: const Icon(Icons.add),
),
FloatingActionButton.extended(
onPressed: () {},
tooltip: 'Extended',
icon: const Icon(Icons.add),
label: const Text('Create'),
),
FloatingActionButton(
onPressed: () {},
tooltip: 'Standard',
child: const Icon(Icons.add),
),
FloatingActionButton.large(
onPressed: () {},
tooltip: 'Large',
child: const Icon(Icons.add),
),
],
),
);
}
}
class Cards extends StatelessWidget {
const Cards({super.key});
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Cards',
tooltipMessage: 'Use Card',
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
children: [
SizedBox(
width: cardWidth,
child: Card(
child: Container(
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
child: Column(
children: [
Align(
alignment: Alignment.topRight,
child: IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {},
),
),
const SizedBox(height: 20),
const Align(
alignment: Alignment.bottomLeft,
child: Text('Elevated'),
)
],
),
),
),
),
SizedBox(
width: cardWidth,
child: Card(
color: Theme.of(context).colorScheme.surfaceVariant,
elevation: 0,
child: Container(
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
child: Column(
children: [
Align(
alignment: Alignment.topRight,
child: IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {},
),
),
const SizedBox(height: 20),
const Align(
alignment: Alignment.bottomLeft,
child: Text('Filled'),
)
],
),
),
),
),
SizedBox(
width: cardWidth,
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).colorScheme.outline,
),
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Container(
padding: const EdgeInsets.fromLTRB(10, 5, 5, 10),
child: Column(
children: [
Align(
alignment: Alignment.topRight,
child: IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {},
),
),
const SizedBox(height: 20),
const Align(
alignment: Alignment.bottomLeft,
child: Text('Outlined'),
)
],
),
),
),
),
],
),
);
}
}
class _ClearButton extends StatelessWidget {
const _ClearButton({required this.controller});
final TextEditingController controller;
@override
Widget build(BuildContext context) => IconButton(
icon: const Icon(Icons.clear),
onPressed: () => controller.clear(),
);
}
class TextFields extends StatefulWidget {
const TextFields({super.key});
@override
State<TextFields> createState() => _TextFieldsState();
}
class _TextFieldsState extends State<TextFields> {
final TextEditingController _controllerFilled = TextEditingController();
final TextEditingController _controllerOutlined = TextEditingController();
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Text fields',
tooltipMessage: 'Use TextField with different InputDecoration',
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(smallSpacing),
child: TextField(
controller: _controllerFilled,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search),
suffixIcon: _ClearButton(controller: _controllerFilled),
labelText: 'Filled',
hintText: 'hint text',
helperText: 'supporting text',
filled: true,
),
),
),
Padding(
padding: const EdgeInsets.all(smallSpacing),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: SizedBox(
width: 200,
child: TextField(
maxLength: 10,
maxLengthEnforcement: MaxLengthEnforcement.none,
controller: _controllerFilled,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search),
suffixIcon: _ClearButton(controller: _controllerFilled),
labelText: 'Filled',
hintText: 'hint text',
helperText: 'supporting text',
filled: true,
errorText: 'error text',
),
),
),
),
const SizedBox(width: smallSpacing),
Flexible(
child: SizedBox(
width: 200,
child: TextField(
controller: _controllerFilled,
enabled: false,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search),
suffixIcon: _ClearButton(controller: _controllerFilled),
labelText: 'Disabled',
hintText: 'hint text',
helperText: 'supporting text',
filled: true,
),
),
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(smallSpacing),
child: TextField(
controller: _controllerOutlined,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search),
suffixIcon: _ClearButton(controller: _controllerOutlined),
labelText: 'Outlined',
hintText: 'hint text',
helperText: 'supporting text',
border: const OutlineInputBorder(),
),
),
),
Padding(
padding: const EdgeInsets.all(smallSpacing),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: SizedBox(
width: 200,
child: TextField(
controller: _controllerOutlined,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search),
suffixIcon:
_ClearButton(controller: _controllerOutlined),
labelText: 'Outlined',
hintText: 'hint text',
helperText: 'supporting text',
errorText: 'error text',
border: const OutlineInputBorder(),
filled: true,
),
),
),
),
const SizedBox(width: smallSpacing),
Flexible(
child: SizedBox(
width: 200,
child: TextField(
controller: _controllerOutlined,
enabled: false,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search),
suffixIcon:
_ClearButton(controller: _controllerOutlined),
labelText: 'Disabled',
hintText: 'hint text',
helperText: 'supporting text',
border: const OutlineInputBorder(),
filled: true,
),
),
),
),
])),
],
),
);
}
}
class Dialogs extends StatefulWidget {
const Dialogs({super.key});
@override
State<Dialogs> createState() => _DialogsState();
}
class _DialogsState extends State<Dialogs> {
void openDialog(BuildContext context) {
showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: const Text('What is a dialog?'),
content: const Text(
'A dialog is a type of modal window that appears in front of app content to provide critical information, or prompt for a decision to be made.'),
actions: <Widget>[
TextButton(
child: const Text('Okay'),
onPressed: () => Navigator.of(context).pop(),
),
FilledButton(
child: const Text('Dismiss'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
);
}
void openFullscreenDialog(BuildContext context) {
showDialog<void>(
context: context,
builder: (context) => Dialog.fullscreen(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Scaffold(
appBar: AppBar(
title: const Text('Full-screen dialog'),
centerTitle: false,
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.of(context).pop(),
),
actions: [
TextButton(
child: const Text('Close'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Dialog',
tooltipMessage:
'Use showDialog with Dialog.fullscreen, AlertDialog, or SimpleDialog',
child: Wrap(
alignment: WrapAlignment.spaceBetween,
children: [
TextButton(
child: const Text(
'Show dialog',
style: TextStyle(fontWeight: FontWeight.bold),
),
onPressed: () => openDialog(context),
),
TextButton(
child: const Text(
'Show full-screen dialog',
style: TextStyle(fontWeight: FontWeight.bold),
),
onPressed: () => openFullscreenDialog(context),
),
],
),
);
}
}
class Dividers extends StatelessWidget {
const Dividers({super.key});
@override
Widget build(BuildContext context) {
return const ComponentDecoration(
label: 'Dividers',
tooltipMessage: 'Use Divider or VerticalDivider',
child: Column(
children: <Widget>[
Divider(key: Key('divider')),
],
),
);
}
}
class Switches extends StatelessWidget {
const Switches({super.key});
@override
Widget build(BuildContext context) {
return const ComponentDecoration(
label: 'Switches',
tooltipMessage: 'Use SwitchListTile or Switch',
child: Column(
children: <Widget>[
SwitchRow(isEnabled: true),
SwitchRow(isEnabled: false),
],
),
);
}
}
class SwitchRow extends StatefulWidget {
const SwitchRow({super.key, required this.isEnabled});
final bool isEnabled;
@override
State<SwitchRow> createState() => _SwitchRowState();
}
class _SwitchRowState extends State<SwitchRow> {
bool value0 = false;
bool value1 = true;
final MaterialStateProperty<Icon?> thumbIcon =
MaterialStateProperty.resolveWith<Icon?>((states) {
if (states.contains(MaterialState.selected)) {
return const Icon(Icons.check);
}
return const Icon(Icons.close);
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
// TODO: use SwitchListTile when thumbIcon is available https://github.com/flutter/flutter/issues/118616
Switch(
value: value0,
onChanged: widget.isEnabled
? (value) {
setState(() {
value0 = value;
});
}
: null,
),
Switch(
thumbIcon: thumbIcon,
value: value1,
onChanged: widget.isEnabled
? (value) {
setState(() {
value1 = value;
});
}
: null,
),
],
);
}
}
class Checkboxes extends StatefulWidget {
const Checkboxes({super.key});
@override
State<Checkboxes> createState() => _CheckboxesState();
}
class _CheckboxesState extends State<Checkboxes> {
bool? isChecked0 = true;
bool? isChecked1;
bool? isChecked2 = false;
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Checkboxes',
tooltipMessage: 'Use CheckboxListTile or Checkbox',
child: Column(
children: <Widget>[
CheckboxListTile(
tristate: true,
value: isChecked0,
title: const Text('Option 1'),
onChanged: (value) {
setState(() {
isChecked0 = value;
});
},
),
CheckboxListTile(
tristate: true,
value: isChecked1,
title: const Text('Option 2'),
onChanged: (value) {
setState(() {
isChecked1 = value;
});
},
),
CheckboxListTile(
tristate: true,
value: isChecked2,
title: const Text('Option 3'),
// TODO: showcase error state https://github.com/flutter/flutter/issues/118616
onChanged: (value) {
setState(() {
isChecked2 = value;
});
},
),
const CheckboxListTile(
tristate: true,
title: Text('Option 4'),
value: true,
onChanged: null,
),
],
),
);
}
}
enum Value { first, second }
class Radios extends StatefulWidget {
const Radios({super.key});
@override
State<Radios> createState() => _RadiosState();
}
enum Options { option1, option2, option3 }
class _RadiosState extends State<Radios> {
Options? _selectedOption = Options.option1;
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Radio buttons',
tooltipMessage: 'Use RadioListTile<T> or Radio<T>',
child: Column(
children: <Widget>[
RadioListTile<Options>(
title: const Text('Option 1'),
value: Options.option1,
groupValue: _selectedOption,
onChanged: (value) {
setState(() {
_selectedOption = value;
});
},
),
RadioListTile<Options>(
title: const Text('Option 2'),
value: Options.option2,
groupValue: _selectedOption,
onChanged: (value) {
setState(() {
_selectedOption = value;
});
},
),
RadioListTile<Options>(
title: const Text('Option 3'),
value: Options.option3,
groupValue: _selectedOption,
onChanged: null,
),
],
),
);
}
}
class ProgressIndicators extends StatefulWidget {
const ProgressIndicators({super.key});
@override
State<ProgressIndicators> createState() => _ProgressIndicatorsState();
}
class _ProgressIndicatorsState extends State<ProgressIndicators> {
bool playProgressIndicator = false;
@override
Widget build(BuildContext context) {
final double? progressValue = playProgressIndicator ? null : 0.7;
return ComponentDecoration(
label: 'Progress indicators',
tooltipMessage:
'Use CircularProgressIndicator or LinearProgressIndicator',
child: Column(
children: <Widget>[
Row(
children: [
IconButton(
isSelected: playProgressIndicator,
selectedIcon: const Icon(Icons.pause),
icon: const Icon(Icons.play_arrow),
onPressed: () {
setState(() {
playProgressIndicator = !playProgressIndicator;
});
},
),
Expanded(
child: Row(
children: <Widget>[
rowDivider,
CircularProgressIndicator(
value: progressValue,
),
rowDivider,
Expanded(
child: LinearProgressIndicator(
value: progressValue,
),
),
rowDivider,
],
),
),
],
),
],
),
);
}
}
const List<NavigationDestination> appBarDestinations = [
NavigationDestination(
tooltip: '',
icon: Icon(Icons.widgets_outlined),
label: 'Components',
selectedIcon: Icon(Icons.widgets),
),
NavigationDestination(
tooltip: '',
icon: Icon(Icons.format_paint_outlined),
label: 'Color',
selectedIcon: Icon(Icons.format_paint),
),
NavigationDestination(
tooltip: '',
icon: Icon(Icons.text_snippet_outlined),
label: 'Typography',
selectedIcon: Icon(Icons.text_snippet),
),
NavigationDestination(
tooltip: '',
icon: Icon(Icons.invert_colors_on_outlined),
label: 'Elevation',
selectedIcon: Icon(Icons.opacity),
)
];
const List<Widget> exampleBarDestinations = [
NavigationDestination(
tooltip: '',
icon: Icon(Icons.explore_outlined),
label: 'Explore',
selectedIcon: Icon(Icons.explore),
),
NavigationDestination(
tooltip: '',
icon: Icon(Icons.pets_outlined),
label: 'Pets',
selectedIcon: Icon(Icons.pets),
),
NavigationDestination(
tooltip: '',
icon: Icon(Icons.account_box_outlined),
label: 'Account',
selectedIcon: Icon(Icons.account_box),
)
];
List<Widget> barWithBadgeDestinations = [
NavigationDestination(
tooltip: '',
icon: Badge.count(count: 1000, child: const Icon(Icons.mail_outlined)),
label: 'Mail',
selectedIcon: Badge.count(count: 1000, child: const Icon(Icons.mail)),
),
const NavigationDestination(
tooltip: '',
icon: Badge(label: Text('10'), child: Icon(Icons.chat_bubble_outline)),
label: 'Chat',
selectedIcon: Badge(label: Text('10'), child: Icon(Icons.chat_bubble)),
),
const NavigationDestination(
tooltip: '',
icon: Badge(child: Icon(Icons.group_outlined)),
label: 'Rooms',
selectedIcon: Badge(child: Icon(Icons.group_rounded)),
),
NavigationDestination(
tooltip: '',
icon: Badge.count(count: 3, child: const Icon(Icons.videocam_outlined)),
label: 'Meet',
selectedIcon: Badge.count(count: 3, child: const Icon(Icons.videocam)),
)
];
class NavigationBars extends StatefulWidget {
const NavigationBars({
super.key,
this.onSelectItem,
required this.selectedIndex,
required this.isExampleBar,
this.isBadgeExample = false,
});
final void Function(int)? onSelectItem;
final int selectedIndex;
final bool isExampleBar;
final bool isBadgeExample;
@override
State<NavigationBars> createState() => _NavigationBarsState();
}
class _NavigationBarsState extends State<NavigationBars> {
late int selectedIndex;
@override
void initState() {
super.initState();
selectedIndex = widget.selectedIndex;
}
@override
void didUpdateWidget(covariant NavigationBars oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedIndex != oldWidget.selectedIndex) {
selectedIndex = widget.selectedIndex;
}
}
@override
Widget build(BuildContext context) {
// App NavigationBar should get first focus.
Widget navigationBar = Focus(
autofocus: !(widget.isExampleBar || widget.isBadgeExample),
child: NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: (index) {
setState(() {
selectedIndex = index;
});
if (!widget.isExampleBar) widget.onSelectItem!(index);
},
destinations: widget.isExampleBar && widget.isBadgeExample
? barWithBadgeDestinations
: widget.isExampleBar
? exampleBarDestinations
: appBarDestinations,
),
);
if (widget.isExampleBar && widget.isBadgeExample) {
navigationBar = ComponentDecoration(
label: 'Badges',
tooltipMessage: 'Use Badge or Badge.count',
child: navigationBar);
} else if (widget.isExampleBar) {
navigationBar = ComponentDecoration(
label: 'Navigation bar',
tooltipMessage: 'Use NavigationBar',
child: navigationBar);
}
return navigationBar;
}
}
class IconToggleButtons extends StatefulWidget {
const IconToggleButtons({super.key});
@override
State<IconToggleButtons> createState() => _IconToggleButtonsState();
}
class _IconToggleButtonsState extends State<IconToggleButtons> {
bool standardSelected = false;
bool filledSelected = false;
bool tonalSelected = false;
bool outlinedSelected = false;
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Icon buttons',
tooltipMessage:
'Use IconButton, IconButton.filled, IconButton.filledTonal, and IconButton.outlined',
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Column(
// Standard IconButton
children: <Widget>[
IconButton(
isSelected: standardSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: () {
setState(() {
standardSelected = !standardSelected;
});
},
),
colDivider,
IconButton(
isSelected: standardSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: null,
),
],
),
Column(
children: <Widget>[
// Filled IconButton
IconButton.filled(
isSelected: filledSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: () {
setState(() {
filledSelected = !filledSelected;
});
},
),
colDivider,
IconButton.filled(
isSelected: filledSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: null,
),
],
),
Column(
children: <Widget>[
// Filled Tonal IconButton
IconButton.filledTonal(
isSelected: tonalSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: () {
setState(() {
tonalSelected = !tonalSelected;
});
},
),
colDivider,
IconButton.filledTonal(
isSelected: tonalSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: null,
),
],
),
Column(
children: <Widget>[
// Outlined IconButton
IconButton.outlined(
isSelected: outlinedSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: () {
setState(() {
outlinedSelected = !outlinedSelected;
});
},
),
colDivider,
IconButton.outlined(
isSelected: outlinedSelected,
icon: const Icon(Icons.settings_outlined),
selectedIcon: const Icon(Icons.settings),
onPressed: null,
),
],
),
],
),
);
}
}
class Chips extends StatefulWidget {
const Chips({super.key});
@override
State<Chips> createState() => _ChipsState();
}
class _ChipsState extends State<Chips> {
bool isFiltered = true;
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Chips',
tooltipMessage:
'Use ActionChip, FilterChip, or InputChip. \nActionChip can also be used for suggestion chip',
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Wrap(
spacing: smallSpacing,
runSpacing: smallSpacing,
children: <Widget>[
ActionChip(
label: const Text('Assist'),
avatar: const Icon(Icons.event),
onPressed: () {},
),
FilterChip(
label: const Text('Filter'),
selected: isFiltered,
onSelected: (selected) {
setState(() => isFiltered = selected);
},
),
InputChip(
label: const Text('Input'),
onPressed: () {},
onDeleted: () {},
),
ActionChip(
label: const Text('Suggestion'),
onPressed: () {},
),
],
),
colDivider,
Wrap(
spacing: smallSpacing,
runSpacing: smallSpacing,
children: <Widget>[
const ActionChip(
label: Text('Assist'),
avatar: Icon(Icons.event),
),
FilterChip(
label: const Text('Filter'),
selected: isFiltered,
onSelected: null,
),
InputChip(
label: const Text('Input'),
onDeleted: () {},
isEnabled: false,
),
const ActionChip(
label: Text('Suggestion'),
),
],
),
],
),
);
}
}
class DatePickers extends StatefulWidget {
const DatePickers({super.key});
@override
State<DatePickers> createState() => _DatePickersState();
}
class _DatePickersState extends State<DatePickers> {
DateTime? selectedDate;
final DateTime _firstDate = DateTime(DateTime.now().year - 2);
final DateTime _lastDate = DateTime(DateTime.now().year + 1);
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Date picker',
tooltipMessage: 'Use showDatePicker',
child: TextButton(
onPressed: () async {
DateTime? date = await showDatePicker(
context: context,
initialDate: selectedDate ?? DateTime.now(),
firstDate: _firstDate,
lastDate: _lastDate,
);
setState(() {
selectedDate = date;
if (selectedDate != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
'Selected Date: ${selectedDate!.day}/${selectedDate!.month}/${selectedDate!.year}'),
));
}
});
},
child: const Text(
'Show date picker',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
);
}
}
class TimePickers extends StatefulWidget {
const TimePickers({super.key});
@override
State<TimePickers> createState() => _TimePickersState();
}
class _TimePickersState extends State<TimePickers> {
TimeOfDay? selectedTime;
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Time picker',
tooltipMessage: 'Use showTimePicker',
child: TextButton(
onPressed: () async {
final TimeOfDay? time = await showTimePicker(
context: context,
initialTime: selectedTime ?? TimeOfDay.now(),
builder: (context, child) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(
alwaysUse24HourFormat: true,
),
child: child!,
);
},
);
setState(() {
selectedTime = time;
if (selectedTime != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content:
Text('Selected time: ${selectedTime!.format(context)}'),
));
}
});
},
child: const Text(
'Show time picker',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
);
}
}
class SegmentedButtons extends StatelessWidget {
const SegmentedButtons({super.key});
@override
Widget build(BuildContext context) {
return const ComponentDecoration(
label: 'Segmented buttons',
tooltipMessage: 'Use SegmentedButton<T>',
child: Column(
children: <Widget>[
SingleChoice(),
colDivider,
MultipleChoice(),
],
),
);
}
}
enum Calendar { day, week, month, year }
class SingleChoice extends StatefulWidget {
const SingleChoice({super.key});
@override
State<SingleChoice> createState() => _SingleChoiceState();
}
class _SingleChoiceState extends State<SingleChoice> {
Calendar calendarView = Calendar.day;
@override
Widget build(BuildContext context) {
return SegmentedButton<Calendar>(
segments: const <ButtonSegment<Calendar>>[
ButtonSegment<Calendar>(
value: Calendar.day,
label: Text('Day'),
icon: Icon(Icons.calendar_view_day)),
ButtonSegment<Calendar>(
value: Calendar.week,
label: Text('Week'),
icon: Icon(Icons.calendar_view_week)),
ButtonSegment<Calendar>(
value: Calendar.month,
label: Text('Month'),
icon: Icon(Icons.calendar_view_month)),
ButtonSegment<Calendar>(
value: Calendar.year,
label: Text('Year'),
icon: Icon(Icons.calendar_today)),
],
selected: <Calendar>{calendarView},
onSelectionChanged: (newSelection) {
setState(() {
// By default there is only a single segment that can be
// selected at one time, so its value is always the first
// item in the selected set.
calendarView = newSelection.first;
});
},
);
}
}
enum Sizes { extraSmall, small, medium, large, extraLarge }
class MultipleChoice extends StatefulWidget {
const MultipleChoice({super.key});
@override
State<MultipleChoice> createState() => _MultipleChoiceState();
}
class _MultipleChoiceState extends State<MultipleChoice> {
Set<Sizes> selection = <Sizes>{Sizes.large, Sizes.extraLarge};
@override
Widget build(BuildContext context) {
return SegmentedButton<Sizes>(
segments: const <ButtonSegment<Sizes>>[
ButtonSegment<Sizes>(value: Sizes.extraSmall, label: Text('XS')),
ButtonSegment<Sizes>(value: Sizes.small, label: Text('S')),
ButtonSegment<Sizes>(value: Sizes.medium, label: Text('M')),
ButtonSegment<Sizes>(
value: Sizes.large,
label: Text('L'),
),
ButtonSegment<Sizes>(value: Sizes.extraLarge, label: Text('XL')),
],
selected: selection,
onSelectionChanged: (newSelection) {
setState(() {
selection = newSelection;
});
},
multiSelectionEnabled: true,
);
}
}
class SnackBarSection extends StatelessWidget {
const SnackBarSection({super.key});
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Snackbar',
tooltipMessage:
'Use ScaffoldMessenger.of(context).showSnackBar with SnackBar',
child: TextButton(
onPressed: () {
final snackBar = SnackBar(
behavior: SnackBarBehavior.floating,
width: 400.0,
content: const Text('This is a snackbar'),
action: SnackBarAction(
label: 'Close',
onPressed: () {},
),
);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
child: const Text(
'Show snackbar',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
);
}
}
class BottomSheetSection extends StatefulWidget {
const BottomSheetSection({super.key});
@override
State<BottomSheetSection> createState() => _BottomSheetSectionState();
}
class _BottomSheetSectionState extends State<BottomSheetSection> {
bool isNonModalBottomSheetOpen = false;
PersistentBottomSheetController<void>? _nonModalBottomSheetController;
@override
Widget build(BuildContext context) {
List<Widget> buttonList = <Widget>[
IconButton(onPressed: () {}, icon: const Icon(Icons.share_outlined)),
IconButton(onPressed: () {}, icon: const Icon(Icons.add)),
IconButton(onPressed: () {}, icon: const Icon(Icons.delete_outline)),
IconButton(onPressed: () {}, icon: const Icon(Icons.archive_outlined)),
IconButton(onPressed: () {}, icon: const Icon(Icons.settings_outlined)),
IconButton(onPressed: () {}, icon: const Icon(Icons.favorite_border)),
];
List<Text> labelList = const <Text>[
Text('Share'),
Text('Add to'),
Text('Trash'),
Text('Archive'),
Text('Settings'),
Text('Favorite')
];
buttonList = List.generate(
buttonList.length,
(index) => Padding(
padding: const EdgeInsets.fromLTRB(20.0, 30.0, 20.0, 20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
buttonList[index],
labelList[index],
],
),
));
return ComponentDecoration(
label: 'Bottom sheet',
tooltipMessage: 'Use showModalBottomSheet<T> or showBottomSheet<T>',
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
children: [
TextButton(
child: const Text(
'Show modal bottom sheet',
style: TextStyle(fontWeight: FontWeight.bold),
),
onPressed: () {
showModalBottomSheet<void>(
showDragHandle: true,
context: context,
// TODO: Remove when this is in the framework https://github.com/flutter/flutter/issues/118619
constraints: const BoxConstraints(maxWidth: 640),
builder: (context) {
return SizedBox(
height: 150,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: buttonList,
),
),
);
},
);
},
),
TextButton(
child: Text(
isNonModalBottomSheetOpen
? 'Hide bottom sheet'
: 'Show bottom sheet',
style: const TextStyle(fontWeight: FontWeight.bold),
),
onPressed: () {
if (isNonModalBottomSheetOpen) {
_nonModalBottomSheetController?.close();
setState(() {
isNonModalBottomSheetOpen = false;
});
return;
} else {
setState(() {
isNonModalBottomSheetOpen = true;
});
}
_nonModalBottomSheetController = showBottomSheet<void>(
elevation: 8.0,
context: context,
// TODO: Remove when this is in the framework https://github.com/flutter/flutter/issues/118619
constraints: const BoxConstraints(maxWidth: 640),
builder: (context) {
return SizedBox(
height: 150,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
children: buttonList,
),
),
);
},
);
},
),
],
),
);
}
}
class BottomAppBars extends StatelessWidget {
const BottomAppBars({super.key});
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Bottom app bar',
tooltipMessage: 'Use BottomAppBar',
child: Column(
children: [
SizedBox(
height: 80,
child: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {},
elevation: 0.0,
child: const Icon(Icons.add),
),
floatingActionButtonLocation:
FloatingActionButtonLocation.endContained,
bottomNavigationBar: BottomAppBar(
child: Row(
children: <Widget>[
const IconButtonAnchorExample(),
IconButton(
tooltip: 'Search',
icon: const Icon(Icons.search),
onPressed: () {},
),
IconButton(
tooltip: 'Favorite',
icon: const Icon(Icons.favorite),
onPressed: () {},
),
],
),
),
),
),
],
),
);
}
}
class IconButtonAnchorExample extends StatelessWidget {
const IconButtonAnchorExample({super.key});
@override
Widget build(BuildContext context) {
return MenuAnchor(
builder: (context, controller, child) {
return IconButton(
onPressed: () {
if (controller.isOpen) {
controller.close();
} else {
controller.open();
}
},
icon: const Icon(Icons.more_vert),
);
},
menuChildren: [
MenuItemButton(
child: const Text('Menu 1'),
onPressed: () {},
),
MenuItemButton(
child: const Text('Menu 2'),
onPressed: () {},
),
SubmenuButton(
menuChildren: <Widget>[
MenuItemButton(
onPressed: () {},
child: const Text('Menu 3.1'),
),
MenuItemButton(
onPressed: () {},
child: const Text('Menu 3.2'),
),
MenuItemButton(
onPressed: () {},
child: const Text('Menu 3.3'),
),
],
child: const Text('Menu 3'),
),
],
);
}
}
class ButtonAnchorExample extends StatelessWidget {
const ButtonAnchorExample({super.key});
@override
Widget build(BuildContext context) {
return MenuAnchor(
builder: (context, controller, child) {
return FilledButton.tonal(
onPressed: () {
if (controller.isOpen) {
controller.close();
} else {
controller.open();
}
},
child: const Text('Show menu'),
);
},
menuChildren: [
MenuItemButton(
leadingIcon: const Icon(Icons.people_alt_outlined),
child: const Text('Item 1'),
onPressed: () {},
),
MenuItemButton(
leadingIcon: const Icon(Icons.remove_red_eye_outlined),
child: const Text('Item 2'),
onPressed: () {},
),
MenuItemButton(
leadingIcon: const Icon(Icons.refresh),
onPressed: () {},
child: const Text('Item 3'),
),
],
);
}
}
class NavigationDrawers extends StatelessWidget {
const NavigationDrawers({super.key, required this.scaffoldKey});
final GlobalKey<ScaffoldState> scaffoldKey;
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Navigation drawer',
tooltipMessage:
'Use NavigationDrawer. For modal navigation drawers, see Scaffold.endDrawer',
child: Column(
children: [
const SizedBox(height: 520, child: NavigationDrawerSection()),
colDivider,
colDivider,
TextButton(
child: const Text('Show modal navigation drawer',
style: TextStyle(fontWeight: FontWeight.bold)),
onPressed: () {
scaffoldKey.currentState!.openEndDrawer();
},
),
],
),
);
}
}
class NavigationDrawerSection extends StatefulWidget {
const NavigationDrawerSection({super.key});
@override
State<NavigationDrawerSection> createState() =>
_NavigationDrawerSectionState();
}
class _NavigationDrawerSectionState extends State<NavigationDrawerSection> {
int navDrawerIndex = 0;
@override
Widget build(BuildContext context) {
return NavigationDrawer(
onDestinationSelected: (selectedIndex) {
setState(() {
navDrawerIndex = selectedIndex;
});
},
selectedIndex: navDrawerIndex,
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(28, 16, 16, 10),
child: Text(
'Mail',
style: Theme.of(context).textTheme.titleSmall,
),
),
...destinations.map((destination) {
return NavigationDrawerDestination(
label: Text(destination.label),
icon: destination.icon,
selectedIcon: destination.selectedIcon,
);
}),
const Divider(indent: 28, endIndent: 28),
Padding(
padding: const EdgeInsets.fromLTRB(28, 16, 16, 10),
child: Text(
'Labels',
style: Theme.of(context).textTheme.titleSmall,
),
),
...labelDestinations.map((destination) {
return NavigationDrawerDestination(
label: Text(destination.label),
icon: destination.icon,
selectedIcon: destination.selectedIcon,
);
}),
],
);
}
}
class ExampleDestination {
const ExampleDestination(this.label, this.icon, this.selectedIcon);
final String label;
final Widget icon;
final Widget selectedIcon;
}
const List<ExampleDestination> destinations = <ExampleDestination>[
ExampleDestination('Inbox', Icon(Icons.inbox_outlined), Icon(Icons.inbox)),
ExampleDestination('Outbox', Icon(Icons.send_outlined), Icon(Icons.send)),
ExampleDestination(
'Favorites', Icon(Icons.favorite_outline), Icon(Icons.favorite)),
ExampleDestination('Trash', Icon(Icons.delete_outline), Icon(Icons.delete)),
];
const List<ExampleDestination> labelDestinations = <ExampleDestination>[
ExampleDestination(
'Family', Icon(Icons.bookmark_border), Icon(Icons.bookmark)),
ExampleDestination(
'School', Icon(Icons.bookmark_border), Icon(Icons.bookmark)),
ExampleDestination('Work', Icon(Icons.bookmark_border), Icon(Icons.bookmark)),
];
class NavigationRails extends StatelessWidget {
const NavigationRails({super.key});
@override
Widget build(BuildContext context) {
return const ComponentDecoration(
label: 'Navigation rail',
tooltipMessage: 'Use NavigationRail',
child: IntrinsicWidth(
child: SizedBox(height: 420, child: NavigationRailSection())),
);
}
}
class NavigationRailSection extends StatefulWidget {
const NavigationRailSection({super.key});
@override
State<NavigationRailSection> createState() => _NavigationRailSectionState();
}
class _NavigationRailSectionState extends State<NavigationRailSection> {
int navRailIndex = 0;
@override
Widget build(BuildContext context) {
return NavigationRail(
onDestinationSelected: (selectedIndex) {
setState(() {
navRailIndex = selectedIndex;
});
},
elevation: 4,
leading: FloatingActionButton(
child: const Icon(Icons.create), onPressed: () {}),
groupAlignment: 0.0,
selectedIndex: navRailIndex,
labelType: NavigationRailLabelType.selected,
destinations: <NavigationRailDestination>[
...destinations.map((destination) {
return NavigationRailDestination(
label: Text(destination.label),
icon: destination.icon,
selectedIcon: destination.selectedIcon,
);
}),
],
);
}
}
class Tabs extends StatefulWidget {
const Tabs({super.key});
@override
State<Tabs> createState() => _TabsState();
}
class _TabsState extends State<Tabs> with TickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Tabs',
tooltipMessage: 'Use TabBar',
child: SizedBox(
height: 80,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _tabController,
tabs: const <Widget>[
Tab(
icon: Icon(Icons.videocam_outlined),
text: 'Video',
iconMargin: EdgeInsets.only(bottom: 0.0),
),
Tab(
icon: Icon(Icons.photo_outlined),
text: 'Photos',
iconMargin: EdgeInsets.only(bottom: 0.0),
),
Tab(
icon: Icon(Icons.audiotrack_sharp),
text: 'Audio',
iconMargin: EdgeInsets.only(bottom: 0.0),
),
],
),
// TODO: Showcase secondary tab bar https://github.com/flutter/flutter/issues/111962
),
),
),
);
}
}
class TopAppBars extends StatelessWidget {
const TopAppBars({super.key});
static final actions = [
IconButton(icon: const Icon(Icons.attach_file), onPressed: () {}),
IconButton(icon: const Icon(Icons.event), onPressed: () {}),
IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
];
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Top app bars',
tooltipMessage:
'Use AppBar, SliverAppBar, SliverAppBar.medium, or SliverAppBar.large',
child: Column(
children: [
AppBar(
title: const Text('Center-aligned'),
leading: const BackButton(),
actions: [
IconButton(
iconSize: 32,
icon: const Icon(Icons.account_circle_outlined),
onPressed: () {},
),
],
centerTitle: true,
),
colDivider,
AppBar(
title: const Text('Small'),
leading: const BackButton(),
actions: actions,
centerTitle: false,
),
colDivider,
SizedBox(
height: 100,
child: CustomScrollView(
slivers: [
SliverAppBar.medium(
title: const Text('Medium'),
leading: const BackButton(),
actions: actions,
),
const SliverFillRemaining(),
],
),
),
colDivider,
SizedBox(
height: 130,
child: CustomScrollView(
slivers: [
SliverAppBar.large(
title: const Text('Large'),
leading: const BackButton(),
actions: actions,
),
const SliverFillRemaining(),
],
),
),
],
),
);
}
}
class Menus extends StatefulWidget {
const Menus({super.key});
@override
State<Menus> createState() => _MenusState();
}
class _MenusState extends State<Menus> {
final TextEditingController colorController = TextEditingController();
final TextEditingController iconController = TextEditingController();
IconLabel? selectedIcon = IconLabel.smile;
ColorLabel? selectedColor;
@override
Widget build(BuildContext context) {
final List<DropdownMenuEntry<ColorLabel>> colorEntries =
<DropdownMenuEntry<ColorLabel>>[];
for (final ColorLabel color in ColorLabel.values) {
colorEntries.add(DropdownMenuEntry<ColorLabel>(
value: color, label: color.label, enabled: color.label != 'Grey'));
}
final List<DropdownMenuEntry<IconLabel>> iconEntries =
<DropdownMenuEntry<IconLabel>>[];
for (final IconLabel icon in IconLabel.values) {
iconEntries
.add(DropdownMenuEntry<IconLabel>(value: icon, label: icon.label));
}
return ComponentDecoration(
label: 'Menus',
tooltipMessage: 'Use MenuAnchor or DropdownMenu<T>',
child: Column(
children: [
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ButtonAnchorExample(),
rowDivider,
IconButtonAnchorExample(),
],
),
colDivider,
Wrap(
alignment: WrapAlignment.spaceAround,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: smallSpacing,
runSpacing: smallSpacing,
children: [
DropdownMenu<ColorLabel>(
controller: colorController,
label: const Text('Color'),
enableFilter: true,
dropdownMenuEntries: colorEntries,
inputDecorationTheme: const InputDecorationTheme(filled: true),
onSelected: (color) {
setState(() {
selectedColor = color;
});
},
),
DropdownMenu<IconLabel>(
initialSelection: IconLabel.smile,
controller: iconController,
leadingIcon: const Icon(Icons.search),
label: const Text('Icon'),
dropdownMenuEntries: iconEntries,
onSelected: (icon) {
setState(() {
selectedIcon = icon;
});
},
),
Icon(
selectedIcon?.icon,
color: selectedColor?.color ?? Colors.grey.withOpacity(0.5),
)
],
),
],
),
);
}
}
enum ColorLabel {
blue('Blue', Colors.blue),
pink('Pink', Colors.pink),
green('Green', Colors.green),
yellow('Yellow', Colors.yellow),
grey('Grey', Colors.grey);
const ColorLabel(this.label, this.color);
final String label;
final Color color;
}
enum IconLabel {
smile('Smile', Icons.sentiment_satisfied_outlined),
cloud(
'Cloud',
Icons.cloud_outlined,
),
brush('Brush', Icons.brush_outlined),
heart('Heart', Icons.favorite);
const IconLabel(this.label, this.icon);
final String label;
final IconData icon;
}
class Sliders extends StatefulWidget {
const Sliders({super.key});
@override
State<Sliders> createState() => _SlidersState();
}
class _SlidersState extends State<Sliders> {
double sliderValue0 = 30.0;
double sliderValue1 = 20.0;
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Sliders',
tooltipMessage: 'Use Slider or RangeSlider',
child: Column(
children: <Widget>[
Slider(
max: 100,
value: sliderValue0,
onChanged: (value) {
setState(() {
sliderValue0 = value;
});
},
),
const SizedBox(height: 20),
Slider(
max: 100,
divisions: 5,
value: sliderValue1,
label: sliderValue1.round().toString(),
onChanged: (value) {
setState(() {
sliderValue1 = value;
});
},
),
],
));
}
}
class SearchAnchors extends StatefulWidget {
const SearchAnchors({super.key});
@override
State<SearchAnchors> createState() => _SearchAnchorsState();
}
class _SearchAnchorsState extends State<SearchAnchors> {
String? selectedColor;
List<ColorItem> searchHistory = <ColorItem>[];
Iterable<Widget> getHistoryList(SearchController controller) {
return searchHistory.map((color) => ListTile(
leading: const Icon(Icons.history),
title: Text(color.label),
trailing: IconButton(
icon: const Icon(Icons.call_missed),
onPressed: () {
controller.text = color.label;
controller.selection =
TextSelection.collapsed(offset: controller.text.length);
}),
onTap: () {
controller.closeView(color.label);
handleSelection(color);
},
));
}
Iterable<Widget> getSuggestions(SearchController controller) {
final String input = controller.value.text;
return ColorItem.values
.where((color) => color.label.contains(input))
.map((filteredColor) => ListTile(
leading: CircleAvatar(backgroundColor: filteredColor.color),
title: Text(filteredColor.label),
trailing: IconButton(
icon: const Icon(Icons.call_missed),
onPressed: () {
controller.text = filteredColor.label;
controller.selection =
TextSelection.collapsed(offset: controller.text.length);
}),
onTap: () {
controller.closeView(filteredColor.label);
handleSelection(filteredColor);
},
));
}
void handleSelection(ColorItem color) {
setState(() {
selectedColor = color.label;
if (searchHistory.length >= 5) {
searchHistory.removeLast();
}
searchHistory.insert(0, color);
});
}
@override
Widget build(BuildContext context) {
return ComponentDecoration(
label: 'Search',
tooltipMessage: 'Use SearchAnchor or SearchAnchor.bar',
child: Column(
children: <Widget>[
SearchAnchor.bar(
barHintText: 'Search colors',
suggestionsBuilder: (context, controller) {
if (controller.text.isEmpty) {
if (searchHistory.isNotEmpty) {
return getHistoryList(controller);
}
return <Widget>[
const Center(
child: Text('No search history.',
style: TextStyle(color: Colors.grey)),
)
];
}
return getSuggestions(controller);
},
),
const SizedBox(height: 20),
if (selectedColor == null)
const Text('Select a color')
else
Text('Last selected color is $selectedColor')
],
),
);
}
}
class ComponentDecoration extends StatefulWidget {
const ComponentDecoration({
super.key,
required this.label,
required this.child,
this.tooltipMessage = '',
});
final String label;
final Widget child;
final String? tooltipMessage;
@override
State<ComponentDecoration> createState() => _ComponentDecorationState();
}
class _ComponentDecorationState extends State<ComponentDecoration> {
final focusNode = FocusNode();
@override
Widget build(BuildContext context) {
return RepaintBoundary(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: smallSpacing),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(widget.label,
style: Theme.of(context).textTheme.titleSmall),
Tooltip(
message: widget.tooltipMessage,
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
child: Icon(Icons.info_outline, size: 16)),
),
],
),
ConstrainedBox(
constraints:
const BoxConstraints.tightFor(width: widthConstraint),
// Tapping within the a component card should request focus
// for that component's children.
child: Focus(
focusNode: focusNode,
canRequestFocus: true,
child: GestureDetector(
onTapDown: (_) {
focusNode.requestFocus();
},
behavior: HitTestBehavior.opaque,
child: Card(
elevation: 0,
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).colorScheme.outlineVariant,
),
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 5.0, vertical: 20.0),
child: Center(
child: widget.child,
),
),
),
),
),
),
],
),
),
);
}
}
class ComponentGroupDecoration extends StatelessWidget {
const ComponentGroupDecoration(
{super.key, required this.label, required this.children});
final String label;
final List<Widget> children;
@override
Widget build(BuildContext context) {
// Fully traverse this component group before moving on
return FocusTraversalGroup(
child: Card(
margin: EdgeInsets.zero,
elevation: 0,
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Center(
child: Column(
children: [
Text(label, style: Theme.of(context).textTheme.titleLarge),
colDivider,
...children
],
),
),
),
),
);
}
}
enum ColorItem {
red('red', Colors.red),
orange('orange', Colors.orange),
yellow('yellow', Colors.yellow),
green('green', Colors.green),
blue('blue', Colors.blue),
indigo('indigo', Colors.indigo),
violet('violet', Color(0xFF8F00FF)),
purple('purple', Colors.purple),
pink('pink', Colors.pink),
silver('silver', Color(0xFF808080)),
gold('gold', Color(0xFFFFD700)),
beige('beige', Color(0xFFF5F5DC)),
brown('brown', Colors.brown),
grey('grey', Colors.grey),
black('black', Colors.black),
white('white', Colors.white);
const ColorItem(this.label, this.color);
final String label;
final Color color;
}