-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: plain tooltip + example * feat: VoicesRichTooltip * chore: remove console prints * feat: light mode shadows * chore: widgets docs * fix: example texts makes more sens now * chore: Tooltips tests * chore: cleanup docs
- Loading branch information
1 parent
9ed3380
commit 1bd7a72
Showing
8 changed files
with
568 additions
and
5 deletions.
There are no files selected for viewing
55 changes: 55 additions & 0 deletions
55
catalyst_voices/lib/widgets/tooltips/voices_plain_tooltip.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart'; | ||
import 'package:flutter/material.dart'; | ||
|
||
/// A simple tooltip widget with a plain text message and a child widget. | ||
/// | ||
/// **Notes:** | ||
/// - The tooltip's colors might need to be adjusted based on the final design. | ||
/// - The tooltip's text is constrained to a maximum width of 200 pixels. | ||
/// | ||
/// **Usage:** | ||
/// ```dart | ||
/// VoicesPlainTooltip( | ||
/// message: "This is a tooltip message.", | ||
/// child: Icon(Icons.info), | ||
/// ) | ||
/// ``` | ||
class VoicesPlainTooltip extends StatelessWidget { | ||
/// The text message to display in the tooltip. | ||
final String message; | ||
|
||
/// The widget that triggers tooltip visibility. | ||
final Widget child; | ||
|
||
const VoicesPlainTooltip({ | ||
super.key, | ||
required this.message, | ||
required this.child, | ||
}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final theme = Theme.of(context); | ||
|
||
return Tooltip( | ||
richMessage: WidgetSpan( | ||
child: ConstrainedBox( | ||
key: const ValueKey('VoicesPlainTooltipContentKey'), | ||
constraints: const BoxConstraints(maxWidth: 200), | ||
child: Text( | ||
message, | ||
style: theme.textTheme.bodySmall?.copyWith( | ||
color: theme.colors.iconsBackground, | ||
), | ||
), | ||
), | ||
), | ||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||
decoration: BoxDecoration( | ||
color: theme.colors.iconsForeground, | ||
borderRadius: BorderRadius.circular(4), | ||
), | ||
child: child, | ||
); | ||
} | ||
} |
171 changes: 171 additions & 0 deletions
171
catalyst_voices/lib/widgets/tooltips/voices_rich_tooltip.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
import 'package:catalyst_voices/widgets/buttons/voices_text_button.dart'; | ||
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart'; | ||
import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; | ||
import 'package:flutter/material.dart'; | ||
|
||
final class VoicesRichTooltipActionData { | ||
final String name; | ||
final VoidCallback onTap; | ||
|
||
VoicesRichTooltipActionData({ | ||
required this.name, | ||
required this.onTap, | ||
}); | ||
} | ||
|
||
/// A tooltip widget with a rich text message (title and message) and | ||
/// optional actions displayed at the bottom. | ||
/// | ||
/// **Notes:** | ||
/// - The tooltip's maximum width is constrained to 312 pixels. | ||
/// - The tooltip's background color and shadow are theme-dependent. | ||
/// - If no actions are provided, the tooltip can be dismissed by tapping | ||
/// anywhere on it. Otherwise, tapping will only trigger the action button | ||
/// taps which will dismiss all tooltips see [Tooltip.dismissAllToolTips]. | ||
/// | ||
/// **Example Usage:** | ||
/// ```dart | ||
/// final actions = [ | ||
/// VoicesRichTooltipActionData( | ||
/// name: "Edit", | ||
/// onTap: () => print("Edit tapped"), | ||
/// ), | ||
/// VoicesRichTooltipActionData( | ||
/// name: "Delete", | ||
/// onTap: () => print("Delete tapped"), | ||
/// ), | ||
/// ]; | ||
/// | ||
/// VoicesRichTooltip( | ||
/// title: "Tooltip Title", | ||
/// message: "This is a tooltip with a descriptive message.", | ||
/// actions: actions, | ||
/// child: Icon(Icons.info), | ||
/// ) | ||
/// ``` | ||
class VoicesRichTooltip extends StatelessWidget { | ||
/// The main title displayed at the top of the tooltip. | ||
final String title; | ||
|
||
/// The descriptive message displayed below the title. | ||
final String message; | ||
|
||
/// (Optional) A list of action buttons displayed at the | ||
/// bottom of the tooltip. Each action has a `name` and an `onTap` callback. | ||
final List<VoicesRichTooltipActionData> actions; | ||
|
||
/// The widget that triggers tooltip visibility. | ||
final Widget child; | ||
|
||
const VoicesRichTooltip({ | ||
super.key, | ||
required this.title, | ||
required this.message, | ||
this.actions = const [], | ||
required this.child, | ||
}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final theme = Theme.of(context); | ||
final isLightTheme = theme.brightness == Brightness.light; | ||
|
||
return Tooltip( | ||
richMessage: WidgetSpan( | ||
child: ConstrainedBox( | ||
key: const ValueKey('VoicesRichTooltipContentKey'), | ||
constraints: const BoxConstraints(maxWidth: 312), | ||
child: Column( | ||
mainAxisSize: MainAxisSize.min, | ||
children: [ | ||
_Content(title, message), | ||
if (actions.isNotEmpty) ...[ | ||
const SizedBox(height: 8), | ||
_ActionsRow(actions), | ||
], | ||
const SizedBox(height: 8), | ||
], | ||
), | ||
), | ||
), | ||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), | ||
decoration: BoxDecoration( | ||
color: theme.colors.onSurfaceNeutralOpaqueLv2, | ||
borderRadius: BorderRadius.circular(12), | ||
boxShadow: isLightTheme ? kElevationToShadow[2] : null, | ||
), | ||
enableTapToDismiss: actions.isEmpty, | ||
child: child, | ||
); | ||
} | ||
} | ||
|
||
class _Content extends StatelessWidget { | ||
final String title; | ||
final String message; | ||
|
||
const _Content( | ||
this.title, | ||
this.message, | ||
); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
final theme = Theme.of(context); | ||
|
||
return Padding( | ||
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16) | ||
.add(const EdgeInsets.only(top: 8)), | ||
child: Column( | ||
mainAxisSize: MainAxisSize.min, | ||
crossAxisAlignment: CrossAxisAlignment.start, | ||
children: [ | ||
Text( | ||
title, | ||
style: theme.textTheme.labelLarge?.copyWith( | ||
color: theme.colors.textPrimary, | ||
), | ||
textAlign: TextAlign.start, | ||
), | ||
const SizedBox(height: 4), | ||
Text( | ||
message, | ||
style: theme.textTheme.bodyMedium?.copyWith( | ||
color: theme.colors.textPrimary, | ||
), | ||
textAlign: TextAlign.start, | ||
), | ||
], | ||
), | ||
); | ||
} | ||
} | ||
|
||
class _ActionsRow extends StatelessWidget { | ||
const _ActionsRow(this.actions); | ||
|
||
final List<VoicesRichTooltipActionData> actions; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return Padding( | ||
padding: const EdgeInsets.symmetric(horizontal: 8), | ||
child: Row( | ||
children: actions | ||
.map<Widget>( | ||
(action) { | ||
return VoicesTextButton( | ||
onTap: () { | ||
Tooltip.dismissAllToolTips(); | ||
action.onTap(); | ||
}, | ||
child: Text(action.name), | ||
); | ||
}, | ||
) | ||
.separatedBy(const SizedBox(width: 8)) | ||
.toList(), | ||
), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
catalyst_voices/test/widgets/tooltips/voices_plain_tooltip_test.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import 'package:catalyst_voices/widgets/tooltips/voices_plain_tooltip.dart'; | ||
import 'package:flutter/gestures.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter_test/flutter_test.dart'; | ||
|
||
import '../../helpers/helpers.dart'; | ||
|
||
void main() { | ||
group('VoicesPlainTooltip', () { | ||
testWidgets('displays the correct message', (tester) async { | ||
// Given | ||
const message = 'This is a tooltip message.'; | ||
const child = Icon(Icons.info); | ||
|
||
const widget = VoicesPlainTooltip( | ||
message: message, | ||
child: child, | ||
); | ||
|
||
// When | ||
await tester.pumpApp(widget); | ||
await tester.pumpAndSettle(); | ||
|
||
final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); | ||
await gesture.addPointer(location: Offset.zero); | ||
addTearDown(gesture.removePointer); | ||
await tester.pump(); | ||
await gesture.moveTo(tester.getCenter(find.byType(Icon))); | ||
await tester.pumpAndSettle(); | ||
|
||
// Then | ||
expect(find.text(message), findsOneWidget); | ||
}); | ||
|
||
testWidgets('is not displayed without hover', (tester) async { | ||
// Given | ||
const message = 'This is a tooltip message.'; | ||
const child = Icon(Icons.info); | ||
|
||
const widget = VoicesPlainTooltip( | ||
message: message, | ||
child: child, | ||
); | ||
|
||
// When | ||
await tester.pumpApp(widget); | ||
await tester.pumpAndSettle(); | ||
|
||
// Then | ||
expect(find.text(message), findsNothing); | ||
}); | ||
|
||
testWidgets('constrains the text width', (tester) async { | ||
// Given | ||
const message = 'This is a very long tooltip message.'; | ||
const child = Icon(Icons.info); | ||
|
||
const widget = VoicesPlainTooltip( | ||
message: message, | ||
child: child, | ||
); | ||
|
||
// When | ||
await tester.pumpApp(widget); | ||
await tester.pumpAndSettle(); | ||
|
||
final gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); | ||
await gesture.addPointer(location: Offset.zero); | ||
addTearDown(gesture.removePointer); | ||
await tester.pump(); | ||
await gesture.moveTo(tester.getCenter(find.byType(Icon))); | ||
await tester.pumpAndSettle(); | ||
|
||
// Then | ||
final size = tester.getSize( | ||
find.byKey(const ValueKey('VoicesPlainTooltipContentKey')), | ||
); | ||
|
||
expect(size.width, 200.0); | ||
}); | ||
}); | ||
} |
Oops, something went wrong.