| // Copyright 2014 The Flutter Authors. 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/foundation.dart' show clampDouble; |
| import 'package:flutter/widgets.dart'; |
| |
| import 'chip.dart'; |
| import 'chip_theme.dart'; |
| import 'color_scheme.dart'; |
| import 'colors.dart'; |
| import 'debug.dart'; |
| import 'icons.dart'; |
| import 'material_state.dart'; |
| import 'text_theme.dart'; |
| import 'theme.dart'; |
| import 'theme_data.dart'; |
| |
| /// A Material Design input chip. |
| /// |
| /// Input chips represent a complex piece of information, such as an entity |
| /// (person, place, or thing) or conversational text, in a compact form. |
| /// |
| /// Input chips can be made selectable by setting [onSelected], deletable by |
| /// setting [onDeleted], and pressable like a button with [onPressed]. They have |
| /// a [label], and they can have a leading icon (see [avatar]) and a trailing |
| /// icon ([deleteIcon]). Colors and padding can be customized. |
| /// |
| /// Requires one of its ancestors to be a [Material] widget. |
| /// |
| /// Input chips work together with other UI elements. They can appear: |
| /// |
| /// * In a [Wrap] widget. |
| /// * In a horizontally scrollable list, for example configured such as a |
| /// [ListView] with [ListView.scrollDirection] set to [Axis.horizontal]. |
| /// |
| /// {@tool dartpad} |
| /// This example shows how to create [InputChip]s with [onSelected] and |
| /// [onDeleted] callbacks. When the user taps the chip, the chip will be selected. |
| /// When the user taps the delete icon, the chip will be deleted. |
| /// |
| /// ** See code in examples/api/lib/material/input_chip/input_chip.0.dart ** |
| /// {@end-tool} |
| /// |
| /// ## Material Design 3 |
| /// |
| /// [InputChip] can be used for Input chips from Material Design 3. |
| /// If [ThemeData.useMaterial3] is true, then [InputChip] |
| /// will be styled to match the Material Design 3 specification for Input |
| /// chips. |
| /// |
| /// See also: |
| /// |
| /// * [Chip], a chip that displays information and can be deleted. |
| /// * [ChoiceChip], allows a single selection from a set of options. Choice |
| /// chips contain related descriptive text or categories. |
| /// * [FilterChip], uses tags or descriptive words as a way to filter content. |
| /// * [ActionChip], represents an action related to primary content. |
| /// * [CircleAvatar], which shows images or initials of people. |
| /// * [Wrap], A widget that displays its children in multiple horizontal or |
| /// vertical runs. |
| /// * <https://material.io/design/components/chips.html> |
| class InputChip extends StatelessWidget |
| implements |
| ChipAttributes, |
| DeletableChipAttributes, |
| SelectableChipAttributes, |
| CheckmarkableChipAttributes, |
| DisabledChipAttributes, |
| TappableChipAttributes { |
| /// Creates an [InputChip]. |
| /// |
| /// The [onPressed] and [onSelected] callbacks must not both be specified at |
| /// the same time. |
| /// |
| /// The [label], [isEnabled], [selected], [autofocus], and [clipBehavior] |
| /// arguments must not be null. The [pressElevation] and [elevation] must be |
| /// null or non-negative. Typically, [pressElevation] is greater than |
| /// [elevation]. |
| const InputChip({ |
| super.key, |
| this.avatar, |
| required this.label, |
| this.labelStyle, |
| this.labelPadding, |
| this.selected = false, |
| this.isEnabled = true, |
| this.onSelected, |
| this.deleteIcon, |
| this.onDeleted, |
| this.deleteIconColor, |
| this.deleteButtonTooltipMessage, |
| this.onPressed, |
| this.pressElevation, |
| this.disabledColor, |
| this.selectedColor, |
| this.tooltip, |
| this.side, |
| this.shape, |
| this.clipBehavior = Clip.none, |
| this.focusNode, |
| this.autofocus = false, |
| this.color, |
| this.backgroundColor, |
| this.padding, |
| this.visualDensity, |
| this.materialTapTargetSize, |
| this.elevation, |
| this.shadowColor, |
| this.surfaceTintColor, |
| this.iconTheme, |
| this.selectedShadowColor, |
| this.showCheckmark, |
| this.checkmarkColor, |
| this.avatarBorder = const CircleBorder(), |
| @Deprecated( |
| 'Migrate to deleteButtonTooltipMessage. ' |
| 'This feature was deprecated after v2.10.0-0.3.pre.' |
| ) |
| this.useDeleteButtonTooltip = true, |
| }) : assert(pressElevation == null || pressElevation >= 0.0), |
| assert(elevation == null || elevation >= 0.0); |
| |
| @override |
| final Widget? avatar; |
| @override |
| final Widget label; |
| @override |
| final TextStyle? labelStyle; |
| @override |
| final EdgeInsetsGeometry? labelPadding; |
| @override |
| final bool selected; |
| @override |
| final bool isEnabled; |
| @override |
| final ValueChanged<bool>? onSelected; |
| @override |
| final Widget? deleteIcon; |
| @override |
| final VoidCallback? onDeleted; |
| @override |
| final Color? deleteIconColor; |
| @override |
| final String? deleteButtonTooltipMessage; |
| @override |
| final VoidCallback? onPressed; |
| @override |
| final double? pressElevation; |
| @override |
| final Color? disabledColor; |
| @override |
| final Color? selectedColor; |
| @override |
| final String? tooltip; |
| @override |
| final BorderSide? side; |
| @override |
| final OutlinedBorder? shape; |
| @override |
| final Clip clipBehavior; |
| @override |
| final FocusNode? focusNode; |
| @override |
| final bool autofocus; |
| @override |
| final MaterialStateProperty<Color?>? color; |
| @override |
| final Color? backgroundColor; |
| @override |
| final EdgeInsetsGeometry? padding; |
| @override |
| final VisualDensity? visualDensity; |
| @override |
| final MaterialTapTargetSize? materialTapTargetSize; |
| @override |
| final double? elevation; |
| @override |
| final Color? shadowColor; |
| @override |
| final Color? surfaceTintColor; |
| @override |
| final Color? selectedShadowColor; |
| @override |
| final bool? showCheckmark; |
| @override |
| final Color? checkmarkColor; |
| @override |
| final ShapeBorder avatarBorder; |
| @override |
| final IconThemeData? iconTheme; |
| @override |
| @Deprecated( |
| 'Migrate to deleteButtonTooltipMessage. ' |
| 'This feature was deprecated after v2.10.0-0.3.pre.' |
| ) |
| final bool useDeleteButtonTooltip; |
| |
| @override |
| Widget build(BuildContext context) { |
| assert(debugCheckHasMaterial(context)); |
| final ChipThemeData? defaults = Theme.of(context).useMaterial3 |
| ? _InputChipDefaultsM3(context, isEnabled, selected) |
| : null; |
| final Widget? resolvedDeleteIcon = deleteIcon |
| ?? (Theme.of(context).useMaterial3 ? const Icon(Icons.clear, size: 18) : null); |
| return RawChip( |
| defaultProperties: defaults, |
| avatar: avatar, |
| label: label, |
| labelStyle: labelStyle, |
| labelPadding: labelPadding, |
| deleteIcon: resolvedDeleteIcon, |
| onDeleted: onDeleted, |
| deleteIconColor: deleteIconColor, |
| useDeleteButtonTooltip: useDeleteButtonTooltip, |
| deleteButtonTooltipMessage: deleteButtonTooltipMessage, |
| onSelected: onSelected, |
| onPressed: onPressed, |
| pressElevation: pressElevation, |
| selected: selected, |
| disabledColor: disabledColor, |
| selectedColor: selectedColor, |
| tooltip: tooltip, |
| side: side, |
| shape: shape, |
| clipBehavior: clipBehavior, |
| focusNode: focusNode, |
| autofocus: autofocus, |
| color: color, |
| backgroundColor: backgroundColor, |
| padding: padding, |
| visualDensity: visualDensity, |
| materialTapTargetSize: materialTapTargetSize, |
| elevation: elevation, |
| shadowColor: shadowColor, |
| surfaceTintColor: surfaceTintColor, |
| selectedShadowColor: selectedShadowColor, |
| showCheckmark: showCheckmark, |
| checkmarkColor: checkmarkColor, |
| isEnabled: isEnabled && (onSelected != null || onDeleted != null || onPressed != null), |
| avatarBorder: avatarBorder, |
| ); |
| } |
| } |
| |
| // BEGIN GENERATED TOKEN PROPERTIES - InputChip |
| |
| // Do not edit by hand. The code between the "BEGIN GENERATED" and |
| // "END GENERATED" comments are generated from data in the Material |
| // Design token database by the script: |
| // dev/tools/gen_defaults/bin/gen_defaults.dart. |
| |
| class _InputChipDefaultsM3 extends ChipThemeData { |
| _InputChipDefaultsM3(this.context, this.isEnabled, this.isSelected) |
| : super( |
| elevation: 0.0, |
| shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))), |
| showCheckmark: true, |
| ); |
| |
| final BuildContext context; |
| final bool isEnabled; |
| final bool isSelected; |
| late final ColorScheme _colors = Theme.of(context).colorScheme; |
| late final TextTheme _textTheme = Theme.of(context).textTheme; |
| |
| @override |
| TextStyle? get labelStyle => _textTheme.labelLarge; |
| |
| @override |
| MaterialStateProperty<Color?>? get color => |
| MaterialStateProperty.resolveWith((Set<MaterialState> states) { |
| if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) { |
| return _colors.onSurface.withOpacity(0.12); |
| } |
| if (states.contains(MaterialState.disabled)) { |
| return null; |
| } |
| if (states.contains(MaterialState.selected)) { |
| return _colors.secondaryContainer; |
| } |
| return null; |
| }); |
| |
| @override |
| Color? get shadowColor => Colors.transparent; |
| |
| @override |
| Color? get surfaceTintColor => Colors.transparent; |
| |
| @override |
| Color? get checkmarkColor => null; |
| |
| @override |
| Color? get deleteIconColor => _colors.onSecondaryContainer; |
| |
| @override |
| BorderSide? get side => !isSelected |
| ? isEnabled |
| ? BorderSide(color: _colors.outline) |
| : BorderSide(color: _colors.onSurface.withOpacity(0.12)) |
| : const BorderSide(color: Colors.transparent); |
| |
| @override |
| IconThemeData? get iconTheme => IconThemeData( |
| color: isEnabled |
| ? null |
| : _colors.onSurface, |
| size: 18.0, |
| ); |
| |
| @override |
| EdgeInsetsGeometry? get padding => const EdgeInsets.all(8.0); |
| |
| /// The chip at text scale 1 starts with 8px on each side and as text scaling |
| /// gets closer to 2 the label padding is linearly interpolated from 8px to 4px. |
| /// Once the widget has a text scaling of 2 or higher than the label padding |
| /// remains 4px. |
| @override |
| EdgeInsetsGeometry? get labelPadding => EdgeInsets.lerp( |
| const EdgeInsets.symmetric(horizontal: 8.0), |
| const EdgeInsets.symmetric(horizontal: 4.0), |
| clampDouble(MediaQuery.textScalerOf(context).textScaleFactor - 1.0, 0.0, 1.0), |
| )!; |
| } |
| |
| // END GENERATED TOKEN PROPERTIES - InputChip |