From 95ecf31f7ee38a07fdbfc0782b6687b04dfa7369 Mon Sep 17 00:00:00 2001 From: Harry Sild <46851868+Kypsis@users.noreply.github.com> Date: Sat, 11 Feb 2023 00:48:28 +0200 Subject: [PATCH] feat: [MDS-379] Finalize the MoonTag (#31) --- example/lib/src/storybook/stories/tag.dart | 103 ++++++++++++ example/lib/src/storybook/storybook.dart | 2 + lib/moon_design.dart | 11 +- lib/src/theme/{ => avatar}/avatar_sizes.dart | 2 +- lib/src/theme/{ => avatar}/avatar_theme.dart | 4 +- lib/src/theme/{ => button}/button_sizes.dart | 2 +- lib/src/theme/{ => button}/button_theme.dart | 2 +- lib/src/theme/tag/tag_sizes.dart | 102 ++++++++++++ lib/src/theme/tag/tag_theme.dart | 53 ++++++ lib/src/theme/theme.dart | 18 +- .../theme/{ => typography}/text_styles.dart | 0 .../theme/{ => typography}/typography.dart | 2 +- lib/src/utils/placeholder_icon.dart | 16 +- lib/src/widgets/avatar/avatar.dart | 104 +++++++----- lib/src/widgets/buttons/button.dart | 8 +- lib/src/widgets/tag/tag.dart | 157 ++++++++++++++++++ 16 files changed, 520 insertions(+), 66 deletions(-) create mode 100644 example/lib/src/storybook/stories/tag.dart rename lib/src/theme/{ => avatar}/avatar_sizes.dart (98%) rename lib/src/theme/{ => avatar}/avatar_theme.dart (95%) rename lib/src/theme/{ => button}/button_sizes.dart (98%) rename lib/src/theme/{ => button}/button_theme.dart (96%) create mode 100644 lib/src/theme/tag/tag_sizes.dart create mode 100644 lib/src/theme/tag/tag_theme.dart rename lib/src/theme/{ => typography}/text_styles.dart (100%) rename lib/src/theme/{ => typography}/typography.dart (94%) create mode 100644 lib/src/widgets/tag/tag.dart diff --git a/example/lib/src/storybook/stories/tag.dart b/example/lib/src/storybook/stories/tag.dart new file mode 100644 index 00000000..0adfd92d --- /dev/null +++ b/example/lib/src/storybook/stories/tag.dart @@ -0,0 +1,103 @@ +import 'package:example/src/storybook/common/options.dart'; +import 'package:flutter/material.dart'; +import 'package:moon_design/moon_design.dart'; +import 'package:storybook_flutter/storybook_flutter.dart'; + +class TagStory extends Story { + TagStory() + : super( + name: "Tags", + builder: (context) { + final customLabelTextKnob = context.knobs.text( + label: "Custom label text", + initial: "MoonTag", + ); + + final colorsKnob = context.knobs.options( + label: "backgroundColor", + description: "MoonColors variants for tag.", + initial: 5, // bulma + options: colorOptions, + ); + + final color = colorTable(context)[colorsKnob]; + + final borderRadiusKnob = context.knobs.sliderInt( + max: 12, + initial: 4, + label: "borderRadius", + description: "Border radius for tag.", + ); + + final tagSizesKnob = context.knobs.options( + label: "tagSize", + description: "Tag size variants.", + initial: MoonTagSize.xs, + options: const [ + Option(label: "x2s", value: MoonTagSize.x2s), + Option(label: "xs", value: MoonTagSize.xs), + ], + ); + + final setRtlModeKnob = context.knobs.boolean( + label: "RTL mode", + description: "Switch between LTR and RTL modes.", + ); + + final setUpperCase = context.knobs.boolean( + label: "isUpperCase", + description: "Sets the text style of the tag to upper case.", + ); + + final showLeftIconKnob = context.knobs.boolean( + label: "Show leftIcon", + description: "Show widget in the leftIcon slot.", + ); + + final showLabelKnob = context.knobs.boolean( + label: "Show label", + description: "Show widget in the label slot.", + initial: true, + ); + + final showRightIconKnob = context.knobs + .boolean(label: "Show rightIcon", description: "Show widget in the rightIcon slot.", initial: true); + + final effectiveIconSize = tagSizesKnob == MoonTagSize.x2s ? 12.0 : 16.0; + + return Directionality( + textDirection: setRtlModeKnob ? TextDirection.rtl : TextDirection.ltr, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 64), + MoonTag( + borderRadius: BorderRadius.circular(borderRadiusKnob.toDouble()), + tagSize: tagSizesKnob, + isUpperCase: setUpperCase, + backgroundColor: color, + leftIcon: showLeftIconKnob + ? MoonPlaceholderIcon( + height: effectiveIconSize, + width: effectiveIconSize, + ) + : null, + label: showLabelKnob + ? Text(setUpperCase ? customLabelTextKnob.toUpperCase() : customLabelTextKnob) + : null, + rightIcon: showRightIconKnob + ? MoonPlaceholderIcon( + height: effectiveIconSize, + width: effectiveIconSize, + ) + : null, + ), + const SizedBox(height: 64), + ], + ), + ), + ); + }, + ); +} diff --git a/example/lib/src/storybook/storybook.dart b/example/lib/src/storybook/storybook.dart index 531ade37..f8e7af01 100644 --- a/example/lib/src/storybook/storybook.dart +++ b/example/lib/src/storybook/storybook.dart @@ -1,6 +1,7 @@ import 'package:example/src/storybook/common/widgets/version.dart'; import 'package:example/src/storybook/stories/avatar.dart'; import 'package:example/src/storybook/stories/button.dart'; +import 'package:example/src/storybook/stories/tag.dart'; import 'package:flutter/material.dart'; import 'package:moon_design/moon_design.dart'; @@ -58,6 +59,7 @@ class StorybookPage extends StatelessWidget { stories: [ AvatarStory(), ButtonStory(), + TagStory(), ], ), const Align( diff --git a/lib/moon_design.dart b/lib/moon_design.dart index 39b21ef9..b3a0f33f 100644 --- a/lib/moon_design.dart +++ b/lib/moon_design.dart @@ -1,8 +1,10 @@ library moon_design; +export 'package:moon_design/src/theme/avatar/avatar_sizes.dart'; +export 'package:moon_design/src/theme/avatar/avatar_theme.dart'; export 'package:moon_design/src/theme/borders.dart'; -export 'package:moon_design/src/theme/button_sizes.dart'; -export 'package:moon_design/src/theme/button_theme.dart'; +export 'package:moon_design/src/theme/button/button_sizes.dart'; +export 'package:moon_design/src/theme/button/button_theme.dart'; export 'package:moon_design/src/theme/colors.dart'; export 'package:moon_design/src/theme/effects/controls_effects.dart'; export 'package:moon_design/src/theme/effects/effects.dart'; @@ -11,9 +13,9 @@ export 'package:moon_design/src/theme/effects/hover_effects.dart'; export 'package:moon_design/src/theme/opacity.dart'; export 'package:moon_design/src/theme/shadows.dart'; export 'package:moon_design/src/theme/sizes.dart'; -export 'package:moon_design/src/theme/text_styles.dart'; export 'package:moon_design/src/theme/theme.dart'; -export 'package:moon_design/src/theme/typography.dart'; +export 'package:moon_design/src/theme/typography/text_styles.dart'; +export 'package:moon_design/src/theme/typography/typography.dart'; export 'package:moon_design/src/utils/extensions.dart'; export 'package:moon_design/src/utils/measure_size.dart'; export 'package:moon_design/src/utils/moon_icon.dart'; @@ -28,3 +30,4 @@ export 'package:moon_design/src/widgets/buttons/secondary_button.dart'; export 'package:moon_design/src/widgets/buttons/tertiary_button.dart'; export 'package:moon_design/src/widgets/effects/focus_effect.dart'; export 'package:moon_design/src/widgets/effects/pulse_effect.dart'; +export 'package:moon_design/src/widgets/tag/tag.dart'; diff --git a/lib/src/theme/avatar_sizes.dart b/lib/src/theme/avatar/avatar_sizes.dart similarity index 98% rename from lib/src/theme/avatar_sizes.dart rename to lib/src/theme/avatar/avatar_sizes.dart index 633667dc..da81179a 100644 --- a/lib/src/theme/avatar_sizes.dart +++ b/lib/src/theme/avatar/avatar_sizes.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:moon_design/src/theme/borders.dart'; import 'package:moon_design/src/theme/sizes.dart'; -import 'package:moon_design/src/theme/text_styles.dart'; +import 'package:moon_design/src/theme/typography/text_styles.dart'; @immutable class MoonAvatarSizes extends ThemeExtension with DiagnosticableTreeMixin { diff --git a/lib/src/theme/avatar_theme.dart b/lib/src/theme/avatar/avatar_theme.dart similarity index 95% rename from lib/src/theme/avatar_theme.dart rename to lib/src/theme/avatar/avatar_theme.dart index 5f7f3365..e911ae14 100644 --- a/lib/src/theme/avatar_theme.dart +++ b/lib/src/theme/avatar/avatar_theme.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:moon_design/src/theme/avatar_sizes.dart'; +import 'package:moon_design/src/theme/avatar/avatar_sizes.dart'; @immutable class MoonAvatarTheme extends ThemeExtension with DiagnosticableTreeMixin { @@ -29,7 +29,7 @@ class MoonAvatarTheme extends ThemeExtension with Diagnosticabl /// Extra large avatar properties. final MoonAvatarSizes xl; - /// 2x extra large avatar properties. + /// (2x) Extra large avatar properties. final MoonAvatarSizes x2l; const MoonAvatarTheme({ diff --git a/lib/src/theme/button_sizes.dart b/lib/src/theme/button/button_sizes.dart similarity index 98% rename from lib/src/theme/button_sizes.dart rename to lib/src/theme/button/button_sizes.dart index 1dba99ef..1b420253 100644 --- a/lib/src/theme/button_sizes.dart +++ b/lib/src/theme/button/button_sizes.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:moon_design/src/theme/borders.dart'; import 'package:moon_design/src/theme/sizes.dart'; -import 'package:moon_design/src/theme/text_styles.dart'; +import 'package:moon_design/src/theme/typography/text_styles.dart'; @immutable class MoonButtonSizes extends ThemeExtension with DiagnosticableTreeMixin { diff --git a/lib/src/theme/button_theme.dart b/lib/src/theme/button/button_theme.dart similarity index 96% rename from lib/src/theme/button_theme.dart rename to lib/src/theme/button/button_theme.dart index db172b83..692339b5 100644 --- a/lib/src/theme/button_theme.dart +++ b/lib/src/theme/button/button_theme.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:moon_design/src/theme/button_sizes.dart'; +import 'package:moon_design/src/theme/button/button_sizes.dart'; @immutable class MoonButtonTheme extends ThemeExtension with DiagnosticableTreeMixin { diff --git a/lib/src/theme/tag/tag_sizes.dart b/lib/src/theme/tag/tag_sizes.dart new file mode 100644 index 00000000..c334c7f2 --- /dev/null +++ b/lib/src/theme/tag/tag_sizes.dart @@ -0,0 +1,102 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/borders.dart'; +import 'package:moon_design/src/theme/sizes.dart'; +import 'package:moon_design/src/theme/typography/text_styles.dart'; + +@immutable +class MoonTagSizes extends ThemeExtension with DiagnosticableTreeMixin { + static final x2s = MoonTagSizes( + height: MoonSizes.sizes.x2s, + gap: MoonSizes.sizes.x5s, + padding: EdgeInsets.symmetric(horizontal: MoonSizes.sizes.x4s), + borderRadius: MoonBorders.borders.interactiveXs, + textStyle: MoonTextStyles.text.text10.copyWith(fontWeight: FontWeight.w400), + upperCaseTextStyle: MoonTextStyles.heading.text9.copyWith(fontWeight: FontWeight.w700), + ); + + static final xs = MoonTagSizes( + height: MoonSizes.sizes.xs, + gap: MoonSizes.sizes.x5s, + padding: EdgeInsets.symmetric(horizontal: MoonSizes.sizes.x4s), + borderRadius: MoonBorders.borders.interactiveXs, + textStyle: MoonTextStyles.text.text12.copyWith(fontWeight: FontWeight.w400), + upperCaseTextStyle: MoonTextStyles.heading.text10, + ); + + /// Tag height. + final double height; + + /// Space between tag children. + final double gap; + + /// Padding around tag children. + final EdgeInsets padding; + + /// Tag border radius. + final BorderRadius borderRadius; + + /// Tag text style. + final TextStyle textStyle; + + /// Tag upper case text style. + final TextStyle upperCaseTextStyle; + + const MoonTagSizes({ + required this.height, + required this.gap, + required this.padding, + required this.borderRadius, + required this.textStyle, + required this.upperCaseTextStyle, + }); + + @override + MoonTagSizes copyWith({ + double? height, + double? gap, + EdgeInsets? padding, + BorderRadius? borderRadius, + TextStyle? textStyle, + TextStyle? upperCaseTextStyle, + }) { + return MoonTagSizes( + height: height ?? this.height, + gap: gap ?? this.gap, + padding: padding ?? this.padding, + borderRadius: borderRadius ?? this.borderRadius, + textStyle: textStyle ?? this.textStyle, + upperCaseTextStyle: upperCaseTextStyle ?? this.upperCaseTextStyle, + ); + } + + @override + MoonTagSizes lerp(ThemeExtension? other, double t) { + if (other is! MoonTagSizes) return this; + + return MoonTagSizes( + height: lerpDouble(height, other.height, t)!, + gap: lerpDouble(gap, other.gap, t)!, + padding: EdgeInsets.lerp(padding, other.padding, t)!, + borderRadius: BorderRadius.lerp(borderRadius, other.borderRadius, t)!, + textStyle: TextStyle.lerp(textStyle, other.textStyle, t)!, + upperCaseTextStyle: TextStyle.lerp(upperCaseTextStyle, other.upperCaseTextStyle, t)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonTagSizes")) + ..add(DoubleProperty("height", height)) + ..add(DoubleProperty("gap", gap)) + ..add(DiagnosticsProperty("padding", padding)) + ..add(DiagnosticsProperty("borderRadius", borderRadius)) + ..add(DiagnosticsProperty("textStyle", textStyle)) + ..add(DiagnosticsProperty("upperCaseTextStyle", upperCaseTextStyle)); + } +} diff --git a/lib/src/theme/tag/tag_theme.dart b/lib/src/theme/tag/tag_theme.dart new file mode 100644 index 00000000..51ed6838 --- /dev/null +++ b/lib/src/theme/tag/tag_theme.dart @@ -0,0 +1,53 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/tag/tag_sizes.dart'; + +@immutable +class MoonTagTheme extends ThemeExtension with DiagnosticableTreeMixin { + static final sizes = MoonTagTheme( + x2s: MoonTagSizes.x2s, + xs: MoonTagSizes.xs, + ); + + /// (2x) Extra small tag properties. + final MoonTagSizes x2s; + + /// Extra small tag properties. + final MoonTagSizes xs; + + const MoonTagTheme({ + required this.x2s, + required this.xs, + }); + + @override + MoonTagTheme copyWith({ + MoonTagSizes? x2s, + MoonTagSizes? xs, + }) { + return MoonTagTheme( + x2s: x2s ?? this.x2s, + xs: xs ?? this.xs, + ); + } + + @override + MoonTagTheme lerp(ThemeExtension? other, double t) { + if (other is! MoonTagTheme) return this; + + return MoonTagTheme( + x2s: x2s.lerp(other.x2s, t), + xs: xs.lerp(other.xs, t), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonTagTheme")) + ..add(DiagnosticsProperty("x2s", x2s)) + ..add(DiagnosticsProperty("xs", xs)); + } +} diff --git a/lib/src/theme/theme.dart b/lib/src/theme/theme.dart index a6e00208..fbede06d 100644 --- a/lib/src/theme/theme.dart +++ b/lib/src/theme/theme.dart @@ -1,15 +1,16 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:moon_design/src/theme/avatar_theme.dart'; +import 'package:moon_design/src/theme/avatar/avatar_theme.dart'; import 'package:moon_design/src/theme/borders.dart'; -import 'package:moon_design/src/theme/button_theme.dart'; +import 'package:moon_design/src/theme/button/button_theme.dart'; import 'package:moon_design/src/theme/colors.dart'; import 'package:moon_design/src/theme/effects/effects.dart'; import 'package:moon_design/src/theme/opacity.dart'; import 'package:moon_design/src/theme/shadows.dart'; import 'package:moon_design/src/theme/sizes.dart'; -import 'package:moon_design/src/theme/typography.dart'; +import 'package:moon_design/src/theme/tag/tag_theme.dart'; +import 'package:moon_design/src/theme/typography/typography.dart'; @immutable class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { @@ -22,6 +23,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { opacity: MoonOpacity.opacities, shadows: MoonShadows.light, sizes: MoonSizes.sizes, + tagTheme: MoonTagTheme.sizes, typography: MoonTypography.textStyles, ); @@ -34,6 +36,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { opacity: MoonOpacity.opacities, shadows: MoonShadows.dark, sizes: MoonSizes.sizes, + tagTheme: MoonTagTheme.sizes, typography: MoonTypography.textStyles, ); @@ -61,6 +64,9 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { /// Moon Design System sizes. final MoonSizes sizes; + /// Moon Design System tag theming. + final MoonTagTheme tagTheme; + /// Moon Design System typography. final MoonTypography typography; @@ -73,6 +79,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { required this.opacity, required this.shadows, required this.sizes, + required this.tagTheme, required this.typography, }); @@ -86,6 +93,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { MoonOpacity? opacity, MoonShadows? shadows, MoonSizes? sizes, + MoonTagTheme? tagTheme, MoonTypography? typography, }) { return MoonTheme( @@ -97,6 +105,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { opacity: opacity ?? this.opacity, shadows: shadows ?? this.shadows, sizes: sizes ?? this.sizes, + tagTheme: tagTheme ?? this.tagTheme, typography: typography ?? this.typography, ); } @@ -114,6 +123,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { opacity: opacity.lerp(other.opacity, t), shadows: shadows.lerp(other.shadows, t), sizes: sizes.lerp(other.sizes, t), + tagTheme: tagTheme.lerp(other.tagTheme, t), typography: typography.lerp(other.typography, t), ); } @@ -131,6 +141,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { ..add(DiagnosticsProperty("moonOpacity", opacity)) ..add(DiagnosticsProperty("moonShadows", shadows)) ..add(DiagnosticsProperty("moonSizes", sizes)) + ..add(DiagnosticsProperty("moonTagTheme", tagTheme)) ..add(DiagnosticsProperty("moonTypography", typography)); } } @@ -145,5 +156,6 @@ extension MoonThemeX on BuildContext { MoonOpacity? get moonOpacity => moonTheme?.opacity; MoonShadows? get moonShadows => moonTheme?.shadows; MoonSizes? get moonSizes => moonTheme?.sizes; + MoonTagTheme? get moonTagTheme => moonTheme?.tagTheme; MoonTypography? get moonTypography => moonTheme?.typography; } diff --git a/lib/src/theme/text_styles.dart b/lib/src/theme/typography/text_styles.dart similarity index 100% rename from lib/src/theme/text_styles.dart rename to lib/src/theme/typography/text_styles.dart diff --git a/lib/src/theme/typography.dart b/lib/src/theme/typography/typography.dart similarity index 94% rename from lib/src/theme/typography.dart rename to lib/src/theme/typography/typography.dart index 79cea73c..4689ab85 100644 --- a/lib/src/theme/typography.dart +++ b/lib/src/theme/typography/typography.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:moon_design/src/theme/text_styles.dart'; +import 'package:moon_design/src/theme/typography/text_styles.dart'; @immutable class MoonTypography extends ThemeExtension with DiagnosticableTreeMixin { diff --git a/lib/src/utils/placeholder_icon.dart b/lib/src/utils/placeholder_icon.dart index 36dd580c..16291ae2 100644 --- a/lib/src/utils/placeholder_icon.dart +++ b/lib/src/utils/placeholder_icon.dart @@ -1,15 +1,17 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; class MoonPlaceholderIcon extends StatelessWidget { - final double width; - final double height; + final double? width; + final double? height; final Color? iconColor; final Color? containerBackgroundColor; const MoonPlaceholderIcon({ super.key, - this.width = 24, - this.height = 24, + this.width, + this.height, this.iconColor, this.containerBackgroundColor, }); @@ -17,7 +19,11 @@ class MoonPlaceholderIcon extends StatelessWidget { @override Widget build(BuildContext context) { final effectiveStrokeColor = iconColor ?? DefaultTextStyle.of(context).style.color ?? Colors.black; - final effectiveSize = (DefaultTextStyle.of(context).style.fontSize ?? 14) <= 12 ? 20 : 24; + final effectiveSize = width != null && height != null + ? min(width!, height!) + : (DefaultTextStyle.of(context).style.fontSize ?? 14) <= 12 + ? 20 + : 24; return CustomPaint( size: Size(effectiveSize.toDouble(), effectiveSize.toDouble()), diff --git a/lib/src/widgets/avatar/avatar.dart b/lib/src/widgets/avatar/avatar.dart index ae86414f..fe1324b4 100644 --- a/lib/src/widgets/avatar/avatar.dart +++ b/lib/src/widgets/avatar/avatar.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:moon_design/src/theme/avatar_sizes.dart'; +import 'package:moon_design/src/theme/avatar/avatar_sizes.dart'; import 'package:moon_design/src/theme/colors.dart'; import 'package:moon_design/src/theme/theme.dart'; import 'package:moon_design/src/utils/extensions.dart'; @@ -48,6 +48,9 @@ class MoonAvatar extends StatelessWidget { /// The background color of the avatar. final Color? backgroundColor; + /// The text color of the avatar. + final Color? textColor; + /// The size of the avatar. final MoonAvatarSize? avatarSize; @@ -57,6 +60,9 @@ class MoonAvatar extends StatelessWidget { /// The background image of the avatar. final ImageProvider? backgroundImage; + /// The semantic label for the avatar. + final String? semanticLabel; + /// The child of the avatar. final Widget? child; @@ -70,9 +76,11 @@ class MoonAvatar extends StatelessWidget { this.showBadge = false, this.badgeColor, this.backgroundColor, + this.textColor, this.avatarSize, this.badgeAlignment = MoonBadgeAlignment.bottomRight, this.backgroundImage, + this.semanticLabel, this.child, }); @@ -152,55 +160,61 @@ class MoonAvatar extends StatelessWidget { final Color effectiveBadgeColor = badgeColor ?? context.moonColors?.roshi100 ?? MoonColors.light.roshi100; final Color effectiveTextColor = - _getTextColor(isDarkMode: context.isDarkMode, backgroundColor: effectiveBackgroundColor); - - return SizedBox( - width: effectiveAvatarWidth, - height: effectiveAvatarHeight, - child: Stack( - children: [ - Positioned.fill( - child: ClipPath( - clipper: AvatarClipper( - showBadge: showBadge, - width: effectiveAvatarWidth, - height: effectiveAvatarHeight, - borderRadiusValue: avatarBorderRadiusValue, - badgeSize: effectiveBadgeSize, - badgeMarginValue: effectiveBadgeMarginValue, - badgeAlignment: badgeAlignment, - textDirection: Directionality.of(context), - ), - child: DefaultTextStyle( - style: effectiveMoonAvatarSize.textStyle.copyWith(color: effectiveTextColor), - child: DecoratedBox( - decoration: BoxDecoration( - color: effectiveBackgroundColor, - image: backgroundImage != null - ? DecorationImage( - image: backgroundImage!, - fit: BoxFit.cover, - ) - : null, + textColor ?? _getTextColor(isDarkMode: context.isDarkMode, backgroundColor: effectiveBackgroundColor); + + return Semantics( + label: semanticLabel, + button: false, + focusable: false, + image: backgroundImage != null, + child: SizedBox( + width: effectiveAvatarWidth, + height: effectiveAvatarHeight, + child: Stack( + children: [ + Positioned.fill( + child: ClipPath( + clipper: AvatarClipper( + showBadge: showBadge, + width: effectiveAvatarWidth, + height: effectiveAvatarHeight, + borderRadiusValue: avatarBorderRadiusValue, + badgeSize: effectiveBadgeSize, + badgeMarginValue: effectiveBadgeMarginValue, + badgeAlignment: badgeAlignment, + textDirection: Directionality.of(context), + ), + child: DefaultTextStyle.merge( + style: effectiveMoonAvatarSize.textStyle.copyWith(color: effectiveTextColor), + child: DecoratedBox( + decoration: BoxDecoration( + color: effectiveBackgroundColor, + image: backgroundImage != null + ? DecorationImage( + image: backgroundImage!, + fit: BoxFit.cover, + ) + : null, + ), + child: Center(child: child), ), - child: Center(child: child), ), ), ), - ), - if (showBadge) - Align( - alignment: _avatarAlignmentMapper(context), - child: Container( - height: effectiveBadgeSize, - width: effectiveBadgeSize, - decoration: BoxDecoration( - color: effectiveBadgeColor, - borderRadius: BorderRadius.circular(effectiveBadgeSize / 2), + if (showBadge) + Align( + alignment: _avatarAlignmentMapper(context), + child: Container( + height: effectiveBadgeSize, + width: effectiveBadgeSize, + decoration: BoxDecoration( + color: effectiveBadgeColor, + borderRadius: BorderRadius.circular(effectiveBadgeSize / 2), + ), ), - ), - ) - ], + ) + ], + ), ), ); } diff --git a/lib/src/widgets/buttons/button.dart b/lib/src/widgets/buttons/button.dart index a1949e81..f179bac4 100644 --- a/lib/src/widgets/buttons/button.dart +++ b/lib/src/widgets/buttons/button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:moon_design/src/theme/button_sizes.dart'; +import 'package:moon_design/src/theme/button/button_sizes.dart'; import 'package:moon_design/src/theme/theme.dart'; import 'package:moon_design/src/widgets/base_control.dart'; @@ -298,10 +298,12 @@ class MoonButton extends StatelessWidget { @override Widget build(BuildContext context) { final MoonButtonSizes effectiveMoonButtonSize = _getMoonButtonSize(context, buttonSize); - final BorderRadius effectiveBorderRadius = borderRadius ?? effectiveMoonButtonSize.borderRadius; - final double effectiveGap = gap ?? effectiveMoonButtonSize.gap; final double effectiveHeight = height ?? effectiveMoonButtonSize.height; + final double effectiveGap = gap ?? effectiveMoonButtonSize.gap; + + final BorderRadius effectiveBorderRadius = borderRadius ?? effectiveMoonButtonSize.borderRadius; + final EdgeInsets effectivePadding = padding ?? effectiveMoonButtonSize.padding; final EdgeInsetsDirectional correctedPadding = EdgeInsetsDirectional.fromSTEB( diff --git a/lib/src/widgets/tag/tag.dart b/lib/src/widgets/tag/tag.dart new file mode 100644 index 00000000..70ea71d2 --- /dev/null +++ b/lib/src/widgets/tag/tag.dart @@ -0,0 +1,157 @@ +import 'package:flutter/material.dart'; +import 'package:moon_design/src/theme/colors.dart'; + +import 'package:moon_design/src/theme/tag/tag_sizes.dart'; +import 'package:moon_design/src/theme/theme.dart'; +import 'package:moon_design/src/utils/extensions.dart'; + +enum MoonTagSize { + x2s, + xs, +} + +class MoonTag extends StatelessWidget { + /// The callback that is called when the tag is tapped or pressed. + final VoidCallback? onTap; + + /// The callback that is called when the tag is long-pressed. + final VoidCallback? onLongPress; + + /// The size of the tag. + final MoonTagSize? tagSize; + + /// The semantic label for the tag. + final String? semanticLabel; + + /// The width of the tag. + final double? width; + + /// The height of the tag. + final double? height; + + /// The gap between the icon and the label. + final double? gap; + + /// The background color of the tag. + final Color? backgroundColor; + + /// The text color of the tag. + final Color? textColor; + + /// The padding of the tag. + final EdgeInsets? padding; + + /// The border radius of the tag. + final BorderRadius? borderRadius; + + /// Whether the tag should use upper case text style. + final bool isUpperCase; + + /// The widget in the left icon slot of the tag. + final Widget? leftIcon; + + /// The widget in the label slot of the tag. + final Widget? label; + + /// The widget in the right icon slot of the tag. + final Widget? rightIcon; + + const MoonTag({ + super.key, + this.onTap, + this.onLongPress, + this.tagSize, + this.semanticLabel, + this.width, + this.height, + this.gap, + this.backgroundColor, + this.textColor, + this.padding, + this.borderRadius, + this.isUpperCase = true, + this.leftIcon, + this.label, + this.rightIcon, + }); + + MoonTagSizes _getMoonTagSize(BuildContext context, MoonTagSize? moonTagSize) { + switch (moonTagSize) { + case MoonTagSize.x2s: + return context.moonTheme?.tagTheme.x2s ?? MoonTagSizes.x2s; + case MoonTagSize.xs: + return context.moonTheme?.tagTheme.xs ?? MoonTagSizes.xs; + default: + return context.moonTheme?.tagTheme.xs ?? MoonTagSizes.xs; + } + } + + Color _getTextColor({required bool isDarkMode, required Color backgroundColor}) { + final backgroundLuminance = backgroundColor.computeLuminance(); + if (backgroundLuminance > 0.5) { + return MoonColors.light.bulma; + } else { + return MoonColors.dark.bulma; + } + } + + @override + Widget build(BuildContext context) { + final MoonTagSizes effectiveMoonTagSize = _getMoonTagSize(context, tagSize); + + final double effectiveHeight = height ?? effectiveMoonTagSize.height; + final double effectiveGap = gap ?? effectiveMoonTagSize.gap; + + final BorderRadius effectiveBorderRadius = borderRadius ?? effectiveMoonTagSize.borderRadius; + + final Color effectiveBackgroundColor = backgroundColor ?? context.moonColors?.gohan ?? MoonColors.light.gohan; + + final Color effectiveTextColor = + textColor ?? _getTextColor(isDarkMode: context.isDarkMode, backgroundColor: effectiveBackgroundColor); + + final EdgeInsets effectivePadding = padding ?? effectiveMoonTagSize.padding; + + final EdgeInsetsDirectional correctedPadding = EdgeInsetsDirectional.fromSTEB( + leftIcon == null && label != null ? effectivePadding.left : 0, + effectivePadding.top, + rightIcon == null && label != null ? effectivePadding.right : 0, + effectivePadding.bottom, + ); + + return GestureDetector( + onTap: onTap, + onLongPress: onLongPress, + child: Container( + height: effectiveHeight, + padding: correctedPadding, + constraints: BoxConstraints(minWidth: effectiveHeight), + decoration: BoxDecoration( + color: effectiveBackgroundColor, + borderRadius: effectiveBorderRadius, + ), + child: DefaultTextStyle.merge( + style: isUpperCase + ? effectiveMoonTagSize.upperCaseTextStyle.copyWith(color: effectiveTextColor) + : effectiveMoonTagSize.textStyle.copyWith(color: effectiveTextColor), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (leftIcon != null) + Padding( + padding: EdgeInsets.symmetric(horizontal: effectiveGap), + child: leftIcon, + ), + if (label != null) label!, + if (rightIcon != null) + Padding( + padding: EdgeInsets.symmetric(horizontal: effectiveGap), + child: rightIcon, + ), + ], + ), + ), + ), + ); + } +}