Updates to FormItem label and A11y (#395)

* feat: 1. add AccessKey support for form item.
2. Support label positioning for different content height.
3. polish demo.

* feat: add a transparent background for label.
This commit is contained in:
Dong Bin
2024-09-11 18:47:42 +08:00
committed by GitHub
parent eb72a717c3
commit 60605de2c8
4 changed files with 146 additions and 68 deletions

View File

@@ -13,59 +13,42 @@
x:DataType="vm:FormDemoViewModel" x:DataType="vm:FormDemoViewModel"
mc:Ignorable="d"> mc:Ignorable="d">
<ScrollViewer> <ScrollViewer>
<StackPanel> <StackPanel HorizontalAlignment="Left" Margin="24 0 0 0 ">
<Grid RowDefinitions="Auto, Auto" ColumnDefinitions="Auto, Auto"> <u:Divider Content="Simple Form"/>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Label Position" VerticalAlignment="Center"></TextBlock> <u:Form LabelPosition="Left" LabelWidth="*">
<TextBlock Grid.Row="1" Grid.Column="0" Text="Label Alignment" VerticalAlignment="Center"></TextBlock> <TextBox Width="300" u:FormItem.Label="Name" />
<u:EnumSelector Grid.Row="0" Grid.Column="1" <TextBox Width="300" u:FormItem.Label="Email" />
Name="position" <TextBox Width="300" u:FormItem.Label="Message" Classes="TextArea"/>
EnumType="common:Position" </u:Form>
Value="{x:Static common:Position.Top}" /> <u:Divider Content="Form accessibility"/>
<u:EnumSelector Grid.Row="1" Grid.Column="1" <u:Form LabelPosition="Left" LabelWidth="*">
Name="alignment" <TextBox Width="300" u:FormItem.Label="_Name" />
EnumType="HorizontalAlignment" <TextBox Width="300" u:FormItem.Label="_Email" />
Value="{x:Static HorizontalAlignment.Left}" /> <TextBox Width="300" u:FormItem.Label="_Message" Classes="TextArea"/>
</Grid> </u:Form>
<u:Form <u:Divider Content="Label on top"/>
DataContext="{Binding Model}" <u:Form LabelPosition="Top" LabelWidth="*">
LabelAlignment="{Binding #alignment.Value}" <TextBox Width="300" u:FormItem.Label="Name" />
LabelPosition="{Binding #position.Value}" <TextBox Width="300" u:FormItem.Label="Email" />
LabelWidth="*"> </u:Form>
<u:FormGroup Header="Information"> <u:Divider Content="FormGroup"/>
<TextBox <u:Form LabelPosition="Left" LabelWidth="*">
Width="300" <u:FormGroup Header="Basic Information">
u:FormItem.IsRequired="True" <TextBox Width="300" u:FormItem.Label="Name" />
u:FormItem.Label="Name" <TextBox Width="300" u:FormItem.Label="Email" />
Text="{Binding Name}" />
<TextBox
Width="300"
u:FormItem.Label="Email"
Text="{Binding Email}" />
<u:NumericDoubleUpDown Value="{Binding Number}" u:FormItem.Label="Number" Width="300"/>
</u:FormGroup> </u:FormGroup>
<u:FormItem Label="Please select a Date"> <u:FormGroup Header="Education Information">
<CalendarDatePicker SelectedDate="{Binding Date}" /> <TextBox Width="300" u:FormItem.Label="Collage" />
<u:FormItem Label="Study Time">
<u:DateRangePicker Width="300"></u:DateRangePicker>
</u:FormItem> </u:FormItem>
<u:FormGroup Header="Education">
<TextBox
HorizontalAlignment="Stretch"
u:FormItem.IsRequired="True"
u:FormItem.Label="Name"
Text="{Binding Name}" />
<TextBox
HorizontalAlignment="Stretch"
u:FormItem.Label="Email"
u:FormItem.IsRequired="True"
Text="{Binding Email}" />
</u:FormGroup> </u:FormGroup>
<u:FormItem Label="Click to Submit"> <Button Content="Submit" Theme="{DynamicResource SolidButton}" u:FormItem.NoLabel="True" HorizontalAlignment="Stretch"/>
<Button Content="Button With Label" /> </u:Form>
</u:FormItem> <u:Divider Content="MVVM setup and validation" />
<CheckBox Content="I Agree User Agreement" u:FormItem.NoLabel="True"></CheckBox> <u:Form LabelPosition="Left" LabelWidth="*" DataContext="{Binding Model}">
<u:FormItem NoLabel="True"> <TextBox Width="300" u:FormItem.Label="Name" Text="{Binding Name}"/>
<Button HorizontalAlignment="Left" Content="No Label" /> <TextBox Width="300" u:FormItem.Label="Email" Text="{Binding Email}" />
</u:FormItem>
</u:Form> </u:Form>
</StackPanel> </StackPanel>
</ScrollViewer> </ScrollViewer>

View File

@@ -1,24 +1,30 @@
<ResourceDictionary <ResourceDictionary
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="https://irihi.tech/ursa"> xmlns:u="https://irihi.tech/ursa"
xmlns:converters="clr-namespace:Ursa.Themes.Semi.Converters">
<!-- Add Resources Here --> <!-- Add Resources Here -->
<ControlTheme x:Key="{x:Type u:Form}" TargetType="u:Form"> <ControlTheme x:Key="{x:Type u:Form}"
<Setter Property="Grid.IsSharedSizeScope" Value="False" /> TargetType="u:Form">
<Setter Property="Grid.IsSharedSizeScope"
Value="False" />
<Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate TargetType="u:Form"> <ControlTemplate TargetType="u:Form">
<DataValidationErrors> <DataValidationErrors>
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}" /> <ItemsPresenter
ItemsPanel="{TemplateBinding ItemsPanel}" />
</DataValidationErrors> </DataValidationErrors>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
<Style Selector="^:fixed-width"> <Style Selector="^:fixed-width">
<Setter Property="Grid.IsSharedSizeScope" Value="True" /> <Setter Property="Grid.IsSharedSizeScope"
Value="True" />
</Style> </Style>
</ControlTheme> </ControlTheme>
<ControlTheme x:Key="{x:Type u:FormGroup}" TargetType="u:FormGroup"> <ControlTheme x:Key="{x:Type u:FormGroup}"
TargetType="u:FormGroup">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate TargetType="u:FormGroup"> <ControlTemplate TargetType="u:FormGroup">
<StackPanel> <StackPanel>
@@ -32,15 +38,17 @@
Height="1" Height="1"
Margin="0,8" Margin="0,8"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
IsVisible="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Header, Converter={x:Static ObjectConverters.IsNotNull}}" Fill="{DynamicResource SemiColorBorder}"
Fill="{DynamicResource SemiColorBorder}" /> IsVisible="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Header, Converter={x:Static ObjectConverters.IsNotNull}}" />
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}" /> <ItemsPresenter
ItemsPanel="{TemplateBinding ItemsPanel}" />
</StackPanel> </StackPanel>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</ControlTheme> </ControlTheme>
<ControlTheme x:Key="{x:Type u:FormItem}" TargetType="u:FormItem"> <ControlTheme x:Key="{x:Type u:FormItem}"
TargetType="u:FormItem">
<Setter Property="Margin" Value="0 8" /> <Setter Property="Margin" Value="0 8" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate TargetType="u:FormItem"> <ControlTemplate TargetType="u:FormItem">
@@ -50,13 +58,19 @@
Margin="0,0,0,4" Margin="0,0,0,4"
HorizontalAlignment="{TemplateBinding LabelAlignment}" HorizontalAlignment="{TemplateBinding LabelAlignment}"
Orientation="Horizontal"> Orientation="Horizontal">
<ContentPresenter Content="{TemplateBinding Label}" FontWeight="{DynamicResource TextBlockTitleFontWeight}" /> <Label
Content="{TemplateBinding Label}"
Background="Transparent"
FontWeight="{DynamicResource TextBlockTitleFontWeight}"
Target="{Binding #PART_ContentPresenter.Content}" />
<TextBlock <TextBlock
Foreground="{DynamicResource SemiRed6}" Foreground="{DynamicResource SemiRed6}"
IsVisible="{TemplateBinding IsRequired}" IsVisible="{TemplateBinding IsRequired}"
Text="*" /> Text="*" />
</StackPanel> </StackPanel>
<ContentPresenter Content="{TemplateBinding Content}" /> <ContentPresenter
Name="PART_ContentPresenter"
Content="{TemplateBinding Content}" />
</StackPanel> </StackPanel>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
@@ -65,7 +79,8 @@
<ControlTemplate TargetType="u:FormItem"> <ControlTemplate TargetType="u:FormItem">
<Grid RowDefinitions="*, *"> <Grid RowDefinitions="*, *">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Label" /> <ColumnDefinition Width="Auto"
SharedSizeGroup="Label" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Border <Border
@@ -74,10 +89,16 @@
Width="{TemplateBinding LabelWidth}"> Width="{TemplateBinding LabelWidth}">
<StackPanel <StackPanel
Name="PART_LabelPanel" Name="PART_LabelPanel"
Margin="8,8,8,0" Margin="{Binding #PART_Label.Bounds.Height, Converter={x:Static converters:FormContentHeightToMarginConverter.Instance}}"
HorizontalAlignment="{TemplateBinding LabelAlignment}" HorizontalAlignment="{TemplateBinding LabelAlignment}"
VerticalAlignment="{Binding #PART_ContentPresenter.Bounds.Height, Converter={x:Static converters:FormContentHeightToAlignmentConverter.Instance}}"
Orientation="Horizontal"> Orientation="Horizontal">
<ContentPresenter Content="{TemplateBinding Label}" FontWeight="{DynamicResource TextBlockTitleFontWeight}" /> <Label
Name="PART_Label"
Content="{TemplateBinding Label}"
Background="Transparent"
FontWeight="{DynamicResource TextBlockTitleFontWeight}"
Target="{Binding #PART_ContentPresenter.Content}" />
<TextBlock <TextBlock
Foreground="{DynamicResource SemiRed6}" Foreground="{DynamicResource SemiRed6}"
IsVisible="{TemplateBinding IsRequired}" IsVisible="{TemplateBinding IsRequired}"
@@ -85,8 +106,11 @@
</StackPanel> </StackPanel>
</Border> </Border>
<ContentPresenter <ContentPresenter
Name="PART_ContentPresenter"
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
Content="{TemplateBinding Content}" /> Content="{TemplateBinding Content}" />
</Grid> </Grid>
</ControlTemplate> </ControlTemplate>
@@ -95,7 +119,8 @@
<Style Selector="^:no-label"> <Style Selector="^:no-label">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate TargetType="u:FormItem"> <ControlTemplate TargetType="u:FormItem">
<ContentPresenter Content="{TemplateBinding Content}" /> <ContentPresenter
Content="{TemplateBinding Content}" />
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>

