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
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,34 @@
<JsonViewer Value="_sampleJson" Class="max-h-80" />
</section>

<section class="border border-border rounded-lg p-6 bg-card mt-6">
<h2 class="text-2xl font-semibold mb-1 text-card-foreground">Resizable</h2>
<p class="text-sm text-muted-foreground mb-4">Drag a handle, or focus it with Tab and use the arrow keys, Home and End. Layout: @string.Join(" / ", _layout.Select(s => $"{s:0}%"))</p>
<div class="h-72 overflow-hidden rounded-lg border border-border">
<ResizablePanelGroup OnLayout="sizes => _layout = sizes">
<ResizablePanel DefaultSize="20" MinSize="15" MaxSize="35">
<div class="flex h-full items-center justify-center p-4 text-sm font-medium">Sidebar</div>
</ResizablePanel>
<ResizableHandle WithHandle="true" />
<ResizablePanel MinSize="30">
<ResizablePanelGroup Direction="vertical">
<ResizablePanel DefaultSize="65">
<div class="flex h-full items-center justify-center p-4 text-sm font-medium">Editor</div>
</ResizablePanel>
<ResizableHandle WithHandle="true" />
<ResizablePanel MinSize="15">
<div class="flex h-full items-center justify-center p-4 text-sm font-medium">Terminal</div>
</ResizablePanel>
</ResizablePanelGroup>
</ResizablePanel>
<ResizableHandle />
<ResizablePanel DefaultSize="25">
<div class="flex h-full items-center justify-center p-4 text-sm font-medium">Inspector</div>
</ResizablePanel>
</ResizablePanelGroup>
</div>
</section>

<section class="border border-border rounded-lg p-6 bg-card mt-6">
<h2 class="text-2xl font-semibold mb-4 text-card-foreground">Toggle Group &amp; Button Group</h2>
<div class="space-y-4">
Expand Down Expand Up @@ -246,6 +274,8 @@
public bool Digest { get; set; } = true;
}

private IReadOnlyList<double> _layout = new[] { 20d, 55d, 25d };

private readonly SignupModel _signup = new();
private bool _signedUp;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<div class="space-y-6">
<div>
<h1 class="text-3xl font-bold text-foreground">New in 0.4</h1>
<p class="text-muted-foreground mt-1">Kbd, AspectRatio, ButtonGroup, ToggleGroup, InputGroup, NumberInput, StatCard, Timeline, TreeView, QrCode, ImageViewer, Chat, PromptSuggestions, Reasoning, ToolCall, Field and JsonViewer.</p>
<p class="text-muted-foreground mt-1">Kbd, AspectRatio, ButtonGroup, ToggleGroup, InputGroup, NumberInput, StatCard, Timeline, TreeView, QrCode, ImageViewer, Chat, PromptSuggestions, Reasoning, ToolCall, Field, JsonViewer and Resizable.</p>
</div>
<NewComponentsDemo />
</div>
66 changes: 66 additions & 0 deletions NET10/BlazorInteractiveServer/Components/UI/ResizableHandle.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
@namespace BlazorInteractiveServer.Components.UI
@using System.Globalization
@using Microsoft.JSInterop
@implements IAsyncDisposable
@inject IJSRuntime JS

<div @ref="_element"
data-handle
data-step="@(Group?.KeyboardStep.ToString(CultureInfo.InvariantCulture))"
data-disabled="@(Disabled ? "true" : null)"
role="separator"
tabindex="@(Disabled ? "-1" : "0")"
aria-orientation="@(IsVertical ? "horizontal" : "vertical")"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="@((int)Math.Round(Group?.SizeAt(_index) ?? 0))"
class="@Shell.Cn("relative flex shrink-0 items-center justify-center bg-border ring-offset-background focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1", IsVertical ? "h-px w-full after:absolute after:inset-x-0 after:top-1/2 after:h-1 after:-translate-y-1/2" : "w-px after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2", Disabled ? "" : IsVertical ? "cursor-row-resize" : "cursor-col-resize", Class)"
@attributes="AdditionalAttributes">
@if (WithHandle)
{
<div class="@Shell.Cn("z-10 flex items-center justify-center rounded-sm border border-border bg-border", IsVertical ? "h-3 w-4" : "h-4 w-3")">
<svg class="@Shell.Cn("h-2.5 w-2.5", IsVertical ? "rotate-90" : "")" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="9" cy="12" r="1" /><circle cx="9" cy="5" r="1" /><circle cx="9" cy="19" r="1" />
<circle cx="15" cy="12" r="1" /><circle cx="15" cy="5" r="1" /><circle cx="15" cy="19" r="1" />
</svg>
</div>
}
</div>

