Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions docs/COMPONENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ Same names, properties and variants as MAUI, except:
| Carousel | Swipe, arrows, dots | Drag with the mouse or touch; the arrows are tab stops and Left / Right move while one has focus; with `AspectRatio="0"` set `Height` |
| Stepper | Shows the active step's view | Every step stays in the tree and only the active one is visible; reached steps are tab stops (Enter / Space) |
| Pagination | Labels hidden on phones | `ShowLabels` defaults to true; every page and arrow is a tab stop (Enter / Space) |
| ButtonGroup | Children are the buttons | Children are collected in `Items` |
| InputGroup | `Keyboard` picks the on-screen keyboard | No `Keyboard`; a click on a control in the prefix or suffix is left to that control |
| Menubar | Tap a title to open its menu | Titles are tab stops: Enter / Space / Down open a menu and Left / Right move between titles; in a menu Up / Down move and Enter picks; children are collected in `Menus` and `Items` |
| Resizable | Drag a divider | Dividers are also tab stops: the arrow keys move them 16px; the pointer shows a resize cursor |
| Navbar | Links and the menu button react to taps | They are also tab stops (Enter / Space); children are collected in `Items` |
| Sidebar | Width animates on collapse | Same, with items as tab stops; children are collected in `Groups` and `Items` |
Expand Down Expand Up @@ -190,6 +193,29 @@ shellui-native add button

---

### Button Group
Buttons joined into one control: only the outer corners are rounded, and neighbouring borders
overlap so `Outline` buttons share one line between them. Each button keeps its own `Clicked`.

```bash
shellui-native add button-group
```

**Properties:** `Orientation` (Horizontal, Vertical) · its children are the buttons

```xml
<ui:ButtonGroup>
<ui:Button Text="Day" Variant="Outline" />
<ui:Button Text="Week" Variant="Outline" />
<ui:Button Text="Month" Variant="Outline" />
</ui:ButtonGroup>
```

It needs the `Button` that ships with it. If `button` is already in your project from an older
CLI version, run `shellui-native update button` first.

---

### Toggle
Two-state button that stays pressed — transparent (or outlined) when off, `Accent` when on.

Expand Down Expand Up @@ -261,6 +287,30 @@ shellui-native add input

---

### Input Group
An input with something before or after the text, inside the same border: an icon, a short
text, or any view. Same height, focus ring and error state as `Input`.

```bash
shellui-native add input-group
```

**Properties:** `Text` (two-way), `Placeholder`, `IsPassword`, `HasError`, `Keyboard` (MAUI),
`PrefixIcon`, `SuffixIcon`, `PrefixText`, `SuffixText`, `Prefix`, `Suffix` (a view; replaces the
icon and text on that side) · **Events:** `TextChanged`, `Completed` · **Methods:** `Focus()`

```xml
<ui:InputGroup PrefixIcon="Search" Placeholder="Search..." />
<ui:InputGroup PrefixText="https://" SuffixText=".com" Placeholder="example" />
<ui:InputGroup Placeholder="you@example.com">
<ui:InputGroup.Suffix>
<ui:Badge Text="Required" Variant="Secondary" />
</ui:InputGroup.Suffix>
</ui:InputGroup>
```

---

### Input OTP
One-time-code input: a row of 40px slots over one hidden text field, so paste, autofill and the
platform keyboard all work. The active slot shows the ring color and a blinking caret.
Expand Down Expand Up @@ -746,6 +796,34 @@ shellui-native add accordion

## Navigation Components

### Menubar
A row of menus, like a desktop app's File / Edit / View. Each menu opens a panel of items under
its title; clicking outside, picking an item or pressing Escape closes it, and opening one menu
closes the other.

```bash
shellui-native add menubar
```

**MenubarMenu:** `Title`, `IsOpen` (read-only) · **Events:** `Opened`, `Closed` · **Methods:**
`SetOpen(bool)`, `Close()`

**MenubarItem:** `Text`, `Icon`, `Shortcut` (a hint at the end of the row; display only),
`IsDestructive`, `IsEnabled` · **Events:** `Clicked` · **MenubarSeparator:** a divider

```xml
<ui:Menubar>
<ui:MenubarMenu Title="File">
<ui:MenubarItem Text="New tab" Shortcut="Ctrl+T" Clicked="OnNewTab" />
<ui:MenubarSeparator />
<ui:MenubarItem Text="Print" Icon="FileText" Clicked="OnPrint" />
</ui:MenubarMenu>
<ui:MenubarMenu Title="Edit"> ... </ui:MenubarMenu>
</ui:Menubar>
```

