refactor(ui): 新增 BTN_PRIMARY/BTN_OUTLINE 等按钮令牌与 LoadingButton 组件

消除 admin 页面按钮样式散落问题:
- BTN_PRIMARY / BTN_PRIMARY_SM:主题绿实心胶囊主操作按钮
- BTN_OUTLINE:描边次要按钮(relative 以承载 spinner 叠加)
- BTN_DANGER_OUTLINE:红色描边危险按钮(清空回收站)
- BTN_CLOSE_ICON / BTN_ICON:× 关闭与方形步进图标按钮
- LoadingButton 组件:封装主题绿三态(loading spinner 叠加 / disabled 灰化 / 正常),
  统一全站 loading 态为 posts.rs 的 spinner 叠加模式(按钮宽度不变,避免布局抖动)
This commit is contained in:
xfy 2026-07-06 00:17:09 +08:00
parent 3c10f72444
commit 545651d304

View File

@ -69,6 +69,36 @@ pub const BTN_TEXT_ACCENT: &str =
pub const BTN_SECONDARY: &str = pub const BTN_SECONDARY: &str =
"px-6 py-2.5 rounded-full text-sm font-medium text-center text-[var(--color-paper-secondary)] bg-[var(--color-paper-entry)] hover:bg-[var(--color-paper-border)] hover:text-[var(--color-paper-primary)] transition-all cursor-pointer"; "px-6 py-2.5 rounded-full text-sm font-medium text-center text-[var(--color-paper-secondary)] bg-[var(--color-paper-entry)] hover:bg-[var(--color-paper-border)] hover:text-[var(--color-paper-primary)] transition-all cursor-pointer";
// --- 主操作按钮(主题绿实心胶囊,全站统一 CTA ---
/// 主操作按钮:主题绿实心胶囊(用于 `<Link>`、无 loading 态的静态按钮)。
pub const BTN_PRIMARY: &str =
"inline-flex items-center justify-center px-5 py-2 text-sm font-medium text-[var(--color-paper-theme)] bg-[var(--color-paper-accent)] rounded-full shadow-sm hover:brightness-110 active:scale-[0.98] transition-all cursor-pointer";
/// 小号主操作按钮:工具栏场景(刷新 / 导出 / 创建备份)。
pub const BTN_PRIMARY_SM: &str =
"inline-flex items-center justify-center px-4 py-1.5 text-sm font-medium text-[var(--color-paper-theme)] bg-[var(--color-paper-accent)] rounded-full hover:brightness-110 active:scale-[0.98] transition-all cursor-pointer";
// --- 描边按钮 ---
/// 描边次要按钮posts 重建、system 刷新列表):`relative` 以承载 spinner 叠加层。
pub const BTN_OUTLINE: &str =
"relative px-4 py-2 rounded-full text-sm font-medium text-paper-primary border border-paper-border hover:border-paper-accent hover:text-paper-accent transition-all cursor-pointer";
/// 红色描边危险按钮trash 清空回收站)。
pub const BTN_DANGER_OUTLINE: &str =
"px-4 py-2 text-sm font-medium text-red-600 dark:text-red-400 border border-red-300 dark:border-red-900/50 rounded-full hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors cursor-pointer";
// --- 图标按钮 ---
/// 关闭图标按钮(× 关闭提示条)。
pub const BTN_CLOSE_ICON: &str =
"shrink-0 text-red-400 hover:text-red-600 cursor-pointer text-lg leading-none";
/// 方形图标按钮trash 步进 /+)。
pub const BTN_ICON: &str =
"w-9 h-9 flex items-center justify-center text-sm text-paper-secondary hover:text-paper-primary hover:bg-paper-theme cursor-pointer transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-paper-accent/40";
// =========================================================================== // ===========================================================================
// 组件 // 组件
// =========================================================================== // ===========================================================================
@ -314,3 +344,63 @@ pub fn FilterTabs(
} }
} }
} }
/// 主操作按钮,内置 loading spinner 叠加(统一三态)。
///
/// 用于全站所有主题绿 CTA执行 / 刷新 / 创建备份 / 发布 / 保存设置)。
/// 三态:
/// - `loading=true`:主题绿底 + 文字隐藏(`opacity-0`+ spinner 绝对居中
/// (按钮宽度不变,避免加载时布局抖动)
/// - `disabled=true`(且未 loading灰色底`bg-paper-tertiary`+
/// `cursor-not-allowed`
/// - 正常:主题绿底 + `hover:brightness-110` + `active:scale-[0.98]`
///
/// Props
/// - `label`正常态显示的文案loading 时隐藏,由 spinner 占位)
/// - `loading`:是否处于加载态
/// - `disabled`是否禁用loading 优先级更高)
/// - `variant``"primary"`(默认,`px-5 py-2`)或 `"sm"``px-4 py-1.5`
/// - `onclick`:点击回调
#[component]
pub fn LoadingButton(
label: String,
loading: bool,
#[props(default = false)] disabled: bool,
#[props(default = "primary")] variant: &'static str,
onclick: EventHandler<()>,
) -> Element {
// 尺寸变体sm 用于工具栏(刷新/导出primary 用于主 CTA执行/发布/保存)。
let size = if variant == "sm" {
"px-4 py-1.5"
} else {
"px-5 py-2 shadow-sm"
};
// 三态背景loading 与正常都是主题绿保持视觉连续disabled 灰化。
let (bg, cursor) = if disabled && !loading {
(
"bg-[var(--color-paper-tertiary)] text-[var(--color-paper-secondary)]",
"cursor-not-allowed",
)
} else {
(
"text-[var(--color-paper-theme)] bg-[var(--color-paper-accent)] hover:brightness-110 active:scale-[0.98]",
"cursor-pointer",
)
};
rsx! {
button {
class: "relative inline-flex items-center justify-center {size} {bg} {cursor} rounded-full text-sm font-medium transition-all",
disabled: loading || disabled,
onclick: move |_| onclick.call(()),
span { class: if loading { "opacity-0" } else { "" }, "{label}" }
if loading {
span {
class: "absolute inset-0 flex items-center justify-center",
dangerous_inner_html: SPINNER_SVG,
}
}
}
}
}