@code {
[CascadingParameter] private ResizablePanelGroup? Group { get; set; }
// Shows a grip on the handle.
[Parameter] public bool WithHandle { get; set; }
[Parameter] public bool Disabled { get; set; }
[Parameter] public string? Class { get; set; }
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object>? AdditionalAttributes { get; set; }

private readonly string _id = Guid.NewGuid().ToString("N");
private ElementReference _element;
private DotNetObjectReference<ResizableHandle>? _ref;
private int _index;

private bool IsVertical => Group?.IsVertical ?? false;

protected override void OnInitialized() => _index = Group?.Register(this) ?? 0;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender || Group == null) return;
_ref = DotNetObjectReference.Create(this);
try { await JS.InvokeVoidAsync("ShellUI.initResizeHandle", _id, _element, _ref); } catch { }
}

[JSInvokable]
public Task OnLayoutChanged(double[] sizes) => Group?.SetLayoutAsync(sizes) ?? Task.CompletedTask;

internal void Refresh() => InvokeAsync(StateHasChanged);

public async ValueTask DisposeAsync()
{
Group?.Unregister(this);
try { await JS.InvokeVoidAsync("ShellUI.disposeResizeHandle", _id); } catch { }
_ref?.Dispose();
}
}
35 changes: 35 additions & 0 deletions NET10/BlazorInteractiveServer/Components/UI/ResizablePanel.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
@namespace BlazorInteractiveServer.Components.UI
@using System.Globalization
@implements IDisposable

<div data-panel
data-size="@Format(Size)"
data-min-size="@Format(MinSize)"
data-max-size="@Format(MaxSize)"
style="@($"flex: {Format(Size)} 1 0px;")"
class="@Shell.Cn("overflow-hidden", Class)"
@attributes="AdditionalAttributes">
@ChildContent
</div>

