diff --git a/Directory.Build.props b/Directory.Build.props index 2edf716..74e0b2d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ - 0.3.2 + 0.3.3 diff --git a/README.md b/README.md index 8922eaf..a871a99 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ | Channel | Version | Notes | |---|---|---| -| Latest stable (recommended) | `0.3.2` | Tailwind CSS `4.3.2` | +| Latest stable (recommended) | `0.3.3` | Tailwind CSS `4.3.2` | | Previous stable | `0.2.1` | Superseded by `0.3.0` | The CLI needs the .NET 10 SDK and works in Blazor projects on .NET 8, 9 and 10. The `ShellUI.Components` NuGet package targets .NET 10 only; on .NET 8 or 9, use the CLI. diff --git a/ShellUI.Tests/PackageBehaviorTests.cs b/ShellUI.Tests/PackageBehaviorTests.cs new file mode 100644 index 0000000..76a2068 --- /dev/null +++ b/ShellUI.Tests/PackageBehaviorTests.cs @@ -0,0 +1,65 @@ +using Bunit; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using ShellUI.Components; +using ShellUI.Components.Models; + +namespace ShellUI.Tests; + +public class PackageBehaviorTests +{ + [Fact] + public void Command_RunsTheItemActionThenRaisesCommandSelected() + { + using var ctx = new BunitContext(); + ctx.JSInterop.Mode = JSRuntimeMode.Loose; + var calls = new List(); + var item = new CommandItem { Title = "Go home", Action = () => { calls.Add("action"); return Task.CompletedTask; } }; + + var command = ctx.Render(p => p + .Add(c => c.IsOpen, true) + .Add(c => c.Commands, new List { item }) + .Add(c => c.CommandSelected, EventCallback.Factory.Create(this, _ => calls.Add("selected")))); + + command.FindAll("button").First(b => b.TextContent.Contains("Go home")).Click(); + + Assert.Equal(new[] { "action", "selected" }, calls); + } + + [Fact] + public void Popover_ClosesOnEscape() + { + using var ctx = new BunitContext(); + bool? open = null; + + var popover = ctx.Render(p => p + .Add(c => c.IsOpen, true) + .Add(c => c.IsOpenChanged, EventCallback.Factory.Create(this, v => open = v)) + .Add(c => c.Trigger, "Open") + .Add(c => c.ChildContent, "Body")); + + popover.Find("div.relative").KeyDown(new KeyboardEventArgs { Key = "Escape" }); + + Assert.False(open); + Assert.DoesNotContain("Body", popover.Markup); + } + + [Fact] + public void Dropdown_ClosesOnEscapeButNotOnOtherKeys() + { + using var ctx = new BunitContext(); + var changes = new List(); + + var dropdown = ctx.Render(p => p + .Add(c => c.IsOpen, true) + .Add(c => c.IsOpenChanged, EventCallback.Factory.Create(this, v => changes.Add(v))) + .Add(c => c.Trigger, "Menu") + .Add(c => c.ChildContent, "Item")); + + dropdown.Find("div.relative").KeyDown(new KeyboardEventArgs { Key = "a" }); + Assert.Empty(changes); + + dropdown.Find("div.relative").KeyDown(new KeyboardEventArgs { Key = "Escape" }); + Assert.Equal(new[] { false }, changes); + } +} diff --git a/VERSIONING_STRATEGY.md b/VERSIONING_STRATEGY.md index 9d51a5e..9bd198e 100644 --- a/VERSIONING_STRATEGY.md +++ b/VERSIONING_STRATEGY.md @@ -6,7 +6,7 @@ ShellUI uses one centralized version for the source templates, CLI tool, and pac | Scope | Value | |---|---| -| Version | `0.3.2` | +| Version | `0.3.3` | | Target framework | .NET 10 | | Tailwind version | `4.3.2` | @@ -18,15 +18,15 @@ The root `Directory.Build.props` supplies the version: ```xml - 0.3.2 + 0.3.3 ``` `Directory.Build.props` composes the package and assembly metadata: -- `Version` is `0.3.2`; with a suffix such as `rc.1` it becomes `0.3.2-rc.1`. -- `AssemblyVersion` and `FileVersion` use the numeric base `0.3.2`. +- `Version` is `0.3.3`; with a suffix such as `rc.1` it becomes `0.3.3-rc.1`. +- `AssemblyVersion` and `FileVersion` use the numeric base `0.3.3`. - `InformationalVersion` includes the prerelease suffix. - Component metadata reads the centralized properties when running from the repository. `VersionHelper` uses assembly metadata as a fallback when its repository search does not find the solution file. @@ -57,7 +57,7 @@ The CLI writes the computed version into `shellui.json` for each installed entry "InstalledComponents": [ { "Name": "button", - "Version": "0.3.2", + "Version": "0.3.3", "InstalledAt": "2026-01-01T00:00:00Z", "IsCustomized": false } diff --git a/docs/CLI_INSTALLATION.md b/docs/CLI_INSTALLATION.md index d23929f..5eacd81 100644 --- a/docs/CLI_INSTALLATION.md +++ b/docs/CLI_INSTALLATION.md @@ -4,7 +4,7 @@ | Context | Version | |---|---| -| Latest stable (recommended) | `0.3.2`, needs the .NET 10 runtime | +| Latest stable (recommended) | `0.3.3`, needs the .NET 10 runtime | | Previous stable | `0.2.1` | | Tailwind | `4.3.2` | @@ -43,7 +43,7 @@ The manifest is `.config/dotnet-tools.json` and uses the installed package versi "isRoot": true, "tools": { "shellui.cli": { - "version": "0.3.2", + "version": "0.3.3", "commands": ["shellui"] } } diff --git a/docs/CLI_SYNTAX.md b/docs/CLI_SYNTAX.md index 7d375c6..14b5e1e 100644 --- a/docs/CLI_SYNTAX.md +++ b/docs/CLI_SYNTAX.md @@ -1,6 +1,6 @@ # ShellUI CLI Syntax -This reference covers CLI `0.3.2`: it runs on .NET 10, sets up Blazor projects on .NET 8, 9 and 10, uses Tailwind CSS `4.3.2`, and has and 76 direct component targets. Older versions, such as `0.2.1`, lack some of these commands. +This reference covers CLI `0.3.3`: it runs on .NET 10, sets up Blazor projects on .NET 8, 9 and 10, uses Tailwind CSS `4.3.2`, and has and 76 direct component targets. Older versions, such as `0.2.1`, lack some of these commands. ## Command prefix @@ -205,7 +205,7 @@ Representative fields look like this: "InstalledComponents": [ { "Name": "button", - "Version": "0.3.2", + "Version": "0.3.3", "InstalledAt": "2026-01-01T00:00:00Z", "IsCustomized": false } diff --git a/docs/FAQ.md b/docs/FAQ.md index 874ba7a..649b6ff 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -6,7 +6,7 @@ Answers for the current ShellUI source and the currently published packages. ### Which version should I use? -Use `0.3.2`. It uses Tailwind CSS `4.3.2` and has 76 direct component targets: +Use `0.3.3`. It uses Tailwind CSS `4.3.2` and has 76 direct component targets: ```bash dotnet tool install -g ShellUI.CLI @@ -145,7 +145,7 @@ A representative Tailwind and component record is: "InstalledComponents": [ { "Name": "button", - "Version": "0.3.2", + "Version": "0.3.3", "InstalledAt": "2026-01-01T00:00:00Z", "IsCustomized": false } diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index 06824f6..be06112 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -1,6 +1,6 @@ # ShellUI Quick Start -This quick start uses ShellUI `0.3.2`: Tailwind CSS `4.3.2` and 76 direct component targets. The CLI needs the .NET 10 SDK and works in Blazor projects on .NET 8, 9 and 10. +This quick start uses ShellUI `0.3.3`: Tailwind CSS `4.3.2` and 76 direct component targets. The CLI needs the .NET 10 SDK and works in Blazor projects on .NET 8, 9 and 10. ## Prerequisites diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md index 7ab8084..e424054 100644 --- a/docs/RELEASE_NOTES.md +++ b/docs/RELEASE_NOTES.md @@ -1,5 +1,23 @@ # ShellUI Release Notes +# ShellUI v0.3.3 + +> A patch release with fixes for both the `ShellUI.Components` NuGet package and the CLI templates. + +## 🐛 Fixes + +- NuGet package: linking `shellui-theme.css` directly logged a 404 on every page load, because the file started with `@import "tailwindcss";` and the browser requested `tailwindcss` as a URL. The theme file now holds only the theme; the precompiled `shellui-all.css` is unchanged. +- `Command` ignored `CommandItem.Action`: selecting an item only raised `CommandSelected`. It now runs the item's `Action`, then raises `CommandSelected`, the same as `CommandPalette`. +- `Dropdown` and `Popover` now close on Escape. +- CLI `SidebarProvider` logged a `JSDisconnectedException` twice on every full page reload, typed URL or closed tab. It now ignores the disconnected circuit when it cleans up. + +New tests cover `Command` running `Action` and Escape closing `Dropdown` and `Popover`. + +## ⬆️ Upgrading + +- **NuGet package:** update to `0.3.3`. +- **CLI:** update the tool, then run `shellui update` to rewrite installed components from the new templates. `update` overwrites the files, so commit or back up any components you customized first. + # ShellUI v0.3.2 > A patch release with component fixes for both the `ShellUI.Components` NuGet package and the CLI templates. diff --git a/scripts/rebuild-precompiled-css.sh b/scripts/rebuild-precompiled-css.sh index 4d8a6c6..86ee88a 100755 --- a/scripts/rebuild-precompiled-css.sh +++ b/scripts/rebuild-precompiled-css.sh @@ -38,10 +38,12 @@ mv "$INPUT_CSS" "$INPUT_CSS.css" INPUT_CSS="$INPUT_CSS.css" COMPONENTS_ROOT="src/ShellUI.Components" -# @source inline, because Tailwind's file extractor misses `[state=…]` classes in plain text. -cat "$COMPONENTS_ROOT/wwwroot/shellui-theme.css" > "$INPUT_CSS" +# The import lives here, not in shellui-theme.css, so the theme can be linked directly without a 404. +printf '@import "tailwindcss";\n\n' > "$INPUT_CSS" +cat "$COMPONENTS_ROOT/wwwroot/shellui-theme.css" >> "$INPUT_CSS" echo "" >> "$INPUT_CSS" +# @source inline, because Tailwind's file extractor misses `[state=…]` classes in plain text. # Chunks of ~500 chars to avoid very long strings. awk ' BEGIN { line=""; } diff --git a/src/ShellUI.CLI/README.md b/src/ShellUI.CLI/README.md index d26ec84..cd218fb 100644 --- a/src/ShellUI.CLI/README.md +++ b/src/ShellUI.CLI/README.md @@ -68,10 +68,10 @@ dotnet watch ## Documentation -- [README](https://github.com/shellui-dev/shellui/blob/v0.3.2/README.md) -- [CLI reference](https://github.com/shellui-dev/shellui/blob/v0.3.2/docs/CLI_SYNTAX.md) -- [Release notes](https://github.com/shellui-dev/shellui/blob/v0.3.2/docs/RELEASE_NOTES.md) +- [README](https://github.com/shellui-dev/shellui/blob/v0.3.3/README.md) +- [CLI reference](https://github.com/shellui-dev/shellui/blob/v0.3.3/docs/CLI_SYNTAX.md) +- [Release notes](https://github.com/shellui-dev/shellui/blob/v0.3.3/docs/RELEASE_NOTES.md) ## License -[MIT](https://github.com/shellui-dev/shellui/blob/v0.3.2/LICENSE.txt) +[MIT](https://github.com/shellui-dev/shellui/blob/v0.3.3/LICENSE.txt) diff --git a/src/ShellUI.Components/Components/Command.razor b/src/ShellUI.Components/Components/Command.razor index ea512a2..b8199b5 100644 --- a/src/ShellUI.Components/Components/Command.razor +++ b/src/ShellUI.Components/Components/Command.razor @@ -125,6 +125,7 @@ private async Task SelectCommand(CommandItem command) { + if (command.Action is not null) await command.Action(); await CommandSelected.InvokeAsync(command); await CloseCommand(); } diff --git a/src/ShellUI.Components/Components/Dropdown.razor b/src/ShellUI.Components/Components/Dropdown.razor index 250b6ac..f015118 100644 --- a/src/ShellUI.Components/Components/Dropdown.razor +++ b/src/ShellUI.Components/Components/Dropdown.razor @@ -1,7 +1,7 @@ @namespace ShellUI.Components -
+
@if (UseCompositional) { @ChildContent @@ -58,4 +58,9 @@ { await ToggleAsync(); } + + private async Task OnKeyDown(KeyboardEventArgs e) + { + if (e.Key == "Escape" && IsOpen) await CloseAsync(); + } } diff --git a/src/ShellUI.Components/Components/Popover.razor b/src/ShellUI.Components/Components/Popover.razor index e713e3f..bd5522a 100644 --- a/src/ShellUI.Components/Components/Popover.razor +++ b/src/ShellUI.Components/Components/Popover.razor @@ -1,7 +1,7 @@ @namespace ShellUI.Components -
+
@if (Trigger is null) { @ChildContent @@ -45,4 +45,9 @@ public async Task CloseAsync() { IsOpen = false; await IsOpenChanged.InvokeAsync(IsOpen); StateHasChanged(); } private async Task Toggle() => await ToggleAsync(); private async Task Close() => await CloseAsync(); + + private async Task OnKeyDown(KeyboardEventArgs e) + { + if (e.Key == "Escape" && IsOpen) await CloseAsync(); + } } diff --git a/src/ShellUI.Components/README.md b/src/ShellUI.Components/README.md index aa9f523..004b8d5 100644 --- a/src/ShellUI.Components/README.md +++ b/src/ShellUI.Components/README.md @@ -77,10 +77,10 @@ shellui theme apply https://tweakcn.com/themes/THEME_ID --emit-override wwwroot/ ## Documentation -- [README](https://github.com/shellui-dev/shellui/blob/v0.3.2/README.md) -- [Tailwind setup](https://github.com/shellui-dev/shellui/blob/v0.3.2/docs/tailwind-setup.md) -- [Release notes](https://github.com/shellui-dev/shellui/blob/v0.3.2/docs/RELEASE_NOTES.md) +- [README](https://github.com/shellui-dev/shellui/blob/v0.3.3/README.md) +- [Tailwind setup](https://github.com/shellui-dev/shellui/blob/v0.3.3/docs/tailwind-setup.md) +- [Release notes](https://github.com/shellui-dev/shellui/blob/v0.3.3/docs/RELEASE_NOTES.md) ## License -[MIT](https://github.com/shellui-dev/shellui/blob/v0.3.2/LICENSE.txt) +[MIT](https://github.com/shellui-dev/shellui/blob/v0.3.3/LICENSE.txt) diff --git a/src/ShellUI.Components/wwwroot/shellui-theme.css b/src/ShellUI.Components/wwwroot/shellui-theme.css index a2c81f9..39a8a34 100644 --- a/src/ShellUI.Components/wwwroot/shellui-theme.css +++ b/src/ShellUI.Components/wwwroot/shellui-theme.css @@ -1,5 +1,3 @@ -@import "tailwindcss"; - :root { --background: oklch(0.9900 0 0); --foreground: oklch(0 0 0); diff --git a/src/ShellUI.Core/Models/ComponentMetadata.cs b/src/ShellUI.Core/Models/ComponentMetadata.cs index d8b458c..3967b74 100644 --- a/src/ShellUI.Core/Models/ComponentMetadata.cs +++ b/src/ShellUI.Core/Models/ComponentMetadata.cs @@ -66,7 +66,7 @@ private static string GetCurrentVersion() if (assemblyVersion != null) return assemblyVersion.InformationalVersion.Split('+')[0]; - return "0.3.2"; + return "0.3.3"; } } diff --git a/src/ShellUI.Templates/Templates/CommandTemplate.cs b/src/ShellUI.Templates/Templates/CommandTemplate.cs index 5aed6e2..8fa0b8a 100644 --- a/src/ShellUI.Templates/Templates/CommandTemplate.cs +++ b/src/ShellUI.Templates/Templates/CommandTemplate.cs @@ -125,6 +125,7 @@ private void UpdateFilteredCommands() private async Task SelectCommand(CommandItem command) { + if (command.Action is not null) await command.Action(); await CommandSelected.InvokeAsync(command); await CloseCommand(); } diff --git a/src/ShellUI.Templates/Templates/DropdownTemplate.cs b/src/ShellUI.Templates/Templates/DropdownTemplate.cs index bb5b644..117d9aa 100644 --- a/src/ShellUI.Templates/Templates/DropdownTemplate.cs +++ b/src/ShellUI.Templates/Templates/DropdownTemplate.cs @@ -18,7 +18,7 @@ public static class DropdownTemplate public static string Content => @"@namespace YourProjectNamespace.Components.UI -
+
@if (UseCompositional) { @ChildContent @@ -75,6 +75,11 @@ private async Task ToggleOpen() { await ToggleAsync(); } + + private async Task OnKeyDown(KeyboardEventArgs e) + { + if (e.Key == ""Escape"" && IsOpen) await CloseAsync(); + } } "; } diff --git a/src/ShellUI.Templates/Templates/PopoverTemplate.cs b/src/ShellUI.Templates/Templates/PopoverTemplate.cs index a0cdaf5..403d0b8 100644 --- a/src/ShellUI.Templates/Templates/PopoverTemplate.cs +++ b/src/ShellUI.Templates/Templates/PopoverTemplate.cs @@ -19,7 +19,7 @@ public class PopoverTemplate public static string Content => @"@namespace YourProjectNamespace.Components.UI -
+
@if (Trigger is null) { @ChildContent @@ -63,6 +63,11 @@ public class PopoverTemplate public async Task CloseAsync() { IsOpen = false; await IsOpenChanged.InvokeAsync(IsOpen); StateHasChanged(); } private async Task Toggle() => await ToggleAsync(); private async Task Close() => await CloseAsync(); + + private async Task OnKeyDown(KeyboardEventArgs e) + { + if (e.Key == ""Escape"" && IsOpen) await CloseAsync(); + } } "; } diff --git a/src/ShellUI.Templates/Templates/SidebarProviderTemplate.cs b/src/ShellUI.Templates/Templates/SidebarProviderTemplate.cs index 15a19da..59d78a0 100644 --- a/src/ShellUI.Templates/Templates/SidebarProviderTemplate.cs +++ b/src/ShellUI.Templates/Templates/SidebarProviderTemplate.cs @@ -128,6 +128,7 @@ public async ValueTask DisposeAsync() { try { await JSRuntime.InvokeVoidAsync(""ShellUI.disposeSidebar"", _handle); } catch (JSException) { } + catch (JSDisconnectedException) { } } _dotnetRef?.Dispose(); diff --git a/src/ShellUI.Templates/VersionHelper.cs b/src/ShellUI.Templates/VersionHelper.cs index 8d93c60..ab559ef 100644 --- a/src/ShellUI.Templates/VersionHelper.cs +++ b/src/ShellUI.Templates/VersionHelper.cs @@ -51,7 +51,7 @@ public static string GetCurrentVersion() return _cachedVersion; } - return "0.3.2"; + return "0.3.3"; } private static string? FindSolutionRoot()