xfy 3831d1af01
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
feat(header): 搜索入口改为图标按钮
将前台 Header 文本导航中的「搜索」项移除,改在 Header 右侧(主题切换按钮
左边)以放大镜图标 Link 呈现。样式与 ThemeToggle 对齐(圆形 padding、
currentColor 图标),适配明暗主题。

- nav.rs: 导航项列表去掉 Search,更新文档注释
- header.rs: 新增 SearchIconLink 组件(SVG 内联,fill=currentColor)
- frontend_layout.rs: right_content 在 ThemeToggle 前插入 SearchIconLink
2026-07-17 10:28:39 +08:00

200 lines
7.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 顶部导航栏组件
//!
//! 提供站点 Logo、响应式导航菜单项与右侧自定义内容区
//! 支持前台布局与后台布局复用,并包含小屏幕下的汉堡菜单。
use dioxus::prelude::*;
use dioxus::router::components::Link;
use crate::router::Route;
/// 导航项配置,用于描述 Header 中的一个链接。
///
/// 字段:
/// - `route`:目标路由
/// - `label`:显示文本
/// - `is_active`:当前是否处于激活状态
#[derive(Clone, PartialEq)]
pub struct NavItemConfig {
/// 目标路由。
pub route: Route,
/// 显示文本。
pub label: &'static str,
/// 当前是否处于激活状态。
pub is_active: bool,
}
/// 顶部导航栏组件。
///
/// Props
/// - `nav_items`:导航项列表
/// - `right_content`:右侧自定义内容(如主题切换、登出按钮)
/// - `max_width`:内部导航的宽度类,需与正文 `max-w-*` 一致以保证左右边缘对齐。
/// 默认 `max-w-3xl`(前台阅读宽度);后台传 `max-w-5xl` 与之同宽。
#[component]
pub fn Header(
nav_items: Vec<NavItemConfig>,
right_content: Element,
#[props(default = "max-w-3xl")] max_width: &'static str,
) -> Element {
let mut mobile_open = use_signal(|| false);
let menu_id = use_memo(|| "mobile-nav-menu".to_string());
rsx! {
header { class: "sticky top-0 z-40 w-full bg-[var(--color-paper-theme)]/70 backdrop-blur-md transition-all duration-300",
nav { class: "{max_width} mx-auto px-6 h-16 flex items-center justify-between",
Link {
class: "text-2xl font-extrabold tracking-tight text-[var(--color-paper-primary)] hover:text-[var(--color-paper-accent)] transition-colors duration-200",
to: Route::Home {},
"Yggdrasil"
}
div { class: "flex items-center gap-2",
// 桌面端导航
ul { class: "hidden md:flex items-center gap-1",
for item in nav_items.iter().cloned() {
NavItem {
key: "{item.label}",
route: item.route,
label: item.label,
is_active: item.is_active,
}
}
}
{right_content}
// 移动端汉堡菜单按钮
button {
class: "md:hidden p-2 rounded-lg text-paper-secondary hover:text-paper-primary hover:bg-paper-entry transition-colors",
r#type: "button",
aria_label: "切换导航菜单",
aria_expanded: "{mobile_open()}",
aria_controls: "{menu_id()}",
onclick: move |_| mobile_open.set(!mobile_open()),
if mobile_open() {
// 关闭图标X
svg {
class: "w-6 h-6",
fill: "none",
stroke: "currentColor",
view_box: "0 0 24 24",
path {
stroke_linecap: "round",
stroke_linejoin: "round",
stroke_width: "2",
d: "M6 18L18 6M6 6l12 12",
}
}
} else {
// 汉堡图标
svg {
class: "w-6 h-6",
fill: "none",
stroke: "currentColor",
view_box: "0 0 24 24",
path {
stroke_linecap: "round",
stroke_linejoin: "round",
stroke_width: "2",
d: "M4 6h16M4 12h16M4 18h16",
}
}
}
}
}
}
// 移动端导航面板
if mobile_open() {
div {
id: "{menu_id()}",
class: "md:hidden border-t border-paper-border bg-paper-theme/95 backdrop-blur-sm",
ul { class: "py-2 px-6 space-y-1",
for item in nav_items.iter().cloned() {
li { key: "{item.label}",
MobileNavItem {
route: item.route,
label: item.label,
is_active: item.is_active,
on_navigate: move |_| mobile_open.set(false),
}
}
}
}
}
}
}
}
}
/// 单个桌面导航项组件,根据 `is_active` 切换高亮样式。
#[component]
fn NavItem(route: Route, label: &'static str, is_active: bool) -> Element {
let base_class = "px-3 py-1 text-base rounded-lg transition-all duration-200";
let class_str = if is_active {
format!("{} font-medium text-paper-accent underline underline-offset-[0.3rem] decoration-2 decoration-paper-accent", base_class)
} else {
format!(
"{} text-paper-secondary hover:text-paper-primary",
base_class
)
};
rsx! {
li {
Link { class: "{class_str}", to: route, "{label}" }
}
}
}
/// 单个移动端导航项组件,点击后关闭菜单。
#[component]
fn MobileNavItem(
route: Route,
label: &'static str,
is_active: bool,
on_navigate: EventHandler<()>,
) -> Element {
let class_str = if is_active {
"block w-full px-3 py-2 text-base font-medium text-paper-accent rounded-lg bg-paper-entry"
} else {
"block w-full px-3 py-2 text-base text-paper-secondary hover:text-paper-primary hover:bg-paper-entry rounded-lg transition-colors"
};
rsx! {
Link {
class: "{class_str}",
to: route,
onclick: move |_| on_navigate.call(()),
"{label}"
}
}
}
/// 搜索图标链接:置于 Header 右侧(主题切换左边),点击跳转搜索页。
///
/// 样式与 `ThemeToggle` 对齐(圆形 padding + currentColor 图标),保持右侧
/// 图标组视觉一致。SVG 来自 `public/icons/search_24dp_E3E3E3_FILL0_wght400_GRAD0_opsz24.svg`
/// 改用 `fill: "currentColor"` 以适配明暗主题。
#[component]
pub fn SearchIconLink() -> Element {
rsx! {
Link {
class: "p-2 rounded-full text-paper-secondary hover:text-paper-accent transition-colors duration-200",
to: Route::Search {},
aria_label: "搜索",
title: "搜索",
svg {
xmlns: "http://www.w3.org/2000/svg",
height: "24px",
view_box: "0 -960 960 960",
width: "24px",
fill: "currentColor",
path {
d: "M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z",
}
}
}
}
}