From daa93b5e88e8780f6d2b1bc7becd66b30a4f085b Mon Sep 17 00:00:00 2001 From: atanasyordanov21 <63714308+atanasyordanov21@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:45:42 +0300 Subject: [PATCH] Component tooltip (#31) * create tooltip * create Widgetbook for ZetaTooltip * add LayoutBuilder * [automated commit] lint format and import sort --------- Co-authored-by: github-actions --- example/lib/home.dart | 2 + .../lib/pages/components/tooltip_example.dart | 71 ++++++ example/widgetbook/main.dart | 2 + .../pages/components/tooltip_widgetbook.dart | 32 +++ lib/src/components/tooltip/tooltip.dart | 212 ++++++++++++++++++ lib/zeta_flutter.dart | 1 + 6 files changed, 320 insertions(+) create mode 100644 example/lib/pages/components/tooltip_example.dart create mode 100644 example/widgetbook/pages/components/tooltip_widgetbook.dart create mode 100644 lib/src/components/tooltip/tooltip.dart diff --git a/example/lib/home.dart b/example/lib/home.dart index a49b8d60..de428915 100644 --- a/example/lib/home.dart +++ b/example/lib/home.dart @@ -23,6 +23,7 @@ import 'package:zeta_example/pages/components/switch_example.dart'; import 'package:zeta_example/pages/components/snackbar_example.dart'; import 'package:zeta_example/pages/components/tabs_example.dart'; import 'package:zeta_example/pages/components/pagination_example.dart'; +import 'package:zeta_example/pages/components/tooltip_example.dart'; import 'package:zeta_example/pages/theme/color_example.dart'; import 'package:zeta_example/pages/components/password_input_example.dart'; import 'package:zeta_example/pages/components/progress_example.dart'; @@ -65,6 +66,7 @@ final List components = [ Component(DateInputExample.name, (context) => const DateInputExample()), Component(PhoneInputExample.name, (context) => const PhoneInputExample()), Component(DialogExample.name, (context) => const DialogExample()), + Component(TooltipExample.name, (context) => const TooltipExample()), Component(NavigationRailExample.name, (context) => const NavigationRailExample()), ]; diff --git a/example/lib/pages/components/tooltip_example.dart b/example/lib/pages/components/tooltip_example.dart new file mode 100644 index 00000000..01912cfc --- /dev/null +++ b/example/lib/pages/components/tooltip_example.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:zeta_example/widgets.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +class TooltipExample extends StatelessWidget { + static const String name = 'Tooltip'; + + const TooltipExample({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ExampleScaffold( + name: 'Tooltip', + child: SizedBox( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('Rounded'), + const SizedBox(height: ZetaSpacing.l), + ZetaTooltip( + child: Text('Label'), + ), + const SizedBox(height: ZetaSpacing.l), + ZetaTooltip( + child: Text('Label'), + arrowDirection: ZetaTooltipArrowDirection.right, + ), + const SizedBox(height: ZetaSpacing.l), + ZetaTooltip( + child: Text('Label'), + arrowDirection: ZetaTooltipArrowDirection.up, + ), + const SizedBox(height: ZetaSpacing.l), + ZetaTooltip( + child: Text('Label'), + arrowDirection: ZetaTooltipArrowDirection.left, + ), + Divider(height: ZetaSpacing.xxl), + Text('Sharp'), + const SizedBox(height: ZetaSpacing.l), + ZetaTooltip( + child: Text('Label'), + rounded: false, + ), + const SizedBox(height: ZetaSpacing.l), + ZetaTooltip( + child: Text('Label'), + arrowDirection: ZetaTooltipArrowDirection.right, + rounded: false, + ), + const SizedBox(height: ZetaSpacing.l), + ZetaTooltip( + child: Text('Label'), + arrowDirection: ZetaTooltipArrowDirection.up, + rounded: false, + ), + const SizedBox(height: ZetaSpacing.l), + ZetaTooltip( + child: Text('Label'), + arrowDirection: ZetaTooltipArrowDirection.left, + rounded: false, + ), + ], + ), + ), + ); + } +} diff --git a/example/widgetbook/main.dart b/example/widgetbook/main.dart index eed19b5b..f5975bb9 100644 --- a/example/widgetbook/main.dart +++ b/example/widgetbook/main.dart @@ -30,6 +30,7 @@ import 'pages/components/stepper_widgetbook.dart'; import 'pages/components/switch_widgetbook.dart'; import 'pages/components/snack_bar_widgetbook.dart'; import 'pages/components/tabs_widgetbook.dart'; +import 'pages/components/tooltip_widgetbook.dart'; import 'pages/theme/color_widgetbook.dart'; import 'pages/theme/radius_widgetbook.dart'; import 'pages/theme/spacing_widgetbook.dart'; @@ -128,6 +129,7 @@ class HotReload extends StatelessWidget { ), WidgetbookUseCase(name: 'Dialog', builder: (context) => dialogUseCase(context)), WidgetbookUseCase(name: 'Navigation Rail', builder: (context) => navigationRailUseCase(context)), + WidgetbookUseCase(name: 'Tooltip', builder: (context) => tooltipUseCase(context)), ]..sort((a, b) => a.name.compareTo(b.name)), ), WidgetbookCategory( diff --git a/example/widgetbook/pages/components/tooltip_widgetbook.dart b/example/widgetbook/pages/components/tooltip_widgetbook.dart new file mode 100644 index 00000000..8212303e --- /dev/null +++ b/example/widgetbook/pages/components/tooltip_widgetbook.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:widgetbook/widgetbook.dart'; +import 'package:zeta_flutter/zeta_flutter.dart'; + +import '../../test/test_components.dart'; + +Widget tooltipUseCase(BuildContext context) { + final text = context.knobs.string( + label: 'Tooltip text', + initialValue: 'Label', + ); + final direction = context.knobs.list( + label: 'Arrow direction', + options: ZetaTooltipArrowDirection.values, + labelBuilder: (direction) => direction.name, + ); + final rounded = context.knobs.boolean(label: 'Rounded', initialValue: true); + return WidgetbookTestWidget( + widget: StatefulBuilder( + builder: (context, setState) { + return Padding( + padding: const EdgeInsets.all(ZetaSpacing.l), + child: ZetaTooltip( + child: Text(text), + rounded: rounded, + arrowDirection: direction, + ), + ); + }, + ), + ); +} diff --git a/lib/src/components/tooltip/tooltip.dart b/lib/src/components/tooltip/tooltip.dart new file mode 100644 index 00000000..b98eb2c9 --- /dev/null +++ b/lib/src/components/tooltip/tooltip.dart @@ -0,0 +1,212 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../zeta_flutter.dart'; + +/// The direction of [ZetaTooltip]'s arrow +enum ZetaTooltipArrowDirection { + /// [up] + up, + + /// [down] + down, + + /// [left] + left, + + /// [right] + right, +} + +/// [ZetaTooltip] +class ZetaTooltip extends StatelessWidget { + /// Constructor for [ZetaTooltip]. + const ZetaTooltip({ + super.key, + required this.child, + this.rounded = true, + this.padding, + this.color, + this.textStyle, + this.arrowDirection = ZetaTooltipArrowDirection.down, + this.maxWidth, + }); + + /// The content of the tooltip. + final Widget child; + + /// Determines if the tooltip should have rounded or sharp corners. + /// Default is `true` (rounded). + final bool rounded; + + /// The padding inside the [ZetaTooltip]. + /// Default is: + /// ``` + /// const EdgeInsets.symmetric( + /// horizontal: ZetaSpacing.xs, + /// vertical: ZetaSpacing.xxs, + /// ) + /// ``` + final EdgeInsets? padding; + + /// The color of the tooltip. + /// Default is `zeta.colors.textDefault`. + final Color? color; + + /// The text style of the tooltip. + /// Default is: + /// ``` + /// ZetaTextStyles.bodyXSmall.copyWith( + /// color: zeta.colors.textInverse, + /// fontWeight: FontWeight.w500, + /// ), + /// ``` + final TextStyle? textStyle; + + /// The direction of the tooltip's arrow. + /// Default is `ZetaTooltipArrowDirection.down`. + final ZetaTooltipArrowDirection arrowDirection; + + /// The maximum width of the tooltip. + final double? maxWidth; + + @override + Widget build(BuildContext context) { + final zeta = Zeta.of(context); + final color = this.color ?? zeta.colors.textDefault; + + return LayoutBuilder( + builder: (context, constraints) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (arrowDirection == ZetaTooltipArrowDirection.up) + Center( + child: CustomPaint( + painter: _FilledArrow( + color: color, + direction: arrowDirection, + ), + size: const Size(8, 4), + ), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (arrowDirection == ZetaTooltipArrowDirection.left) + Center( + child: CustomPaint( + painter: _FilledArrow( + color: color, + direction: arrowDirection, + ), + size: const Size(4, 8), + ), + ), + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: maxWidth ?? constraints.maxWidth, + ), + child: DecoratedBox( + decoration: BoxDecoration( + color: color, + borderRadius: rounded ? ZetaRadius.minimal : null, + ), + child: Padding( + padding: padding ?? + const EdgeInsets.symmetric( + horizontal: ZetaSpacing.xs, + vertical: ZetaSpacing.xxs, + ), + child: DefaultTextStyle( + style: textStyle ?? + ZetaTextStyles.bodyXSmall.copyWith( + color: zeta.colors.textInverse, + fontWeight: FontWeight.w500, + ), + child: child, + ), + ), + ), + ), + if (arrowDirection == ZetaTooltipArrowDirection.right) + Center( + child: CustomPaint( + painter: _FilledArrow( + color: color, + direction: arrowDirection, + ), + size: const Size(4, 8), + ), + ), + ], + ), + if (arrowDirection == ZetaTooltipArrowDirection.down) + Center( + child: CustomPaint( + painter: _FilledArrow( + color: color, + direction: arrowDirection, + ), + size: const Size(8, 4), + ), + ), + ], + ); + }, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('rounded', rounded)) + ..add(DiagnosticsProperty('padding', padding)) + ..add(ColorProperty('color', color)) + ..add(DiagnosticsProperty('textStyle', textStyle)) + ..add(EnumProperty('arrowDirection', arrowDirection)) + ..add(DoubleProperty('maxWidth', maxWidth)); + } +} + +class _FilledArrow extends CustomPainter { + _FilledArrow({ + required this.direction, + this.color, + }); + + final Color? color; + final ZetaTooltipArrowDirection direction; + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..style = PaintingStyle.fill + ..color = color ?? Colors.transparent; + final path = switch (direction) { + ZetaTooltipArrowDirection.up => Path() + ..moveTo(size.width / 2, 0) + ..lineTo(0, size.height) + ..lineTo(size.width, size.height) + ..lineTo(size.width / 2, 0), + ZetaTooltipArrowDirection.left => Path() + ..moveTo(size.width, 0) + ..lineTo(0, size.height / 2) + ..lineTo(size.width, size.height) + ..lineTo(size.width, 0), + ZetaTooltipArrowDirection.down => Path() + ..lineTo(size.width, 0) + ..lineTo(size.width / 2, size.height) + ..lineTo(0, 0), + ZetaTooltipArrowDirection.right => Path() + ..lineTo(size.width, size.height / 2) + ..lineTo(0, size.height) + ..lineTo(0, 0), + }; + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} diff --git a/lib/zeta_flutter.dart b/lib/zeta_flutter.dart index 9181ca42..3638bf90 100644 --- a/lib/zeta_flutter.dart +++ b/lib/zeta_flutter.dart @@ -41,6 +41,7 @@ export 'src/components/stepper/stepper.dart'; export 'src/components/switch/zeta_switch.dart'; export 'src/components/tabs/tab.dart'; export 'src/components/tabs/tab_bar.dart'; +export 'src/components/tooltip/tooltip.dart'; export 'src/theme/color_extensions.dart'; export 'src/theme/color_scheme.dart'; export 'src/theme/color_swatch.dart';