@@ -136,10 +136,9 @@
-
+
@code {
private bool _isMobileMenuOpen = false;
@@ -153,45 +152,52 @@
{
new CommandItem
{
- Title = "Home",
+ Title = "Home",
+ Group = "Navigation",
Description = "Go to homepage",
Shortcut = "Ctrl+H",
Action = async () => { NavigationManager.NavigateTo("/"); await Task.CompletedTask; }
},
new CommandItem
{
- Title = "Dashboard",
+ Title = "Dashboard",
+ Group = "Navigation",
Description = "View dashboard",
Shortcut = "Ctrl+D",
Action = async () => { NavigationManager.NavigateTo("/dashboard"); await Task.CompletedTask; }
},
new CommandItem
{
- Title = "Documentation",
+ Title = "Documentation",
+ Group = "Resources",
Description = "View ShellUI documentation",
Action = async () => { NavigationManager.NavigateTo("/docs"); await Task.CompletedTask; }
},
new CommandItem
{
- Title = "Blog",
+ Title = "Blog",
+ Group = "Resources",
Description = "Read our latest blog posts",
Action = async () => { NavigationManager.NavigateTo("/blog"); await Task.CompletedTask; }
},
new CommandItem
{
- Title = "Showcase",
+ Title = "Showcase",
+ Group = "Community",
Description = "See ShellUI in action",
Action = async () => { NavigationManager.NavigateTo("/showcase"); await Task.CompletedTask; }
},
new CommandItem
{
- Title = "Sponsors",
+ Title = "Sponsors",
+ Group = "Community",
Description = "Support ShellUI development",
Action = async () => { NavigationManager.NavigateTo("/sponsors"); await Task.CompletedTask; }
},
new CommandItem
{
- Title = "GitHub",
+ Title = "GitHub",
+ Group = "Community",
Description = "View source code on GitHub",
Action = async () => await JSRuntime.InvokeVoidAsync("ShellUI.openUrl", "https://github.com/shellui-dev/shellui", "_blank")
}
@@ -213,10 +219,6 @@
_isCommandPaletteOpen = true;
}
- private void OnCommandPaletteChanged(bool isOpen)
- {
- _isCommandPaletteOpen = isOpen;
- }
private async Task HandleCommandSelected(CommandItem command)
{
@@ -226,14 +228,4 @@
await command.Action();
}
}
-
- private void HandleKeyDown(KeyboardEventArgs e)
- {
- // Ctrl+K to open command palette
- if (e.CtrlKey && e.Key == "k")
- {
- _isCommandPaletteOpen = !_isCommandPaletteOpen;
- StateHasChanged();
- }
- }
}
\ No newline at end of file
diff --git a/NET10/BlazorInteractiveServer/Components/Pages/Components/Index.razor b/NET10/BlazorInteractiveServer/Components/Pages/Components/Index.razor
index 9d08cb4..23fff23 100644
--- a/NET10/BlazorInteractiveServer/Components/Pages/Components/Index.razor
+++ b/NET10/BlazorInteractiveServer/Components/Pages/Components/Index.razor
@@ -11,6 +11,11 @@
Browse ShellUI components with usage examples and variants.
+
+ New in 0.4 →
+ Chat, StatCard, TreeView, Timeline, ToggleGroup, ButtonGroup, InputGroup, NumberInput, QrCode, ImageViewer, AspectRatio and Kbd.
+
+
+
+
New in 0.4
+
+
diff --git a/NET10/BlazorInteractiveServer/Components/UI/AspectRatio.razor b/NET10/BlazorInteractiveServer/Components/UI/AspectRatio.razor
new file mode 100644
index 0000000..02a289b
--- /dev/null
+++ b/NET10/BlazorInteractiveServer/Components/UI/AspectRatio.razor
@@ -0,0 +1,15 @@
+@namespace BlazorInteractiveServer.Components.UI
+
+
*]:absolute [&>*]:inset-0 [&>*]:h-full [&>*]:w-full", Class)" style="aspect-ratio: @RatioCss" @attributes="AdditionalAttributes">
+ @ChildContent
+
+
+@code {
+ [Parameter] public double Ratio { get; set; } = 16d / 9d;
+ [Parameter] public RenderFragment? ChildContent { get; set; }
+ [Parameter] public string? Class { get; set; }
+ [Parameter(CaptureUnmatchedValues = true)]
+ public Dictionary
? AdditionalAttributes { get; set; }
+
+ private string RatioCss => Ratio.ToString(System.Globalization.CultureInfo.InvariantCulture);
+}
diff --git a/NET10/BlazorInteractiveServer/Components/UI/ButtonGroup.razor b/NET10/BlazorInteractiveServer/Components/UI/ButtonGroup.razor
new file mode 100644
index 0000000..1d5967e
--- /dev/null
+++ b/NET10/BlazorInteractiveServer/Components/UI/ButtonGroup.razor
@@ -0,0 +1,18 @@
+@namespace BlazorInteractiveServer.Components.UI
+
+
+ @ChildContent
+
+
+@code {
+ [Parameter] public string Orientation { get; set; } = "horizontal";
+ [Parameter] public RenderFragment? ChildContent { get; set; }
+ [Parameter] public string? Class { get; set; }
+ [Parameter(CaptureUnmatchedValues = true)]
+ public Dictionary? AdditionalAttributes { get; set; }
+
+ private bool IsVertical => string.Equals(Orientation, "vertical", StringComparison.OrdinalIgnoreCase);
+
+ private const string HorizontalClass = "flex-row [&>*]:rounded-none [&>*:first-child]:rounded-l-md [&>*:last-child]:rounded-r-md [&>*:not(:first-child)]:-ml-px [&>*]:focus-visible:z-10";
+ private const string VerticalClass = "flex-col [&>*]:rounded-none [&>*:first-child]:rounded-t-md [&>*:last-child]:rounded-b-md [&>*:not(:first-child)]:-mt-px [&>*]:focus-visible:z-10";
+}
diff --git a/NET10/BlazorInteractiveServer/Components/UI/Chart.razor b/NET10/BlazorInteractiveServer/Components/UI/Chart.razor
index d49df32..0faff4c 100644
--- a/NET10/BlazorInteractiveServer/Components/UI/Chart.razor
+++ b/NET10/BlazorInteractiveServer/Components/UI/Chart.razor
@@ -20,9 +20,7 @@
[Parameter] public string? Class { get; set; }
[Parameter] public string? Height { get; set; } = "400px";
[Parameter] public string? Width { get; set; } = "100%";
- /// Show ApexCharts' zoom/pan/download toolbar. Off by default (shadcn aesthetic).
[Parameter] public bool ShowToolbar { get; set; }
- /// Show the series legend. Defaults to true; set false on single-series charts for a cleaner look.
[Parameter] public bool ShowLegend { get; set; } = true;
[Parameter] public RenderFragment? ChildContent { get; set; }
diff --git a/NET10/BlazorInteractiveServer/Components/UI/Chat.razor b/NET10/BlazorInteractiveServer/Components/UI/Chat.razor
new file mode 100644
index 0000000..012a3e0
--- /dev/null
+++ b/NET10/BlazorInteractiveServer/Components/UI/Chat.razor
@@ -0,0 +1,30 @@
+@namespace BlazorInteractiveServer.Components.UI
+
+
+ @if (Header != null)
+ {
+
@Header
+ }
+ @* column-reverse keeps the scroll anchored to the newest message without JS *@
+
+ @if (Footer != null)
+ {
+
@Footer
+ }
+
+
+@code {
+ [Parameter] public string? Height { get; set; }
+ [Parameter] public RenderFragment? Header { get; set; }
+ [Parameter] public RenderFragment? ChildContent { get; set; }
+ [Parameter] public RenderFragment? Footer { get; set; }
+ [Parameter] public string? Class { get; set; }
+ [Parameter(CaptureUnmatchedValues = true)]
+ public Dictionary? AdditionalAttributes { get; set; }
+}
diff --git a/NET10/BlazorInteractiveServer/Components/UI/ChatInput.razor b/NET10/BlazorInteractiveServer/Components/UI/ChatInput.razor
new file mode 100644
index 0000000..34eb126
--- /dev/null
+++ b/NET10/BlazorInteractiveServer/Components/UI/ChatInput.razor
@@ -0,0 +1,94 @@
+@namespace BlazorInteractiveServer.Components.UI
+
+
+
+@code {
+ [Parameter] public string? Value { get; set; }
+ [Parameter] public EventCallback ValueChanged { get; set; }
+ [Parameter] public EventCallback OnSubmit { get; set; }
+ [Parameter] public string Placeholder { get; set; } = "Send a message...";
+ [Parameter] public bool Disabled { get; set; }
+ [Parameter] public bool IsLoading { get; set; }
+ [Parameter] public int Rows { get; set; } = 1;
+ [Parameter] public RenderFragment? Actions { get; set; }
+ [Parameter] public string? Class { get; set; }
+ [Parameter(CaptureUnmatchedValues = true)]
+ public Dictionary? AdditionalAttributes { get; set; }
+
+ private ElementReference _textarea;
+ private int _inputKey;
+ private bool _ignoreNextInput;
+ private bool _refocus;
+
+ private bool CanSubmit => !Disabled && !IsLoading && !string.IsNullOrWhiteSpace(Value);
+
+ // Enter sends; the browser's newline still arrives as the next input event, so it is dropped and the textarea re-created.
+ private async Task HandleKeyDownAsync(KeyboardEventArgs e)
+ {
+ if (e.Key != "Enter" || e.ShiftKey) return;
+ _ignoreNextInput = true;
+ await SubmitAsync();
+ }
+
+ private async Task HandleInputAsync(ChangeEventArgs e)
+ {
+ if (_ignoreNextInput)
+ {
+ _ignoreNextInput = false;
+ _inputKey++;
+ _refocus = true;
+ return;
+ }
+ Value = e.Value?.ToString();
+ await ValueChanged.InvokeAsync(Value);
+ }
+
+ private async Task SubmitAsync()
+ {
+ if (!CanSubmit) return;
+ var text = Value!.Trim();
+ Value = "";
+ await ValueChanged.InvokeAsync(Value);
+ await OnSubmit.InvokeAsync(text);
+ }
+
+ protected override async Task OnAfterRenderAsync(bool firstRender)
+ {
+ if (!_refocus) return;
+ _refocus = false;
+ await _textarea.FocusAsync();
+ }
+}
diff --git a/NET10/BlazorInteractiveServer/Components/UI/ChatMessage.razor b/NET10/BlazorInteractiveServer/Components/UI/ChatMessage.razor
new file mode 100644
index 0000000..d27817d
--- /dev/null
+++ b/NET10/BlazorInteractiveServer/Components/UI/ChatMessage.razor
@@ -0,0 +1,65 @@
+@namespace BlazorInteractiveServer.Components.UI
+
+
+ @if (Avatar != null)
+ {
+
@Avatar
+ }
+
+ @if (!string.IsNullOrEmpty(Name) || !string.IsNullOrEmpty(Timestamp))
+ {
+
+ @if (!string.IsNullOrEmpty(Name))
+ {
+ @Name
+ }
+ @if (!string.IsNullOrEmpty(Timestamp))
+ {
+ @Timestamp
+ }
+
+ }
+
+ @if (Text != null)
+ {
+ @Text
+ }
+ @ChildContent
+ @if (IsStreaming)
+ {
+
+
+
+
+
+ }
+
+ @if (Actions != null)
+ {
+
@Actions
+ }
+
+
+
+@code {
+ [Parameter] public string Role { get; set; } = "assistant";
+ [Parameter] public string? Text { get; set; }
+ [Parameter] public string? Name { get; set; }
+ [Parameter] public string? Timestamp { get; set; }
+ [Parameter] public bool IsStreaming { get; set; }
+ [Parameter] public RenderFragment? Avatar { get; set; }
+ [Parameter] public RenderFragment? Actions { get; set; }
+ [Parameter] public RenderFragment? ChildContent { get; set; }
+ [Parameter] public string? Class { get; set; }
+ [Parameter(CaptureUnmatchedValues = true)]
+ public Dictionary? AdditionalAttributes { get; set; }
+
+ private bool IsUser => string.Equals(Role, "user", StringComparison.OrdinalIgnoreCase);
+
+ private string BubbleClass => Role.ToLowerInvariant() switch
+ {
+ "user" => "bg-primary text-primary-foreground",
+ "system" => "border border-border bg-background italic text-muted-foreground",
+ _ => "bg-muted text-foreground"
+ };
+}
diff --git a/NET10/BlazorInteractiveServer/Components/UI/Command.razor b/NET10/BlazorInteractiveServer/Components/UI/Command.razor
index 0fa7813..ed81025 100644
--- a/NET10/BlazorInteractiveServer/Components/UI/Command.razor
+++ b/NET10/BlazorInteractiveServer/Components/UI/Command.razor
@@ -1,172 +1,198 @@
-@using Microsoft.JSInterop
@using BlazorInteractiveServer.Components.Models
-