feat: stash-ui 0.1.0(40 个组件 + 文档站 + 主题机制)

This commit is contained in:
stash
2026-10-28 20:44:18 +08:00
commit 52296b3368
153 changed files with 17129 additions and 0 deletions
+244
View File
@@ -0,0 +1,244 @@
import { useState } from 'react'
import { Card, Grid, Layout, Segmented, Space, Tag, Text, Title } from '../../src'
import { Demo } from '../components/Demo'
import { PropsTable } from '../components/PropsTable'
import { Section } from '../components/Section'
/** 文档里的迷你页面内容:只为了把 Layout 的结构摆出来 */
function DemoHeader() {
return (
<div className="layout-bar">
<span className="layout-brand">STASH</span>
<span className="layout-bar-actions">
<span className="layout-nav-item is-active">工作台</span>
<span className="layout-nav-item">常用推荐</span>
<span className="layout-nav-item">设置</span>
</span>
</div>
)
}
function DemoSider({ compact = false }: { compact?: boolean }) {
return (
<div className="layout-side">
<span className="layout-nav-item is-active">常用站点</span>
<span className="layout-nav-item">前端工具</span>
{!compact && <span className="layout-nav-item">设计资源</span>}
{!compact && <span className="layout-nav-item">推荐电影</span>}
</div>
)
}
function DemoAside() {
return (
<div className="layout-aside">
<Text size="sm" strong>
目录
</Text>
<span className="layout-aside-item is-active">一、内容库结构</span>
<span className="layout-aside-item">二、令牌与主题</span>
<span className="layout-aside-item">三、暗色模式</span>
<Text size="xs" tone="muted">
共 3 节
</Text>
</div>
)
}
function DemoArticle() {
return (
<div className="layout-content">
<Title level={3}>内容库结构</Title>
<Text size="sm" tone="sub">
分区 → 分组 → 条目三层;侧栏由内容树派生,首页卡片由 section_block 决定。
</Text>
<Space gap="sm">
<Tag>设计系统</Tag>
<Tag tone="success">已发布</Tag>
</Space>
</div>
)
}
function DemoCards() {
return (
<div className="layout-content">
<Title level={3}>常用站点</Title>
<Text size="sm" tone="sub">
内容区放任何组件:卡片、列表、表格。Layout 只负责它坐在哪里。
</Text>
<Grid columns={2} gap="sm">
<Card padding="md">
<Text strong>GitHub</Text>
</Card>
<Card padding="md">
<Text strong>Figma</Text>
</Card>
</Grid>
</div>
)
}
const RECIPES = [
{ value: 'top', label: '顶栏 + 内容' },
{ value: 'sider', label: '左栏 + 内容' },
{ value: 'aside', label: '内容 + 右栏' },
{ value: 'three', label: '三栏' },
{ value: 'narrow', label: '窄内容' },
] as const
type RecipeValue = (typeof RECIPES)[number]['value']
const CODE: Record<RecipeValue, string> = {
top: `<Layout header={<Header />} footer={<Footer />}>
<Grid columns={2}>…</Grid>
</Layout>`,
sider: `<Layout header={<Header />} sider={<Nav />} siderWidth={180}>
<Title>常用站点</Title>
</Layout>`,
aside: `<Layout header={<Header />} aside={<Toc />} maxWidth={960}>
<Article />
</Layout>`,
three: `<Layout
header={<Header />}
sider={<Nav />}
aside={<Toc />}
siderWidth={160}
asideWidth={200}
>
<Content />
</Layout>`,
narrow: `<Layout header={<Header />} maxWidth={640}>
<Settings />
</Layout>`,
}
export function PageLayout() {
const [recipe, setRecipe] = useState<RecipeValue>('three')
return (
<Section
id="layout"
title="页面骨架"
api="Layout"
desc="页头 / 侧栏 / 内容 / 右栏 / 页脚的外壳:只做结构(区域、最大宽度、吸顶页头),不写业务内容。下面五个是实际项目里最常用的配方。响应式:≤1200px 右栏堆到内容下方,≤1100px 侧栏堆到内容上方,内容都不隐藏。"
>
<Demo
title="五种常用布局"
desc="切换配方看结构差异:顶栏+内容(首页)、左栏+内容(管理页)、内容+右栏(文章)、三栏(工作台)、窄内容(设置/阅读)。"
layout="column"
code={CODE[recipe]}
>
<Segmented options={[...RECIPES]} value={recipe} onChange={(value) => setRecipe(value as RecipeValue)} />
<div className="layout-frame" style={{ height: 440 }}>
<Layout
header={<DemoHeader />}
sider={recipe === 'sider' || recipe === 'three' ? <DemoSider compact /> : undefined}
aside={recipe === 'aside' || recipe === 'three' ? <DemoAside /> : undefined}
footer={<span>© 2026 Stash</span>}
siderWidth={recipe === 'three' ? 150 : 180}
asideWidth={190}
maxWidth={recipe === 'narrow' ? 560 : recipe === 'aside' ? 900 : 1280}
style={{ minHeight: 420 }}
>
{recipe === 'aside' || recipe === 'three' ? <DemoArticle /> : null}
{recipe === 'top' || recipe === 'sider' ? <DemoCards /> : null}
{recipe === 'narrow' ? (
<div className="layout-content">
<Title level={4}>设置</Title>
<Text size="sm" tone="sub">
单栏 + 560 最大宽度,阅读型页面常用这个组合。
</Text>
<Card padding="md">
<Text size="sm">自动同步</Text>
</Card>
<Card padding="md">
<Text size="sm">每周摘要提醒</Text>
</Card>
</div>
) : null}
</Layout>
</div>
</Demo>
<Demo
title="页头可关闭吸顶"
desc="长表单页可以把 stickyHeader 关掉,避免页头一直挡住内容。"
layout="column"
code={`<Layout header={<Header />} stickyHeader={false} maxWidth={720}>
<Form />
</Layout>`}
>
<div className="layout-frame" style={{ height: 300 }}>
<Layout header={<DemoHeader />} maxWidth={720} stickyHeader={false} style={{ minHeight: 280 }}>
<div className="layout-content">
<Title level={4}>新建条目</Title>
<Text size="sm" tone="sub">
stickyHeader=false:页头随内容滚走,适合长表单 / 长文阅读。
</Text>
</div>
</Layout>
</div>
</Demo>
<Demo
title="整屏 + 内部滚动"
desc="height='screen' 锁视口、页头与侧栏固定;scrollable 让内容区自己滚,hideScrollbar 隐藏滚动条(应用型界面)。"
layout="column"
code={`<Layout height="screen" scrollable hideScrollbar contentId="demo-scroll"
header={<Header />} sider={<Nav />} style={{ height: 320 }}>
<LongContent />
</Layout>`}
>
<div className="layout-frame" style={{ height: 320, overflow: 'hidden' }}>
<Layout
header={<DemoHeader />}
sider={<DemoSider compact />}
headerHeight={56}
height="screen"
scrollable
hideScrollbar
contentId="demo-scroll"
style={{ height: 320 }}
>
<div className="layout-content">
{Array.from({ length: 12 }, (_, index) => (
<Card padding="md" key={index}>
<Text size="sm">第 {index + 1} 行:只有内容区滚动,页头和侧栏保持不动。</Text>
</Card>
))}
</div>
</Layout>
</div>
</Demo>
<PropsTable
rows={[
{ name: 'header', type: 'ReactNode', desc: '页头,默认吸顶' },
{ name: 'sider', type: 'ReactNode', desc: '左侧栏,固定宽度' },
{ name: 'aside', type: 'ReactNode', desc: '右侧栏,固定宽度(目录 / 聚合 / 广告位)' },
{ name: 'footer', type: 'ReactNode', desc: '页脚' },
{ name: 'children', type: 'ReactNode', required: true, desc: '内容区' },
{ name: 'maxWidth', type: "number | 'full'", defaultValue: '1280', desc: '内容最大宽度' },
{ name: 'siderWidth', type: 'number', defaultValue: '260', desc: '左侧栏宽度' },
{ name: 'asideWidth', type: 'number', defaultValue: '300', desc: '右侧栏宽度' },
{ name: 'stickyHeader', type: 'boolean', defaultValue: 'true', desc: '页头是否吸顶' },
{ name: 'contained', type: 'boolean', defaultValue: 'false', desc: '页身套居中容器(页头仍通栏)' },
{ name: 'gap', type: 'number', defaultValue: '0', desc: '侧栏 / 内容 / 右栏间距(px)' },
{ name: 'contentPadding', type: "'none' | 'sm' | 'md' | 'lg'", defaultValue: "'md'", desc: '内容留白 0 / 12 / 20 / 24' },
{ name: 'contentOffset', type: 'number', defaultValue: '0', desc: '内容区顶部偏移(给悬浮搜索留位)' },
{ name: 'gutter', type: 'number', defaultValue: '0', desc: 'contained 容器的左右留白' },
{ name: 'headerHeight', type: 'number', defaultValue: '60', desc: '页头最小高度' },
{ name: 'headerVariant', type: "'blur' | 'solid'", defaultValue: "'blur'", desc: '半透明模糊 / 实底 + 边框' },
{ name: 'siderBordered', type: 'boolean', defaultValue: 'true', desc: '侧栏右侧分隔线' },
{ name: 'height', type: "'auto' | 'screen'", defaultValue: "'auto'", desc: 'screen 锁视口' },
{ name: 'scrollable', type: 'boolean', defaultValue: 'false', desc: '内容区内部滚动' },
{ name: 'hideScrollbar', type: 'boolean', defaultValue: 'false', desc: '内部滚动条隐藏' },
{ name: 'contentId', type: 'string', desc: '内容区 id(ScrollTop 用)' },
]}
/>
</Section>
)
}