Skip to content

Commit f579ef4

Browse files
Copilotbdlukaa
andauthored
InfoBadge style variants (success, attention, critical), dot badges and adaptive sizing (#1277)
* Initial plan * Add InfoBadge named constructors and improve sizing Co-authored-by: bdlukaa <45696119+bdlukaa@users.noreply.github.com> * Fix documentation and test assertions Co-authored-by: bdlukaa <45696119+bdlukaa@users.noreply.github.com> * Refactor InfoBadge to use severity and improve API * Update CHANGELOG.md --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: bdlukaa <45696119+bdlukaa@users.noreply.github.com> Co-authored-by: Bruno D'Luka <brunodlukaa@gmail.com>
1 parent 319aba2 commit f579ef4

File tree

4 files changed

+139
-47
lines changed

4 files changed

+139
-47
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
- Updated content management to only display the current visible item at once. ([#1101](https://github.com/bdlukaa/fluent_ui/issues/1101))
4040
- Expose `NavigationView` important data using `NavigationView.dataOf(context)`
4141
- Added `NavigationPane.acrylicDisabled`, which allows disabling acrylic material effect of the pane overlays
42-
42+
- `InfoBadge` style variants (success, attention, critical), dot badges and adaptive sizing ([#1277](https://github.com/bdlukaa/fluent_ui/pull/1277))
4343

4444
## 4.13.0
4545

example/lib/screens/surface/info_badge.dart

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,9 @@ class _InfoBadgePageState extends State<InfoBadgePage> with PageMixin {
5454
),
5555
subtitle(content: const Text('InfoBadge with Icon')),
5656
const CodeSnippetCard(
57-
codeSnippet: '''InfoBadge(
58-
source: Icon(WindowsIcons.message),
59-
),
57+
codeSnippet: '''InfoBadge(source: Icon(WindowsIcons.message)),
6058
''',
61-
child: Row(children: [InfoBadge(source: Icon(WindowsIcons.message))]),
59+
child: InfoBadge(source: Icon(WindowsIcons.asterisk, size: 10)),
6260
),
6361
subtitle(content: const Text('Dot InfoBadge')),
6462
const CodeSnippetCard(
@@ -68,21 +66,11 @@ class _InfoBadgePageState extends State<InfoBadgePage> with PageMixin {
6866
),
6967
subtitle(content: const Text('Customized InfoBadge')),
7068
const CodeSnippetCard(
71-
codeSnippet: '''InfoBadge(
69+
codeSnippet: '''InfoBadge.success(
7270
source: const Text('1'),
73-
color: Colors.errorPrimaryColor,
74-
foregroundColor: Colors.white,
7571
),
7672
''',
77-
child: Row(
78-
children: [
79-
InfoBadge(
80-
source: Text('1'),
81-
color: Colors.errorPrimaryColor,
82-
foregroundColor: Colors.white,
83-
),
84-
],
85-
),
73+
child: InfoBadge.success(source: Text('1')),
8674
),
8775
subtitle(content: const Text('InfoBadge inside a NavigationView')),
8876
description(
Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import 'package:fluent_ui/fluent_ui.dart';
22

3+
typedef InfoBadgeSeverity = InfoBarSeverity;
4+
35
/// Badging is a non-intrusive and intuitive way to display notifications or
46
/// bring focus to an area within an app - whether that be for notifications,
57
/// indicating new content, or showing an alert. An `InfoBadge` is a small
@@ -20,56 +22,106 @@ import 'package:fluent_ui/fluent_ui.dart';
2022
/// * [NavigationView], which provides top-level navigation for your app
2123
class InfoBadge extends StatelessWidget {
2224
/// Creates an info badge.
23-
const InfoBadge({super.key, this.source, this.color, this.foregroundColor});
25+
const InfoBadge({
26+
super.key,
27+
this.source,
28+
this.color,
29+
this.foregroundColor,
30+
this.severity = InfoBarSeverity.info,
31+
});
32+
33+
/// Creates an attention info badge.
34+
///
35+
/// Uses the default accent color to draw attention to important content.
36+
const InfoBadge.attention({super.key, this.source, this.foregroundColor})
37+
: severity = InfoBarSeverity.warning,
38+
color = null;
39+
40+
/// Creates an informational info badge.
41+
///
42+
/// Uses the default accent color to indicate informational content.
43+
const InfoBadge.informational({super.key, this.source, this.foregroundColor})
44+
: severity = InfoBarSeverity.info,
45+
color = null;
46+
47+
/// Creates a success info badge.
48+
///
49+
/// Uses the success color to indicate successful completion or positive status.
50+
const InfoBadge.success({super.key, this.source, this.foregroundColor})
51+
: severity = InfoBarSeverity.success,
52+
color = null;
53+
54+
/// Creates a critical info badge.
55+
///
56+
/// Uses the error color to indicate critical issues or errors.
57+
const InfoBadge.critical({super.key, this.source, this.foregroundColor})
58+
: severity = InfoBarSeverity.error,
59+
color = null;
2460

2561
/// The source of the badge.
2662
///
2763
/// Usually a [Text] or an [Icon]
2864
final Widget? source;
2965

30-
/// The background color of the badge. If null, the current
31-
/// [FluentTheme.accentColor] is used
32-
///
33-
/// Some other used colors are:
34-
///
35-
/// * [Colors.errorPrimaryColor]
36-
/// * [Colors.successPrimaryColor]
37-
/// * [Colors.warningPrimaryColor]
66+
/// The background color of the badge. If not provided, the color will
67+
/// be decided based on the [severity].
3868
final Color? color;
3969

4070
/// The foreground color.
41-
///
42-
/// Applied to [Text]s and [Icon]s
4371
final Color? foregroundColor;
4472

73+
/// The severity of the badge.
74+
final InfoBadgeSeverity severity;
75+
4576
@override
4677
Widget build(BuildContext context) {
4778
assert(debugCheckHasFluentTheme(context));
4879

4980
final theme = FluentTheme.of(context);
5081
final color =
51-
this.color ?? theme.accentColor.defaultBrushFor(theme.brightness);
82+
this.color ??
83+
switch (severity) {
84+
InfoBarSeverity.info => theme.accentColor.defaultBrushFor(
85+
theme.brightness,
86+
),
87+
InfoBarSeverity.warning =>
88+
theme.resources.systemFillColorSolidAttentionBackground,
89+
InfoBarSeverity.success => theme.resources.systemFillColorSuccess,
90+
InfoBarSeverity.error => theme.resources.systemFillColorCritical,
91+
};
5292
final foregroundColor =
5393
this.foregroundColor ?? theme.resources.textOnAccentFillColorPrimary;
5494

95+
if (source == null) {
96+
return Container(
97+
width: 6,
98+
height: 6,
99+
decoration: BoxDecoration(
100+
color: color,
101+
borderRadius: BorderRadius.circular(100),
102+
),
103+
);
104+
}
105+
55106
return Container(
56-
constraints: source == null
57-
? const BoxConstraints(maxWidth: 10, maxHeight: 10)
58-
: const BoxConstraints(minWidth: 16, minHeight: 16, maxHeight: 16),
107+
constraints: const BoxConstraints(
108+
minWidth: 16,
109+
minHeight: 16,
110+
maxHeight: 16,
111+
),
112+
padding: const EdgeInsetsDirectional.only(start: 4, end: 4, bottom: 1),
59113
decoration: BoxDecoration(
60114
color: color,
61115
borderRadius: BorderRadius.circular(100),
62116
),
63-
child: source == null
64-
? null
65-
: DefaultTextStyle.merge(
66-
textAlign: TextAlign.center,
67-
style: TextStyle(color: foregroundColor, fontSize: 11),
68-
child: IconTheme.merge(
69-
data: IconThemeData(color: foregroundColor, size: 8),
70-
child: source!,
71-
),
72-
),
117+
child: DefaultTextStyle.merge(
118+
textAlign: TextAlign.center,
119+
style: TextStyle(color: foregroundColor, fontSize: 11),
120+
child: IconTheme.merge(
121+
data: IconThemeData(color: foregroundColor, size: 12),
122+
child: source!,
123+
),
124+
),
73125
);
74126
}
75127
}

test/info_badge_test.dart

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,12 @@ void main() {
1010
await tester.pumpWidget(wrapApp(child: const InfoBadge()));
1111

1212
final container = tester.widget<Container>(find.byType(Container));
13-
expect(
14-
container.constraints,
15-
const BoxConstraints(maxWidth: 10, maxHeight: 10),
16-
);
17-
1813
final decoration = container.decoration! as BoxDecoration;
1914
expect(decoration.borderRadius, BorderRadius.circular(100));
2015

16+
// Verify size through constraints on the Container
17+
expect(container.constraints?.minWidth, 6);
18+
expect(container.constraints?.minHeight, 6);
2119
expect(container.child, isNull);
2220
});
2321

@@ -39,4 +37,58 @@ void main() {
3937
final decoration = container.decoration! as BoxDecoration;
4038
expect(decoration.color, testColor);
4139
});
40+
41+
testWidgets('InfoBadge.success uses success color', (tester) async {
42+
await tester.pumpWidget(
43+
wrapApp(child: const InfoBadge.success(source: Text('1'))),
44+
);
45+
46+
final container = tester.widget<Container>(find.byType(Container));
47+
final decoration = container.decoration! as BoxDecoration;
48+
expect(decoration.color, Colors.successPrimaryColor);
49+
});
50+
51+
testWidgets('InfoBadge.critical uses error color', (tester) async {
52+
await tester.pumpWidget(
53+
wrapApp(child: const InfoBadge.critical(source: Text('!'))),
54+
);
55+
56+
final container = tester.widget<Container>(find.byType(Container));
57+
final decoration = container.decoration! as BoxDecoration;
58+
expect(decoration.color, Colors.errorPrimaryColor);
59+
});
60+
61+
testWidgets('InfoBadge.informational uses accent color', (tester) async {
62+
await tester.pumpWidget(
63+
wrapApp(child: const InfoBadge.informational(source: Text('i'))),
64+
);
65+
66+
final container = tester.widget<Container>(find.byType(Container));
67+
final decoration = container.decoration! as BoxDecoration;
68+
// Should use accent color (not a specific fixed color)
69+
expect(decoration.color, isNotNull);
70+
});
71+
72+
testWidgets('InfoBadge.attention uses accent color', (tester) async {
73+
await tester.pumpWidget(
74+
wrapApp(child: const InfoBadge.attention(source: Text('!'))),
75+
);
76+
77+
final container = tester.widget<Container>(find.byType(Container));
78+
final decoration = container.decoration! as BoxDecoration;
79+
// Should use accent color (not a specific fixed color)
80+
expect(decoration.color, isNotNull);
81+
});
82+
83+
testWidgets('Info Badge with source has padding', (tester) async {
84+
await tester.pumpWidget(
85+
wrapApp(child: const InfoBadge(source: Text('10'))),
86+
);
87+
88+
final container = tester.widget<Container>(find.byType(Container));
89+
expect(
90+
container.padding,
91+
const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
92+
);
93+
});
4294
}

0 commit comments

Comments
 (0)