Skip to content

Commit 12952db

Browse files
Copilothaavamoa
andcommitted
Add Toolbar component with ToolbarButton items
Co-authored-by: haavamoa <2527084+haavamoa@users.noreply.github.com>
1 parent 71759ed commit 12952db

10 files changed

Lines changed: 334 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## [55.3.0]
2+
- [Toolbar] Added `Toolbar` component with `ToolbarButton` items. The toolbar provides a cross-platform horizontal bar of icon buttons, aligned with Apple HIG toolbars and Material 3 toolbars.
3+
14
## [55.2.2]
25
- [iOS26][Tip] Added more padding.
36

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<dui:ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
5+
xmlns:dui="http://dips.com/mobile.ui"
6+
xmlns:local="clr-namespace:Components.ComponentsSamples.Toolbar"
7+
x:DataType="local:ToolbarSamplesViewModel"
8+
x:Class="Components.ComponentsSamples.Toolbar.ToolbarSamples"
9+
Title="Toolbar">
10+
11+
<dui:ContentPage.BindingContext>
12+
<local:ToolbarSamplesViewModel />
13+
</dui:ContentPage.BindingContext>
14+
15+
<Grid RowDefinitions="*, Auto">
16+
17+
<dui:ScrollView Padding="{dui:Sizes size_4}">
18+
<dui:VerticalStackLayout>
19+
<dui:Label Text="Toolbar with icon buttons"
20+
Style="{dui:Styles Label=SectionHeader}" />
21+
<dui:Label Text="A toolbar can be placed at the bottom of a page and contain icon buttons for common actions."
22+
Style="{dui:Styles Label=UI200}" />
23+
24+
<dui:Label Text="Toolbar with command bindings"
25+
Style="{dui:Styles Label=SectionHeader}"
26+
Margin="{dui:Thickness Top=size_4}" />
27+
<dui:Label Text="{Binding LastAction}"
28+
Style="{dui:Styles Label=UI200}" />
29+
<dui:Toolbar>
30+
<dui:ToolbarButton Icon="{dui:Icons edit_line}"
31+
Title="Edit"
32+
Command="{Binding EditCommand}" />
33+
<dui:ToolbarButton Icon="{dui:Icons filter_line}"
34+
Title="Filter"
35+
Command="{Binding FilterCommand}" />
36+
<dui:ToolbarButton Icon="{dui:Icons plus_line}"
37+
Title="Add"
38+
Command="{Binding AddCommand}" />
39+
</dui:Toolbar>
40+
41+
<dui:Label Text="Toolbar with a disabled button"
42+
Style="{dui:Styles Label=SectionHeader}"
43+
Margin="{dui:Thickness Top=size_4}" />
44+
<dui:Toolbar>
45+
<dui:ToolbarButton Icon="{dui:Icons save_line}"
46+
Title="Save"
47+
Command="{Binding SaveCommand}"
48+
IsEnabled="True" />
49+
<dui:ToolbarButton Icon="{dui:Icons delete_line}"
50+
Title="Delete"
51+
IsEnabled="False" />
52+
</dui:Toolbar>
53+
54+
<dui:Label Text="Toolbar with a single button"
55+
Style="{dui:Styles Label=SectionHeader}"
56+
Margin="{dui:Thickness Top=size_4}" />
57+
<dui:Toolbar>
58+
<dui:ToolbarButton Icon="{dui:Icons filter_line}"
59+
Title="Filter"
60+
Command="{Binding FilterCommand}" />
61+
</dui:Toolbar>
62+
63+
</dui:VerticalStackLayout>
64+
</dui:ScrollView>
65+
66+
<dui:Toolbar Grid.Row="1">
67+
<dui:ToolbarButton Icon="{dui:Icons edit_line}"
68+
Title="Edit"
69+
Command="{Binding EditCommand}" />
70+
<dui:ToolbarButton Icon="{dui:Icons save_line}"
71+
Title="Save"
72+
Command="{Binding SaveCommand}" />
73+
<dui:ToolbarButton Icon="{dui:Icons filter_line}"
74+
Title="Filter"
75+
Command="{Binding FilterCommand}" />
76+
<dui:ToolbarButton Icon="{dui:Icons plus_line}"
77+
Title="Add"
78+
Command="{Binding AddCommand}" />
79+
</dui:Toolbar>
80+
81+
</Grid>
82+
</dui:ContentPage>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Components.ComponentsSamples.Toolbar;
2+
3+
public partial class ToolbarSamples
4+
{
5+
public ToolbarSamples()
6+
{
7+
InitializeComponent();
8+
}
9+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using DIPS.Mobile.UI.MVVM;
2+
3+
namespace Components.ComponentsSamples.Toolbar;
4+
5+
internal class ToolbarSamplesViewModel : ViewModel
6+
{
7+
private string m_lastAction = "None";
8+
9+
public Command EditCommand => new(OnEdit);
10+
public Command SaveCommand => new(OnSave);
11+
public Command FilterCommand => new(OnFilter);
12+
public Command AddCommand => new(OnAdd);
13+
14+
public string LastAction
15+
{
16+
get => m_lastAction;
17+
set => RaiseWhenSet(ref m_lastAction, value);
18+
}
19+
20+
private void OnEdit() => LastAction = "Edit tapped";
21+
private void OnSave() => LastAction = "Save tapped";
22+
private void OnFilter() => LastAction = "Filter tapped";
23+
private void OnAdd() => LastAction = "Add tapped";
24+
}

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()),
7678
new(SampleType.Accessibility, "VoiceOver/TalkBack", () => new VoiceOverSamples()),
7779