View File

@@ -0,0 +1,36 @@
using System.Globalization;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Layout;
namespace Ursa.Themes.Semi.Converters;
public class FormContentHeightToAlignmentConverter: IValueConverter
{
public static FormContentHeightToAlignmentConverter Instance = new(32);
public double Threshold { get; set; }
public FormContentHeightToAlignmentConverter()
{
Threshold = 32;
}
// ReSharper disable once ConvertToPrimaryConstructor
// Justification: need to keep the default constructor for XAML
public FormContentHeightToAlignmentConverter(double threshold)
{
Threshold = threshold;
}
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if(value is not double d) return VerticalAlignment.Center;
return d > Threshold ? VerticalAlignment.Top : VerticalAlignment.Center;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,34 @@
using System.Globalization;
using Avalonia;
using Avalonia.Data.Converters;
namespace Ursa.Themes.Semi.Converters;
public class FormContentHeightToMarginConverter: IValueConverter
{
public static FormContentHeightToMarginConverter Instance = new();
public double Threshold { get; set; }
public FormContentHeightToMarginConverter()
{
Threshold = 32;
}
// ReSharper disable once ConvertToPrimaryConstructor
// Justification: need to keep the default constructor for XAML
public FormContentHeightToMarginConverter(double threshold)
{
Threshold = threshold;
}
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if(value is not double d) return new Thickness(0);
return d > Threshold ? new Thickness(0, 8, 8, 0) : new Thickness(0, 0, 8, 0);
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}