Files
CNWei 707cf98fe0 feat(ui): 新增站点外壳组件 Brand / SearchField / Ticker
把应用里各自手写的三块收进库,风格沿用前台、行为参数化:

- Brand:logo 图片或首字圆形标记 + 名称,href 决定是链接还是纯展示,size/tone 可选
- SearchField:语义版搜索框(onChange 直接给字符串),variant 切 plain / card,
  支持 shortcut 提示与 hotkey 聚焦
- Ticker:无限滚动信息条,内部三份内容按实测宽度位移,循环不跳帧;
  speed / paused / label 可配
- 令牌补展示级字号 --su-font-size-2xl / --su-line-height-2xl(32 / 40)
- 展示页新增 #site-chrome(Brand / Ticker / SearchField),COMPONENTS.md 补契约与 changelog
2026-11-01 15:28:17 +08:00

99 lines
4.8 KiB
TypeScript
Raw Permalink 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.
import { useState } from 'react'
import { Brand, SearchField, Tag, Ticker } from '../../src'
import { Demo } from '../components/Demo'
import { PropsTable } from '../components/PropsTable'
import { Section } from '../components/Section'
const TICKER_ITEMS = ['共 3 个本地应用', 'git_helper', '实时同步已连接', '上次扫描 12 秒前']
export function SiteChrome() {
const [keyword, setKeyword] = useState('')
return (
<Section
id="site-chrome"
title="站点外壳"
api="Brand / Ticker / SearchField"
desc="页面上「每站都要有、但和业务无关」的几块:品牌标识、顶部跑马灯、搜索框。它们只吃 props,不读路由也不请求接口。"
>
<Demo
title="品牌标识 Brand"
desc="有 logo 图片就用图片,没有就用首字圆形标记;传 href 变成链接,不传就是纯展示。"
layout="column"
code={`<Brand name="STASH" mark="S" href="/" />
<Brand name="Docs" mark="D" size="sm" tone="soft" />`}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--su-space-6)' }}>
<Brand name="STASH" mark="S" href="/" />
<Brand name="Docs" mark="D" size="sm" tone="soft" />
<Brand name="只有文字" />
<Brand mark="N" />
</div>
</Demo>
<Demo
title="跑马灯 Ticker"
desc="内部渲染三份内容、按实测宽度位移,首尾相接不跳帧;speed 是一份内容滚完的秒数。"
layout="column"
code={`<Ticker speed={24} label="运行状态">
{items.map((text) => <span key={text}>{text}</span>)}
</Ticker>`}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--su-space-2)', width: '100%' }}>
<Brand name="STASH" mark="S" />
<Ticker speed={18} label="运行状态示例">
{TICKER_ITEMS.map((text) => (
<span
key={text}
style={{ color: 'var(--su-text-sub)', fontSize: 'var(--su-font-size-sm)', whiteSpace: 'nowrap' }}
>
{text}
</span>
))}
</Ticker>
</div>
</Demo>
<Demo
title="搜索框 SearchField"
desc="语义版 Input:对外只给字符串;variant=card 是「悬浮搜索条」的实底+阴影,plain 与普通输入框一致。"
layout="column"
code={`<SearchField value={keyword} onChange={setKeyword} placeholder="搜索项目…" />
<SearchField value={keyword} onChange={setKeyword} variant="card" shortcut="⌘K" />`}
>
<SearchField value={keyword} onChange={setKeyword} placeholder="搜索项目…" maxWidth={360} />
<SearchField
value={keyword}
onChange={setKeyword}
placeholder="悬浮搜索条(card)"
variant="card"
shortcut="⌘K"
maxWidth={420}
/>
<Tag tone="neutral">当前输入:{keyword === '' ? '(空)' : keyword}</Tag>
</Demo>
<PropsTable
rows={[
{ name: 'Brand.name', type: 'string', desc: '品牌文字,可留空只显示标记' },
{ name: 'Brand.mark', type: 'string', desc: '标记里的字,留空取 name 首字' },
{ name: 'Brand.logo', type: 'string', desc: 'logo 图片地址;留空用 mark' },
{ name: 'Brand.href', type: 'string', desc: '传了渲染成 <a>,不传是 <span>' },
{ name: 'Brand.size', type: "'sm' | 'md'", defaultValue: "'md'", desc: '标记 24 / 32' },
{ name: 'Brand.tone', type: "'solid' | 'soft'", defaultValue: "'solid'", desc: '深底白字 / 主色浅底' },
{ name: 'Ticker.children', type: 'ReactNode', required: true, desc: '一组内容,组件负责复制三份' },
{ name: 'Ticker.speed', type: 'number', defaultValue: '24', desc: '一份内容滚完的秒数' },
{ name: 'Ticker.paused', type: 'boolean', defaultValue: 'false', desc: '暂停但保留进度' },
{ name: 'Ticker.label', type: 'string', desc: '读屏用的整体描述' },
{ name: 'SearchField.value', type: 'string', required: true, desc: '受控值' },
{ name: 'SearchField.onChange', type: '(value: string) => void', required: true, desc: '直接给字符串,不用取 event' },
{ name: 'SearchField.variant', type: "'plain' | 'card'", defaultValue: "'plain'", desc: '普通输入框 / 悬浮搜索条' },
{ name: 'SearchField.size', type: "'md' | 'lg'", defaultValue: "'lg'", desc: '36 / 44 高' },
{ name: 'SearchField.maxWidth', type: 'number | string', defaultValue: '480', desc: '最大宽度' },
{ name: 'SearchField.hotkey', type: 'string', desc: 'Ctrl/Cmd + 该键聚焦并全选(需同时传 inputRef)' },
]}
/>
</Section>
)
}