7880

src/library/DIPS.Mobile.UI/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
[assembly: XmlnsDefinition("http://dips.com/mobile.ui","DIPS.Mobile.UI.Components.TiffViewer")]
101101
[assembly: XmlnsDefinition("http://dips.com/mobile.ui","DIPS.Mobile.UI.Components.Gallery")]
102102
[assembly: XmlnsDefinition("http://dips.com/mobile.ui","DIPS.Mobile.UI.Effects.Accessibility")]
103+
[assembly: XmlnsDefinition("http://dips.com/mobile.ui","DIPS.Mobile.UI.Components.Toolbar")]
103104

104105

105106

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace DIPS.Mobile.UI.Components.Toolbar;
2+
3+
public partial class Toolbar
4+
{
5+
/// <summary>
6+
/// <see cref="Buttons"/>
7+
/// </summary>
8+
public static readonly BindableProperty ButtonsProperty = BindableProperty.Create(
9+
nameof(Buttons),
10+
typeof(IList<ToolbarButton>),
11+
typeof(Toolbar),
12+
defaultValueCreator: _ => new List<ToolbarButton>(),
13+
propertyChanged: (bindable, _, _) => ((Toolbar)bindable).OnButtonsChanged());
14+
15+
/// <summary>
16+
/// The buttons to display in the toolbar.
17+
/// </summary>
18+
public IList<ToolbarButton> Buttons
19+
{
20+
get => (IList<ToolbarButton>)GetValue(ButtonsProperty);
21+
set => SetValue(ButtonsProperty, value);
22+
}
23+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using DIPS.Mobile.UI.Components.Buttons;
2+
using DIPS.Mobile.UI.Components.Dividers;
3+
using DIPS.Mobile.UI.Resources.Styles;
4+
using DIPS.Mobile.UI.Resources.Styles.Button;
5+
6+
namespace DIPS.Mobile.UI.Components.Toolbar;
7+
8+
/// <summary>
9+
/// A cross-platform toolbar component that displays a horizontal bar of icon buttons.
10+
/// </summary>
11+
/// <remarks>
12+
/// iOS: https://developer.apple.com/design/human-interface-guidelines/toolbars
13+
/// Android: https://m3.material.io/components/toolbars/overview
14+
/// </remarks>
15+
[ContentProperty(nameof(Buttons))]
16+
public partial class Toolbar : ContentView
17+
{
18+
private readonly Grid m_buttonsGrid = new();
19+
20+
public Toolbar()
21+
{
22+
this.SetAppThemeColor(BackgroundColorProperty, ColorName.color_surface_default);
23+
24+
var topBorder = new Divider
25+
{
26+
HeightRequest = Sizes.GetSize(SizeName.stroke_small)
27+
};
28+
29+
Content = new VerticalStackLayout
30+
{
31+
Spacing = 0,
32+
Children = { topBorder, m_buttonsGrid }
33+
};
34+
}
35+
36+
private void OnButtonsChanged()
37+
{
38+
m_buttonsGrid.ColumnDefinitions.Clear();
39+
m_buttonsGrid.Children.Clear();
40+
41+
if (Buttons is null || Buttons.Count == 0)
42+
return;
43+
44+
for (var i = 0; i < Buttons.Count; i++)
45+
{
46+
m_buttonsGrid.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Star));
47+
var toolbarButton = Buttons[i];
48+
toolbarButton.BindingContext = BindingContext;
49+
var buttonView = CreateButtonView(toolbarButton);
50+
Grid.SetColumn(buttonView, i);
51+
m_buttonsGrid.Add(buttonView);
52+
}
53+
}
54+
55+
protected override void OnBindingContextChanged()
56+
{
57+
base.OnBindingContextChanged();
58+
59+
if (Buttons is null)
60+
return;
61+
62+
foreach (var toolbarButton in Buttons)
63+
{
64+
toolbarButton.BindingContext = BindingContext;
65+
}
66+
}
67+
68+
private static View CreateButtonView(ToolbarButton toolbarButton)
69+
{
70+
var button = new Button
71+
{
72+
Style = Styles.GetButtonStyle(ButtonStyle.GhostIconSmall),
73+
HorizontalOptions = LayoutOptions.Center,
74+
};
75+
76+
button.SetBinding(Button.ImageSourceProperty, new Binding(nameof(ToolbarButton.Icon), source: toolbarButton));
77+
button.SetBinding(IsEnabledProperty, new Binding(nameof(ToolbarButton.IsEnabled), source: toolbarButton));
78+
button.SetBinding(Button.CommandProperty, new Binding(nameof(ToolbarButton.Command), source: toolbarButton));
79+
button.SetBinding(Button.CommandParameterProperty, new Binding(nameof(ToolbarButton.CommandParameter), source: toolbarButton));
80+
81+
if (!string.IsNullOrEmpty(toolbarButton.Title))
82+
{
83+
SemanticProperties.SetDescription(button, toolbarButton.Title);
84+
}
85+
86+
return button;
87+
}
88+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System.ComponentModel;
2+
using System.Windows.Input;
3+
4+
namespace DIPS.Mobile.UI.Components.Toolbar;
5+
6+
public partial class ToolbarButton
7+
{
8+
/// <summary>
9+
/// <see cref="Title"/>
10+
/// </summary>
11+
public static readonly BindableProperty TitleProperty = BindableProperty.Create(
12+
nameof(Title),
13+
typeof(string),
14+
typeof(ToolbarButton));
15+
16+
/// <summary>
17+
/// <see cref="Icon"/>
18+
/// </summary>
19+
public static readonly BindableProperty IconProperty = BindableProperty.Create(
20+
nameof(Icon),
21+
typeof(ImageSource),
22+
typeof(ToolbarButton));
23+
24+
/// <summary>
25+
/// <see cref="Command"/>
26+
/// </summary>
27+
public static readonly BindableProperty CommandProperty = BindableProperty.Create(
28+
nameof(Command),
29+
typeof(ICommand),
30+
typeof(ToolbarButton));
31+
32+
/// <summary>
33+
/// <see cref="CommandParameter"/>
34+
/// </summary>
35+
public static readonly BindableProperty CommandParameterProperty = BindableProperty.Create(
36+
nameof(CommandParameter),
37+
typeof(object),
38+
typeof(ToolbarButton));
39+
40+
/// <summary>
41+
/// <see cref="IsEnabled"/>
42+
/// </summary>
43+
public static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(
44+
nameof(IsEnabled),
45+
typeof(bool),
46+
typeof(ToolbarButton),
47+
defaultValue: true);
48+
49+
/// <summary>
50+
/// The title of the toolbar button, used as the accessibility label.
51+
/// </summary>
52+
public string? Title
53+
{
54+
get => (string?)GetValue(TitleProperty);
55+
set => SetValue(TitleProperty, value);
56+
}
57+
58+
/// <summary>
59+
/// The icon to display in the toolbar button.
60+
/// </summary>
61+
[TypeConverter(nameof(ImageSourceConverter))]
62+
public ImageSource? Icon
63+
{
64+
get => (ImageSource?)GetValue(IconProperty);
65+
set => SetValue(IconProperty, value);
66+
}
67+
68+
/// <summary>
69+
/// The command to execute when the button is tapped.
70+
/// </summary>
71+
public ICommand? Command
72+
{
73+
get => (ICommand?)GetValue(CommandProperty);
74+
set => SetValue(CommandProperty, value);
75+
}
76+
77+
/// <summary>
78+
/// The parameter to pass to <see cref="Command"/> when the button is tapped.
79+
/// </summary>
80+
public object? CommandParameter
81+
{
82+
get => (object?)GetValue(CommandParameterProperty);
83+
set => SetValue(CommandParameterProperty, value);
84+
}
85+
86+
/// <summary>
87+
/// Determines whether the button is enabled.
88+
/// </summary>
89+
public bool IsEnabled
90+
{
91+
get => (bool)GetValue(IsEnabledProperty);
92+
set => SetValue(IsEnabledProperty, value);
93+
}
94+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace DIPS.Mobile.UI.Components.Toolbar;
2+
3+
/// <summary>
4+
/// A button to display in a <see cref="Toolbar"/>.
5+
/// </summary>
6+
public partial class ToolbarButton : Element
7+
{
8+
}

0 commit comments

Comments
 (0)