Move page back control into the topbar next to the menu.
build-and-deploy / deploy (push) Successful in 4m6s
build-and-deploy / deploy (push) Successful in 4m6s
Keep the chevron back button on the same sticky header row (left) as the menu icon (right). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,14 +1,20 @@
|
||||
@inherits LayoutComponentBase
|
||||
@implements IDisposable
|
||||
@inject AuthStateService Auth
|
||||
@inject TopbarNavService TopbarNav
|
||||
|
||||
<div class="app-shell @(sidebarOpen ? "sidebar-open" : null) @(hasDock ? "has-player-dock" : null)">
|
||||
<header class="topbar">
|
||||
<div class="topbar-start">
|
||||
@if (TopbarNav.ShowBack)
|
||||
{
|
||||
<button type="button" class="back-btn" @onclick="TopbarNav.GoBackAsync" aria-label="بازگشت">‹</button>
|
||||
}
|
||||
</div>
|
||||
<a class="brand" href="/">تفسیر قرآن</a>
|
||||
<button class="icon-btn" type="button" @onclick="ToggleSidebar" aria-label="منو">
|
||||
<span class="burger"></span>
|
||||
</button>
|
||||
<a class="brand" href="/">تفسیر قرآن</a>
|
||||
<div class="topbar-spacer"></div>
|
||||
</header>
|
||||
|
||||
<div class="backdrop" @onclick="CloseSidebar"></div>
|
||||
@@ -33,6 +39,7 @@
|
||||
{
|
||||
Auth.Changed += OnAuthChanged;
|
||||
Session.Changed += OnSessionChanged;
|
||||
TopbarNav.Changed += OnTopbarChanged;
|
||||
hasDock = Session.HasTrack;
|
||||
}
|
||||
|
||||
@@ -46,6 +53,7 @@
|
||||
}
|
||||
|
||||
private void OnAuthChanged() => InvokeAsync(StateHasChanged);
|
||||
private void OnTopbarChanged() => InvokeAsync(StateHasChanged);
|
||||
private void OnSessionChanged()
|
||||
{
|
||||
hasDock = Session.HasTrack;
|
||||
@@ -58,5 +66,6 @@
|
||||
{
|
||||
Auth.Changed -= OnAuthChanged;
|
||||
Session.Changed -= OnSessionChanged;
|
||||
TopbarNav.Changed -= OnTopbarChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
@page "/tafsir/{Slug}"
|
||||
@page "/tafsir/{Slug}/surah/{SurahNumber:int}"
|
||||
@page "/tafsir/{Slug}/surah/{SurahNumber:int}/ayah/{AyahNumber:int}"
|
||||
@implements IDisposable
|
||||
@inject TafsirApiClient Api
|
||||
@inject IDeviceIdStore DeviceStore
|
||||
@inject NavigationManager Nav
|
||||
@inject PlayerSessionService Player
|
||||
@inject TopbarNavService TopbarNav
|
||||
|
||||
<section class="tafsir-page">
|
||||
<header class="page-header page-header-row">
|
||||
<header class="page-header">
|
||||
@if (loadingMeta)
|
||||
{
|
||||
<SkeletonBlock Variant="lines" Lines="1" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="back-btn" @onclick="GoBack" aria-label="بازگشت">‹</button>
|
||||
<h1 class="page-title-inline">
|
||||
<span>@title</span>
|
||||
@if (!string.IsNullOrWhiteSpace(author))
|
||||
@@ -155,6 +156,8 @@
|
||||
private string deviceId = "";
|
||||
private bool ready;
|
||||
|
||||
protected override void OnInitialized() => TopbarNav.SetBack(GoBack);
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (!firstRender) return;
|
||||
@@ -163,6 +166,8 @@
|
||||
await LoadRouteAsync();
|
||||
}
|
||||
|
||||
public void Dispose() => TopbarNav.ClearBack();
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (!ready) return;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace TafsirQuran.UI.Services;
|
||||
|
||||
/// <summary>Lets pages put a back control in the shared topbar (aligned with the menu icon).</summary>
|
||||
public sealed class TopbarNavService
|
||||
{
|
||||
private Func<Task>? _back;
|
||||
|
||||
public event Action? Changed;
|
||||
|
||||
public bool ShowBack => _back is not null;
|
||||
|
||||
public void SetBack(Action back) => SetBack(() =>
|
||||
{
|
||||
back();
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
public void SetBack(Func<Task> back)
|
||||
{
|
||||
_back = back;
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
public void ClearBack()
|
||||
{
|
||||
if (_back is null) return;
|
||||
_back = null;
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
public async Task GoBackAsync()
|
||||
{
|
||||
if (_back is null) return;
|
||||
await _back();
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ public static class UiServiceCollectionExtensions
|
||||
services.AddScoped<IAuthTokenStore, BrowserAuthTokenStore>();
|
||||
services.AddScoped<AuthStateService>();
|
||||
services.AddScoped<PlayerSessionService>();
|
||||
services.AddScoped<TopbarNavService>();
|
||||
services.AddScoped(sp =>
|
||||
{
|
||||
var http = new HttpClient { BaseAddress = new Uri(normalized) };
|
||||
|
||||
@@ -43,26 +43,39 @@ button, input { font: inherit; }
|
||||
|
||||
.topbar {
|
||||
position: sticky; top: 0; z-index: 20;
|
||||
display: flex; align-items: center; gap: 0.75rem;
|
||||
display: grid;
|
||||
grid-template-columns: 44px 1fr 44px;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.85rem 1rem;
|
||||
backdrop-filter: blur(14px);
|
||||
background: rgba(243, 239, 230, 0.82);
|
||||
border-bottom: 1px solid var(--line);
|
||||
direction: ltr; /* back left, menu right — same row */
|
||||
}
|
||||
|
||||
.topbar-start {
|
||||
width: 44px; height: 44px;
|
||||
display: grid; place-items: center;
|
||||
}
|
||||
|
||||
.brand {
|
||||
font-weight: 700;
|
||||
font-size: 1.15rem;
|
||||
letter-spacing: -0.02em;
|
||||
text-align: center;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
width: 42px; height: 42px;
|
||||
border: 1px solid var(--line);
|
||||
width: 44px; height: 44px;
|
||||
border: 1px solid rgba(28, 43, 36, 0.14);
|
||||
border-radius: 12px;
|
||||
background: var(--surface);
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 8px rgba(28, 43, 36, 0.08);
|
||||
display: grid; place-items: center;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.burger, .burger::before, .burger::after {
|
||||
@@ -281,14 +294,6 @@ button, input { font: inherit; }
|
||||
}
|
||||
.chip:disabled { opacity: 0.45; cursor: default; }
|
||||
|
||||
.page-header-row {
|
||||
display: grid;
|
||||
grid-template-columns: 44px 1fr;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
direction: ltr; /* back button physical left, like TOC */
|
||||
}
|
||||
.page-title-inline {
|
||||
margin: 0; font-size: clamp(1.15rem, 3.5vw, 1.45rem); font-weight: 700;
|
||||
display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.35rem;
|
||||
|
||||
Reference in New Issue
Block a user