---

### Navbar
Top bar (56px, border below): a brand, links and actions at the end. Narrower than
`CollapseBelow`, the links move into a menu that a button opens under the bar; picking a link
Expand Down
9 changes: 6 additions & 3 deletions docs/COMPONENTS_ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Prioritized list of components to create for ShellUI Native, aligned with [Shell

Last revised: **2026-10-11** (second prerelease, `0.1.0-alpha.2`).

**Status at a glance:** P0–P6 ✅ done · P7 done except chart · `0.1.0-alpha.2`: 63 families, 94 CLI targets, each for MAUI and Avalonia.
**Status at a glance:** P0–P6 ✅ done · P7 done except chart · `0.1.0-alpha.2`: 63 families, 94 CLI targets, each for MAUI and Avalonia · since then: button-group, input-group, menubar.

---

Expand Down Expand Up @@ -293,5 +293,8 @@ P7 items marked ✅ above — 58 families, 89 CLI targets, MAUI only.
resizable, navbar, sidebar, date-range-picker and file-upload on both platforms — 63 families,
94 CLI targets. From here on a component lands on both platforms together.

**Backlog:** chart (P7); and the ShellUI Blazor targets not yet ported (button-group,
input-group, command / command-palette, menubar, navigation-menu, data-table, …).
**After `0.1.0-alpha.2`, from ShellUI's Blazor set** (both platforms): button-group ✅,
input-group ✅ and menubar ✅ (2026-10-11; 66 families, 97 CLI targets).

**Backlog:** chart (P7); and the ShellUI Blazor targets not yet ported (command /
command-palette, navigation-menu, data-table, …).
15 changes: 10 additions & 5 deletions docs/DEVELOPMENT_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ into it, not directly into `main`.
```
main ← Phase 1 (PRs #1–#5), release 0.1.0-alpha.1 (PR #6), Phase 2 Avalonia (PRs #7–#13),
layout, navigation and pickers on both platforms (PR #14) merged
└─ chore/release-v0.1.0-alpha.2 ← second prerelease, the first with Avalonia (active)
release 0.1.0-alpha.2 (PR #15), CI action bumps (PR #16) merged
└─ feat/button-group-input-group-menubar ← first batch from ShellUI's Blazor set (active)
└─ feat/winui ← Phase 3 (conditional)
```

Expand All @@ -35,8 +36,8 @@ main ← Phase 1 (PRs #1–#5), release 0.1.0-alpha.1 (PR #6), Phase 2 Avaloni

| Branch | Base | Status | Purpose |
|--------|------|--------|---------|
| `main` | — | Phase 1, release `0.1.0-alpha.1`, Phase 2 and PR #14 merged | 97 registry entries (94 CLI targets, 63 families), each with MAUI and Avalonia content |
| `chore/release-v0.1.0-alpha.2` | `main` | **Active** | Second prerelease: the first with Avalonia, plus five new components |
| `main` | — | Phase 1, Phase 2 and PR #14 merged; released as `0.1.0-alpha.2` | 97 registry entries (94 CLI targets, 63 families), each with MAUI and Avalonia content |
| `feat/button-group-input-group-menubar` | `main` | **Active** | button-group, input-group and menubar for MAUI and Avalonia (100 registry entries, 97 CLI targets, 66 families) |

---

Expand Down Expand Up @@ -434,7 +435,7 @@ After merge: tag `v0.1.0-alpha.1` on `main` and push the tag.

---

## Release `0.1.0-alpha.2` — `chore/release-v0.1.0-alpha.2` (**active**)
## Release `0.1.0-alpha.2` — `chore/release-v0.1.0-alpha.2` (**released**; tag `v0.1.0-alpha.2`)

Second NuGet prerelease of `ShellUI.Native.CLI`: the first with Avalonia templates, plus
resizable, navbar, sidebar, date-range-picker and file-upload on both platforms.
Expand All @@ -449,7 +450,7 @@ resizable, navbar, sidebar, date-range-picker and file-upload on both platforms.
target), each building with 0 warnings; `update calendar` fixes a project that has the
alpha.1 Calendar

After merge: tag `v0.1.0-alpha.2` on `main` and push the tag.
Tagged `v0.1.0-alpha.2` on `main` 2026-10-11; published to NuGet by the Release workflow.

---

Expand Down Expand Up @@ -519,6 +520,10 @@ Cross-desktop (Windows + macOS + Linux) from one XAML codebase.
(`feat/p6-layout-navigation`): resizable, navbar and sidebar, which finish P6, then
date-range-picker (Calendar gains `RangeStart` / `RangeEnd`) and file-upload from P7; 97
registry entries. A release branch follows
- [x] After the release, the first batch from ShellUI's Blazor set
(`feat/button-group-input-group-menubar`): button-group (Button gains an internal corner
override, so an older `button` needs `shellui-native update button`), input-group and
menubar (one template: Menubar, MenubarMenu, MenubarItem, MenubarSeparator)
- [ ] Replace the generated `Icon.cs` with the `ShellIcons.Maui` / `ShellIcons.Avalonia` packages
once they are on NuGet. Their names already match (`IconName`, `Icon`, `Kind` on Avalonia), so
components change little: token tinting binds the package's `Color` (MAUI) or `Foreground`
Expand Down
10 changes: 9 additions & 1 deletion examples/Avalonia.Demo/Components/UI/Button.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class Button : Border, IShellFocusable
private DispatcherTimer? _spinning;
private bool _hovered;
private bool _pressed;
private CornerRadius? _corners;

static Button()
{
Expand Down Expand Up @@ -180,14 +181,21 @@ protected override void OnKeyDown(KeyEventArgs e)
e.Handled = true;
}

// Set by ButtonGroup so joined buttons only round their outer corners; null restores the default.
internal void SetCorners(CornerRadius? corners)
{
_corners = corners;
UpdateVisualState();
}

private void UpdateVisualState()
{
_style = ButtonVariants.GetStyle(Variant, Size);

Height = _style.Height;
Width = _style.Width;
Padding = _style.Padding;
CornerRadius = new CornerRadius(_style.CornerRadius);
CornerRadius = _corners ?? new CornerRadius(_style.CornerRadius);
BorderThickness = new Thickness(_style.Border.HasValue ? 1 : 0);
if (_style.Border.HasValue)
this.Token(BorderBrushProperty, _style.Border.Value);
Expand Down
64 changes: 64 additions & 0 deletions examples/Avalonia.Demo/Components/UI/ButtonGroup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Avalonia;
using Avalonia.Automation;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Metadata;

namespace AvaloniaDemo.Components.UI;

// Button group — buttons joined into one control: only the outer corners are rounded and
// neighbouring borders overlap, so Outline buttons share a single line between them.
// <ui:ButtonGroup>
// <ui:Button Text="Day" Variant="Outline" />
// <ui:Button Text="Week" Variant="Outline" />
// <ui:Button Text="Month" Variant="Outline" />
// </ui:ButtonGroup>
public class ButtonGroup : Border
{
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<ButtonGroup, Orientation>(nameof(Orientation), Orientation.Horizontal);

private readonly StackPanel _stack = new() { Orientation = Orientation.Horizontal };

static ButtonGroup()
{
OrientationProperty.Changed.AddClassHandler<ButtonGroup>((g, _) => g.Arrange());
}

public Orientation Orientation
{
get => GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}

[Content]
public Controls Items => _stack.Children;

public ButtonGroup()
{
Child = _stack;
HorizontalAlignment = HorizontalAlignment.Left;
AutomationProperties.SetName(this, "Button group");
_stack.Children.CollectionChanged += (_, _) => Arrange();
}

private void Arrange()
{
var vertical = Orientation == Orientation.Vertical;
_stack.Orientation = Orientation;
var radius = ShellTheme.RadiusMd;
var last = _stack.Children.Count - 1;
for (var i = 0; i <= last; i++)
{
var child = _stack.Children[i];
var start = i == 0 ? radius : 0;
var end = i == last ? radius : 0;
// -1 so two 1px borders draw as one line.
child.Margin = i == 0 ? default : vertical ? new Thickness(0, -1, 0, 0) : new Thickness(-1, 0, 0, 0);
// Stacked buttons share the widest one's width.
child.HorizontalAlignment = vertical ? HorizontalAlignment.Stretch : HorizontalAlignment.Left;
if (child is Button button)
button.SetCorners(vertical ? new CornerRadius(start, start, end, end) : new CornerRadius(start, end, end, start));
}
}
}
Loading
Loading