import cx from 'clsx'; import * as React from 'react'; import { Search } from '~/components/shared/FeatherIcons'; import s from './PageHeader.module.scss'; /** * 页面顶栏的一套零件,代理 / 连接 / 规则 / 日志四个页面共用, * 样式和断点行为全在 PageHeader.module.scss 里。 * * 每个零件都接 className,页面自己的模块可以往上叠特有的东西 * (比如连接页窄屏下要重排 order)。 */ export function PageHeader({ children, className, }: { children: React.ReactNode; className?: string; }) { return
{children}
; } export function HeaderTitle({ children }: { children: React.ReactNode }) { return

{children}

; } /** 窄屏下强制换行,让它后面的东西独占下一行 */ export function HeaderRowBreak({ className }: { className?: string }) { return ; } export function HeaderTabs({ label, children, className, }: { label: string; children: React.ReactNode; className?: string; }) { return (
{children}
); } /** 计数超过三位就收成 999+,否则标签宽度会跟着数字跳 */ function formatQty(n: number) { return n < 1000 ? String(n) : '999+'; } export function HeaderTab({ active, label, count, onClick, }: { active: boolean; label: string; /** 省略则不显示计数 */ count?: number; onClick: () => void; }) { return ( ); } /** * 搜索框。传 value/onChange 就是受控输入,传 children 则由调用方 * 自己塞输入组件(比如挂在 jotai atom 上的 TextFilter)。 */ export function HeaderSearch({ placeholder, value, onChange, children, className, }: { placeholder?: string; value?: string; onChange?: (value: string) => void; children?: React.ReactNode; className?: string; }) { return (
{children ?? ( onChange?.(e.target.value)} /> )}
); } export function HeaderActions({ children, className, }: { children: React.ReactNode; className?: string; }) { return
{children}
; } type ButtonVariant = 'ghost' | 'primary' | 'danger' | 'paused'; const variantClass: Record = { ghost: s.btnGhost, primary: s.btnPrimary, danger: s.btnDanger, paused: s.btnPaused, }; export function HeaderButton({ variant = 'ghost', icon, label, /** 悬停提示,省略则用 label。展开说明按钮作用时才需要单独给 */ title, /** * 文字收起的宽度门槛。'sm' 只在窄屏收(默认,适合主要操作), * 'md' 中等宽度就收(次要操作,给主要操作腾地方) */ hideLabelAt = 'sm', busy, disabled, onClick, }: { variant?: ButtonVariant; icon: React.ReactNode; label: string; title?: string; hideLabelAt?: 'sm' | 'md'; busy?: boolean; disabled?: boolean; onClick: () => void; }) { return ( ); } export function HeaderIconButton({ icon, label, active, expanded, onClick, }: { icon: React.ReactNode; label: string; active?: boolean; expanded?: boolean; onClick: () => void; }) { return ( ); } /** 顶栏里的