Recreated solution with web app template. Improved theme. Switched to ShadcnBlazor library

This commit is contained in:
2025-12-25 19:16:53 +01:00
parent 0cc35300f1
commit a2d4edc0e5
272 changed files with 2441 additions and 14449 deletions

View File

@@ -0,0 +1,9 @@
@using ShadcnBlazor.Sidebars
<header
class="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)"
style="--header-height: calc(var(--spacing) * 12)">
<div class="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
<SidebarTrigger ClassName="-ml-1"/>
</div>
</header>

View File

@@ -0,0 +1,124 @@
@using System.Diagnostics.CodeAnalysis
@using LucideBlazor
@using ShadcnBlazor.Sidebars
@inject NavigationManager Navigation
@implements IDisposable
@{
var url = new Uri(Navigation.Uri);
}
<Sidebar Variant="SidebarVariant.Sidebar" Collapsible="SidebarCollapsible.Icon">
<SidebarHeader>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton>
<a href="/" class="flex flex-row items-center">
<img alt="Logo" src="/_content/Moonlight.Frontend/logo.svg" class="size-6"/>
<span class="ms-2.5 text-lg font-semibold">Moonlight</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
@foreach (var group in Items.GroupBy(x => x.Group))
{
<SidebarGroup>
@if (!string.IsNullOrWhiteSpace(group.Key))
{
<SidebarGroupLabel>
@group.Key
</SidebarGroupLabel>
}
<SidebarGroupContent>
<SidebarMenu>
@foreach (var item in group)
{
var isActive = item.IsExactPath
? item.Path == url.LocalPath
: url.LocalPath.StartsWith(item.Path, StringComparison.OrdinalIgnoreCase);
<SidebarMenuItem @key="item">
<SidebarMenuButton IsActive="@isActive">
<Slot>
<a href="@item.Path" @attributes="context">
<DynamicComponent Type="item.IconType"/>
<span>@item.Name</span>
</a>
</Slot>
</SidebarMenuButton>
</SidebarMenuItem>
}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
}
</SidebarContent>
<SidebarFooter>
<NavUser/>
</SidebarFooter>
</Sidebar>
@code
{
private readonly List<SidebarItem> Items = new();
protected override void OnInitialized()
{
Items.AddRange([
new()
{
Name = "Overview",
IconType = typeof(LayoutDashboardIcon),
Path = "/",
IsExactPath = true
},
new()
{
Name = "Users",
IconType = typeof(UsersRoundIcon),
Path = "/users",
IsExactPath = false,
Group = "Admin"
},
]);
Navigation.LocationChanged += OnLocationChanged;
}
private async void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{
try
{
await InvokeAsync(StateHasChanged);
}
catch (Exception)
{
// Ignored
}
}
private record SidebarItem
{
public string Name { get; set; }
public string Path { get; set; }
public bool IsExactPath { get; set; }
public string? Group { get; set; }
// Used to prevent the IL-Trimming from removing this type as its dynamically assigned a type and we
// need it to work properly
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public Type IconType { get; set; }
}
public void Dispose()
{
Navigation.LocationChanged -= OnLocationChanged;
}
}

View File

@@ -0,0 +1,25 @@
@using ShadcnBlazor.Extras.AlertDialogs
@using ShadcnBlazor.Extras.Alerts
@using ShadcnBlazor.Extras.Dialogs
@using ShadcnBlazor.Extras.Toasts
@using ShadcnBlazor.Sidebars
@inherits LayoutComponentBase
<SidebarProvider DefaultOpen="true">
<AppSidebar/>
<SidebarInset>
<AppHeader/>
<div class="mx-8 my-8 max-w-full">
<AlertLauncher/>
@Body
</div>
<ToastLauncher/>
<DialogLauncher/>
<AlertDialogLauncher/>
</SidebarInset>
</SidebarProvider>

View File

@@ -0,0 +1,76 @@
@using System.Security.Claims
@using LucideBlazor
@using Microsoft.AspNetCore.Components.Authorization
@using ShadcnBlazor.Avatars
@using ShadcnBlazor.Dropdowns
@using ShadcnBlazor.Interop
@using ShadcnBlazor.Sidebars
@inject NavigationManager Navigation
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger>
<Slot>
<SidebarMenuButton
Size="SidebarMenuButtonSize.Lg"
ClassName="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
@attributes="context">
<Avatar className="h-8 w-8 rounded-lg">
<AvatarImage src="https://github.com/ghost.png" alt="Ghost"/>
<AvatarFallback className="rounded-lg">GH</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-medium">@Username</span>
<span class="truncate text-xs">@Email</span>
</div>
<ChevronsUpDownIcon ClassName="ml-auto size-4"/>
</SidebarMenuButton>
</Slot>
</DropdownMenuTrigger>
<DropdownMenuContent
ClassName="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
Side="@(SidebarProvider.IsMobile ? PositionSide.Bottom : PositionSide.Right)"
Align="PositionAlignment.End">
<DropdownMenuLabel ClassName="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar ClassName="h-8 w-8 rounded-lg">
<AvatarImage src="https://github.com/ghost.png" alt="Ghost"/>
<AvatarFallback className="rounded-lg">GH</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-medium">@Username</span>
<span class="truncate text-xs">@Email</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator/>
<DropdownMenuItem OnClick="Logout">
<LogOutIcon/>
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
@code
{
[CascadingParameter] public SidebarProvider SidebarProvider { get; set; }
[CascadingParameter] public Task<AuthenticationState> AuthState { get; set; }
private string Username;
private string Email;
protected override async Task OnInitializedAsync()
{
var authState = await AuthState;
Username = authState.User.FindFirst(ClaimTypes.Name)?.Value ?? "N/A";
Email = authState.User.FindFirst(ClaimTypes.Email)?.Value ?? "N/A";
}
private void Logout() => Navigation.NavigateTo("/api/auth/logout", true);
}