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
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project>
<!-- One version for every ShellUI package -->
<PropertyGroup>
<ShellUIVersion>0.3.3</ShellUIVersion>
<ShellUIVersion>0.3.4</ShellUIVersion>
<ShellUIVersionSuffix></ShellUIVersionSuffix>
</PropertyGroup>

Expand Down
2 changes: 2 additions & 0 deletions NET10/BlazorInteractiveServer/wwwroot/input.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@custom-variant dark (&:is(.dark *));

:root {
color-scheme: light;
--background: oklch(0.9900 0 0);
--foreground: oklch(0 0 0);
--card: oklch(1 0 0);
Expand Down Expand Up @@ -58,6 +59,7 @@
}

.dark {
color-scheme: dark;
--background: oklch(0 0 0);
--foreground: oklch(1 0 0);
--card: oklch(0.1400 0 0);
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

| Channel | Version | Notes |
|---|---|---|
| Latest stable (recommended) | `0.3.3` | Tailwind CSS `4.3.2` |
| Latest stable (recommended) | `0.3.4` | 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.
Expand Down
42 changes: 42 additions & 0 deletions ShellUI.Tests/ColorSchemeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.IO;
using ShellUI.CLI.Services;
using ShellUI.Templates;
using Xunit;

namespace ShellUI.Tests;

public class ColorSchemeTests
{
[Fact]
public void InitTheme_SetsColorSchemeForBothThemes()
{
Assert.Contains("color-scheme: light;", CssTemplates.InputCss);
Assert.Contains("color-scheme: dark;", CssTemplates.InputCss);
}

[Fact]
public void AppliedTheme_SetsColorSchemeForBothThemes()
{
var css = ThemeService.BuildThemeCss(new TweakcnTheme
{
Name = "t",
ThemeVars = new(),
LightVars = new() { ["background"] = "#fff" },
DarkVars = new() { ["background"] = "#000" },
});

Assert.Contains(":root {\n color-scheme: light;", css.Replace("\r\n", "\n"));
Assert.Contains(".dark {\n color-scheme: dark;", css.Replace("\r\n", "\n"));
}

[Fact]
public void PackageTheme_SetsColorSchemeForBothThemes()
{
var root = new DirectoryInfo(System.AppContext.BaseDirectory);
while (root != null && !File.Exists(Path.Combine(root.FullName, "ShellUI.slnx"))) root = root.Parent;
var css = File.ReadAllText(Path.Combine(root!.FullName, "src", "ShellUI.Components", "wwwroot", "shellui-theme.css"));

Assert.Contains("color-scheme: light;", css);
Assert.Contains("color-scheme: dark;", css);
}
}
10 changes: 10 additions & 0 deletions ShellUI.Tests/PackageBehaviorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,14 @@ public void Dropdown_ClosesOnEscapeButNotOnOtherKeys()
dropdown.Find("div.relative").KeyDown(new KeyboardEventArgs { Key = "Escape" });
Assert.Equal(new[] { false }, changes);
}

[Fact]
public void TimePicker_ShowsASetValue()
{
using var ctx = new BunitContext();

var picker = ctx.Render<TimePicker>(p => p.Add(c => c.Value, new TimeSpan(9, 5, 0)));

Assert.Contains("09:05", picker.Markup);
}
}
72 changes: 72 additions & 0 deletions ShellUI.Tests/PackageRenderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,78 @@ public void EveryPackageComponent_RendersWithoutParameterBindingErrors()
Assert.True(offenders.Count == 0, "Package components that fail parameter binding:\n " + string.Join("\n ", offenders));
}

