refactor(admin): 文章搜索框改用 FormInput 公共组件
Some checks are pending
CI / check (push) Waiting to run
CI / build (push) Blocked by required conditions

- AllPostsList 的手写 <input> 替换为 forms::FormInput +
  INPUT_INLINE_CLASS,与 asset_picker / assets 的搜索栏先例统一
  (修正圆角 rounded-lg→rounded-2xl,补 min-w-0 / transition-colors)。
- AGENTS.md Code Conventions 新增第 17 条:编写 rsx 前必须先查
  src/components/{ui,forms}.rs 的现有组件/样式常量,禁止第二套约定。
This commit is contained in:
xfy 2026-07-28 17:40:54 +08:00
parent b4e15ecbff
commit 6c515dd894
2 changed files with 8 additions and 6 deletions

View File

@ -154,6 +154,7 @@ make docker-multiarch IMAGE=ghcr.io/owner/yggdrasil:latest # amd64+arm64, push
- **`unreachable!()` is permitted ONLY in `#[cfg(not(feature = "server"))]` stubs** of server functions — these branches are compiled out of the real server build and exist solely to satisfy the WASM target's type checker. - **`unreachable!()` is permitted ONLY in `#[cfg(not(feature = "server"))]` stubs** of server functions — these branches are compiled out of the real server build and exist solely to satisfy the WASM target's type checker.
- **Build-tool binaries** (`src/bin/*`) and **`#[cfg(test)]` modules** are exempt — `unwrap`/`expect`/`panic` are idiomatic in tests and one-shot codegen tools. - **Build-tool binaries** (`src/bin/*`) and **`#[cfg(test)]` modules** are exempt — `unwrap`/`expect`/`panic` are idiomatic in tests and one-shot codegen tools.
- **If clippy's `unwrap_used` / `expect_used` lints are later wired in** (`[lints]` table in `Cargo.toml`), the exemptions above are the intended `allow` set; do not relax them further without a documented invariant. - **If clippy's `unwrap_used` / `expect_used` lints are later wired in** (`[lints]` table in `Cargo.toml`), the exemptions above are the intended `allow` set; do not relax them further without a documented invariant.
17. **Reuse shared UI components — never hand-roll a duplicate.** Before writing any `rsx!` element with an inline Tailwind class string, scan `src/components/` for an existing component or style constant and reuse it. Two modules own this surface: `ui.rs` (display atoms — `Pagination`, `StatusBadge`, `Tooltip`, `Popover`, `FilterTabs`, `LoadingButton`; plus class constants `BTN_PRIMARY`/`BTN_PRIMARY_SM`/`BTN_OUTLINE`/`BTN_DANGER_OUTLINE`/`BTN_SECONDARY`/`BTN_TEXT_*`/`BTN_SOLID_*`/`BTN_CLOSE_ICON`/`BTN_ICON`/`ADMIN_CARD_CLASS`/`ADMIN_TABLE_CLASS`/`ADMIN_ROW_HOVER`/`BADGE_BASE`/`CHECKBOX_CLASS`/`SPINNER_SVG`) and `forms.rs` (form controls — `FormInput`, `FormSelect`, `FormLabel`, `AlertBox`; plus class constants `INPUT_CLASS`/`INPUT_INLINE_CLASS`/`BUTTON_PRIMARY_CLASS`/`FORM_SELECT_COMPACT_CLASS`). Use `FormInput` with the right `class` prop (`INPUT_CLASS` = full-width form field, `INPUT_INLINE_CLASS` = flank a button / fill remaining width) instead of a bespoke `<input class="...">`; use `FormSelect` instead of a native `<select>` (OS-rendered popups can't follow the theme). For composite patterns (search bars, filter toolbars, action rows), grep for an existing instance and mirror its structure — e.g. the `FormInput { r#type: "search", class: INPUT_INLINE_CLASS, ... }` flanked by `BTN_PRIMARY`/`BTN_OUTLINE` search bar already lives in `asset_picker.rs` and `assets.rs`. A second convention beside an existing one is PROHIBITED — if the existing component is missing a feature, extend it rather than spawning a parallel one.
## MCP Server ## MCP Server

View File

@ -19,6 +19,7 @@ use crate::api::posts::{
CreatePostResponse, PostStatsResponse, RebuildResult, CreatePostResponse, PostStatsResponse, RebuildResult,
}; };
use crate::components::empty_state::{EmptyState, EmptyStateAction}; use crate::components::empty_state::{EmptyState, EmptyStateAction};
use crate::components::forms::{FormInput, INPUT_INLINE_CLASS};
use crate::components::skeletons::delayed_skeleton::DelayedSkeleton; use crate::components::skeletons::delayed_skeleton::DelayedSkeleton;
use crate::components::skeletons::posts_skeleton::PostsSkeleton; use crate::components::skeletons::posts_skeleton::PostsSkeleton;
use crate::components::ui::{ use crate::components::ui::{
@ -190,13 +191,13 @@ fn AllPostsList() -> Element {
rsx! { rsx! {
// 搜索条:按标题过滤文章(仅管理后台用,覆盖草稿)。 // 搜索条:按标题过滤文章(仅管理后台用,覆盖草稿)。
div { class: "flex gap-2 mb-4", div { class: "flex gap-2 mb-4",
input { FormInput {
class: "flex-1 px-4 py-2 border border-paper-border rounded-lg bg-paper-entry text-paper-primary placeholder:text-paper-tertiary focus:outline-none focus:border-paper-accent focus:ring-1 focus:ring-paper-accent/30", r#type: "search",
r#type: "text",
placeholder: "搜索文章标题...", placeholder: "搜索文章标题...",
value: "{search_input()}", value: search_input(),
oninput: move |e| search_input.set(e.value()), class: INPUT_INLINE_CLASS,
onkeydown: move |e| { oninput: move |v: String| search_input.set(v),
onkeydown: move |e: KeyboardEvent| {
if e.key() == Key::Enter { if e.key() == Key::Enter {
submit_search(); submit_search();
} }