Files
decePubClient/Shared/NavMenu.razor
T

228 lines
8.9 KiB
Plaintext
Raw Normal View History

2022-12-15 20:03:40 +01:00
@inherits LocalizableComponentBase
2026-10-06 22:44:57 +02:00
@* On a phone the menu opens from the hamburger over the page, and closes after a choice (any navigation, a persona
switch), on a tap outside it or on Escape; from md it is the sidebar, which scrolls on its own when the window is
short (a phone held sideways). *@
<nav class="flex justify-between items-center md:items-start pt-3 px-3 md:p-0 md:h-full md:overflow-y-auto md:overscroll-contain"
@onkeydown="OnKeyDown">
2022-02-14 01:51:52 +01:00
2026-10-06 22:44:57 +02:00
@if (isMenuOpen)
{
<div class="md:hidden fixed inset-0 z-40 bg-black/30" aria-hidden="true" @onclick="CloseMenu"></div>
}
2022-02-14 01:51:52 +01:00
<div class="relative md:w-52">
2026-10-06 22:44:57 +02:00
<DButton Size="DSize.Sm" class="md:hidden" OnClick="ToggleNavMenu" aria-label="@Localizer["Menu"]" title="@Localizer["Menu"]"
aria-expanded="@(isMenuOpen ? "true" : "false")" aria-controls="nav-panel" data-nav-toggle>
<i class="ion-md-menu text-lg" aria-hidden="true"></i>
</DButton>
2026-10-06 22:44:57 +02:00
<div id="nav-panel" data-nav-panel
class="@(isMenuOpen ? "block" : "hidden") md:block absolute bg-base-100 md:relative top-full md:top-auto mt-2 w-[min(18rem,calc(100vw-1.5rem))] md:w-full max-h-[calc(100dvh-5rem)] md:max-h-none overflow-y-auto overscroll-contain rounded-lg neo-raised neo-sm md:shadow-none! z-50">
<div class="flex flex-col gap-3 p-3 md:p-4">
2022-02-14 01:51:52 +01:00
@if (CascadingState.IsSignedIn)
{
<DDropdown @bind-IsOpen="isPersonaMenuOpen" CloseOnContentClick="true" class="w-full rounded-xl bg-cover bg-no-repeat bg-right"
TriggerClass="flex w-full gap-3 p-3 md:p-4 neo-raised neo-xs rounded-xl text-left cursor-pointer"
ContentClass="mt-2 flex flex-col gap-2 p-2 rounded-box w-full" title="@Localizer["Switch persona"]"
style="@PersonaBackground">
<Trigger>
<img alt="" class="h-8 w-8 md:h-12 md:w-12 flex-none object-cover rounded-full neo-raised neo-2xs"
src="@(CascadingState.Persona?.Avatar ?? "imgs/icon-192.png")" />
<span class="flex flex-col w-full min-w-0 gap-1">
<b class="truncate" title="@CascadingState.Persona?.Name">@(CascadingState.Persona?.Name ?? Localizer["No persona"])</b>
<span class="truncate text-xs opacity-70">@(CascadingState.Persona is { } persona ? "@" + persona.Acct : null)</span>
</span>
<i class="ion-md-swap self-center opacity-60"></i>
</Trigger>
<ChildContent>
@foreach (var persona in CascadingState.Personas)
{
<DButton Size="DSize.Sm" class="justify-start whitespace-nowrap" Active="persona.Id == CascadingState.PersonaId"
Disabled="IsDefaultDisabled || persona.Id == CascadingState.PersonaId" OnClick="() => SwitchPersona(persona.Id)">
<img alt="" class="h-5 w-5 object-cover rounded-full" src="@(persona.PictureURL ?? "imgs/icon-192.png")" />
<span class="truncate">@(persona.Name ?? persona.UserName)</span>
</DButton>
}
</ChildContent>
</DDropdown>
}
2022-02-14 01:51:52 +01:00
<NavLink ActiveClass="btn-active" class="btn justify-start" href="/" Match="NavLinkMatch.All">
<i class="ion-md-home"></i>
2022-12-15 20:03:40 +01:00
<span>@Localizer["Home"]</span>
2022-02-14 01:51:52 +01:00
</NavLink>
@if (CascadingState.IsSignedIn)
{
<NavLink ActiveClass="btn-active" class="btn justify-start" href="search">
<i class="ion-md-search"></i>
<span>@Localizer["Search"]</span>
</NavLink>
<NavLink ActiveClass="btn-active" class="btn justify-start" href="explore">
<i class="ion-md-compass"></i>
<span>@Localizer["Explore"]</span>
</NavLink>
<NavLink ActiveClass="btn-active" class="btn justify-start" href="lists">
<i class="ion-md-list"></i>
<span>@Localizer["Lists"]</span>
</NavLink>
<NavLink ActiveClass="btn-active" class="btn justify-start" href="groups">
<i class="ion-md-people"></i>
<span>@Localizer["Groups"]</span>
</NavLink>
@if (CascadingState.PersonaId is { } personaId)
{
<NavLink ActiveClass="btn-active" class="btn justify-start" href="@($"account/{Uri.EscapeDataString(personaId)}")">
<i class="ion-md-person"></i>
<span>@Localizer["My profile"]</span>
</NavLink>
}
}
<NavLink ActiveClass="btn-active" class="btn justify-start" href="settings">
<i class="ion-md-settings"></i>
2022-12-15 20:03:40 +01:00
<span>@Localizer["Settings"]</span>
2022-02-14 01:51:52 +01:00
</NavLink>
<AuthorizeView Policy="@Policies.IsModerator">
<NavLink ActiveClass="btn-active" class="btn justify-start" href="administration">
<i class="ion-md-switch text-error"></i>
<span>@Localizer["Administration"]</span>
</NavLink>
</AuthorizeView>
@if (CascadingState.IsSignedIn)
{
<NavLink ActiveClass="btn-active" class="btn justify-start" href="logout">
<i class="ion-md-log-out text-error"></i>
<span>@Localizer["Logout"]</span>
</NavLink>
}
else
{
<NavLink ActiveClass="btn-active" class="btn justify-start" href="login">
<i class="ion-md-log-in text-success"></i>
<span>@Localizer["Login"]</span>
</NavLink>
}
2022-02-14 01:51:52 +01:00
<div class="flex items-center gap-3">
<DButton Size="DSize.Sm" Shape="DButtonShape.Circle" class="flex-none" OnClick="ResetToOriginalColour"
title="@Localizer["Reset"]">
<i class="ion-md-refresh"></i>
</DButton>
<DRange TValue="short" class="neo-hue-track w-full" Size="DSize.Xs" Min="0" Max="359" Step="1"
Value="ThemeIndexColour" ValueChanged="UpdateThemeColour" ValueExpression="() => ThemeIndexColour" />
2022-02-14 01:51:52 +01:00
</div>
<div class="flex justify-end">
<DToggle Value="IsDark" ValueChanged="UpdateThemeDarkMode" ValueExpression="() => IsDark" class="touch-target"
aria-label="@Localizer["Dark mode"]" title="@Localizer["Dark mode"]">
<UncheckedIcon>
<i class="ion-md-sunny"></i>
</UncheckedIcon>
<CheckedIcon>
<i class="ion-md-moon"></i>
</CheckedIcon>
</DToggle>
</div>
2022-02-14 01:51:52 +01:00
</div>
</div>
</div>
2026-10-06 22:44:57 +02:00
<a class="md:hidden grid place-items-center h-11 w-11" href="" aria-label="@Localizer["Home"]" title="@Localizer["Home"]">
2022-02-14 01:51:52 +01:00
<img alt="" class="h-[30px]" src="imgs/icon-512.png">
2026-10-06 22:44:57 +02:00
</a>
2022-02-14 01:51:52 +01:00
2026-10-06 22:44:57 +02:00
<a class="md:hidden grid place-items-center h-11 w-11" href="settings" aria-label="@Localizer["Settings"]" title="@Localizer["Settings"]">
<img alt="@CascadingState.Persona?.Name" class="object-cover w-[30px] h-[30px] rounded-full neo-raised neo-2xs"
src="@(CascadingState.Persona?.Avatar ?? "imgs/icon-192.png")">
</a>
2022-02-14 01:51:52 +01:00
</nav>
2022-02-02 20:26:14 +01:00
@code {
2026-10-06 22:44:57 +02:00
bool isMenuOpen;
bool isPersonaMenuOpen;
string PersonaBackground => CascadingState.Persona?.Header is { Length: > 0 } header && !header.Contains("/missing-")
? $"background-image:linear-gradient(to left, var(--color-base-100) 55%, transparent), url('{header}');"
: null;
2022-02-14 01:51:52 +01:00
// The toggle and the slider work on the variant that is showing; with system theming that is the OS's choice.
bool IsDark => CascadingState.Status.IsDarkTheme();
short ThemeIndexColour => IsDark ? CascadingState.PublicCacheData.PageSettings.DarkThemeIndexColour : CascadingState.PublicCacheData.PageSettings.LightThemeIndexColour;
protected override void OnInitialized()
2022-02-14 01:51:52 +01:00
{
IsLoading = false;
2026-10-06 22:44:57 +02:00
Navigation.LocationChanged += OnLocationChanged;
}
void OnLocationChanged(object sender, LocationChangedEventArgs e)
{
if (!isMenuOpen)
return;
isMenuOpen = false;
InvokeAsync(StateHasChanged);
}
void CloseMenu() => isMenuOpen = false;
void OnKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Escape")
isMenuOpen = false;
}
public override async ValueTask DisposeAsync()
{
Navigation.LocationChanged -= OnLocationChanged;
await base.DisposeAsync();
2022-02-14 01:51:52 +01:00
}
async Task SwitchPersona(string personaId)
{
2026-10-06 22:44:57 +02:00
isMenuOpen = false;
var result = await CascadingState.SwitchPersona(personaId);
if (result.IsInvalid)
Toast.AddResponseError(result);
}
2026-10-06 22:44:57 +02:00
private void ToggleNavMenu() => isMenuOpen = !isMenuOpen;
2022-02-14 01:51:52 +01:00
protected async Task ResetToOriginalColour()
{
var isDark = IsDark;
await CascadingState.UpdatePageSettings(settings => settings.SetHue(isDark,
isDark ? PageSettings.DefaultDarkThemeIndexColour : PageSettings.DefaultLightThemeIndexColour));
2022-02-14 01:51:52 +01:00
// if (AuthData?.User != null)
// {
// AuthData.User.UserSettings.ThemeIndexColour = ThemeIndexColour;
// await Storage.SetItemAsync(nameof(AuthData), AuthData);
// }
}
// Picking a hue also leaves the grey themes, which otherwise ignore it.
protected async Task UpdateThemeColour(short indexColour)
2022-02-14 01:51:52 +01:00
{
var isDark = IsDark;
await CascadingState.UpdatePageSettings(settings => settings.SetHue(isDark, indexColour));
2022-02-14 01:51:52 +01:00
// if (AuthData?.User != null)
// {
// AuthData.User.UserSettings.ThemeIndexColour = ThemeIndexColour;
// await Storage.SetItemAsync(nameof(AuthData), AuthData);
// }
}
// Choosing light or dark here leaves system theming; Settings can turn it back on.
2022-02-14 01:51:52 +01:00
protected async Task UpdateThemeDarkMode(bool isDarkMode)
{
await CascadingState.UpdatePageSettings(settings => settings.SetDarkMode(isDarkMode));
2022-02-14 01:51:52 +01:00
// if (AuthData?.User != null)
// {
// AuthData.User.UserSettings.ThemeIsDarkMode = ThemeIsDarkMode;
// await Storage.SetItemAsync(nameof(AuthData), AuthData);
// }
}
}