// A component that renders empty but throws once it has a value fails here (the TimePicker "HH:mm" bug).
[Fact]
public void ValueBearingComponents_RenderWithASampleValue()
{
var types = typeof(Shell).Assembly.GetExportedTypes()
.Where(t => typeof(IComponent).IsAssignableFrom(t) && !t.IsAbstract && t.Namespace == "ShellUI.Components")
.Select(t => t.IsGenericTypeDefinition ? Close(t) : t)
.OfType<Type>()
.ToList();

var tried = 0;
var offenders = new List<string>();
foreach (var type in types)
{
var values = SampleValues(type);
if (values.Count == 0 || !Renders(type, new Dictionary<string, object>(), out _)) continue;

tried++;
if (!Renders(type, values, out var error))
offenders.Add($"{type.Name} ({string.Join(", ", values.Keys)}): {error}");
}

Assert.True(tried >= 10, $"Only {tried} components had a value to set; the sample types may need extending.");
Assert.True(offenders.Count == 0, "Package components that throw once a value is set:\n " + string.Join("\n ", offenders));
}

private static readonly System.Text.RegularExpressions.Regex ValueLike = new("Value|Date|Time|Selected");

private static Dictionary<string, object> SampleValues(Type type)
{
var values = new Dictionary<string, object>();
foreach (var p in type.GetProperties())
{
if (!p.IsDefined(typeof(ParameterAttribute), true) || !p.CanWrite || !ValueLike.IsMatch(p.Name)) continue;
var t = Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType;
object? sample =
t == typeof(string) ? "b" :
t == typeof(DateTime) ? new DateTime(2026, 3, 14) :
t == typeof(DateOnly) ? new DateOnly(2026, 3, 14) :
t == typeof(TimeSpan) ? new TimeSpan(9, 5, 0) :
t == typeof(TimeOnly) ? new TimeOnly(9, 5) :
t == typeof(int) ? 42 :
t == typeof(double) ? 42.5 :
t == typeof(decimal) ? 42.5m :
t == typeof(bool) ? true :
t == typeof(List<string>) ? new List<string> { "a", "b" } :
t == typeof(IEnumerable<string>) ? new List<string> { "a", "b" } :
null;
if (sample != null) values[p.Name] = sample;
}
return values;
}

private static bool Renders(Type type, Dictionary<string, object> parameters, out string error)
{
using var ctx = new BunitContext();
ctx.JSInterop.Mode = JSRuntimeMode.Loose;
ctx.Services.AddShellUISonner();
ctx.Services.AddScoped<IThemeService, ThemeService>();
try
{
ctx.Render<DynamicComponent>(p => p.Add(c => c.Type, type).Add(c => c.Parameters, parameters));
error = "";
return true;
}
catch (Exception ex)
{
error = Flatten(ex);
return false;
}
}

