Skip to content
Merged
82 changes: 76 additions & 6 deletions SignalSelectionWizardWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,58 @@
</Setter.Value>
</Setter>
</Style>

<!-- Modern search-field clear affordance: quiet at rest, obvious on hover/focus,
and large enough to hit without looking like a separate toolbar button. -->
<Style x:Key="GlobalSearchClearButton" TargetType="Button">
<Setter Property="Width" Value="28"/>
<Setter Property="Height" Value="28"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="2,0,3,0"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="#667085"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="ClipToBounds" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="Chrome"
Width="28" Height="28"
CornerRadius="8"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Chrome" Property="Background" Value="#EEF3F8"/>
<Setter TargetName="Chrome" Property="BorderBrush" Value="#D8E2EC"/>
<Setter Property="Foreground" Value="#475467"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Chrome" Property="Background" Value="#E3EBF4"/>
<Setter TargetName="Chrome" Property="BorderBrush" Value="#C9D7E5"/>
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter TargetName="Chrome" Property="Background" Value="#EEF4FF"/>
<Setter TargetName="Chrome" Property="BorderBrush" Value="#7AA7F7"/>
<Setter Property="Foreground" Value="#2563EB"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Chrome" Property="Opacity" Value="0.45"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>

<Grid Margin="12">
Expand Down Expand Up @@ -246,13 +298,31 @@
</Style>
</TextBlock.Style>
</TextBlock>
<Button Grid.Column="2"
Content="×"
Style="{StaticResource MiniChipButton}"
Padding="8,2"
Margin="3"
<Button x:Name="GlobalSearchClearButton"
Grid.Column="2"
Click="ClearGlobalFilter_Click"
ToolTip="Clear global search"/>
ToolTip="Clear search"
AutomationProperties.Name="Clear global search">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource GlobalSearchClearButton}">
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=GlobalSearchTextBox}" Value="">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<Viewbox Width="14" Height="14"
HorizontalAlignment="Center" VerticalAlignment="Center"
Stretch="Uniform" SnapsToDevicePixels="True">
<Path Data="{StaticResource LucideX}"
Fill="Transparent"
Stroke="{Binding Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
StrokeThickness="1.9"
StrokeStartLineCap="Round" StrokeEndLineCap="Round"
StrokeLineJoin="Round"/>
</Viewbox>
</Button>
</Grid>
</Border>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System.Xml.Linq;

namespace ARSAS.Tests;

public sealed class SignalSelectionGlobalSearchClearRegressionTests
{
[Fact]
public void GlobalSearchClear_UsesLucideVectorModernHitTargetAndHidesWhenEmpty()
{
var source = File.ReadAllText(FindRepoFile("SignalSelectionWizardWindow.xaml"));
var document = XDocument.Parse(source);
XNamespace presentation = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
XNamespace x = "http://schemas.microsoft.com/winfx/2006/xaml";

var reusableStyle = document.Descendants(presentation + "Style")
.Single(style => (string?)style.Attribute(x + "Key") == "GlobalSearchClearButton");
var reusableSetters = reusableStyle.Elements(presentation + "Setter").ToArray();

string? SetterValue(string property)
=> reusableSetters.Single(setter => (string?)setter.Attribute("Property") == property)
.Attribute("Value")?.Value;

Assert.Equal("28", SetterValue("Width"));
Assert.Equal("28", SetterValue("Height"));
Assert.Equal("Transparent", SetterValue("Background"));
Assert.Equal("{x:Null}", SetterValue("FocusVisualStyle"));
Assert.Equal("False", SetterValue("ClipToBounds"));

var clearButton = document.Descendants(presentation + "Button")
.Single(button => (string?)button.Attribute(x + "Name") == "GlobalSearchClearButton");

Assert.Equal("ClearGlobalFilter_Click", (string?)clearButton.Attribute("Click"));
Assert.Equal("Clear search", (string?)clearButton.Attribute("ToolTip"));
Assert.Equal("Clear global search", (string?)clearButton.Attribute("AutomationProperties.Name"));
Assert.Null(clearButton.Attribute("Content"));
Assert.Null(clearButton.Attribute("Style"));

var icon = clearButton.Descendants(presentation + "Path").Single();
Assert.Equal("{StaticResource LucideX}", (string?)icon.Attribute("Data"));
Assert.Equal("1.9", (string?)icon.Attribute("StrokeThickness"));

var viewbox = clearButton.Descendants(presentation + "Viewbox").Single();
Assert.Equal("14", (string?)viewbox.Attribute("Width"));
Assert.Equal("14", (string?)viewbox.Attribute("Height"));

var localStyle = clearButton.Element(presentation + "Button.Style")?
.Element(presentation + "Style")
?? throw new Xunit.Sdk.XunitException("Global search clear button local style is missing.");
Assert.Equal("{StaticResource GlobalSearchClearButton}", (string?)localStyle.Attribute("BasedOn"));

var emptyTextTrigger = localStyle.Descendants(presentation + "DataTrigger")
.Single(trigger => (string?)trigger.Attribute("Value") == string.Empty);
Assert.Contains("ElementName=GlobalSearchTextBox", (string?)emptyTextTrigger.Attribute("Binding") ?? string.Empty, StringComparison.Ordinal);
Assert.Contains(emptyTextTrigger.Elements(presentation + "Setter"), setter =>
(string?)setter.Attribute("Property") == "Visibility" &&
(string?)setter.Attribute("Value") == "Collapsed");

Assert.DoesNotContain("Content=\"×\"", source, StringComparison.Ordinal);
Assert.DoesNotContain("Style=\"{StaticResource MiniChipButton}\"", clearButton.ToString(), StringComparison.Ordinal);
}

private static string FindRepoFile(string relativePath)
{
DirectoryInfo? directory = new(AppContext.BaseDirectory);
while (directory != null)
{
var candidate = Path.Combine(directory.FullName, relativePath);
if (File.Exists(candidate))
return candidate;
directory = directory.Parent;
}

throw new FileNotFoundException(
$"Could not locate repository file '{relativePath}' from '{AppContext.BaseDirectory}'.");
}
}
Loading