diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..b7a39fac --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[**/*.dart] +max_line_length = 120 \ No newline at end of file diff --git a/example/lib/src/storybook/stories/chip.dart b/example/lib/src/storybook/stories/chip.dart new file mode 100644 index 00000000..fbc6f58b --- /dev/null +++ b/example/lib/src/storybook/stories/chip.dart @@ -0,0 +1,130 @@ +import 'package:example/src/storybook/common/options.dart'; +import 'package:example/src/storybook/common/widgets/text_divider.dart'; +import 'package:flutter/material.dart'; +import 'package:moon_design/moon_design.dart'; +import 'package:storybook_flutter/storybook_flutter.dart'; + +class ChipStory extends Story { + ChipStory() + : super( + name: "Chip", + builder: (context) { + final customLabelTextKnob = context.knobs.text( + label: "Custom label text", + initial: "MoonChip", + ); + + final colorsKnob = context.knobs.options( + label: "backgroundColor", + description: "MoonColors variants for the chip.", + initial: 5, // bulma + options: colorOptions, + ); + + final color = colorTable(context)[colorsKnob]; + + final isActiveKnob = context.knobs.boolean( + label: "isActive", + description: "Whether the chip is active/selected.", + ); + + final showBorderKnob = context.knobs.boolean( + label: "showBorder", + description: "Show border when isActive.", + ); + + final borderRadiusKnob = context.knobs.sliderInt( + max: 28, + initial: 8, + label: "borderRadius", + description: "Border radius for the chip.", + ); + + final chipSizesKnob = context.knobs.options( + label: "MoonChipSize", + description: "Chip size variants.", + initial: MoonChipSize.md, + options: const [ + Option(label: "sm", value: MoonChipSize.sm), + Option(label: "md", value: MoonChipSize.md), + ], + ); + + final setRtlModeKnob = context.knobs.boolean( + label: "RTL mode", + description: "Switch between LTR and RTL modes.", + ); + + final showLeftIconKnob = context.knobs.boolean( + label: "Show leftIcon", + description: "Show widget in the leftIcon slot.", + initial: true, + ); + + 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.", + ); + + return Directionality( + textDirection: setRtlModeKnob ? TextDirection.rtl : TextDirection.ltr, + child: Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 64), + const TextDivider(text: "Regular chip"), + const SizedBox(height: 32), + MoonChip( + isActive: isActiveKnob, + borderRadius: BorderRadius.circular(borderRadiusKnob.toDouble()), + showBorder: showBorderKnob, + chipSize: chipSizesKnob, + backgroundColor: color, + leftIcon: showLeftIconKnob ? const MoonPlaceholderIcon() : null, + label: showLabelKnob ? Text(customLabelTextKnob) : null, + rightIcon: showRightIconKnob ? const MoonPlaceholderIcon() : null, + ), + const SizedBox(height: 40), + const TextDivider(text: "Ghost chip"), + const SizedBox(height: 32), + MoonGhostChip( + isActive: isActiveKnob, + borderRadius: BorderRadius.circular(borderRadiusKnob.toDouble()), + showBorder: showBorderKnob, + chipSize: chipSizesKnob, + leftIcon: showLeftIconKnob ? const MoonPlaceholderIcon() : null, + label: showLabelKnob ? Text(customLabelTextKnob) : null, + rightIcon: showRightIconKnob ? const MoonPlaceholderIcon() : null, + ), + const SizedBox(height: 40), + const TextDivider(text: "Preset chip"), + const SizedBox(height: 32), + MoonChip( + isActive: isActiveKnob, + activeColor: context.moonColors!.dodoria100, + backgroundColor: context.moonColors!.krillin100, + hoverEffectColor: context.moonColors!.chiChi10, + borderWidth: 2, + showBorder: showBorderKnob, + chipSize: chipSizesKnob, + leftIcon: showLeftIconKnob ? const MoonPlaceholderIcon() : null, + label: showLabelKnob ? Text(customLabelTextKnob) : null, + rightIcon: showRightIconKnob ? const MoonPlaceholderIcon() : null, + ), + const SizedBox(height: 64), + ], + ), + ), + ), + ); + }, + ); +} diff --git a/example/lib/src/storybook/stories/tag.dart b/example/lib/src/storybook/stories/tag.dart index 809589da..0adfd92d 100644 --- a/example/lib/src/storybook/stories/tag.dart +++ b/example/lib/src/storybook/stories/tag.dart @@ -6,7 +6,7 @@ import 'package:storybook_flutter/storybook_flutter.dart'; class TagStory extends Story { TagStory() : super( - name: "Tag", + name: "Tags", builder: (context) { final customLabelTextKnob = context.knobs.text( label: "Custom label text", diff --git a/example/lib/src/storybook/storybook.dart b/example/lib/src/storybook/storybook.dart index f8e7af01..29af2a4a 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/chip.dart'; import 'package:example/src/storybook/stories/tag.dart'; import 'package:flutter/material.dart'; @@ -59,6 +60,7 @@ class StorybookPage extends StatelessWidget { stories: [ AvatarStory(), ButtonStory(), + ChipStory(), TagStory(), ], ), diff --git a/lib/moon_design.dart b/lib/moon_design.dart index b3a0f33f..6a39d470 100644 --- a/lib/moon_design.dart +++ b/lib/moon_design.dart @@ -5,6 +5,8 @@ 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/button_sizes.dart'; export 'package:moon_design/src/theme/button/button_theme.dart'; +export 'package:moon_design/src/theme/chip/chip_sizes.dart'; +export 'package:moon_design/src/theme/chip/chip_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'; @@ -28,6 +30,8 @@ export 'package:moon_design/src/widgets/buttons/ghost_button.dart'; export 'package:moon_design/src/widgets/buttons/primary_button.dart'; 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/chips/chip.dart'; +export 'package:moon_design/src/widgets/chips/ghost_chip.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/chip/chip_sizes.dart b/lib/src/theme/chip/chip_sizes.dart new file mode 100644 index 00000000..3156a068 --- /dev/null +++ b/lib/src/theme/chip/chip_sizes.dart @@ -0,0 +1,92 @@ +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 MoonChipSizes extends ThemeExtension with DiagnosticableTreeMixin { + static final sm = MoonChipSizes( + height: MoonSizes.sizes.sm, + gap: MoonSizes.sizes.x5s, + padding: EdgeInsets.symmetric(horizontal: MoonSizes.sizes.x4s), + borderRadius: MoonBorders.borders.interactiveXs, + textStyle: MoonTextStyles.heading.text14, + ); + + static final md = MoonChipSizes( + height: MoonSizes.sizes.md, + gap: MoonSizes.sizes.x4s, + padding: EdgeInsets.symmetric(horizontal: MoonSizes.sizes.x4s), + borderRadius: MoonBorders.borders.interactiveSm, + textStyle: MoonTextStyles.heading.text14, + ); + + /// Chip height. + final double height; + + /// Space between chip children. + final double gap; + + /// Padding around chip children. + final EdgeInsets padding; + + /// Chip border radius. + final BorderRadius borderRadius; + + /// Chip text style. + final TextStyle textStyle; + + const MoonChipSizes({ + required this.height, + required this.gap, + required this.padding, + required this.borderRadius, + required this.textStyle, + }); + + @override + MoonChipSizes copyWith({ + double? height, + double? gap, + EdgeInsets? padding, + BorderRadius? borderRadius, + TextStyle? textStyle, + }) { + return MoonChipSizes( + height: height ?? this.height, + gap: gap ?? this.gap, + padding: padding ?? this.padding, + borderRadius: borderRadius ?? this.borderRadius, + textStyle: textStyle ?? this.textStyle, + ); + } + + @override + MoonChipSizes lerp(ThemeExtension? other, double t) { + if (other is! MoonChipSizes) return this; + + return MoonChipSizes( + 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)!, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonChipSizes")) + ..add(DoubleProperty("height", height)) + ..add(DoubleProperty("gap", gap)) + ..add(DiagnosticsProperty("padding", padding)) + ..add(DiagnosticsProperty("borderRadius", borderRadius)) + ..add(DiagnosticsProperty("textStyle", textStyle)); + } +} diff --git a/lib/src/theme/chip/chip_theme.dart b/lib/src/theme/chip/chip_theme.dart new file mode 100644 index 00000000..6f94fdcb --- /dev/null +++ b/lib/src/theme/chip/chip_theme.dart @@ -0,0 +1,56 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/chip/chip_sizes.dart'; + +@immutable +class MoonChipTheme extends ThemeExtension with DiagnosticableTreeMixin { + static final sizes = MoonChipTheme( + sm: MoonChipSizes.sm, + md: MoonChipSizes.md, + ); + + /// Small chip properties. + final MoonChipSizes sm; + + /// Medium chip properties. + final MoonChipSizes md; + + const MoonChipTheme({ + required this.sm, + required this.md, + }); + + @override + MoonChipTheme copyWith({ + MoonChipSizes? xs, + MoonChipSizes? sm, + MoonChipSizes? md, + MoonChipSizes? lg, + MoonChipSizes? xl, + }) { + return MoonChipTheme( + sm: sm ?? this.sm, + md: md ?? this.md, + ); + } + + @override + MoonChipTheme lerp(ThemeExtension? other, double t) { + if (other is! MoonChipTheme) return this; + + return MoonChipTheme( + sm: sm.lerp(other.sm, t), + md: md.lerp(other.md, t), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty("type", "MoonChipTheme")) + ..add(DiagnosticsProperty("sm", sm)) + ..add(DiagnosticsProperty("md", md)); + } +} diff --git a/lib/src/theme/effects/effects.dart b/lib/src/theme/effects/effects.dart index 9a937d3c..cffd32ef 100644 --- a/lib/src/theme/effects/effects.dart +++ b/lib/src/theme/effects/effects.dart @@ -11,14 +11,14 @@ class MoonEffects extends ThemeExtension with DiagnosticableTreeMix controlScaleEffect: MoonControlsEffects.controlScaleEffect, controlPulseEffect: MoonControlsEffects.controlPulseEffect, controlFocusEffect: MoonFocusEffects.lightFocusEffect, - buttonHoverEffect: MoonHoverEffects.lightButtonHoverEffect, + controlHoverEffect: MoonHoverEffects.lightHoverEffect, ); static final dark = MoonEffects( controlScaleEffect: MoonControlsEffects.controlScaleEffect, controlPulseEffect: MoonControlsEffects.controlPulseEffect, controlFocusEffect: MoonFocusEffects.darkFocusEffect, - buttonHoverEffect: MoonHoverEffects.darkButtonHoverEffect, + controlHoverEffect: MoonHoverEffects.darkHoverEffect, ); /// Control widgets scale effect. @@ -30,14 +30,14 @@ class MoonEffects extends ThemeExtension with DiagnosticableTreeMix /// Control widgets focus effect. final MoonFocusEffects controlFocusEffect; - /// Button hover effect. - final MoonHoverEffects buttonHoverEffect; + /// Control widgets hover effect. + final MoonHoverEffects controlHoverEffect; const MoonEffects({ required this.controlScaleEffect, required this.controlPulseEffect, required this.controlFocusEffect, - required this.buttonHoverEffect, + required this.controlHoverEffect, }); @override @@ -45,13 +45,13 @@ class MoonEffects extends ThemeExtension with DiagnosticableTreeMix MoonControlsEffects? controlScaleEffect, MoonControlsEffects? controlPulseEffect, MoonFocusEffects? controlFocusEffect, - MoonHoverEffects? buttonHoverEffect, + MoonHoverEffects? controlHoverEffect, }) { return MoonEffects( controlScaleEffect: controlScaleEffect ?? this.controlScaleEffect, controlPulseEffect: controlPulseEffect ?? this.controlPulseEffect, controlFocusEffect: controlFocusEffect ?? this.controlFocusEffect, - buttonHoverEffect: buttonHoverEffect ?? this.buttonHoverEffect, + controlHoverEffect: controlHoverEffect ?? this.controlHoverEffect, ); } @@ -63,7 +63,7 @@ class MoonEffects extends ThemeExtension with DiagnosticableTreeMix controlScaleEffect: controlScaleEffect.lerp(other.controlScaleEffect, t), controlPulseEffect: controlPulseEffect.lerp(other.controlPulseEffect, t), controlFocusEffect: controlFocusEffect.lerp(other.controlFocusEffect, t), - buttonHoverEffect: buttonHoverEffect.lerp(other.buttonHoverEffect, t), + controlHoverEffect: controlHoverEffect.lerp(other.controlHoverEffect, t), ); } @@ -75,6 +75,6 @@ class MoonEffects extends ThemeExtension with DiagnosticableTreeMix ..add(DiagnosticsProperty("controlScaleEffect", controlScaleEffect)) ..add(DiagnosticsProperty("controlPulseEffect", controlPulseEffect)) ..add(DiagnosticsProperty("controlFocusEffect", controlFocusEffect)) - ..add(DiagnosticsProperty("buttonHoverEffect", buttonHoverEffect)); + ..add(DiagnosticsProperty("controlHoverEffect", controlHoverEffect)); } } diff --git a/lib/src/theme/effects/hover_effects.dart b/lib/src/theme/effects/hover_effects.dart index 5f49c8fd..691dab24 100644 --- a/lib/src/theme/effects/hover_effects.dart +++ b/lib/src/theme/effects/hover_effects.dart @@ -5,14 +5,14 @@ import 'package:moon_design/src/theme/colors.dart'; @immutable class MoonHoverEffects extends ThemeExtension with DiagnosticableTreeMixin { - static final lightButtonHoverEffect = MoonHoverEffects( + static final lightHoverEffect = MoonHoverEffects( primaryHoverColor: MoonColors.light.heles, secondaryHoverColor: MoonColors.light.jiren, hoverCurve: Curves.easeInOut, hoverDuration: const Duration(milliseconds: 150), ); - static final darkButtonHoverEffect = MoonHoverEffects( + static final darkHoverEffect = MoonHoverEffects( primaryHoverColor: MoonColors.dark.heles, secondaryHoverColor: MoonColors.dark.jiren, hoverCurve: Curves.easeInOut, diff --git a/lib/src/theme/theme.dart b/lib/src/theme/theme.dart index fbede06d..1f3c412e 100644 --- a/lib/src/theme/theme.dart +++ b/lib/src/theme/theme.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.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/button_theme.dart'; +import 'package:moon_design/src/theme/chip/chip_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'; @@ -18,6 +19,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { avatarTheme: MoonAvatarTheme.sizes, borders: MoonBorders.borders, buttonTheme: MoonButtonTheme.sizes, + chipTheme: MoonChipTheme.sizes, colors: MoonColors.light, effects: MoonEffects.light, opacity: MoonOpacity.opacities, @@ -31,6 +33,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { avatarTheme: MoonAvatarTheme.sizes, borders: MoonBorders.borders, buttonTheme: MoonButtonTheme.sizes, + chipTheme: MoonChipTheme.sizes, colors: MoonColors.dark, effects: MoonEffects.dark, opacity: MoonOpacity.opacities, @@ -49,6 +52,9 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { /// Moon Design System buttons theming. final MoonButtonTheme buttonTheme; + /// Moon Design System chip theming. + final MoonChipTheme chipTheme; + /// Moon Design System colors. final MoonColors colors; @@ -72,8 +78,9 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { const MoonTheme({ required this.avatarTheme, - required this.buttonTheme, required this.borders, + required this.buttonTheme, + required this.chipTheme, required this.colors, required this.effects, required this.opacity, @@ -88,6 +95,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { MoonAvatarTheme? avatarTheme, MoonBorders? borders, MoonButtonTheme? buttonTheme, + MoonChipTheme? chipTheme, MoonColors? colors, MoonEffects? effects, MoonOpacity? opacity, @@ -100,6 +108,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { avatarTheme: avatarTheme ?? this.avatarTheme, borders: borders ?? this.borders, buttonTheme: buttonTheme ?? this.buttonTheme, + chipTheme: chipTheme ?? this.chipTheme, colors: colors ?? this.colors, effects: effects ?? this.effects, opacity: opacity ?? this.opacity, @@ -118,6 +127,7 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { avatarTheme: avatarTheme.lerp(other.avatarTheme, t), borders: borders.lerp(other.borders, t), buttonTheme: buttonTheme.lerp(other.buttonTheme, t), + chipTheme: chipTheme.lerp(other.chipTheme, t), colors: colors.lerp(other.colors, t), effects: effects.lerp(other.effects, t), opacity: opacity.lerp(other.opacity, t), @@ -136,13 +146,14 @@ class MoonTheme extends ThemeExtension with DiagnosticableTreeMixin { ..add(DiagnosticsProperty("MoonAvatarTheme", avatarTheme)) ..add(DiagnosticsProperty("MoonBorders", borders)) ..add(DiagnosticsProperty("MoonButtonTheme", buttonTheme)) - ..add(DiagnosticsProperty("moonColors", colors)) + ..add(DiagnosticsProperty("MoonChipTheme", chipTheme)) + ..add(DiagnosticsProperty("MoonColors", colors)) ..add(DiagnosticsProperty("MoonEffects", effects)) - ..add(DiagnosticsProperty("moonOpacity", opacity)) - ..add(DiagnosticsProperty("moonShadows", shadows)) - ..add(DiagnosticsProperty("moonSizes", sizes)) - ..add(DiagnosticsProperty("moonTagTheme", tagTheme)) - ..add(DiagnosticsProperty("moonTypography", typography)); + ..add(DiagnosticsProperty("MoonOpacity", opacity)) + ..add(DiagnosticsProperty("MoonShadows", shadows)) + ..add(DiagnosticsProperty("MoonSizes", sizes)) + ..add(DiagnosticsProperty("MoonTagTheme", tagTheme)) + ..add(DiagnosticsProperty("MoonTypography", typography)); } } @@ -151,6 +162,7 @@ extension MoonThemeX on BuildContext { MoonAvatarTheme? get moonAvatarTheme => moonTheme?.avatarTheme; MoonBorders? get moonBorders => moonTheme?.borders; MoonButtonTheme? get moonButtonTheme => moonTheme?.buttonTheme; + MoonChipTheme? get moonChipTheme => moonTheme?.chipTheme; MoonColors? get moonColors => moonTheme?.colors; MoonEffects? get moonEffects => moonTheme?.effects; MoonOpacity? get moonOpacity => moonTheme?.opacity; diff --git a/lib/src/widgets/base_control.dart b/lib/src/widgets/base_control.dart index 0df37d45..e9a44789 100644 --- a/lib/src/widgets/base_control.dart +++ b/lib/src/widgets/base_control.dart @@ -1,11 +1,7 @@ -import 'package:figma_squircle/figma_squircle.dart'; import 'package:flutter/material.dart'; -import 'package:moon_design/src/theme/borders.dart'; -import 'package:moon_design/src/theme/colors.dart'; import 'package:moon_design/src/theme/effects/controls_effects.dart'; import 'package:moon_design/src/theme/effects/focus_effects.dart'; -import 'package:moon_design/src/theme/effects/hover_effects.dart'; import 'package:moon_design/src/theme/opacity.dart'; import 'package:moon_design/src/theme/theme.dart'; import 'package:moon_design/src/utils/extensions.dart'; @@ -40,9 +36,6 @@ class MoonBaseControl extends StatefulWidget { /// Whether this control should ensure that it has a minimal touch target size. final bool ensureMinimalTouchTargetSize; - /// Whether this control should show a border. - final bool showBorder; - /// Whether this control should show a focus effect. final bool showFocusEffect; @@ -64,12 +57,6 @@ class MoonBaseControl extends StatefulWidget { /// The minimum size of the touch target. final double minTouchTargetSize; - /// The height of the control. - final double? height; - - /// The border width of the control. - final double? borderWidth; - /// The opacity of the control when it is disabled. final double? disabledOpacityValue; @@ -85,27 +72,15 @@ class MoonBaseControl extends StatefulWidget { /// The background color of the control. final Color? backgroundColor; - /// The border color of the control. - final Color? borderColor; - - /// The text color of the control. - final Color? textColor; - /// The color of the focus effect. final Color? focusEffectColor; - /// The color of the hover effect. - final Color? hoverEffectColor; - /// The color of the pulse effect. final Color? pulseEffectColor; /// The duration of the focus effect. final Duration? focusEffectDuration; - /// The duration of the hover effect. - final Duration? hoverEffectDuration; - /// The duration of the scale effect. final Duration? scaleEffectDuration; @@ -115,9 +90,6 @@ class MoonBaseControl extends StatefulWidget { /// The curve of the focus effect. final Curve? focusEffectCurve; - /// The curve of the hover effect. - final Curve? hoverEffectCurve; - /// The curve of the pulse effect. final Curve? pulseEffectCurve; @@ -142,7 +114,6 @@ class MoonBaseControl extends StatefulWidget { this.autofocus = false, this.isFocusable = true, this.ensureMinimalTouchTargetSize = false, - this.showBorder = false, this.showFocusEffect = true, this.showPulseEffect = false, this.showPulseEffectJiggle = true, @@ -150,24 +121,17 @@ class MoonBaseControl extends StatefulWidget { this.semanticTypeIsButton = false, this.semanticLabel, this.minTouchTargetSize = 40.0, - this.height, - this.borderWidth, this.disabledOpacityValue, this.focusEffectExtent, this.pulseEffectExtent, this.scaleEffectScalar, this.backgroundColor, - this.borderColor, - this.textColor, this.focusEffectColor, - this.hoverEffectColor, this.pulseEffectColor, this.focusEffectDuration, - this.hoverEffectDuration, this.pulseEffectDuration, this.scaleEffectDuration, this.focusEffectCurve, - this.hoverEffectCurve, this.pulseEffectCurve, this.scaleEffectCurve, this.borderRadius = BorderRadius.zero, @@ -190,7 +154,6 @@ class _MoonBaseControlState extends State { bool get _isEnabled => widget.onTap != null || widget.onLongPress != null; bool get _canAnimateFocus => widget.showFocusEffect && _isEnabled && _isFocused; - bool get _canAnimateHover => _isEnabled && (_isHovered || _isFocused || _isPressed); bool get _canAnimatePulse => widget.showPulseEffect && _isEnabled; bool get _canAnimateScale => widget.showScaleAnimation && _isEnabled && _isPressed; @@ -273,19 +236,6 @@ class _MoonBaseControlState extends State { } } - Color _getTextColor({required bool isDarkMode, required bool isHovered, required bool isFocused}) { - if (widget.textColor != null && (!isHovered && !isFocused)) return widget.textColor!; - if (widget.backgroundColor == null && isDarkMode) return MoonColors.dark.bulma; - if (widget.backgroundColor == null && !isDarkMode) return MoonColors.light.bulma; - - final backgroundLuminance = widget.backgroundColor!.computeLuminance(); - if (backgroundLuminance > 0.5) { - return MoonColors.light.bulma; - } else { - return MoonColors.dark.bulma; - } - } - @override void initState() { super.initState(); @@ -326,18 +276,9 @@ class _MoonBaseControlState extends State { @override Widget build(BuildContext context) { - final Color effectiveTextColor = - _getTextColor(isDarkMode: context.isDarkMode, isHovered: _isHovered, isFocused: _isFocused); - final double effectiveDisabledOpacityValue = widget.disabledOpacityValue ?? context.moonOpacity?.disabled ?? MoonOpacity.opacities.disabled; - // Border props - final BorderRadius effectiveBorderRadius = widget.borderRadius ?? BorderRadius.circular(0); - final Color effectiveBorderColor = widget.borderColor ?? context.moonColors?.trunks ?? MoonColors.light.trunks; - final double effectiveBorderWidth = - widget.borderWidth ?? context.moonBorders?.borderWidth ?? MoonBorders.borders.borderWidth; - // Focus effect props final Color effectiveFocusEffectColor = widget.focusEffectColor ?? context.moonEffects?.controlFocusEffect.effectColor ?? @@ -357,21 +298,6 @@ class _MoonBaseControlState extends State { final Color focusColor = _getFocusColor(isDarkMode: context.isDarkMode, focusColor: effectiveFocusEffectColor); - // Hover effect props - final Color effectiveHoverEffectColor = widget.hoverEffectColor ?? - context.moonEffects?.buttonHoverEffect.primaryHoverColor ?? - MoonHoverEffects.lightButtonHoverEffect.primaryHoverColor; - - final Curve effectiveHoverEffectCurve = widget.hoverEffectCurve ?? - context.moonEffects?.buttonHoverEffect.hoverCurve ?? - MoonHoverEffects.lightButtonHoverEffect.hoverCurve; - - final Duration effectiveHoverEffectDuration = widget.hoverEffectDuration ?? - context.moonEffects?.buttonHoverEffect.hoverDuration ?? - MoonHoverEffects.lightButtonHoverEffect.hoverDuration; - - final Color hoverColor = Color.alphaBlend(effectiveHoverEffectColor, widget.backgroundColor ?? Colors.transparent); - // Pulse effect props final Color effectivePulseEffectColor = widget.pulseEffectColor ?? context.moonEffects?.controlPulseEffect.effectColor ?? @@ -437,72 +363,34 @@ class _MoonBaseControlState extends State { onShowFocusHighlight: _handleFocus, onFocusChange: _handleFocusChange, actions: _actions, - child: AnimatedDefaultTextStyle( - duration: effectiveHoverEffectDuration, - curve: effectiveHoverEffectCurve, - style: TextStyle(color: effectiveTextColor), - child: TouchTargetPadding( - minSize: widget.ensureMinimalTouchTargetSize - ? Size(widget.minTouchTargetSize, widget.minTouchTargetSize) - : Size.zero, - child: AnimatedScale( - scale: _canAnimateScale ? effectiveScaleEffectScalar : 1, - duration: effectiveScaleEffectDuration, - curve: effectiveScaleEffectCurve, - child: MoonPulseEffect( - show: _canAnimatePulse, - showJiggle: widget.showPulseEffectJiggle, - childBorderRadius: widget.borderRadius, - effectColor: effectivePulseEffectColor, - effectExtent: effectivePulseEffectExtent, - effectCurve: effectivePulseEffectCurve, - effectDuration: effectivePulseEffectDuration, - child: AnimatedOpacity( - opacity: _isEnabled ? 1 : effectiveDisabledOpacityValue, - duration: const Duration(milliseconds: 150), - curve: Curves.easeInOut, - child: AnimatedContainer( - height: widget.height, - duration: effectiveHoverEffectDuration, - curve: effectiveHoverEffectCurve, - decoration: ShapeDecoration( - color: _canAnimateHover ? hoverColor : widget.backgroundColor, - shape: SmoothRectangleBorder( - side: BorderSide( - color: effectiveBorderColor, - width: widget.showBorder ? effectiveBorderWidth : 0, - style: widget.showBorder ? BorderStyle.solid : BorderStyle.none, - ), - borderRadius: SmoothBorderRadius.only( - topLeft: SmoothRadius( - cornerRadius: effectiveBorderRadius.topLeft.x, - cornerSmoothing: 1, - ), - topRight: SmoothRadius( - cornerRadius: effectiveBorderRadius.topRight.x, - cornerSmoothing: 1, - ), - bottomLeft: SmoothRadius( - cornerRadius: effectiveBorderRadius.bottomLeft.x, - cornerSmoothing: 1, - ), - bottomRight: SmoothRadius( - cornerRadius: effectiveBorderRadius.bottomRight.x, - cornerSmoothing: 1, - ), - ), - ), - ), - child: MoonFocusEffect( - show: _canAnimateFocus, - effectColor: focusColor, - effectExtent: effectiveFocusEffectExtent, - effectCurve: effectiveFocusEffectCurve, - effectDuration: effectiveFocusEffectDuration, - childBorderRadius: widget.borderRadius, - child: child, - ), - ), + child: TouchTargetPadding( + minSize: widget.ensureMinimalTouchTargetSize + ? Size(widget.minTouchTargetSize, widget.minTouchTargetSize) + : Size.zero, + child: AnimatedScale( + scale: _canAnimateScale ? effectiveScaleEffectScalar : 1, + duration: effectiveScaleEffectDuration, + curve: effectiveScaleEffectCurve, + child: MoonPulseEffect( + show: _canAnimatePulse, + showJiggle: widget.showPulseEffectJiggle, + childBorderRadius: widget.borderRadius, + effectColor: effectivePulseEffectColor, + effectExtent: effectivePulseEffectExtent, + effectCurve: effectivePulseEffectCurve, + effectDuration: effectivePulseEffectDuration, + child: AnimatedOpacity( + opacity: _isEnabled ? 1 : effectiveDisabledOpacityValue, + duration: const Duration(milliseconds: 150), + curve: Curves.easeInOut, + child: MoonFocusEffect( + show: _canAnimateFocus, + effectColor: focusColor, + effectExtent: effectiveFocusEffectExtent, + effectCurve: effectiveFocusEffectCurve, + effectDuration: effectiveFocusEffectDuration, + childBorderRadius: widget.borderRadius, + child: child, ), ), ), diff --git a/lib/src/widgets/buttons/button.dart b/lib/src/widgets/buttons/button.dart index f179bac4..18373d83 100644 --- a/lib/src/widgets/buttons/button.dart +++ b/lib/src/widgets/buttons/button.dart @@ -1,7 +1,12 @@ +import 'package:figma_squircle/figma_squircle.dart'; import 'package:flutter/material.dart'; +import 'package:moon_design/src/theme/borders.dart'; import 'package:moon_design/src/theme/button/button_sizes.dart'; +import 'package:moon_design/src/theme/colors.dart'; +import 'package:moon_design/src/theme/effects/hover_effects.dart'; import 'package:moon_design/src/theme/theme.dart'; +import 'package:moon_design/src/utils/extensions.dart'; import 'package:moon_design/src/widgets/base_control.dart'; enum MoonButtonSize { @@ -13,10 +18,10 @@ enum MoonButtonSize { } class MoonButton extends StatelessWidget { - /// The callback that is called when the control is tapped or pressed. + /// The callback that is called when the button is tapped or pressed. final VoidCallback? onTap; - /// The callback that is called when the control is long-pressed. + /// The callback that is called when the button is long-pressed. final VoidCallback? onLongPress; /// The size of the button. @@ -278,6 +283,8 @@ class MoonButton extends StatelessWidget { ); } + bool get _isEnabled => onTap != null || onLongPress != null; + MoonButtonSizes _getMoonButtonSize(BuildContext context, MoonButtonSize? moonButtonSize) { switch (moonButtonSize) { case MoonButtonSize.xs: @@ -291,7 +298,20 @@ class MoonButton extends StatelessWidget { case MoonButtonSize.xl: return context.moonTheme?.buttonTheme.xl ?? MoonButtonSizes.xl; default: - return context.moonTheme?.buttonTheme.md ?? MoonButtonSizes.xs; + return context.moonTheme?.buttonTheme.md ?? MoonButtonSizes.md; + } + } + + Color _getTextColor({required bool isDarkMode, required bool isHovered, required bool isFocused}) { + if (textColor != null && (!isHovered && !isFocused)) return textColor!; + if (backgroundColor == null && isDarkMode) return MoonColors.dark.bulma; + if (backgroundColor == null && !isDarkMode) return MoonColors.light.bulma; + + final backgroundLuminance = backgroundColor!.computeLuminance(); + if (backgroundLuminance > 0.5) { + return MoonColors.light.bulma; + } else { + return MoonColors.dark.bulma; } } @@ -302,8 +322,6 @@ class MoonButton extends StatelessWidget { 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( @@ -313,6 +331,25 @@ class MoonButton extends StatelessWidget { effectivePadding.bottom, ); + final BorderRadius effectiveBorderRadius = borderRadius ?? effectiveMoonButtonSize.borderRadius; + final Color effectiveBorderColor = borderColor ?? context.moonColors?.trunks ?? MoonColors.light.trunks; + final double effectiveBorderWidth = + borderWidth ?? context.moonBorders?.borderWidth ?? MoonBorders.borders.borderWidth; + + final Color effectiveHoverEffectColor = hoverEffectColor ?? + context.moonEffects?.controlHoverEffect.primaryHoverColor ?? + MoonHoverEffects.lightHoverEffect.primaryHoverColor; + + final Curve effectiveHoverEffectCurve = hoverEffectCurve ?? + context.moonEffects?.controlHoverEffect.hoverCurve ?? + MoonHoverEffects.lightHoverEffect.hoverCurve; + + final Duration effectiveHoverEffectDuration = hoverEffectDuration ?? + context.moonEffects?.controlHoverEffect.hoverDuration ?? + MoonHoverEffects.lightHoverEffect.hoverDuration; + + final Color hoverColor = Color.alphaBlend(effectiveHoverEffectColor, backgroundColor ?? Colors.transparent); + return MoonBaseControl( onTap: onTap, onLongPress: onLongPress, @@ -325,20 +362,12 @@ class MoonButton extends StatelessWidget { focusNode: focusNode, autofocus: autofocus, isFocusable: isFocusable, - showBorder: showBorder, showFocusEffect: showFocusEffect, backgroundColor: backgroundColor, - borderColor: borderColor, - height: effectiveHeight, - borderWidth: borderWidth, - textColor: textColor, focusEffectColor: focusEffectColor, focusEffectExtent: focusEffectExtent, focusEffectDuration: focusEffectDuration, focusEffectCurve: focusEffectCurve, - hoverEffectColor: hoverEffectColor, - hoverEffectDuration: hoverEffectDuration, - hoverEffectCurve: hoverEffectCurve, showScaleAnimation: showScaleAnimation, scaleEffectScalar: scaleEffectScalar, scaleEffectDuration: scaleEffectDuration, @@ -350,15 +379,50 @@ class MoonButton extends StatelessWidget { pulseEffectDuration: pulseEffectDuration, pulseEffectCurve: pulseEffectCurve, builder: (context, isEnabled, isHovered, isFocused, isPressed) { + final Color effectiveTextColor = + _getTextColor(isDarkMode: context.isDarkMode, isHovered: isHovered, isFocused: isFocused); + + final bool canAnimateHover = _isEnabled && (isHovered || isFocused || isPressed); + return AnimatedContainer( - duration: const Duration(milliseconds: 150), - curve: Curves.easeInOut, + duration: effectiveHoverEffectDuration, + curve: effectiveHoverEffectCurve, width: width, height: effectiveHeight, padding: correctedPadding, constraints: BoxConstraints(minWidth: effectiveHeight), - child: DefaultTextStyle.merge( - style: TextStyle(fontSize: effectiveMoonButtonSize.textStyle.fontSize), + decoration: ShapeDecoration( + color: canAnimateHover ? hoverColor : backgroundColor, + shape: SmoothRectangleBorder( + side: BorderSide( + color: effectiveBorderColor, + width: showBorder ? effectiveBorderWidth : 0, + style: showBorder ? BorderStyle.solid : BorderStyle.none, + ), + borderRadius: SmoothBorderRadius.only( + topLeft: SmoothRadius( + cornerRadius: effectiveBorderRadius.topLeft.x, + cornerSmoothing: 1, + ), + topRight: SmoothRadius( + cornerRadius: effectiveBorderRadius.topRight.x, + cornerSmoothing: 1, + ), + bottomLeft: SmoothRadius( + cornerRadius: effectiveBorderRadius.bottomLeft.x, + cornerSmoothing: 1, + ), + bottomRight: SmoothRadius( + cornerRadius: effectiveBorderRadius.bottomRight.x, + cornerSmoothing: 1, + ), + ), + ), + ), + child: AnimatedDefaultTextStyle( + style: TextStyle(color: effectiveTextColor, fontSize: effectiveMoonButtonSize.textStyle.fontSize), + duration: effectiveHoverEffectDuration, + curve: effectiveHoverEffectCurve, child: isFullWidth ? Stack( fit: StackFit.expand, diff --git a/lib/src/widgets/chips/chip.dart b/lib/src/widgets/chips/chip.dart new file mode 100644 index 00000000..7d369b12 --- /dev/null +++ b/lib/src/widgets/chips/chip.dart @@ -0,0 +1,316 @@ +import 'package:figma_squircle/figma_squircle.dart'; +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/borders.dart'; +import 'package:moon_design/src/theme/chip/chip_sizes.dart'; +import 'package:moon_design/src/theme/colors.dart'; +import 'package:moon_design/src/theme/effects/hover_effects.dart'; +import 'package:moon_design/src/theme/theme.dart'; +import 'package:moon_design/src/utils/extensions.dart'; +import 'package:moon_design/src/widgets/base_control.dart'; + +enum MoonChipSize { + sm, + md, +} + +class MoonChip extends StatelessWidget { + /// The callback that is called when the chip is tapped or pressed. + final VoidCallback? onTap; + + /// The callback that is called when the chip is long-pressed. + final VoidCallback? onLongPress; + + /// The size of the chip. + final MoonChipSize? chipSize; + + /// The focus node for the chip. + final FocusNode? focusNode; + + /// The semantic label for the chip. + final String? semanticLabel; + + /// The width of the chip. + final double? width; + + /// The height of the chip. + final double? height; + + /// The opacity value of the chip when it is disabled. + final double? disabledOpacityValue; + + /// The border width of the chip. + final double? borderWidth; + + /// The gap between the icon and the label. + final double? gap; + + /// The extent of the focus effect. + final double? focusEffectExtent; + + /// The minimum size of the touch target. + final double minTouchTargetSize; + + /// Whether the chip should automatically be focused when it is mounted. + final bool autofocus; + + /// Whether the chip is active/selected. + final bool isActive; + + /// Whether the chip should be focusable. + final bool isFocusable; + + /// Whether this chip should ensure that it has a minimal touch target size. + final bool ensureMinimalTouchTargetSize; + + /// Whether the chip should show a border. + final bool showBorder; + + /// Whether the chip should show a focus effect. + final bool showFocusEffect; + + /// The background color of the chip. + final Color? backgroundColor; + + /// The border and text color of the active/selected chip. + final Color? activeColor; + + /// The border color of the chip. + final Color? borderColor; + + /// The text color of the chip. + final Color? textColor; + + /// The color of the focus effect. + final Color? focusEffectColor; + + /// The color of the hover effect. + final Color? hoverEffectColor; + + /// The duration of the focus effect. + final Duration? focusEffectDuration; + + /// The duration of the hover effect. + final Duration? hoverEffectDuration; + + /// The curve of the focus effect. + final Curve? focusEffectCurve; + + /// The curve of the hover effect. + final Curve? hoverEffectCurve; + + /// The padding of the chip. + final EdgeInsets? padding; + + /// The border radius of the chip. + final BorderRadius? borderRadius; + + /// The widget in the left icon slot of the chip. + final Widget? leftIcon; + + /// The widget in the label slot of the chip. + final Widget? label; + + /// The widget in the right icon slot of the chip. + final Widget? rightIcon; + + const MoonChip({ + super.key, + this.onTap, + this.onLongPress, + this.chipSize, + this.focusNode, + this.semanticLabel, + this.width, + this.height, + this.disabledOpacityValue, + this.borderWidth, + this.gap, + this.focusEffectExtent, + this.minTouchTargetSize = 40, + this.autofocus = false, + this.isActive = false, + this.isFocusable = true, + this.ensureMinimalTouchTargetSize = false, + this.showBorder = false, + this.showFocusEffect = true, + this.backgroundColor, + this.activeColor, + this.borderColor, + this.textColor, + this.focusEffectColor, + this.hoverEffectColor, + this.focusEffectDuration, + this.hoverEffectDuration, + this.focusEffectCurve, + this.hoverEffectCurve, + this.padding, + this.borderRadius, + this.label, + this.leftIcon, + this.rightIcon, + }); + + MoonChipSizes _getMoonChipSize(BuildContext context, MoonChipSize? moonChipSize) { + switch (moonChipSize) { + case MoonChipSize.sm: + return context.moonTheme?.chipTheme.sm ?? MoonChipSizes.sm; + case MoonChipSize.md: + return context.moonTheme?.chipTheme.md ?? MoonChipSizes.md; + + default: + return context.moonTheme?.chipTheme.md ?? MoonChipSizes.sm; + } + } + + Color _getTextColor({ + required bool isDarkMode, + required Color backgroundColor, + required Color activeColor, + required Color? textColor, + required bool isActive, + }) { + if (isActive) return activeColor; + if (textColor != null) return textColor; + + final backgroundLuminance = backgroundColor.computeLuminance(); + if (backgroundLuminance > 0.5) { + return MoonColors.light.bulma; + } else { + return MoonColors.dark.bulma; + } + } + + Color _getBorderColor({required bool isActive, required Color activeColor}) => + isActive ? activeColor : borderColor ?? Colors.transparent; + + @override + Widget build(BuildContext context) { + final MoonChipSizes effectiveMoonChipSize = _getMoonChipSize(context, chipSize); + + final double effectiveHeight = height ?? effectiveMoonChipSize.height; + final double effectiveGap = gap ?? effectiveMoonChipSize.gap; + + final EdgeInsets effectivePadding = padding ?? effectiveMoonChipSize.padding; + + final EdgeInsetsDirectional correctedPadding = EdgeInsetsDirectional.fromSTEB( + leftIcon == null && label != null ? effectivePadding.left : 0, + effectivePadding.top, + rightIcon == null && label != null ? effectivePadding.right : 0, + effectivePadding.bottom, + ); + + final BorderRadius effectiveBorderRadius = borderRadius ?? effectiveMoonChipSize.borderRadius; + final double effectiveBorderWidth = + borderWidth ?? context.moonBorders?.borderWidth ?? MoonBorders.borders.borderWidth; + + final Color effectiveActiveColor = activeColor ?? context.moonColors?.piccolo ?? MoonColors.light.piccolo; + + final Color effectiveBackgroundColor = backgroundColor ?? context.moonColors?.gohan ?? MoonColors.light.gohan; + + final Color effectiveHoverEffectColor = hoverEffectColor ?? + context.moonEffects?.controlHoverEffect.secondaryHoverColor ?? + MoonHoverEffects.lightHoverEffect.secondaryHoverColor; + + final Curve effectiveHoverEffectCurve = hoverEffectCurve ?? + context.moonEffects?.controlHoverEffect.hoverCurve ?? + MoonHoverEffects.lightHoverEffect.hoverCurve; + + final Duration effectiveHoverEffectDuration = hoverEffectDuration ?? + context.moonEffects?.controlHoverEffect.hoverDuration ?? + MoonHoverEffects.lightHoverEffect.hoverDuration; + + return MoonBaseControl( + onTap: onTap ?? () {}, + onLongPress: onLongPress, + semanticLabel: semanticLabel, + borderRadius: effectiveBorderRadius, + disabledOpacityValue: disabledOpacityValue, + minTouchTargetSize: minTouchTargetSize, + ensureMinimalTouchTargetSize: ensureMinimalTouchTargetSize, + focusNode: focusNode, + autofocus: autofocus, + isFocusable: isFocusable, + showFocusEffect: showFocusEffect, + backgroundColor: backgroundColor, + focusEffectColor: focusEffectColor, + focusEffectExtent: focusEffectExtent, + focusEffectDuration: focusEffectDuration, + focusEffectCurve: focusEffectCurve, + showScaleAnimation: false, + builder: (context, isEnabled, isHovered, isFocused, isPressed) { + final bool canAnimate = isActive || isHovered || isFocused; + + final Color effectiveBorderColor = + borderColor ?? _getBorderColor(isActive: canAnimate, activeColor: effectiveActiveColor); + + final Color effectiveTextColor = _getTextColor( + isActive: canAnimate, + isDarkMode: context.isDarkMode, + activeColor: effectiveActiveColor, + backgroundColor: effectiveBackgroundColor, + textColor: textColor, + ); + + return AnimatedContainer( + duration: effectiveHoverEffectDuration, + curve: effectiveHoverEffectCurve, + width: width, + height: effectiveHeight, + padding: correctedPadding, + constraints: BoxConstraints(minWidth: effectiveHeight), + decoration: ShapeDecoration( + color: canAnimate ? effectiveHoverEffectColor : effectiveBackgroundColor, + shape: SmoothRectangleBorder( + side: BorderSide( + color: effectiveBorderColor, + width: showBorder ? effectiveBorderWidth : 0, + style: showBorder ? BorderStyle.solid : BorderStyle.none, + ), + borderRadius: SmoothBorderRadius.only( + topLeft: SmoothRadius( + cornerRadius: effectiveBorderRadius.topLeft.x, + cornerSmoothing: 1, + ), + topRight: SmoothRadius( + cornerRadius: effectiveBorderRadius.topRight.x, + cornerSmoothing: 1, + ), + bottomLeft: SmoothRadius( + cornerRadius: effectiveBorderRadius.bottomLeft.x, + cornerSmoothing: 1, + ), + bottomRight: SmoothRadius( + cornerRadius: effectiveBorderRadius.bottomRight.x, + cornerSmoothing: 1, + ), + ), + ), + ), + child: AnimatedDefaultTextStyle( + duration: effectiveHoverEffectDuration, + curve: effectiveHoverEffectCurve, + style: TextStyle(fontSize: effectiveMoonChipSize.textStyle.fontSize, 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, + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/src/widgets/chips/ghost_chip.dart b/lib/src/widgets/chips/ghost_chip.dart new file mode 100644 index 00000000..a2a0cefb --- /dev/null +++ b/lib/src/widgets/chips/ghost_chip.dart @@ -0,0 +1,180 @@ +import 'package:flutter/material.dart'; + +import 'package:moon_design/src/theme/colors.dart'; +import 'package:moon_design/src/theme/theme.dart'; +import 'package:moon_design/src/widgets/chips/chip.dart'; + +class MoonGhostChip extends StatelessWidget { + /// The callback that is called when the chip is tapped or pressed. + final VoidCallback? onTap; + + /// The callback that is called when the chip is long-pressed. + final VoidCallback? onLongPress; + + /// The size of the chip. + final MoonChipSize? chipSize; + + /// The focus node for the chip. + final FocusNode? focusNode; + + /// The semantic label for the chip. + final String? semanticLabel; + + /// The width of the chip. + final double? width; + + /// The height of the chip. + final double? height; + + /// The opacity value of the chip when it is disabled. + final double? disabledOpacityValue; + + /// The border width of the chip. + final double? borderWidth; + + /// The gap between the icon and the label. + final double? gap; + + /// The extent of the focus effect. + final double? focusEffectExtent; + + /// The minimum size of the touch target. + final double minTouchTargetSize; + + /// Whether the chip should automatically be focused when it is mounted. + final bool autofocus; + + /// Whether the chip is active/selected. + final bool isActive; + + /// Whether the chip should be focusable. + final bool isFocusable; + + /// Whether this chip should ensure that it has a minimal touch target size. + final bool ensureMinimalTouchTargetSize; + + /// Whether the chip should show a border. + final bool showBorder; + + /// Whether the chip should show a focus effect. + final bool showFocusEffect; + + /// The border and text color of the active/selected chip. + final Color? activeColor; + + /// The border color of the chip. + final Color? borderColor; + + /// The text color of the chip. + final Color? textColor; + + /// The color of the focus effect. + final Color? focusEffectColor; + + /// The color of the hover effect. + final Color? hoverEffectColor; + + /// The duration of the focus effect. + final Duration? focusEffectDuration; + + /// The duration of the hover effect. + final Duration? hoverEffectDuration; + + /// The curve of the focus effect. + final Curve? focusEffectCurve; + + /// The curve of the hover effect. + final Curve? hoverEffectCurve; + + /// The padding of the chip. + final EdgeInsets? padding; + + /// The border radius of the chip. + final BorderRadius? borderRadius; + + /// The widget in the left icon slot of the chip. + final Widget? leftIcon; + + /// The widget in the label slot of the chip. + final Widget? label; + + /// The widget in the right icon slot of the chip. + final Widget? rightIcon; + + const MoonGhostChip({ + super.key, + this.onTap, + this.onLongPress, + this.chipSize, + this.focusNode, + this.semanticLabel, + this.width, + this.height, + this.disabledOpacityValue, + this.borderWidth, + this.gap, + this.focusEffectExtent, + this.minTouchTargetSize = 40, + this.autofocus = false, + this.isActive = false, + this.isFocusable = true, + this.ensureMinimalTouchTargetSize = false, + this.showBorder = false, + this.showFocusEffect = true, + this.activeColor, + this.borderColor, + this.textColor, + this.focusEffectColor, + this.hoverEffectColor, + this.focusEffectDuration, + this.hoverEffectDuration, + this.focusEffectCurve, + this.hoverEffectCurve, + this.padding, + this.borderRadius, + this.label, + this.leftIcon, + this.rightIcon, + }); + + @override + Widget build(BuildContext context) { + final effectiveTextColor = textColor ?? context.moonColors?.bulma ?? MoonColors.light.bulma; + + return MoonChip( + key: key, + onTap: onTap, + onLongPress: onLongPress, + width: width, + height: height, + gap: gap, + padding: padding, + isActive: isActive, + activeColor: activeColor, + backgroundColor: Colors.transparent, + borderColor: borderColor, + textColor: effectiveTextColor, + chipSize: chipSize, + showBorder: showBorder, + borderRadius: borderRadius, + disabledOpacityValue: disabledOpacityValue, + showFocusEffect: showFocusEffect, + autofocus: autofocus, + focusNode: focusNode, + isFocusable: isFocusable, + focusEffectColor: focusEffectColor, + focusEffectCurve: focusEffectCurve, + focusEffectDuration: focusEffectDuration, + focusEffectExtent: focusEffectExtent, + hoverEffectColor: hoverEffectColor, + hoverEffectCurve: hoverEffectCurve, + hoverEffectDuration: hoverEffectDuration, + ensureMinimalTouchTargetSize: ensureMinimalTouchTargetSize, + minTouchTargetSize: minTouchTargetSize, + semanticLabel: semanticLabel, + leftIcon: leftIcon, + label: label, + rightIcon: rightIcon, + ); + } +} diff --git a/lib/src/widgets/tag/tag.dart b/lib/src/widgets/tag/tag.dart index 70ea71d2..8939aff1 100644 --- a/lib/src/widgets/tag/tag.dart +++ b/lib/src/widgets/tag/tag.dart @@ -1,3 +1,4 @@ +import 'package:figma_squircle/figma_squircle.dart'; import 'package:flutter/material.dart'; import 'package:moon_design/src/theme/colors.dart'; @@ -125,9 +126,28 @@ class MoonTag extends StatelessWidget { height: effectiveHeight, padding: correctedPadding, constraints: BoxConstraints(minWidth: effectiveHeight), - decoration: BoxDecoration( + decoration: ShapeDecoration( color: effectiveBackgroundColor, - borderRadius: effectiveBorderRadius, + shape: SmoothRectangleBorder( + borderRadius: SmoothBorderRadius.only( + topLeft: SmoothRadius( + cornerRadius: effectiveBorderRadius.topLeft.x, + cornerSmoothing: 1, + ), + topRight: SmoothRadius( + cornerRadius: effectiveBorderRadius.topRight.x, + cornerSmoothing: 1, + ), + bottomLeft: SmoothRadius( + cornerRadius: effectiveBorderRadius.bottomLeft.x, + cornerSmoothing: 1, + ), + bottomRight: SmoothRadius( + cornerRadius: effectiveBorderRadius.bottomRight.x, + cornerSmoothing: 1, + ), + ), + ), ), child: DefaultTextStyle.merge( style: isUpperCase