@code {
[CascadingParameter] private ResizablePanelGroup? Group { get; set; }
// Starting size in percent; panels without one share what's left.
[Parameter] public double? DefaultSize { get; set; }
[Parameter] public double MinSize { get; set; }
[Parameter] public double MaxSize { get; set; } = 100;
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public string? Class { get; set; }
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object>? AdditionalAttributes { get; set; }

private double Size => Group?.SizeOf(this) ?? DefaultSize ?? 100;

private static string Format(double value) => value.ToString("0.###", CultureInfo.InvariantCulture);

protected override void OnInitialized() => Group?.Register(this);

internal void Refresh() => InvokeAsync(StateHasChanged);

public void Dispose() => Group?.Unregister(this);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
@namespace BlazorInteractiveServer.Components.UI

<CascadingValue Value="this" IsFixed="true">
<div data-panel-group
data-direction="@(IsVertical ? "vertical" : "horizontal")"
class="@Shell.Cn("flex h-full w-full", IsVertical ? "flex-col" : "", Class)"
@attributes="AdditionalAttributes">
@ChildContent
</div>
</CascadingValue>

@code {
// "horizontal" or "vertical".
[Parameter] public string Direction { get; set; } = "horizontal";
// Percent a focused handle moves per arrow key press.
[Parameter] public double KeyboardStep { get; set; } = 10;
// Raised after a drag or key press with every panel's size in percent.
[Parameter] public EventCallback<double[]> OnLayout { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public string? Class { get; set; }
[Parameter(CaptureUnmatchedValues = true)]
public Dictionary<string, object>? AdditionalAttributes { get; set; }

private readonly List<ResizablePanel> _panels = new();
private readonly List<ResizableHandle> _handles = new();
private double[]? _sizes;

public bool IsVertical => string.Equals(Direction, "vertical", StringComparison.OrdinalIgnoreCase);

// Each panel's size in percent.
public IReadOnlyList<double> Layout => Sizes;

private double[] Sizes => _sizes ??= InitialSizes();

internal void Register(ResizablePanel panel)
{
_panels.Add(panel);
_sizes = null;
}

internal void Unregister(ResizablePanel panel)
{
if (_panels.Remove(panel)) _sizes = null;
}

// A handle sits between the panel registered before it and the next one.
internal int Register(ResizableHandle handle)
{
_handles.Add(handle);
return _panels.Count - 1;
}

internal void Unregister(ResizableHandle handle) => _handles.Remove(handle);

internal double SizeOf(ResizablePanel panel)
{
var index = _panels.IndexOf(panel);
return index < 0 || index >= Sizes.Length ? 0 : Sizes[index];
}

internal double SizeAt(int index) => index < 0 || index >= Sizes.Length ? 0 : Sizes[index];

internal async Task SetLayoutAsync(double[] sizes)
{
if (sizes.Length != _panels.Count) return;
_sizes = sizes;
foreach (var panel in _panels) panel.Refresh();
foreach (var handle in _handles) handle.Refresh();
await OnLayout.InvokeAsync(sizes);
}

// Panels with a DefaultSize keep it; the rest share what's left.
private double[] InitialSizes()
{
var given = _panels.Where(p => p.DefaultSize.HasValue).Sum(p => p.DefaultSize!.Value);
var open = _panels.Count(p => !p.DefaultSize.HasValue);
var share = open == 0 ? 0 : Math.Max(0, 100 - given) / open;
var sizes = _panels.Select(p => p.DefaultSize ?? share).ToArray();
var total = sizes.Sum();
return total <= 0 ? sizes : sizes.Select(s => s * 100 / total).ToArray();
}
}
24 changes: 23 additions & 1 deletion NET10/BlazorInteractiveServer/shellui.json
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,8 @@
"Name": "shellui-js",
"Version": "0.4.0-alpha.3",
"InstalledAt": "2026-09-26T17:01:28.0644172Z",
"IsCustomized": false
"IsCustomized": false,
"Hash": "9c3ba9f1ae9021769b3974e7291f2cc630b0adda780a74449b20c38887817107"
},
{
"Name": "command-palette",
Expand Down Expand Up @@ -527,6 +528,27 @@
"InstalledAt": "2026-10-22T20:46:37.5961373Z",
"IsCustomized": false,
"Hash": "3c8be5c6eab39cf83cace12e187720489f44a7b26e246db1aa2992438939a126"
},
{
"Name": "resizable-panel",
"Version": "0.4.0-alpha.3",
"InstalledAt": "2026-10-23T04:15:19.5653945Z",
"IsCustomized": false,
"Hash": "134292069d4b021761c388e1e60e6511b60edbfdbb6c0a16f4525bc9a665879a"
},
{
"Name": "resizable-handle",
"Version": "0.4.0-alpha.3",
"InstalledAt": "2026-10-23T04:15:19.5668845Z",
"IsCustomized": false,
"Hash": "2638781d970ac7214ca2c91e53c726ac6cc99126afccdca9faa4f826897d2dd4"
},
{
"Name": "resizable",
"Version": "0.4.0-alpha.3",
"InstalledAt": "2026-10-23T04:15:19.5687157Z",
"IsCustomized": false,
"Hash": "daa562195351bf7d92bea2d3859d51f158e5b36379daf761f8697cb2769fda85"
}
],
"ProjectType": 1
Expand Down
2 changes: 1 addition & 1 deletion NET10/BlazorInteractiveServer/wwwroot/app.css

Large diffs are not rendered by default.

Loading
Loading