feat(ui): tasks island with rows, chips, add-task, selection
TaskRowView with status chip (EqStatus converter + parameter), StrikeIfTrue, NotNullToBool converters. TasksIslandView with header, add-task TextBox (Enter=AddCommand), ItemsControl + flat Button for selection. Converters registered in App.axaml. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="ClaudeDo.App.App"
|
||||
xmlns:local="using:ClaudeDo.App"
|
||||
xmlns:converters="using:ClaudeDo.Ui.Converters"
|
||||
RequestedThemeVariant="Dark">
|
||||
|
||||
<Application.Resources>
|
||||
@@ -10,6 +11,11 @@
|
||||
<ResourceInclude Source="avares://ClaudeDo.Ui/Design/Tokens.axaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- Converters -->
|
||||
<converters:NotNullToBoolConverter x:Key="NotNullToBool"/>
|
||||
<converters:StrikeIfTrueConverter x:Key="StrikeIfTrue"/>
|
||||
<converters:EqStatusConverter x:Key="EqStatus"/>
|
||||
|
||||
<!-- Accent: Forest Teal -->
|
||||
<SolidColorBrush x:Key="AccentBrush" Color="#3d9474"/>
|
||||
<SolidColorBrush x:Key="AccentLightBrush" Color="#6bb89e"/>
|
||||
|
||||
26
src/ClaudeDo.Ui/Converters/EqStatusConverter.cs
Normal file
26
src/ClaudeDo.Ui/Converters/EqStatusConverter.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using ClaudeDo.Data.Models;
|
||||
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
|
||||
|
||||
namespace ClaudeDo.Ui.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when the bound TaskStatus equals the ConverterParameter string.
|
||||
/// Usage: Classes.running="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Running}"
|
||||
/// </summary>
|
||||
public class EqStatusConverter : IValueConverter
|
||||
{
|
||||
public static EqStatusConverter Instance { get; } = new();
|
||||
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is TaskStatus status && parameter is string name &&
|
||||
Enum.TryParse<TaskStatus>(name, ignoreCase: true, out var target))
|
||||
return status == target;
|
||||
return false;
|
||||
}
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
15
src/ClaudeDo.Ui/Converters/NotNullToBoolConverter.cs
Normal file
15
src/ClaudeDo.Ui/Converters/NotNullToBoolConverter.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace ClaudeDo.Ui.Converters;
|
||||
|
||||
public class NotNullToBoolConverter : IValueConverter
|
||||
{
|
||||
public static NotNullToBoolConverter Instance { get; } = new();
|
||||
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> value is not null;
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
16
src/ClaudeDo.Ui/Converters/StrikeIfTrueConverter.cs
Normal file
16
src/ClaudeDo.Ui/Converters/StrikeIfTrueConverter.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace ClaudeDo.Ui.Converters;
|
||||
|
||||
public class StrikeIfTrueConverter : IValueConverter
|
||||
{
|
||||
public static StrikeIfTrueConverter Instance { get; } = new();
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> value is true ? TextDecorations.Strikethrough : null;
|
||||
|
||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
57
src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml
Normal file
57
src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml
Normal file
@@ -0,0 +1,57 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands"
|
||||
x:Class="ClaudeDo.Ui.Views.Islands.TaskRowView"
|
||||
x:DataType="vm:TaskRowViewModel">
|
||||
<Border Classes="task-row" Classes.selected="{Binding IsSelected}">
|
||||
<Grid ColumnDefinitions="36,*,32" Margin="10,10">
|
||||
<!-- Done toggle -->
|
||||
<Button Grid.Column="0" Classes="check-btn" VerticalAlignment="Center"
|
||||
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).ToggleDoneCommand}"
|
||||
CommandParameter="{Binding}">
|
||||
<Ellipse Width="18" Height="18" Classes="task-check"
|
||||
Classes.done="{Binding Done}"/>
|
||||
</Button>
|
||||
|
||||
<!-- Title + chip row + live tail -->
|
||||
<StackPanel Grid.Column="1" Spacing="6" VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Title}" FontSize="14"
|
||||
Foreground="{DynamicResource TextBrush}"
|
||||
TextDecorations="{Binding Done, Converter={StaticResource StrikeIfTrue}}"/>
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Border Classes="chip"
|
||||
Classes.running="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Running}"
|
||||
Classes.review="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Done}"
|
||||
Classes.error="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Failed}"
|
||||
Classes.queued="{Binding Status, Converter={StaticResource EqStatus}, ConverterParameter=Queued}">
|
||||
<TextBlock Text="{Binding Status}" FontSize="10"
|
||||
FontFamily="{DynamicResource MonoFamily}" Margin="6,2"/>
|
||||
</Border>
|
||||
<Border Classes="chip">
|
||||
<TextBlock Text="{Binding ListName}" FontSize="10" Margin="6,2"
|
||||
Foreground="{DynamicResource TextDimBrush}"/>
|
||||
</Border>
|
||||
<Border Classes="chip" IsVisible="{Binding Branch, Converter={StaticResource NotNullToBool}}">
|
||||
<TextBlock Text="{Binding Branch}" FontFamily="{DynamicResource MonoFamily}"
|
||||
FontSize="10" Margin="6,2"/>
|
||||
</Border>
|
||||
<Border Classes="chip" IsVisible="{Binding DiffStat, Converter={StaticResource NotNullToBool}}">
|
||||
<TextBlock Text="{Binding DiffStat}" FontFamily="{DynamicResource MonoFamily}"
|
||||
FontSize="10" Margin="6,2"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding LiveTail}" FontFamily="{DynamicResource MonoFamily}"
|
||||
FontSize="11" Foreground="{DynamicResource TextMuteBrush}"
|
||||
TextTrimming="CharacterEllipsis" MaxLines="1"
|
||||
IsVisible="{Binding LiveTail, Converter={StaticResource NotNullToBool}}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Star toggle -->
|
||||
<Button Grid.Column="2" Classes="icon-btn" VerticalAlignment="Center"
|
||||
Command="{Binding $parent[ItemsControl].((vm:TasksIslandViewModel)DataContext).ToggleStarCommand}"
|
||||
CommandParameter="{Binding}">
|
||||
<PathIcon Width="14" Height="14"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
8
src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml.cs
Normal file
8
src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace ClaudeDo.Ui.Views.Islands;
|
||||
|
||||
public partial class TaskRowView : UserControl
|
||||
{
|
||||
public TaskRowView() { InitializeComponent(); }
|
||||
}
|
||||
@@ -1,8 +1,47 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:ClaudeDo.Ui.ViewModels.Islands"
|
||||
xmlns:islands="using:ClaudeDo.Ui.Views.Islands"
|
||||
x:Class="ClaudeDo.Ui.Views.Islands.TasksIslandView"
|
||||
x:DataType="vm:TasksIslandViewModel">
|
||||
<TextBlock Margin="14" Text="Tasks (placeholder)"
|
||||
Foreground="{DynamicResource TextDimBrush}"/>
|
||||
<DockPanel LastChildFill="True">
|
||||
|
||||
<!-- Header -->
|
||||
<Border DockPanel.Dock="Top" Classes="island-header">
|
||||
<StackPanel Margin="18,14" Spacing="4">
|
||||
<TextBlock Classes="eyebrow" Text="{Binding HeaderEyebrow}"/>
|
||||
<TextBlock FontFamily="{DynamicResource SansFamily}" FontSize="24"
|
||||
FontWeight="SemiBold" Foreground="{DynamicResource TextBrush}"
|
||||
Text="{Binding HeaderTitle}"/>
|
||||
<TextBlock FontFamily="{DynamicResource MonoFamily}" FontSize="11"
|
||||
Foreground="{DynamicResource TextMuteBrush}" Text="{Binding Subtitle}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Add-task row -->
|
||||
<Border DockPanel.Dock="Top" Margin="18,8,18,4">
|
||||
<TextBox Watermark="Add a task…" Text="{Binding NewTaskTitle, Mode=TwoWay}">
|
||||
<TextBox.KeyBindings>
|
||||
<KeyBinding Gesture="Enter" Command="{Binding AddCommand}"/>
|
||||
</TextBox.KeyBindings>
|
||||
</TextBox>
|
||||
</Border>
|
||||
|
||||
<!-- Task list -->
|
||||
<ScrollViewer>
|
||||
<ItemsControl ItemsSource="{Binding Items}" Margin="10,4">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="vm:TaskRowViewModel">
|
||||
<Button Classes="flat" HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{Binding $parent[ItemsControl].DataContext.SelectCommand}"
|
||||
CommandParameter="{Binding}">
|
||||
<islands:TaskRowView/>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
|
||||
Reference in New Issue
Block a user