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
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user