feat: Implement mobile search functionality and a sidebar close button, along with responsive styling adjustments for mobile layouts.

This commit is contained in:
Ho Ngoc Hai
2026-02-12 23:59:09 +07:00
parent d4f0e1ca03
commit f5123f3871
2 changed files with 118 additions and 10 deletions

View File

@@ -19,6 +19,9 @@
<div class="mkt-sidebar__logo">
<div class="mkt-sidebar__logo-icon">M</div>
<span class="mkt-sidebar__logo-text">tMARKETING</span>
<button class="mkt-sidebar-close" @onclick="CloseSidebar" title="Đóng menu">
<i data-lucide="x"></i>
</button>
</div>
@* Navigation / Điều hướng *@
@@ -85,7 +88,23 @@
<button class="mkt-mobile-toggle" @onclick="ToggleSidebar">
<i data-lucide="menu"></i>
</button>
<span class="mkt-mobile-bar__title">tMARKETING</span>
@if (_searchOpen)
{
<div class="mkt-mobile-search" style="display:flex;">
<i data-lucide="search"></i>
<input type="text" placeholder="Tìm kiếm..." @bind="SearchQuery" />
</div>
<button class="mkt-mobile-search-btn" style="display:flex;" @onclick="ToggleSearch">
<i data-lucide="x"></i>
</button>
}
else
{
<span class="mkt-mobile-bar__title">tMARKETING</span>
<button class="mkt-mobile-search-btn" style="display:flex;" @onclick="ToggleSearch">
<i data-lucide="search"></i>
</button>
}
</div>
@Body
</main>
@@ -93,6 +112,8 @@
@code {
private bool _sidebarOpen = false;
private bool _searchOpen = false;
private string SearchQuery { get; set; } = string.Empty;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
@@ -103,6 +124,7 @@
private void ToggleSidebar() => _sidebarOpen = !_sidebarOpen;
private void CloseSidebar() => _sidebarOpen = false;
private void ToggleSearch() => _searchOpen = !_searchOpen;
private void Logout() => NavigationManager.NavigateTo("/login");
private MudTheme _theme = new()

View File

@@ -615,6 +615,70 @@
font-weight: 700;
letter-spacing: 1px;
color: var(--mkt-yellow-primary);
flex: 1;
}
/* ── Mobile search ── */
.mkt-mobile-search {
display: none;
position: relative;
flex: 1;
}
.mkt-mobile-search input {
width: 100%;
height: 32px;
padding: 0 12px 0 32px;
background: var(--mkt-bg-page);
border: 1px solid var(--mkt-border);
border-radius: 6px;
color: var(--mkt-text-primary);
font-size: 12px;
outline: none;
}
.mkt-mobile-search input:focus {
border-color: var(--mkt-yellow-primary);
}
.mkt-mobile-search i {
position: absolute;
left: 8px;
top: 50%;
transform: translateY(-50%);
width: 14px;
height: 14px;
color: var(--mkt-text-secondary);
pointer-events: none;
}
.mkt-mobile-search-btn {
display: none;
width: 32px;
height: 32px;
border-radius: 6px;
background: transparent;
border: 1px solid var(--mkt-border);
color: var(--mkt-text-secondary);
align-items: center;
justify-content: center;
cursor: pointer;
flex-shrink: 0;
}
/* ── Sidebar close button (mobile) ── */
.mkt-sidebar-close {
display: none;
width: 32px;
height: 32px;
border-radius: 6px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid var(--mkt-border);
color: var(--mkt-text-secondary);
align-items: center;
justify-content: center;
cursor: pointer;
margin-left: auto;
}
@media (max-width: 768px) {
@@ -625,7 +689,8 @@
left: -260px;
top: 0;
bottom: 0;
z-index: 1000;
width: 260px;
z-index: 1001;
transition: left 0.3s ease;
}
@@ -637,8 +702,13 @@
display: block;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
background: rgba(0, 0, 0, 0.6);
z-index: 1000;
cursor: pointer;
}
.mkt-sidebar-close {
display: flex;
}
.mkt-mobile-toggle {
@@ -649,35 +719,51 @@
display: flex;
}
/* ── Topbar — integrate hamburger ── */
.mkt-mobile-search-btn {
display: flex;
}
/* ── Topbar — compact ── */
.mkt-topbar {
padding: 12px 16px;
gap: 8px;
padding: 10px 16px;
gap: 6px;
}
.mkt-topbar__left {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.mkt-topbar__title {
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mkt-topbar__subtitle {
font-size: 10px;
display: none;
}
.mkt-topbar__right {
flex-wrap: wrap;
gap: 6px;
flex-wrap: nowrap;
gap: 4px;
flex-shrink: 0;
}
.mkt-topbar__right .mkt-search {
display: none;
}
.mkt-topbar__right .mkt-btn-ghost,
.mkt-topbar__right .mkt-btn-primary {
padding: 6px 8px;
font-size: 11px;
gap: 4px;
}
.mkt-topbar__right .mkt-btn-ghost span,
.mkt-topbar__right .mkt-btn-primary span {
display: none;