private static Type? Close(Type definition)
{
foreach (var candidate in new[] { typeof(Row), typeof(string), typeof(int) })
Expand Down
10 changes: 5 additions & 5 deletions VERSIONING_STRATEGY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ShellUI uses one centralized version for the source templates, CLI tool, and pac

| Scope | Value |
|---|---|
| Version | `0.3.3` |
| Version | `0.3.4` |
| Target framework | .NET 10 |
| Tailwind version | `4.3.2` |

Expand All @@ -18,15 +18,15 @@ The root `Directory.Build.props` supplies the version:

```xml
<PropertyGroup>
<ShellUIVersion>0.3.3</ShellUIVersion>
<ShellUIVersion>0.3.4</ShellUIVersion>
<ShellUIVersionSuffix></ShellUIVersionSuffix>
</PropertyGroup>
```

`Directory.Build.props` composes the package and assembly metadata:

- `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`.
- `Version` is `0.3.4`; with a suffix such as `rc.1` it becomes `0.3.4-rc.1`.
- `AssemblyVersion` and `FileVersion` use the numeric base `0.3.4`.
- `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.

Expand Down Expand Up @@ -57,7 +57,7 @@ The CLI writes the computed version into `shellui.json` for each installed entry
"InstalledComponents": [
{
"Name": "button",
"Version": "0.3.3",
"Version": "0.3.4",
"InstalledAt": "2026-01-01T00:00:00Z",
"IsCustomized": false
}
Expand Down
4 changes: 2 additions & 2 deletions docs/CLI_INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

| Context | Version |
|---|---|
| Latest stable (recommended) | `0.3.3`, needs the .NET 10 runtime |
| Latest stable (recommended) | `0.3.4`, needs the .NET 10 runtime |
| Previous stable | `0.2.1` |
| Tailwind | `4.3.2` |

Expand Down Expand Up @@ -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.3",
"version": "0.3.4",
"commands": ["shellui"]
}
}
Expand Down
4 changes: 2 additions & 2 deletions docs/CLI_SYNTAX.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ShellUI CLI Syntax

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.
This reference covers CLI `0.3.4`: 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

Expand Down Expand Up @@ -205,7 +205,7 @@ Representative fields look like this:
"InstalledComponents": [
{
"Name": "button",
"Version": "0.3.3",
"Version": "0.3.4",
"InstalledAt": "2026-01-01T00:00:00Z",
"IsCustomized": false
}
Expand Down
4 changes: 2 additions & 2 deletions docs/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Answers for the current ShellUI source and the currently published packages.

### Which version should I use?

Use `0.3.3`. It uses Tailwind CSS `4.3.2` and has 76 direct component targets:
Use `0.3.4`. It uses Tailwind CSS `4.3.2` and has 76 direct component targets:

```bash
dotnet tool install -g ShellUI.CLI
Expand Down Expand Up @@ -145,7 +145,7 @@ A representative Tailwind and component record is:
"InstalledComponents": [
{
"Name": "button",
"Version": "0.3.3",
"Version": "0.3.4",
"InstalledAt": "2026-01-01T00:00:00Z",
"IsCustomized": false
}
Expand Down
2 changes: 1 addition & 1 deletion docs/QUICKSTART.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ShellUI Quick Start

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.
This quick start uses ShellUI `0.3.4`: 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

Expand Down
16 changes: 16 additions & 0 deletions docs/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# ShellUI Release Notes

# ShellUI v0.3.4

> A patch release with a `TimePicker` fix for the `ShellUI.Components` NuGet package and dark-theme fixes for both the package and the CLI.

## 🐛 Fixes

- NuGet package: `TimePicker` threw a `FormatException` whenever `Value` was set, because it formatted the `TimeSpan` with a date pattern. It now shows the time, the same as the CLI template.
- Dark theme: the browser kept drawing its own controls light on a dark page (the `<input type="date">` calendar, number spinners, scrollbars, autofill and `<select>` lists). The themes now set `color-scheme` to `light` in `:root` and `dark` in `.dark`: in `shellui-theme.css` and `shellui-all.css` in the package, and in the theme `shellui init` and `shellui theme apply` write.

A new test renders every package component that takes a value with a sample value set, so a component that only fails once it has a value fails CI.

## ⬆️ Upgrading

- **NuGet package:** update to `0.3.4`.
- **CLI:** update the tool. Existing projects keep their `input.css`: add `color-scheme: light;` inside `:root` and `color-scheme: dark;` inside `.dark`, or run `shellui theme apply` again if you use a tweakcn theme.

# ShellUI v0.3.3

> A patch release with fixes for both the `ShellUI.Components` NuGet package and the CLI templates.
Expand Down
8 changes: 4 additions & 4 deletions src/ShellUI.CLI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ dotnet watch

## Documentation

- [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)
- [README](https://github.com/shellui-dev/shellui/blob/v0.3.4/README.md)
- [CLI reference](https://github.com/shellui-dev/shellui/blob/v0.3.4/docs/CLI_SYNTAX.md)
- [Release notes](https://github.com/shellui-dev/shellui/blob/v0.3.4/docs/RELEASE_NOTES.md)

## License

[MIT](https://github.com/shellui-dev/shellui/blob/v0.3.3/LICENSE.txt)
[MIT](https://github.com/shellui-dev/shellui/blob/v0.3.4/LICENSE.txt)
8 changes: 5 additions & 3 deletions src/ShellUI.CLI/Services/ThemeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ public static string BuildThemeCss(TweakcnTheme theme)
sb.AppendLine(BeginMarker);
sb.AppendLine($"/* Theme: {theme.Name} — fetched from tweakcn */");

WriteBlock(sb, ":root", theme.LightVars);
WriteBlock(sb, ".dark", theme.DarkVars);
WriteBlock(sb, ":root", "light", theme.LightVars);
WriteBlock(sb, ".dark", "dark", theme.DarkVars);

if (theme.ThemeVars.Count > 0)
{
Expand All @@ -118,11 +118,13 @@ public static string BuildThemeCss(TweakcnTheme theme)
return sb.ToString();
}

private static void WriteBlock(StringBuilder sb, string selector, Dictionary<string, string> vars)
// color-scheme makes the browser's own controls (date popups, scrollbars, select lists) follow the theme.
private static void WriteBlock(StringBuilder sb, string selector, string colorScheme, Dictionary<string, string> vars)
{
if (vars.Count == 0) return;
sb.AppendLine();
sb.AppendLine($"{selector} {{");
sb.AppendLine($" color-scheme: {colorScheme};");
foreach (var (k, v) in vars.OrderBy(kv => kv.Key, StringComparer.Ordinal))
sb.AppendLine($" --{k}: {v};");
sb.AppendLine("}");
Expand Down
2 changes: 1 addition & 1 deletion src/ShellUI.Components/Components/TimePicker.razor
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
disabled="@Disabled"
class="@Shell.Cn("flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", Disabled ? "cursor-not-allowed opacity-50" : "")">
<span class="@(Value == null ? "text-muted-foreground" : "")">
@(Value?.ToString("HH:mm") ?? Placeholder)
@(Value?.ToString(@"hh\:mm") ?? Placeholder)
</span>
<svg class="h-4 w-4 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
Expand Down
8 changes: 4 additions & 4 deletions src/ShellUI.Components/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.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)
- [README](https://github.com/shellui-dev/shellui/blob/v0.3.4/README.md)
- [Tailwind setup](https://github.com/shellui-dev/shellui/blob/v0.3.4/docs/tailwind-setup.md)
- [Release notes](https://github.com/shellui-dev/shellui/blob/v0.3.4/docs/RELEASE_NOTES.md)

## License

[MIT](https://github.com/shellui-dev/shellui/blob/v0.3.3/LICENSE.txt)
[MIT](https://github.com/shellui-dev/shellui/blob/v0.3.4/LICENSE.txt)
2 changes: 2 additions & 0 deletions src/ShellUI.Components/wwwroot/shellui-theme.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
:root {
color-scheme: light;
--background: oklch(0.9900 0 0);
--foreground: oklch(0 0 0);
--card: oklch(1 0 0);
Expand Down Expand Up @@ -54,6 +55,7 @@
}

.dark {
color-scheme: dark;
--background: oklch(0 0 0);
--foreground: oklch(1 0 0);
--card: oklch(0.1400 0 0);
Expand Down
2 changes: 1 addition & 1 deletion src/ShellUI.Core/Models/ComponentMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private static string GetCurrentVersion()
if (assemblyVersion != null)
return assemblyVersion.InformationalVersion.Split('+')[0];

return "0.3.3";
return "0.3.4";
}
}

2 changes: 2 additions & 0 deletions src/ShellUI.Templates/CssTemplates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public static class CssTemplates
@custom-variant dark (&:is(.dark *));

:root {
color-scheme: light;
--background: oklch(0.9900 0 0);
--foreground: oklch(0 0 0);
--card: oklch(1 0 0);
Expand Down Expand Up @@ -90,6 +91,7 @@ public static class CssTemplates
}

.dark {
color-scheme: dark;
--background: oklch(0 0 0);
--foreground: oklch(1 0 0);
--card: oklch(0.1400 0 0);
Expand Down
Loading
Loading