Skip to content

Commit 28b4d8e

Browse files
haavamoaCopilot
andauthored
Add cross-platform Toolbar component (#827)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: haavamoa <2527084+haavamoa@users.noreply.github.com>
1 parent d5ac59b commit 28b4d8e

28 files changed

Lines changed: 3160 additions & 5 deletions

.github/copilot-instructions.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99

1010
Add corrections to the relevant section (Critical Patterns, Common Pitfalls, etc.) or create a new section if needed.
1111

12+
## Skills
13+
**IMPORTANT**: Before performing any task, always check `.github/skills/` and `.github/agents/` for applicable skill and agent files. Each subfolder contains a `SKILL.md` or agent definition with specific workflows, templates, trigger phrases, and rules that MUST be followed when their trigger conditions match.
14+
15+
**Workflow**: List all folders in `.github/skills/` and `.github/agents/` (if they exist), read each definition file to check if it applies to the current task, and follow its steps exactly.
16+
1217
## Project Overview
1318
DIPS.Mobile.UI is a .NET MAUI component library for iOS and Android mobile apps in the healthcare domain. Components follow a design system with resources (colors, sizes, icons) auto-generated from Figma via DIPS.Mobile.DesignTokens pipeline.
1419

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## [55.5.0]
2+
- [Toolbar] Added new cross-platform Toolbar component for bottom action bars
3+
- [ContentPage] Added `BottomToolbar` property to attach a toolbar to any content page
4+
15
## [55.4.1]
26
- [SearchPage][Android] Fix bug where `ScrollableHeader` were not added.
37

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
<?xml
2+
version="1.0"
3+
encoding="utf-8"?>
4+
5+
<dui:ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
6+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
7+
xmlns:dui="http://dips.com/mobile.ui"
8+
xmlns:local="clr-namespace:Components.ComponentsSamples.Toolbar"
9+
x:DataType="local:ToolbarSamplesViewModel"
10+
x:Class="Components.ComponentsSamples.Toolbar.ToolbarSamples"
11+
Title="Documents">
12+
<dui:ContentPage.BottomToolbar>
13+
<dui:Toolbar x:Name="bottomToolbar"
14+
HorizontalAlignment="End"
15+
HidesOnScrollFor="{x:Reference scrollView}">
16+
<dui:ToolbarGroup>
17+
<dui:ToolbarTaskButton Title="{Binding SignTitle}"
18+
BadgeCount="{Binding EditBadgeCount}"
19+
IsVisible="{Binding IsSignVisible}"
20+
IsBusy="{Binding IsSignBusy}"
21+
IsFinished="{Binding IsSignFinished}"
22+
Command="{Binding SignCommand}">
23+
<dui:ToolbarTaskButton.HandleError>
24+
<dui:ErrorHandler HasError="{Binding HasSignError}"
25+
ErrorTappedCommand="{Binding SignErrorTappedCommand}" />
26+
</dui:ToolbarTaskButton.HandleError>
27+
</dui:ToolbarTaskButton>
28+
</dui:ToolbarGroup>
29+
<dui:ToolbarGroup>
30+
<dui:ToolbarButton Icon="{dui:Icons edit_line}"
31+
Title="Edit"
32+
Command="{Binding EditCommand}" />
33+
<dui:ToolbarButton Icon="{dui:Icons tab_more_fill}"
34+
Title="More actions">
35+
<dui:ToolbarButton.Menu>
36+
<dui:ContextMenu>
37+
<dui:ContextMenuItem Title="Copy"
38+
Command="{Binding CopyCommand}" />
39+
<dui:ContextMenuItem Title="Share"
40+
Command="{Binding ShareCommand}" />
41+
<dui:ContextMenuItem Title="Print"
42+
Command="{Binding PrintCommand}" />
43+
<dui:ContextMenuItem Title="Delete"
44+
Command="{Binding DeleteCommand}" />
45+
</dui:ContextMenu>
46+
</dui:ToolbarButton.Menu>
47+
</dui:ToolbarButton>
48+
</dui:ToolbarGroup>
49+
</dui:Toolbar>
50+
</dui:ContentPage.BottomToolbar>
51+
<dui:ContentPage.BindingContext>
52+
<local:ToolbarSamplesViewModel />
53+
</dui:ContentPage.BindingContext>
54+
55+
<dui:ContentPage.ToolbarItems>
56+
<ToolbarItem Text="Close"
57+
Clicked="OnCloseClicked" />
58+
</dui:ContentPage.ToolbarItems>
59+
60+
<dui:ScrollView x:Name="scrollView"
61+
HandlerChanged="OnScrollViewHandlerChanged">
62+
<dui:VerticalStackLayout Spacing="0">
63+
<!-- Toolbar configuration section -->
64+
<dui:VerticalStackLayout Padding="{dui:Sizes size_4}"
65+
Spacing="{dui:Sizes size_1}">
66+
<dui:Label Text="Toolbar configuration"
67+
Style="{dui:Styles Label=SectionHeader}" />
68+
<dui:Label Text="Use these controls to test toolbar behavior"
69+
Style="{dui:Styles Label=UI200}"
70+
TextColor="{dui:Colors color_text_subtle}" />
71+
</dui:VerticalStackLayout>
72+
73+
<dui:VerticalStackLayout Padding="{dui:Sizes size_4}"
74+
Spacing="0">
75+
<Grid ColumnDefinitions="*,Auto"
76+
Padding="0">
77+
<dui:Label Text="Show Sign button"
78+
Style="{dui:Styles Label=UI200}"
79+
VerticalOptions="Center" />
80+
<dui:Switch Grid.Column="1"
81+
IsToggled="{Binding IsSignVisible}" />
82+
</Grid>
83+
<Grid ColumnDefinitions="*,Auto"
84+
Padding="0">
85+
<dui:Label Text="Show/Hide toolbar"
86+
Style="{dui:Styles Label=UI200}"
87+
VerticalOptions="Center" />
88+
<dui:Switch Grid.Column="1"
89+
IsToggled="True"
90+
Toggled="OnToolbarVisibilityToggled" />
91+
</Grid>
92+
<Grid ColumnDefinitions="*,Auto"
93+
Padding="0">
94+
<dui:Label Text="Hides on scroll"
95+
Style="{dui:Styles Label=UI200}"
96+
VerticalOptions="Center" />
97+
<dui:Switch Grid.Column="1"
98+
IsToggled="True"
99+
Toggled="OnHidesOnScrollToggled" />
100+
</Grid>
101+
<Grid ColumnDefinitions="*,Auto"
102+
Padding="0">
103+
<dui:Label Text="Sign is busy"
104+
Style="{dui:Styles Label=UI200}"
105+
VerticalOptions="Center" />
106+
<dui:Switch Grid.Column="1"
107+
IsToggled="{Binding IsSignBusy}" />
108+
</Grid>
109+
<Grid ColumnDefinitions="*,Auto"
110+
Padding="0">
111+
<dui:Label Text="Sign is finished"
112+
Style="{dui:Styles Label=UI200}"
113+
VerticalOptions="Center" />
114+
<dui:Switch Grid.Column="1"
115+
IsToggled="{Binding IsSignFinished}" />
116+
</Grid>
117+
<Grid ColumnDefinitions="*,Auto"
118+
Padding="0">
119+
<dui:Label Text="Sign title"
120+
Style="{dui:Styles Label=UI200}"
121+
VerticalOptions="Center" />
122+
<Entry Grid.Column="1"
123+
Text="{Binding SignTitle}"
124+
WidthRequest="120"
125+
VerticalOptions="Center" />
126+
</Grid>
127+
<Grid ColumnDefinitions="*,Auto"
128+
Padding="0">
129+
<dui:Label Text="Sign has error"
130+
Style="{dui:Styles Label=UI200}"
131+
VerticalOptions="Center" />
132+
<dui:Switch Grid.Column="1"
133+
IsToggled="{Binding HasSignError}" />
134+
</Grid>
135+
<Grid ColumnDefinitions="*,Auto"
136+
Padding="0">
137+
<dui:Label Text="Edit badge count"
138+
Style="{dui:Styles Label=UI200}"
139+
VerticalOptions="Center" />
140+
<dui:HorizontalStackLayout Grid.Column="1"
141+
Spacing="{dui:Sizes size_1}"
142+
VerticalOptions="Center">
143+
<dui:Button Text="-"
144+
WidthRequest="40"
145+
HeightRequest="40"
146+
Command="{Binding DecrementBadgeCommand}" />
147+
<dui:Label Text="{Binding EditBadgeCount}"
148+
Style="{dui:Styles Label=UI200}"
149+
VerticalOptions="Center"
150+
HorizontalTextAlignment="Center"
151+
WidthRequest="30" />
152+
<dui:Button Text="+"
153+
WidthRequest="40"
154+
HeightRequest="40"
155+
Command="{Binding IncrementBadgeCommand}" />
156+
</dui:HorizontalStackLayout>
157+
</Grid>
158+
</dui:VerticalStackLayout>
159+
160+
<dui:VerticalStackLayout Padding="{dui:Sizes size_4}"
161+
Spacing="{dui:Sizes size_2}">
162+
<dui:Label Text="Task actions"
163+
Style="{dui:Styles Label=UI100}" />
164+
<dui:Button Text="Simulate sign error"
165+
Command="{Binding SimulateSignErrorCommand}" />
166+
</dui:VerticalStackLayout>
167+
168+
<BoxView HeightRequest="1"
169+
Color="{dui:Colors color_border_default}"
170+
Margin="{dui:Sizes size_4}" />
171+
172+
<!-- Fake document section (for scroll testing) -->
173+
<dui:VerticalStackLayout Padding="{dui:Sizes size_4}"
174+
Spacing="{dui:Sizes size_1}">
175+
<dui:Label Text="Admission Note"
176+
Style="{dui:Styles Label=SectionHeader}" />
177+
<dui:Label Text="Created 15.03.2026 by Dr. Karen Olsen"
178+
Style="{dui:Styles Label=UI200}"
179+
TextColor="{dui:Colors color_text_subtle}" />
180+
<dui:Label Text="This is a fake document to demonstrate toolbar scroll behavior."
181+
Style="{dui:Styles Label=UI200}"
182+
TextColor="{dui:Colors color_text_subtle}" />
183+
</dui:VerticalStackLayout>
184+
185+
<!-- Document content -->
186+
<dui:VerticalStackLayout Padding="{dui:Sizes size_4}"
187+
Spacing="{dui:Sizes size_3}">
188+
<dui:Label Text="Subjective"
189+
Style="{dui:Styles Label=UI100}" />
190+
<dui:Label
191+
Text="Patient presents with chest pain radiating to left arm, onset 2 hours ago. Reports shortness of breath and mild nausea. No previous history of cardiac events. Currently on Metformin 500mg for type 2 diabetes."
192+
Style="{dui:Styles Label=UI200}" />
193+
194+
<dui:Label Text="Objective"
195+
Style="{dui:Styles Label=UI100}" />
196+
<dui:Label
197+
Text="BP 145/92 mmHg, HR 98 bpm, SpO2 96% on room air. Temperature 37.1°C. Heart sounds: Regular rhythm, no murmurs detected. Lung auscultation: Clear bilateral breath sounds. ECG shows ST-segment changes in leads V3-V5."
198+
Style="{dui:Styles Label=UI200}" />
199+
200+
<dui:Label Text="Assessment"
201+
Style="{dui:Styles Label=UI100}" />
202+
<dui:Label
203+
Text="Suspected acute coronary syndrome. Differential includes unstable angina, NSTEMI. Risk factors include diabetes, age, and hypertension. HEART score calculated at 6 — high risk."
204+
Style="{dui:Styles Label=UI200}" />
205+
206+
<dui:Label Text="Plan"
207+
Style="{dui:Styles Label=UI100}" />
208+
<dui:Label
209+
Text="1. Administer Aspirin 300mg stat&#10;2. IV access and start normal saline&#10;3. Serial troponin measurements at 0h and 3h&#10;4. Continuous cardiac monitoring&#10;5. Cardiology consult for possible catheterization&#10;6. Hold Metformin pending renal function results&#10;7. Pain management with Morphine 2-4mg IV PRN"
210+
Style="{dui:Styles Label=UI200}" />
211+
212+
<dui:Label Text="Allergies"
213+
Style="{dui:Styles Label=UI100}" />
214+
<dui:Label Text="Penicillin (rash), Sulfonamides (anaphylaxis)"
215+
Style="{dui:Styles Label=UI200}" />
216+
217+
<dui:Label Text="Follow-up"
218+
Style="{dui:Styles Label=UI100}" />
219+
<dui:Label
220+
Text="Reassess after troponin results. If positive, proceed with invasive strategy within 24 hours per ESC guidelines. If negative, consider CT coronary angiography. Update patient and next of kin on findings."
221+
Style="{dui:Styles Label=UI200}"
222+
Margin="{dui:Thickness Bottom=size_12}" />
223+
</dui:VerticalStackLayout>
224+
225+
</dui:VerticalStackLayout>
226+
</dui:ScrollView>
227+
228+
229+
</dui:ContentPage>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
namespace Components.ComponentsSamples.Toolbar;
2+
3+
public partial class ToolbarSamples
4+
{
5+
public ToolbarSamples()
6+
{
7+
InitializeComponent();
8+
}
9+
10+
private async void OnCloseClicked(object? sender, EventArgs e)
11+
{
12+
await Navigation.PopModalAsync();
13+
}
14+
15+
private void OnScrollViewHandlerChanged(object? sender, EventArgs e)
16+
{
17+
bottomToolbar.HidesOnScrollFor = scrollView;
18+
}
19+
20+
private void OnToolbarVisibilityToggled(object? sender, ToggledEventArgs e)
21+
{
22+
if (e.Value)
23+
{
24+
bottomToolbar.Show();
25+
}
26+
else
27+
{
28+
bottomToolbar.Hide();
29+
}
30+
}
31+
32+
private void OnHidesOnScrollToggled(object? sender, ToggledEventArgs e)
33+
{
34+
bottomToolbar.HidesOnScrollFor = e.Value ? scrollView : null;
35+
}
36+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using DIPS.Mobile.UI.Components.Alerting.Dialog;
2+
using DIPS.Mobile.UI.MVVM;
3+
4+
namespace Components.ComponentsSamples.Toolbar;
5+
6+
internal class ToolbarSamplesViewModel : ViewModel
7+
{
8+
public Command SignCommand => new(async () =>
9+
{
10+
IsSignBusy = true;
11+
await Task.Delay(1000);
12+
IsSignBusy = false;
13+
IsSignFinished = true;
14+
await Task.Delay(2000);
15+
IsSignFinished = false;
16+
IsSignVisible = false;
17+
});
18+
19+
public Command SimulateSignErrorCommand => new(async () =>
20+
{
21+
IsSignBusy = true;
22+
await Task.Delay(1000);
23+
IsSignBusy = false;
24+
HasSignError = true;
25+
});
26+
public Command EditCommand => new(() => { });
27+
public Command CopyCommand => new(() => { });
28+
public Command DeleteCommand => new(() => { });
29+
public Command ShareCommand => new(() => { });
30+
public Command PrintCommand => new(() => { });
31+
32+
public Command SignErrorTappedCommand => new(async () =>
33+
{
34+
await DialogService.ShowMessage(configurator =>
35+
{
36+
configurator
37+
.SetTitle("Sign failed")
38+
.SetDescription("The signing service is currently unavailable. Please try again.");
39+
});
40+
HasSignError = false;
41+
});
42+
43+
public Command IncrementBadgeCommand => new(() => EditBadgeCount++);
44+
45+
public Command DecrementBadgeCommand => new(() =>
46+
{
47+
if (EditBadgeCount > 0)
48+
EditBadgeCount--;
49+
});
50+
51+
public bool IsSignVisible
52+
{
53+
get;
54+
set => RaiseWhenSet(ref field, value);
55+
} = true;
56+
57+
public bool IsSignBusy
58+
{
59+
get;
60+
set => RaiseWhenSet(ref field, value);
61+
}
62+
63+
public bool IsSignFinished
64+
{
65+
get;
66+
set => RaiseWhenSet(ref field, value);
67+
}
68+
69+
public bool HasSignError
70+
{
71+
get;
72+
set => RaiseWhenSet(ref field, value);
73+
}
74+
75+
public string SignTitle
76+
{
77+
get;
78+
set => RaiseWhenSet(ref field, value);
79+
} = "Sign";
80+
81+
public int EditBadgeCount
82+
{
83+
get;
84+
set => RaiseWhenSet(ref field, value);
85+
}
86+
}

src/app/Components/REGISTER_YOUR_SAMPLES_HERE.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using Components.ComponentsSamples.Sorting;
2222
using Components.ComponentsSamples.SyntaxHighlighting;
2323
using Components.ComponentsSamples.TabView;
24+
using Components.ComponentsSamples.Toolbar;
2425
using Components.ComponentsSamples.Tags;
2526
using Components.ComponentsSamples.Text;
2627
using Components.ComponentsSamples.TextFields;
@@ -73,6 +74,7 @@ public static List<Sample> RegisterSamples()
7374
new(SampleType.Components, "Zoom Container", () => new PanZoomContainerSample()),
7475
new(SampleType.Components, "Gallery", () => new GallerySample()),
7576
new(SampleType.Components, "TIFF Viewer", () => new TiffViewerSample()),
77+
new(SampleType.Components, "Toolbar", () => new ToolbarSamples(), isModal: true),
7678
new(SampleType.Accessibility, "VoiceOver/TalkBack", () => new VoiceOverSamples()),
7779

7880

0 commit comments

Comments
 (0)