feat(ui): add reusable ModalShell control

This commit is contained in:
mika kuns
2026-05-30 16:38:02 +02:00
parent 16b0d1177a
commit c20fbe3613
3 changed files with 82 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctl="using:ClaudeDo.Ui.Views.Controls">
<ControlTheme x:Key="{x:Type ctl:ModalShell}" TargetType="ctl:ModalShell">
<Setter Property="Template">
<ControlTemplate>
<Border Background="{DynamicResource SurfaceBrush}"
BorderBrush="{DynamicResource LineBrush}"
BorderThickness="1"
CornerRadius="{DynamicResource ModalCornerRadius}"
ClipToBounds="True">
<DockPanel>
<Border Name="PART_TitleBar" DockPanel.Dock="Top" Height="36"
Background="{DynamicResource DeepBrush}"
BorderBrush="{DynamicResource LineBrush}"
BorderThickness="0,0,0,1">
<Grid ColumnDefinitions="*,Auto" Margin="14,0">
<TextBlock Text="{TemplateBinding Title}"
FontFamily="{DynamicResource MonoFont}"
FontSize="{DynamicResource FontSizeMono}"
LetterSpacing="1.4"
Foreground="{DynamicResource TextBrush}"
VerticalAlignment="Center"/>
<Button Grid.Column="1" Classes="icon-btn" Content="✕"
FontSize="{DynamicResource FontSizeBody}"
Command="{TemplateBinding CloseCommand}"
VerticalAlignment="Center"/>
</Grid>
</Border>
<Border Name="PART_Footer" DockPanel.Dock="Bottom"
Background="{DynamicResource DeepBrush}"
BorderBrush="{DynamicResource LineBrush}"
BorderThickness="0,1,0,0"
IsVisible="{TemplateBinding Footer, Converter={x:Static ObjectConverters.IsNotNull}}">
<ContentPresenter Content="{TemplateBinding Footer}" Margin="16,8"/>
</Border>
<ContentPresenter Content="{TemplateBinding Content}"/>
</DockPanel>
</Border>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>

View File

@@ -0,0 +1,38 @@
using System;
using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
namespace ClaudeDo.Ui.Views.Controls;
/// <summary>Reusable modal chrome: titlebar (drag + close) wrapping a body and optional footer.</summary>
public class ModalShell : ContentControl
{
public static readonly StyledProperty<string?> TitleProperty =
AvaloniaProperty.Register<ModalShell, string?>(nameof(Title));
public static readonly StyledProperty<object?> FooterProperty =
AvaloniaProperty.Register<ModalShell, object?>(nameof(Footer));
public static readonly StyledProperty<ICommand?> CloseCommandProperty =
AvaloniaProperty.Register<ModalShell, ICommand?>(nameof(CloseCommand));
public string? Title { get => GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
public object? Footer { get => GetValue(FooterProperty); set => SetValue(FooterProperty, value); }
public ICommand? CloseCommand { get => GetValue(CloseCommandProperty); set => SetValue(CloseCommandProperty, value); }
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
if (e.NameScope.Find<Border>("PART_TitleBar") is { } bar)
bar.PointerPressed += OnTitleBarPressed;
}
private void OnTitleBarPressed(object? sender, PointerPressedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed && VisualRoot is Window w)
w.BeginMoveDrag(e);
}
}