feat: migrate docs to Fumadocs framework

- Auto-generated sidebar from file structure
- Built-in search and TOC
- MDX native support with frontmatter
- Removed hand-written Sidebar component
- Docs now at /docs/* route (Landing/Blocks/Create/Examples unchanged)
- Content in content/docs/ as MDX files
This commit is contained in:
sunzhongyi
2026-05-21 14:12:50 +08:00
parent 9f36e2c0ef
commit 729e6ab287
40 changed files with 2561 additions and 2090 deletions
+3
View File
@@ -0,0 +1,3 @@
import type { GetOutput } from "fumadocs-mdx/config"
export declare const docs: GetOutput<typeof import("../source.config.ts").docs>
export declare const meta: GetOutput<typeof import("../source.config.ts").meta>
+17
View File
@@ -0,0 +1,17 @@
import { toRuntime, toRuntimeAsync } from "fumadocs-mdx"
import * as docs_0 from "../content/docs/grid-system.mdx?collection=docs&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
import * as docs_1 from "../content/docs/components/article.mdx?collection=docs&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
import * as docs_2 from "../content/docs/components/masthead.mdx?collection=docs&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
import * as docs_3 from "../content/docs/components/media.mdx?collection=docs&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
import * as docs_4 from "../content/docs/components/rule.mdx?collection=docs&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
import * as docs_5 from "../content/docs/examples/responsive.mdx?collection=docs&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
import * as docs_6 from "../content/docs/examples/spanning.mdx?collection=docs&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
import * as docs_7 from "../content/docs/text/index.mdx?collection=docs&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
import * as docs_8 from "../content/docs/theme/index.mdx?collection=docs&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
import * as meta_0 from "../content/docs/meta.json?collection=meta&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
import * as meta_1 from "../content/docs/examples/meta.json?collection=meta&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
import * as meta_2 from "../content/docs/components/meta.json?collection=meta&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
import * as meta_3 from "../content/docs/text/meta.json?collection=meta&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
import * as meta_4 from "../content/docs/theme/meta.json?collection=meta&hash=4f3bc6e48351eef47611191451d86bf2ebc0c4238119c3a0c8870a549ef72ca3"
export const docs = [toRuntime("doc", docs_0, {"path":"grid-system.mdx","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/grid-system.mdx"}), toRuntime("doc", docs_1, {"path":"components/article.mdx","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/components/article.mdx"}), toRuntime("doc", docs_2, {"path":"components/masthead.mdx","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/components/masthead.mdx"}), toRuntime("doc", docs_3, {"path":"components/media.mdx","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/components/media.mdx"}), toRuntime("doc", docs_4, {"path":"components/rule.mdx","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/components/rule.mdx"}), toRuntime("doc", docs_5, {"path":"examples/responsive.mdx","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/examples/responsive.mdx"}), toRuntime("doc", docs_6, {"path":"examples/spanning.mdx","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/examples/spanning.mdx"}), toRuntime("doc", docs_7, {"path":"text/index.mdx","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/text/index.mdx"}), toRuntime("doc", docs_8, {"path":"theme/index.mdx","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/theme/index.mdx"})];
export const meta = [toRuntime("meta", meta_0, {"path":"meta.json","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/meta.json"}), toRuntime("meta", meta_1, {"path":"examples/meta.json","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/examples/meta.json"}), toRuntime("meta", meta_2, {"path":"components/meta.json","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/components/meta.json"}), toRuntime("meta", meta_3, {"path":"text/meta.json","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/text/meta.json"}), toRuntime("meta", meta_4, {"path":"theme/meta.json","absolutePath":"/Users/joi-com/Desktop/space/newspaperui/packages/docs/content/docs/theme/meta.json"})];
+11
View File
@@ -0,0 +1,11 @@
// source.config.ts
import { defineDocs, defineConfig } from "fumadocs-mdx/config";
var { docs, meta } = defineDocs({
dir: "content/docs"
});
var source_config_default = defineConfig();
export {
source_config_default as default,
docs,
meta
};
@@ -1,219 +0,0 @@
'use client';
import {
Layout,
Section,
Article,
Layer,
Headline,
Subhead,
BodyText,
} from 'newspaperui';
import { Demo } from '@/components/Demo';
import { PropsTable } from '@/components/PropsTable';
export default function ArticlePage() {
return (
<Layout columns={24} maxWidth="900px" padding="2rem">
<Section columns={24}>
<Article span={24}>
<Headline weight="Medium" as="h1">
Article + Layer
</Headline>
<Subhead weight="Medium">
Article grid-column span Layer
</Subhead>
<Headline weight="Low" as="h2">
Article API
</Headline>
<PropsTable
data={[
{
name: 'span',
type: 'number',
description: '跨多少列(≤ Section.columns)。缺省时占满全宽。',
},
{
name: 'breakable',
type: 'boolean',
default: 'true',
description: '允许打印时跨页断开。',
},
{
name: 'className',
type: 'string',
description: '自定义 CSS 类名。',
},
{
name: 'style',
type: 'CSSProperties',
description: '自定义内联样式。',
},
{
name: 'children',
type: 'ReactNode',
required: true,
description: '文章内容。',
},
]}
/>
<Headline weight="Low" as="h2">
Demo
</Headline>
<Demo
title="8 + 16 跨栏"
description="左侧 8 列 + 右侧 16 列,模拟短讯 + 主稿布局。"
code={`<Layout columns={24}>
<Section columns={24}>
<Article span={8}>
<Headline weight="Low">短讯</Headline>
<BodyText>...</BodyText>
</Article>
<Article span={16}>
<Headline weight="Medium">主稿</Headline>
<BodyText columns={2}>...</BodyText>
</Article>
</Section>
</Layout>`}
>
<Layout columns={24} padding="0">
<Section columns={24}>
<Article span={8}>
<Headline weight="Low" as="h3">
</Headline>
<BodyText weight="Low">
<p>
Markets responded cautiously through the morning session. The pound
traded sideways against the dollar, gilts firmed by three basis points.
</p>
</BodyText>
</Article>
<Article span={16}>
<Headline weight="Medium" as="h3">
稿
</Headline>
<BodyText columns={2}>
<p>
Whitehall officials confirmed late Tuesday that the long-anticipated
review of national infrastructure funding will be tabled before the
recess. The 248-page document, drafted across three departments,
recommends a recalibration of regional priorities and a measured shift
toward rail electrification.
</p>
<p>
Critics inside the cabinet caution that the timing risks overshadowing
the chancellor&rsquo;s autumn statement, while supporters describe the
proposals as the most coherent strategic blueprint in a generation.
</p>
</BodyText>
</Article>
</Section>
</Layout>
</Demo>
<Headline weight="Low" as="h2" style={{ marginTop: '2rem' }}>
Layer API
</Headline>
<BodyText weight="Low">
<p>
Layer 使 CSS position 广sticky
</p>
</BodyText>
<PropsTable
data={[
{
name: 'position',
type: "'absolute' | 'fixed' | 'sticky'",
default: "'absolute'",
description: 'CSS position 属性。',
},
{
name: 'top',
type: 'string | number',
description: '距离顶部的距离。',
},
{
name: 'left',
type: 'string | number',
description: '距离左侧的距离。',
},
{
name: 'right',
type: 'string | number',
description: '距离右侧的距离。',
},
{
name: 'bottom',
type: 'string | number',
description: '距离底部的距离。',
},
{
name: 'zIndex',
type: 'number',
description: 'z-index 层级。',
},
{
name: 'children',
type: 'ReactNode',
required: true,
description: '浮动层内容。',
},
]}
/>
<Demo
title="Layer 浮动拉引"
description="在文章旁边添加浮动的拉引文字。父容器需要 position: relative。"
code={`<div style={{ position: 'relative', minHeight: '200px' }}>
<Article span={16}>
<Headline weight="Low">主要文章</Headline>
<BodyText>...</BodyText>
</Article>
<Layer position="absolute" top="0" right="0">
<aside>浮动内容</aside>
</Layer>
</div>`}
>
<Layout columns={24} padding="0">
<Section columns={24}>
<div style={{ position: 'relative', minHeight: '200px', gridColumn: 'span 24' }}>
<Article span={16}>
<Headline weight="Low" as="h3">
</Headline>
<BodyText>
<p>
In Manchester, regional officials greeted the announcement with
measured optimism. The mayor&rsquo;s office is expected to issue a
formal response by week&rsquo;s end.
</p>
</BodyText>
</Article>
<Layer position="absolute" top="0" right="0">
<aside
style={{
width: '180px',
padding: '1rem',
borderTop: '1px solid var(--nui-rule-hairline)',
borderBottom: '1px solid var(--nui-rule-hairline)',
fontFamily: 'var(--font-family-display)',
fontSize: '18px',
lineHeight: 1.3,
color: 'var(--nui-text-primary)',
}}
>
&ldquo;We have lobbied for this kind of clarity for the better part of
a decade.&rdquo;
</aside>
</Layer>
</div>
</Section>
</Layout>
</Demo>
</Article>
</Section>
</Layout>
);
}
@@ -1,148 +0,0 @@
'use client';
import {
Layout,
Section,
Article,
Headline,
Subhead,
BodyText,
Masthead,
} from 'newspaperui';
import { Demo } from '@/components/Demo';
import { PropsTable } from '@/components/PropsTable';
export default function MastheadPage() {
return (
<Layout columns={24} maxWidth="900px" padding="2rem">
<Section columns={24}>
<Article span={24}>
<Headline weight="Medium" as="h1">
Masthead
</Headline>
<Subhead weight="Medium">
variant 线
</Subhead>
<Headline weight="Low" as="h2">
API
</Headline>
<PropsTable
data={[
{
name: 'title',
type: 'string',
required: true,
description: '报名文字。',
},
{
name: 'variant',
type: "'classic' | 'blackletter' | 'modern'",
default: "'classic'",
description: '视觉风格。',
},
{
name: 'kicker',
type: 'string',
description: '报名上方的小标(如 "All the News That\'s Fit to Print")。',
},
{
name: 'edition',
type: 'string',
description: '版次(如 "Vol. CLXXII No. 59,678")。',
},
{
name: 'date',
type: 'string',
description: '日期(如 "Monday, May 19, 2026")。',
},
{
name: 'price',
type: 'string',
description: '售价(如 "$3.00")。',
},
]}
/>
<Headline weight="Low" as="h2">
Classic
</Headline>
<Demo
title="classic variant"
description="双线居中,Cormorant Garamond 衬线。"
code={`<Masthead
variant="classic"
title="The Daily Chronicle"
kicker="All the News That's Fit to Print"
edition="Vol. CLXXII No. 59,678"
date="Monday, May 19, 2026"
price="$3.00"
/>`}
>
<Masthead
variant="classic"
title="The Daily Chronicle"
kicker="All the News That's Fit to Print"
edition="Vol. CLXXII No. 59,678"
date="Monday, May 19, 2026"
price="$3.00"
/>
</Demo>
<Headline weight="Low" as="h2">
Blackletter
</Headline>
<Demo
title="blackletter variant"
description="UnifrakturMaguntia 哥特体,The Times 风格。"
code={`<Masthead
variant="blackletter"
title="The Evening Standard"
edition="Late Final Edition"
date="Monday, May 19, 2026"
price="£2.50"
/>`}
>
<Masthead
variant="blackletter"
title="The Evening Standard"
edition="Late Final Edition"
date="Monday, May 19, 2026"
price="£2.50"
/>
</Demo>
<Headline weight="Low" as="h2">
Modern
</Headline>
<Demo
title="modern variant"
description="左对齐 + accent 强调色,适合数字优先的出版物。"
code={`<Masthead
variant="modern"
title="The Observer"
kicker="Sunday Edition"
date="May 19, 2026"
/>`}
>
<Masthead
variant="modern"
title="The Observer"
kicker="Sunday Edition"
date="May 19, 2026"
/>
</Demo>
<Headline weight="Low" as="h2">
使
</Headline>
<BodyText weight="Low">
<p>
Masthead <code>gridColumn: 1 / -1</code> Section
Layout Section Rule 线
</p>
</BodyText>
</Article>
</Section>
</Layout>
);
}
@@ -1,166 +0,0 @@
'use client';
import {
Layout,
Section,
Article,
Headline,
Subhead,
BodyText,
Figure,
PullQuote,
} from 'newspaperui';
import { Demo } from '@/components/Demo';
import { PropsTable } from '@/components/PropsTable';
export default function MediaPage() {
return (
<Layout columns={24} maxWidth="900px" padding="2rem">
<Section columns={24}>
<Article span={24}>
<Headline weight="Medium" as="h1">
</Headline>
<Subhead weight="Medium">
ImageFigureVideoPullQuote
</Subhead>
<Headline weight="Low" as="h2">
Image
</Headline>
<BodyText weight="Low">
<p>
<code>display: block; width: 100%</code> Section
span
</p>
</BodyText>
<PropsTable
data={[
{ name: 'src', type: 'string', required: true, description: '图片 URL。' },
{ name: 'alt', type: 'string', required: true, description: '替代文本。' },
{ name: 'span', type: 'number', description: '跨栏数。' },
]}
/>
<Headline weight="Low" as="h2">
Figure
</Headline>
<BodyText weight="Low">
<p>
Image + Caption + Credit figcaption
</p>
</BodyText>
<PropsTable
data={[
{ name: 'src', type: 'string', required: true, description: '图片 URL。' },
{ name: 'alt', type: 'string', required: true, description: '替代文本。' },
{ name: 'caption', type: 'string', description: '图片说明。' },
{ name: 'credit', type: 'string', description: '摄影师/来源(small-caps 渲染)。' },
{ name: 'span', type: 'number', description: '跨栏数。' },
]}
/>
<Demo
title="Figure 带 caption 和 credit"
description="使用 Unsplash 占位图演示完整效果。"
code={`<Figure
src="https://images.unsplash.com/photo-1495020689067-958852a7765e?w=800&h=400&fit=crop"
alt="Newspapers on a wooden table"
caption="A view of the morning editions, arranged on the newsroom table."
credit="Photograph by Roman Kraft / Unsplash"
/>`}
>
<Figure
src="https://images.unsplash.com/photo-1495020689067-958852a7765e?w=800&h=400&fit=crop"
alt="Newspapers on a wooden table"
caption="A view of the morning editions, arranged on the newsroom table."
credit="Photograph by Roman Kraft / Unsplash"
/>
</Demo>
<Headline weight="Low" as="h2">
Video
</Headline>
<PropsTable
data={[
{ name: 'src', type: 'string', required: true, description: '视频 URL。' },
{ name: 'poster', type: 'string', description: '封面图 URL。' },
{ name: 'caption', type: 'string', description: '视频说明。' },
{ name: 'credit', type: 'string', description: '来源。' },
{ name: 'controls', type: 'boolean', default: 'true', description: '显示播放控件。' },
{ name: 'span', type: 'number', description: '跨栏数。' },
]}
/>
<BodyText weight="Low">
<p>
Video Figure <code>&lt;video&gt;</code>
caption/credit
</p>
</BodyText>
<Headline weight="Low" as="h2">
PullQuote
</Headline>
<BodyText weight="Low">
<p>
hairline + Display BodyText
<code>spanAllColumns</code> <code>column-span: all</code>
</p>
</BodyText>
<PropsTable
data={[
{ name: 'weight', type: "'High' | 'Medium'", default: "'High'", description: '视觉权重。' },
{ name: 'span', type: 'number', description: '在 Section 栅格内跨栏。' },
{ name: 'spanAllColumns', type: 'boolean', default: 'false', description: '在多栏 BodyText 内跨所有栏。' },
{ name: 'author', type: 'string', description: '引用来源。' },
{ name: 'align', type: "'left' | 'center'", default: "'left'", description: '文本对齐。' },
]}
/>
<Demo
title="PullQuote 独立使用"
code={`<PullQuote author="Senior Cabinet Official">
We have lobbied for this kind of clarity for the better part of a decade.
</PullQuote>`}
>
<PullQuote author="Senior Cabinet Official">
We have lobbied for this kind of clarity for the better part of a decade.
</PullQuote>
</Demo>
<Demo
title="PullQuote 在多栏 BodyText 内跨栏"
description="spanAllColumns 使 PullQuote 通过 column-span: all 跨越所有文字栏。"
code={`<BodyText columns={3}>
<p>...</p>
<PullQuote spanAllColumns author="...">...</PullQuote>
<p>...</p>
</BodyText>`}
>
<BodyText columns={3}>
<p>
Whitehall officials confirmed late Tuesday that the long-anticipated review
of national infrastructure funding will be tabled before the recess. The
248-page document, drafted across three departments, recommends a
recalibration of regional priorities and a measured shift toward rail
electrification.
</p>
<PullQuote spanAllColumns author="Senior Cabinet Official">
The most coherent strategic blueprint in a generation.
</PullQuote>
<p>
Markets responded cautiously through the morning session. The pound traded
sideways against the dollar, gilts firmed by three basis points, and the
FTSE 100 closed marginally lower as defensive sectors absorbed the
day&rsquo;s modest outflows.
</p>
<p>
In Manchester, regional officials greeted the announcement with measured
optimism. The mayor&rsquo;s office is expected to issue a formal response by
week&rsquo;s end, focusing on commitments to the trans-Pennine corridor.
</p>
</BodyText>
</Demo>
</Article>
</Section>
</Layout>
);
}
@@ -1,134 +0,0 @@
'use client';
import {
Layout,
Section,
Article,
Headline,
Subhead,
BodyText,
Rule,
} from 'newspaperui';
import { Demo } from '@/components/Demo';
import { PropsTable } from '@/components/PropsTable';
export default function RulePage() {
return (
<Layout columns={24} maxWidth="900px" padding="2rem">
<Section columns={24}>
<Article span={24}>
<Headline weight="Medium" as="h1">
Rule 线
</Headline>
<Subhead weight="Medium">
线 variant hairlinedoublethick
</Subhead>
<Headline weight="Low" as="h2">
API
</Headline>
<PropsTable
data={[
{
name: 'variant',
type: "'hairline' | 'double' | 'thick'",
default: "'hairline'",
description: '线型。',
},
{
name: 'orientation',
type: "'horizontal' | 'vertical'",
default: "'horizontal'",
description: '方向。',
},
{
name: 'span',
type: 'number',
description: '横向时占多少列。缺省时 1 / -1 全跨。',
},
{
name: 'className',
type: 'string',
description: '自定义 CSS 类名。',
},
{
name: 'style',
type: 'CSSProperties',
description: '自定义内联样式。',
},
]}
/>
<Headline weight="Low" as="h2">
variant
</Headline>
<Demo title="hairline / double / thick" description="水平分隔线对照。">
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
<div>
<BodyText weight="Low">
<p>hairline</p>
</BodyText>
<Rule variant="hairline" />
</div>
<div>
<BodyText weight="Low">
<p>double</p>
</BodyText>
<Rule variant="double" />
</div>
<div>
<BodyText weight="Low">
<p>thick</p>
</BodyText>
<Rule variant="thick" />
</div>
</div>
</Demo>
<Headline weight="Low" as="h2">
</Headline>
<Demo
title="垂直分隔线"
description="在两栏之间放置垂直 Rule。"
code={`<div style={{ display: 'flex', gap: '1.5rem', height: '120px' }}>
<div>左栏内容</div>
<Rule variant="hairline" orientation="vertical" />
<div>右栏内容</div>
</div>`}
>
<div style={{ display: 'flex', gap: '1.5rem', height: '120px', alignItems: 'stretch' }}>
<div style={{ flex: 1 }}>
<BodyText weight="Low">
<p>
Markets responded cautiously through the morning session. The pound
traded sideways against the dollar.
</p>
</BodyText>
</div>
<Rule variant="hairline" orientation="vertical" />
<div style={{ flex: 1 }}>
<BodyText weight="Low">
<p>
Analysts at three of the City&rsquo;s largest houses framed the move as
a holding pattern.
</p>
</BodyText>
</div>
</div>
</Demo>
<Headline weight="Low" as="h2">
使
</Headline>
<BodyText weight="Low">
<p>
Rule Section <code>gridColumn: 1 / -1</code>
<code>span</code> prop Rule
flex
</p>
</BodyText>
</Article>
</Section>
</Layout>
);
}
@@ -1,258 +0,0 @@
'use client';
import {
Layout,
Section,
Article,
Headline,
Subhead,
BodyText,
Rule,
} from 'newspaperui';
import { CodeBlock } from '@/components/CodeBlock';
export default function ResponsivePage() {
return (
<Layout columns={24} maxWidth="900px" padding="2rem">
<Section columns={24}>
<Article span={24}>
<Headline weight="Medium" as="h1">
</Headline>
<Subhead weight="Medium">
NewspaperUI CSS media query + container query
JavaScript
</Subhead>
<Headline weight="Low" as="h2">
</Headline>
<BodyText>
<p>
</p>
</BodyText>
<div style={{ overflowX: 'auto', margin: '1rem 0 2rem' }}>
<table
style={{
width: '100%',
borderCollapse: 'collapse',
fontFamily: 'var(--font-family-meta)',
fontSize: '13px',
}}
>
<thead>
<tr style={{ borderBottom: '2px solid var(--nui-rule-decorative)' }}>
<th style={{ textAlign: 'left', padding: '0.5rem' }}></th>
<th style={{ textAlign: 'left', padding: '0.5rem' }}></th>
<th style={{ textAlign: 'left', padding: '0.5rem' }}></th>
<th style={{ textAlign: 'left', padding: '0.5rem' }}></th>
</tr>
</thead>
<tbody>
<tr style={{ borderBottom: '1px solid var(--nui-rule-hairline)' }}>
<td style={{ padding: '0.5rem' }}>Mobile</td>
<td style={{ padding: '0.5rem' }}>&lt; 768px</td>
<td style={{ padding: '0.5rem' }}>1 </td>
<td style={{ padding: '0.5rem' }}>BodyText columns=1</td>
</tr>
<tr style={{ borderBottom: '1px solid var(--nui-rule-hairline)', background: 'var(--nui-bg-surface)' }}>
<td style={{ padding: '0.5rem' }}>Tablet</td>
<td style={{ padding: '0.5rem' }}>768px - 1024px</td>
<td style={{ padding: '0.5rem' }}>12 </td>
<td style={{ padding: '0.5rem' }}>BodyText columns=2</td>
</tr>
<tr style={{ borderBottom: '1px solid var(--nui-rule-hairline)' }}>
<td style={{ padding: '0.5rem' }}>Desktop</td>
<td style={{ padding: '0.5rem' }}>&gt; 1024px</td>
<td style={{ padding: '0.5rem' }}>24 </td>
<td style={{ padding: '0.5rem' }}></td>
</tr>
</tbody>
</table>
</div>
<Headline weight="Low" as="h2">
</Headline>
<BodyText>
<p>
NewspaperUI 使 JavaScript responsive CSS
media query container query
</p>
<p>
Section <code>gridTemplateColumns</code> inline style
CSS
</p>
</BodyText>
<CodeBlock
title="自定义响应式 CSS"
language="css"
code={`/* 在你的 globals.css 或组件 CSS 中 */
@media (max-width: 768px) {
.nui-section {
grid-template-columns: 1fr !important;
}
.nui-article {
grid-column: span 1 !important;
}
}
@media (min-width: 769px) and (max-width: 1024px) {
.nui-section[data-columns="24"] {
grid-template-columns: repeat(12, 1fr) !important;
}
}`}
/>
<BodyText>
<p>
使 container query Section
</p>
</BodyText>
<CodeBlock
title="Container Query 方案"
language="css"
code={`/* 给 Section 添加 container-type */
.nui-section {
container-type: inline-size;
}
@container (max-width: 600px) {
.nui-article {
grid-column: 1 / -1 !important;
}
}`}
/>
<Rule variant="hairline" />
<Headline weight="Low" as="h2">
</Headline>
<BodyText weight="Low">
<p>
使
DevTools responsive mode
</p>
</BodyText>
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem', margin: '1.5rem 0' }}>
<div>
<div
className="nui-small-caps"
style={{
fontFamily: 'var(--font-family-meta)',
fontSize: '11px',
color: 'var(--nui-text-muted)',
letterSpacing: '0.08em',
marginBottom: '0.5rem',
}}
>
Desktop (1024px+) &mdash; 24
</div>
<div
style={{
border: '1px solid var(--nui-rule-hairline)',
padding: '1rem',
background: 'var(--nui-bg-surface)',
}}
>
<Layout columns={24} padding="0" maxWidth="100%">
<Section columns={24}>
<Article span={6}>
<Headline weight="Low" as="h4">Briefing</Headline>
<BodyText weight="Low"><p>Short news items.</p></BodyText>
</Article>
<Article span={12}>
<Headline weight="Medium" as="h4">Lead Story</Headline>
<BodyText><p>Main article content with full detail.</p></BodyText>
</Article>
<Article span={6}>
<Headline weight="Low" as="h4">Markets</Headline>
<BodyText weight="Low"><p>FTSE, gilts, sterling.</p></BodyText>
</Article>
</Section>
</Layout>
</div>
</div>
<div>
<div
className="nui-small-caps"
style={{
fontFamily: 'var(--font-family-meta)',
fontSize: '11px',
color: 'var(--nui-text-muted)',
letterSpacing: '0.08em',
marginBottom: '0.5rem',
}}
>
Tablet (768-1024px) &mdash; 12
</div>
<div
style={{
border: '1px solid var(--nui-rule-hairline)',
padding: '1rem',
background: 'var(--nui-bg-surface)',
maxWidth: '600px',
}}
>
<Layout columns={12} padding="0" maxWidth="100%">
<Section columns={12}>
<Article span={6}>
<Headline weight="Low" as="h4">Lead Story</Headline>
<BodyText weight="Low"><p>Main article.</p></BodyText>
</Article>
<Article span={6}>
<Headline weight="Low" as="h4">Secondary</Headline>
<BodyText weight="Low"><p>Supporting content.</p></BodyText>
</Article>
</Section>
</Layout>
</div>
</div>
<div>
<div
className="nui-small-caps"
style={{
fontFamily: 'var(--font-family-meta)',
fontSize: '11px',
color: 'var(--nui-text-muted)',
letterSpacing: '0.08em',
marginBottom: '0.5rem',
}}
>
Mobile (&lt;768px) &mdash;
</div>
<div
style={{
border: '1px solid var(--nui-rule-hairline)',
padding: '1rem',
background: 'var(--nui-bg-surface)',
maxWidth: '375px',
}}
>
<Layout columns={1} padding="0" maxWidth="100%">
<Section columns={1}>
<Article span={1}>
<Headline weight="Low" as="h4">Lead Story</Headline>
<BodyText weight="Low"><p>Full-width single column.</p></BodyText>
</Article>
<Article span={1}>
<Headline weight="Low" as="h4">Secondary</Headline>
<BodyText weight="Low"><p>Stacked below.</p></BodyText>
</Article>
</Section>
</Layout>
</div>
</div>
</div>
</Article>
</Section>
</Layout>
);
}
@@ -1,236 +0,0 @@
'use client';
import {
Layout,
Section,
Article,
Headline,
Subhead,
BodyText,
Rule,
} from 'newspaperui';
import { Demo } from '@/components/Demo';
export default function SpanningPage() {
return (
<Layout columns={24} maxWidth="900px" padding="2rem">
<Section columns={24}>
<Article span={24}>
<Headline weight="Medium" as="h1">
</Headline>
<Subhead weight="Medium">
24
</Subhead>
</Article>
</Section>
<Rule variant="hairline" />
<Section columns={24}>
<Article span={24}>
<Headline weight="Low" as="h2">
8 + 16
</Headline>
</Article>
</Section>
<Demo title="8 + 16" description="短讯 + 主稿。">
<Layout columns={24} padding="0">
<Section columns={24}>
<Article span={8}>
<Headline weight="Low" as="h3">
Briefing
</Headline>
<BodyText weight="Low">
<p>
Markets responded cautiously through the morning session. The pound
traded sideways against the dollar.
</p>
</BodyText>
</Article>
<Article span={16}>
<Headline weight="Medium" as="h3">
Infrastructure Review Tabled
</Headline>
<BodyText>
<p>
Whitehall officials confirmed late Tuesday that the long-anticipated
review of national infrastructure funding will be tabled before the
recess. The 248-page document recommends a recalibration of regional
priorities.
</p>
</BodyText>
</Article>
</Section>
</Layout>
</Demo>
<Section columns={24}>
<Article span={24}>
<Headline weight="Low" as="h2">
6 + 12 + 6
</Headline>
</Article>
</Section>
<Demo title="6 + 12 + 6" description="三栏对称布局,中间主体 + 两侧辅助。">
<Layout columns={24} padding="0">
<Section columns={24}>
<Article span={6}>
<Headline weight="Low" as="h3">
Weather
</Headline>
<BodyText weight="Low">
<p>Partly cloudy, 18C. Wind NW 12 mph. Sunset 20:47.</p>
</BodyText>
</Article>
<Article span={12}>
<Headline weight="Medium" as="h3">
Chancellor Outlines Spending Envelope
</Headline>
<BodyText>
<p>
The chancellor confirmed a three-year spending envelope that prioritizes
rail electrification and regional connectivity. The announcement was
greeted with cautious optimism across the House.
</p>
</BodyText>
</Article>
<Article span={6}>
<Headline weight="Low" as="h3">
Markets
</Headline>
<BodyText weight="Low">
<p>FTSE 100: 7,842 (-0.2%). Gilts +3bp. Sterling flat.</p>
</BodyText>
</Article>
</Section>
</Layout>
</Demo>
<Section columns={24}>
<Article span={24}>
<Headline weight="Low" as="h2">
12 + 12
</Headline>
</Article>
</Section>
<Demo title="12 + 12" description="对称双栏,适合对比报道或并列文章。">
<Layout columns={24} padding="0">
<Section columns={24}>
<Article span={12}>
<Headline weight="Low" as="h3">
View from Westminster
</Headline>
<BodyText>
<p>
Critics inside the cabinet caution that the timing risks overshadowing
the chancellor&rsquo;s autumn statement. The opposition has called for a
full debate before any commitments are made.
</p>
</BodyText>
</Article>
<Article span={12}>
<Headline weight="Low" as="h3">
View from the Regions
</Headline>
<BodyText>
<p>
In Manchester, regional officials greeted the announcement with measured
optimism. The mayor&rsquo;s office is expected to issue a formal response
by week&rsquo;s end.
</p>
</BodyText>
</Article>
</Section>
</Layout>
</Demo>
<Section columns={24}>
<Article span={24}>
<Headline weight="Low" as="h2">
4 + 16 + 4
</Headline>
</Article>
</Section>
<Demo title="4 + 16 + 4" description="窄侧栏 + 宽主体,适合带注释的长文。">
<Layout columns={24} padding="0">
<Section columns={24}>
<Article span={4}>
<BodyText weight="Low">
<p style={{ fontStyle: 'italic', fontSize: '12px' }}>
Editor&rsquo;s note: This report was filed before the evening session.
</p>
</BodyText>
</Article>
<Article span={16}>
<Headline weight="Medium" as="h3">
A Standing Technical Commission
</Headline>
<BodyText>
<p>
Beyond the immediate fiscal arithmetic, the review&rsquo;s most
consequential proposal may be its quietest: a standing technical
commission, modeled on Australia&rsquo;s Infrastructure Australia, to
depoliticize project sequencing.
</p>
</BodyText>
</Article>
<Article span={4}>
<BodyText weight="Low">
<p style={{ fontStyle: 'italic', fontSize: '12px' }}>
Related: &ldquo;How Australia reformed infrastructure planning&rdquo;
&mdash; p. 12
</p>
</BodyText>
</Article>
</Section>
</Layout>
</Demo>
<Section columns={24}>
<Article span={24}>
<Headline weight="Low" as="h2">
24
</Headline>
</Article>
</Section>
<Demo title="24 全宽" description="单篇全宽文章,适合社论或特稿。">
<Layout columns={24} padding="0">
<Section columns={24}>
<Article span={24}>
<Headline weight="High" as="h3">
The Quiet Revolution in Treasury Forecasting
</Headline>
<BodyText columns={4} dropCap>
<p>
Whitehall officials confirmed late Tuesday that the long-anticipated
review of national infrastructure funding will be tabled before the
recess. The 248-page document, drafted across three departments,
recommends a recalibration of regional priorities and a measured shift
toward rail electrification.
</p>
<p>
Markets responded cautiously through the morning session. The pound
traded sideways against the dollar, gilts firmed by three basis points,
and the FTSE 100 closed marginally lower as defensive sectors absorbed
the day&rsquo;s modest outflows.
</p>
<p>
In Manchester, regional officials greeted the announcement with measured
optimism. The mayor&rsquo;s office is expected to issue a formal response
by week&rsquo;s end, focusing on commitments to the trans-Pennine
corridor and the long-deferred upgrade of suburban tram capacity.
</p>
<p>
Beyond the immediate fiscal arithmetic, the review&rsquo;s most
consequential proposal may be its quietest: a standing technical
commission to depoliticize project sequencing. Whether that body acquires
teeth will be determined by the legislation expected in the spring.
</p>
</BodyText>
</Article>
</Section>
</Layout>
</Demo>
</Layout>
);
}
@@ -1,225 +0,0 @@
'use client';
import {
Layout,
Section,
Article,
Headline,
Subhead,
BodyText,
} from 'newspaperui';
import { Demo } from '@/components/Demo';
import { PropsTable } from '@/components/PropsTable';
export default function GridSystemPage() {
return (
<Layout columns={24} maxWidth="900px" padding="2rem">
<Section columns={24}>
<Article span={24}>
<Headline weight="Medium" as="h1">
</Headline>
<Subhead weight="Medium">
24 Layout Section grid-template-columns
Article grid-column span CSS Grid
</Subhead>
<Headline weight="Low" as="h2">
24
</Headline>
<BodyText>
<p>
24 24
2 / 3 / 4 / 6 / 8 / 12 1/32/31/43/4
</p>
</BodyText>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(24, 1fr)',
gap: '4px',
margin: '1.5rem 0 2rem',
}}
>
{Array.from({ length: 24 }, (_, i) => (
<div
key={i}
style={{
background: 'var(--nui-bg-surface)',
border: '1px solid var(--nui-rule-hairline)',
textAlign: 'center',
padding: '0.5rem 0',
fontFamily: 'var(--font-family-meta)',
fontSize: '11px',
color: 'var(--nui-text-muted)',
letterSpacing: '0.04em',
}}
>
{i + 1}
</div>
))}
</div>
<Headline weight="Low" as="h2">
Layout API
</Headline>
<BodyText>
<p>
<code>Layout</code> LayoutContextSection
useLayout clamp
</p>
</BodyText>
<PropsTable
data={[
{
name: 'columns',
type: 'number',
default: '24',
description: '栅格总列数。决定子 Section 的最大 columns。',
},
{
name: 'maxWidth',
type: 'string',
default: "'1280px'",
description: '页面最大宽度。',
},
{
name: 'padding',
type: 'string',
default: "'var(--nui-space-6)'",
description: '页面内边距。',
},
{
name: 'theme',
type: "'light' | 'dark'",
description:
'强制主题;省略时跟随 documentElement.dataset.theme。',
},
{
name: 'children',
type: 'ReactNode',
required: true,
description: '子内容(通常是若干 Section)。',
},
]}
/>
<Headline weight="Low" as="h2">
Section API
</Headline>
<BodyText>
<p>
<code>Section</code>
<code>display: grid; grid-template-columns: repeat(N, 1fr)</code>
Article grid-column span
</p>
</BodyText>
<PropsTable
data={[
{
name: 'columns',
type: 'number',
required: true,
description: 'Section 内部栅格列数(≤ Layout.columns)。',
},
{
name: 'gap',
type: 'string',
default: "'var(--nui-gutter)'",
description: '栏间间距。',
},
{
name: 'breakable',
type: 'boolean',
default: 'true',
description: '允许打印时跨页断开。',
},
{
name: 'divider',
type: "'none' | 'top' | 'bottom' | 'both'",
default: "'none'",
description: '上下方向的 hairline 分隔线。',
},
]}
/>
<Headline weight="Low" as="h2">
Article API
</Headline>
<BodyText>
<p>
<code>Article</code> Section grid-column span N
span Section
</p>
</BodyText>
<PropsTable
data={[
{
name: 'span',
type: 'number',
description: '跨多少列(0 &lt; span ≤ Section.columns)。',
},
{
name: 'breakable',
type: 'boolean',
default: 'true',
description: '允许打印时跨页断开。',
},
{
name: 'children',
type: 'ReactNode',
required: true,
description: '文章内容。',
},
]}
/>
<Headline weight="Low" as="h2">
</Headline>
<Demo
title="8 + 16 跨栏"
description="左侧短讯 8 列 + 右侧主稿 16 列。"
code={`<Layout columns={24}>
<Section columns={24}>
<Article span={8}>
<Headline weight="Low">短讯</Headline>
<BodyText>...</BodyText>
</Article>
<Article span={16}>
<Headline weight="Medium">主稿</Headline>
<BodyText>...</BodyText>
</Article>
</Section>
</Layout>`}
>
<Layout columns={24} padding="0">
<Section columns={24}>
<Article span={8}>
<Headline weight="Low" as="h3">
</Headline>
<BodyText weight="Low">
<p> 8 </p>
</BodyText>
</Article>
<Article span={16}>
<Headline weight="Medium" as="h3">
稿
</Headline>
<BodyText>
<p>
16 稿
</p>
</BodyText>
</Article>
</Section>
</Layout>
</Demo>
</Article>
</Section>
</Layout>
);
}
-19
View File
@@ -1,19 +0,0 @@
import { Sidebar } from '../../components/Sidebar';
export default function DocsLayout({ children }: { children: React.ReactNode }) {
return (
<div style={{ display: 'flex', minHeight: 'calc(100vh - 65px)' }}>
<Sidebar />
<main
style={{
flex: 1,
padding: '2rem 3rem',
maxWidth: 'calc(100% - 240px)',
overflow: 'auto',
}}
>
{children}
</main>
</div>
);
}
-266
View File
@@ -1,266 +0,0 @@
'use client';
import {
Layout,
Section,
Article,
Headline,
Subhead,
Kicker,
BodyText,
Quote,
Byline,
Dateline,
Caption,
} from 'newspaperui';
import { visualWeights, resolveFontSize } from 'newspaperui-theme';
import { Demo } from '@/components/Demo';
const longText = `Whitehall officials confirmed late Tuesday that the long-anticipated review of national infrastructure funding will be tabled before the recess. The 248-page document, drafted across three departments, recommends a recalibration of regional priorities and a measured shift toward rail electrification. Critics inside the cabinet caution that the timing risks overshadowing the chancellor's autumn statement, while supporters describe the proposals as the most coherent strategic blueprint in a generation.`;
const longText2 = `Markets responded cautiously through the morning session. The pound traded sideways against the dollar, gilts firmed by three basis points, and the FTSE 100 closed marginally lower as defensive sectors absorbed the day's modest outflows. Analysts at three of the City's largest houses framed the move as a holding pattern, awaiting further detail on the spending envelope rather than a verdict on its substance.`;
const longText3 = `In Manchester, regional officials greeted the announcement with measured optimism. "We have lobbied for this kind of clarity for the better part of a decade," said one senior figure who requested anonymity to discuss internal deliberations. The mayor's office is expected to issue a formal response by week's end, focusing on commitments to the trans-Pennine corridor and the long-deferred upgrade of suburban tram capacity.`;
const longText4 = `Beyond the immediate fiscal arithmetic, the review's most consequential proposal may be its quietest: a standing technical commission, modeled on Australia's Infrastructure Australia, to depoliticize project sequencing. Whether that body acquires teeth — or settles into the advisory torpor that has consumed earlier attempts — will be determined by the legislation expected in the spring.`;
interface WeightRow {
component: string;
weight: string;
fontFamily: string;
fontSize: string;
fontWeight: number;
lineHeight: number;
color: string;
}
function buildWeightRows(): WeightRow[] {
const rows: WeightRow[] = [];
for (const [comp, weights] of Object.entries(visualWeights)) {
for (const [w, cfg] of Object.entries(weights)) {
if (!cfg) continue;
rows.push({
component: comp,
weight: w,
fontFamily: cfg.fontFamily,
fontSize: resolveFontSize(cfg.fontSize),
fontWeight: cfg.fontWeight,
lineHeight: cfg.lineHeight,
color: cfg.color,
});
}
}
return rows;
}
export default function TextPage() {
const weightRows = buildWeightRows();
return (
<Layout columns={24} maxWidth="900px" padding="2rem">
<Section columns={24}>
<Article span={24}>
<Headline weight="Medium" as="h1">
</Headline>
<Subhead weight="Medium">
Headline Caption
token
</Subhead>
<Headline weight="Low" as="h2">
Headline
</Headline>
<Demo title="High / Medium / Low">
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
<div>
<Kicker>weight=&quot;High&quot;</Kicker>
<Headline weight="High" as="h3">
A Quiet Revolution in the Treasury Forecast
</Headline>
</div>
<div>
<Kicker>weight=&quot;Medium&quot;</Kicker>
<Headline weight="Medium" as="h3">
Whitehall Confirms Infrastructure Review
</Headline>
</div>
<div>
<Kicker>weight=&quot;Low&quot;</Kicker>
<Headline weight="Low" as="h3">
Briefing: regional rail commitments
</Headline>
</div>
</div>
</Demo>
<Headline weight="Low" as="h2">
Subhead
</Headline>
<Demo title="High / Medium">
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<Subhead weight="High">
A measured recalibration of regional priorities is expected to define the
chancellor&rsquo;s autumn agenda.
</Subhead>
<Subhead weight="Medium">
Officials emphasized that the headline figures should be read as a planning
envelope rather than a binding allocation.
</Subhead>
</div>
</Demo>
<Headline weight="Low" as="h2">
Kicker
</Headline>
<Demo title="朱红 small-caps,挂在 Headline 上方">
<div>
<Kicker>POLITICS · WHITEHALL</Kicker>
<Headline weight="Medium" as="h3">
A Standing Technical Commission, Quietly Proposed
</Headline>
</div>
</Demo>
<Headline weight="Low" as="h2">
BodyText
</Headline>
<Demo title="单栏" description="默认形态,适合中等宽度的文章正文。">
<BodyText>
<p>{longText}</p>
<p>{longText2}</p>
</BodyText>
</Demo>
<Demo
title="三栏 + 首字下沉"
description="开启 columns={3} 与 dropCap,第一段首字母自动下沉占 2-3 行。"
code={`<BodyText columns={3} dropCap>
<p>...</p>
<p>...</p>
</BodyText>`}
>
<BodyText columns={3} dropCap>
<p>{longText}</p>
<p>{longText2}</p>
<p>{longText3}</p>
<p>{longText4}</p>
</BodyText>
</Demo>
<Demo
title="两栏(无 dropCap"
description="columns={2},栏间细线由 nui-column-rule 提供。"
>
<BodyText columns={2}>
<p>{longText}</p>
<p>{longText2}</p>
<p>{longText3}</p>
</BodyText>
</Demo>
<Headline weight="Low" as="h2">
Quote
</Headline>
<Demo title="block / inline 对照">
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<Quote variant="block">
We have lobbied for this kind of clarity for the better part of a decade.
</Quote>
<BodyText>
<p>
<Quote variant="inline">
</Quote>
</p>
</BodyText>
</div>
</Demo>
<Headline weight="Low" as="h2">
Byline / Dateline / Caption
</Headline>
<Demo title="元信息组件">
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
<div>
<Byline>BY ALICE SMITH</Byline>
<Dateline>LONDON &mdash;</Dateline>
</div>
<figure style={{ margin: 0 }}>
<div
style={{
height: '160px',
background: 'var(--nui-bg-surface)',
border: '1px solid var(--nui-rule-hairline)',
}}
/>
<Caption credit="Photograph by Jane Doe">
A view of the Treasury terrace at dusk; the new commission will report
here from May.
</Caption>
</figure>
</div>
</Demo>
<Headline weight="Low" as="h2">
</Headline>
<BodyText weight="Low">
<p>
<code>visualWeights</code>
token theme
</p>
</BodyText>
<div style={{ overflowX: 'auto', margin: '1rem 0 2rem' }}>
<table
style={{
width: '100%',
borderCollapse: 'collapse',
fontFamily: 'var(--font-family-meta)',
fontSize: '13px',
}}
>
<thead>
<tr style={{ borderBottom: '2px solid var(--nui-rule-decorative)' }}>
<th style={{ textAlign: 'left', padding: '0.5rem' }}></th>
<th style={{ textAlign: 'left', padding: '0.5rem' }}></th>
<th style={{ textAlign: 'left', padding: '0.5rem' }}></th>
<th style={{ textAlign: 'left', padding: '0.5rem' }}></th>
<th style={{ textAlign: 'left', padding: '0.5rem' }}></th>
<th style={{ textAlign: 'left', padding: '0.5rem' }}></th>
<th style={{ textAlign: 'left', padding: '0.5rem' }}></th>
</tr>
</thead>
<tbody>
{weightRows.map((row, idx) => (
<tr
key={`${row.component}-${row.weight}`}
style={{
borderBottom: '1px solid var(--nui-rule-hairline)',
background:
idx % 2 === 0 ? 'transparent' : 'var(--nui-bg-surface)',
}}
>
<td style={{ padding: '0.5rem' }}>{row.component}</td>
<td style={{ padding: '0.5rem' }}>{row.weight}</td>
<td style={{ padding: '0.5rem', fontSize: '11px' }}>
<code>{row.fontFamily}</code>
</td>
<td style={{ padding: '0.5rem' }}>{row.fontSize}</td>
<td style={{ padding: '0.5rem' }}>{row.fontWeight}</td>
<td style={{ padding: '0.5rem' }}>{row.lineHeight}</td>
<td style={{ padding: '0.5rem', fontSize: '11px' }}>
<code>{row.color}</code>
</td>
</tr>
))}
</tbody>
</table>
</div>
</Article>
</Section>
</Layout>
);
}
-216
View File
@@ -1,216 +0,0 @@
'use client';
import {
Layout,
Section,
Article,
Headline,
Subhead,
BodyText,
} from 'newspaperui';
import { ThemeToggle } from '@/components/ThemeToggle';
interface Swatch {
token: string;
hex: string;
label?: string;
}
const textSwatches: Swatch[] = [
{ token: '--nui-text-primary', hex: '#1A1A1A', label: '标题、主文本' },
{ token: '--nui-text-body', hex: '#22201C', label: '正文' },
{ token: '--nui-text-secondary', hex: '#4A4742', label: 'Subhead、次级' },
{ token: '--nui-text-muted', hex: '#6E6A63', label: 'Caption、注释' },
{ token: '--nui-text-quote', hex: '#2E2A24', label: 'Quote 主体' },
];
const surfaceSwatches: Swatch[] = [
{ token: '--nui-bg-page', hex: '#F7F4ED', label: 'Warm off-white 页面底' },
{ token: '--nui-bg-surface', hex: '#FBF9F4', label: '次级面板背景' },
{ token: '--nui-rule-hairline', hex: '#C9C2B2', label: '细线分隔' },
{ token: '--nui-rule-decorative', hex: '#1A1A1A', label: '强调线' },
{ token: '--nui-highlight', hex: '#F2E9C8', label: '旧报纸黄' },
];
const accentSwatches: Swatch[] = [
{ token: '--nui-accent-primary', hex: '#7A1F1F', label: 'Brick red, Kicker / Masthead 强调' },
{ token: '--nui-accent-ink-blue', hex: '#1B2A4A', label: 'The Times 蓝' },
{ token: '--nui-highlight', hex: '#F2E9C8', label: '高亮底色' },
];
interface FontFamily {
token: string;
sample: string;
description: string;
}
const families: FontFamily[] = [
{
token: '--font-family-masthead',
sample: 'The Daily Chronicle',
description: 'Cormorant Garamond — 报头',
},
{
token: '--font-family-blackletter',
sample: 'The Daily Chronicle',
description: 'UnifrakturMaguntia — Blackletter preset',
},
{
token: '--font-family-display',
sample: 'A Quiet Revolution',
description: 'Source Serif 4 — Display 大字头条',
},
{
token: '--font-family-headline',
sample: 'Whitehall confirms review',
description: 'Source Serif 4 — Headline / Subhead',
},
{
token: '--font-family-body',
sample: 'In Manchester, regional officials greeted the announcement.',
description: 'Source Serif 4 — 正文',
},
{
token: '--font-family-meta',
sample: 'BY ALICE SMITH · LONDON',
description: 'Inter — small-caps 元信息',
},
];
function SwatchGrid({ swatches }: { swatches: Swatch[] }) {
return (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
gap: '0.75rem',
margin: '1rem 0 1.5rem',
}}
>
{swatches.map((s) => (
<div
key={s.token}
style={{ border: '1px solid var(--nui-rule-hairline)', overflow: 'hidden' }}
>
<div style={{ background: s.hex, height: '64px' }} />
<div
style={{
padding: '0.5rem 0.75rem',
fontFamily: 'var(--font-family-meta)',
fontSize: '11px',
background: 'var(--nui-bg-surface)',
}}
>
<code style={{ display: 'block', fontSize: '11px' }}>{s.token}</code>
<div
style={{
color: 'var(--nui-text-muted)',
marginTop: '0.15rem',
letterSpacing: '0.04em',
}}
>
{s.hex}
{s.label ? ` · ${s.label}` : ''}
</div>
</div>
</div>
))}
</div>
);
}
export default function ThemePage() {
return (
<Layout columns={24} maxWidth="900px" padding="2rem">
<Section columns={24}>
<Article span={24}>
<Headline weight="Medium" as="h1">
</Headline>
<Subhead weight="Medium">
+ warm off-white使#000使#FFF
</Subhead>
<Headline weight="Low" as="h2">
</Headline>
<BodyText weight="Low">
<p>
#14110D
</p>
</BodyText>
<div style={{ margin: '1rem 0 2rem' }}>
<ThemeToggle />
</div>
<Headline weight="Low" as="h2">
token
</Headline>
<SwatchGrid swatches={textSwatches} />
<Headline weight="Low" as="h2">
线 token
</Headline>
<SwatchGrid swatches={surfaceSwatches} />
<Headline weight="Low" as="h2">
token
</Headline>
<SwatchGrid swatches={accentSwatches} />
<Headline weight="Low" as="h2">
</Headline>
<BodyText weight="Low">
<p>
Cormorant Garamond Source Serif 4
穿Inter small-caps Blackletter preset
UnifrakturMaguntia
</p>
</BodyText>
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: '1rem',
margin: '1rem 0 2rem',
}}
>
{families.map((f) => (
<div
key={f.token}
style={{
borderTop: '1px solid var(--nui-rule-hairline)',
paddingTop: '0.75rem',
}}
>
<div
className="nui-small-caps"
style={{
fontFamily: 'var(--font-family-meta)',
fontSize: '11px',
color: 'var(--nui-text-muted)',
letterSpacing: '0.08em',
marginBottom: '0.25rem',
}}
>
{f.token} &middot; {f.description}
</div>
<div
style={{
fontFamily: `var(${f.token})`,
fontSize: '32px',
color: 'var(--nui-text-primary)',
lineHeight: 1.1,
}}
>
{f.sample}
</div>
</div>
))}
</div>
</Article>
</Section>
</Layout>
);
}
@@ -0,0 +1,22 @@
import { source } from '@/lib/source';
import { DocsPage, DocsBody } from 'fumadocs-ui/page';
import { notFound } from 'next/navigation';
export default async function Page(props: { params: Promise<{ slug?: string[] }> }) {
const params = await props.params;
const page = source.getPage(params.slug);
if (!page) notFound();
const MDX = page.data.body;
return (
<DocsPage toc={page.data.toc}>
<DocsBody>
<MDX />
</DocsBody>
</DocsPage>
);
}
export function generateStaticParams() {
return source.generateParams();
}
+11
View File
@@ -0,0 +1,11 @@
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { source } from '@/lib/source';
import type { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout tree={source.pageTree}>
{children}
</DocsLayout>
);
}
+6 -2
View File
@@ -1,5 +1,7 @@
import './globals.css';
import 'fumadocs-ui/style.css';
import type { Metadata } from 'next';
import { RootProvider } from 'fumadocs-ui/provider';
import { Header } from '../components/Header';
export const metadata: Metadata = {
@@ -9,10 +11,12 @@ export const metadata: Metadata = {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh">
<body>
<html lang="zh" suppressHydrationWarning>
<body className="flex flex-col min-h-screen">
<RootProvider>
<Header />
{children}
</RootProvider>
</body>
</html>
);
+1 -1
View File
@@ -45,7 +45,7 @@ export default function LandingPage() {
24 React components · CSS Grid + Multi-column · / English /
</p>
<div style={{ display: 'flex', justifyContent: 'center', gap: '1rem', fontFamily: 'var(--font-family-meta)', fontSize: '13px' }}>
<Link href="/grid-system" style={{ color: 'var(--nui-accent-primary)', textDecoration: 'none', fontWeight: 600 }}>Documentation </Link>
<Link href="/docs/grid-system" style={{ color: 'var(--nui-accent-primary)', textDecoration: 'none', fontWeight: 600 }}>Documentation </Link>
<Link href="/create" style={{ color: 'var(--nui-text-secondary)', textDecoration: 'none' }}>Create Theme</Link>
<Link href="/blocks" style={{ color: 'var(--nui-text-secondary)', textDecoration: 'none' }}>All Blocks</Link>
<a href="https://github.com/joisun/newspaperui" style={{ color: 'var(--nui-text-muted)', textDecoration: 'none' }}>GitHub</a>
+3 -3
View File
@@ -3,9 +3,9 @@ import Link from 'next/link';
import { usePathname } from 'next/navigation';
const navItems = [
{ label: 'Docs', href: '/grid-system' },
{ label: 'Components', href: '/components/article' },
{ label: 'Themes', href: '/theme' },
{ label: 'Docs', href: '/docs/grid-system' },
{ label: 'Components', href: '/docs/components/article' },
{ label: 'Themes', href: '/docs/theme' },
{ label: 'Blocks', href: '/blocks' },
{ label: 'Create', href: '/create' },
];
-174
View File
@@ -1,174 +0,0 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
function cx(...args: (string | false | null | undefined)[]): string {
return args.filter(Boolean).join(' ');
}
interface NavItem {
label: string;
href: string;
children?: NavItem[];
}
const nav: NavItem[] = [
{ label: '概览', href: '/' },
{ label: '栅格系统', href: '/grid-system' },
{
label: '布局组件',
href: '/components/article',
children: [
{ label: 'Layout', href: '/components/article' },
{ label: 'Section', href: '/components/article' },
{ label: 'Article', href: '/components/article' },
{ label: 'Layer', href: '/components/article' },
{ label: 'Masthead', href: '/components/masthead' },
{ label: 'Rule', href: '/components/rule' },
{ label: 'Footer', href: '/components/rule' },
{ label: 'Sidebar', href: '/components/rule' },
{ label: 'BreakingNewsBanner', href: '/components/rule' },
{ label: 'Folio', href: '/components/rule' },
{ label: 'IndexBox', href: '/components/rule' },
{ label: 'Factbox', href: '/components/rule' },
],
},
{
label: '文本组件',
href: '/text',
children: [
{ label: 'Headline', href: '/text' },
{ label: 'Subhead', href: '/text' },
{ label: 'Kicker', href: '/text' },
{ label: 'BodyText', href: '/text' },
{ label: 'Quote', href: '/text' },
{ label: 'Byline', href: '/text' },
{ label: 'Dateline', href: '/text' },
{ label: 'Caption', href: '/text' },
{ label: 'AuthorCard', href: '/text' },
{ label: 'JumpLine', href: '/text' },
],
},
{
label: '媒体组件',
href: '/components/media',
children: [
{ label: 'Image', href: '/components/media' },
{ label: 'Figure', href: '/components/media' },
{ label: 'Video', href: '/components/media' },
{ label: 'PullQuote', href: '/components/media' },
{ label: 'RelatedArticles', href: '/components/media' },
],
},
{ label: '主题与颜色', href: '/theme' },
{ label: 'Create 主题', href: '/create' },
{ label: 'Blocks', href: '/blocks' },
{
label: '示例',
href: '/examples/spanning',
children: [
{ label: '跨栏布局', href: '/examples/spanning' },
{ label: '响应式', href: '/examples/responsive' },
{ label: 'NYT 头版', href: '/examples/nyt-frontpage' },
{ label: 'Blackletter', href: '/examples/blackletter-frontpage' },
],
},
];
export function Sidebar() {
const pathname = usePathname();
return (
<aside
style={{
width: '240px',
flexShrink: 0,
padding: '2rem 1rem',
borderRight: '1px solid var(--nui-rule-hairline)',
background: 'var(--nui-bg-surface)',
fontFamily: 'var(--font-family-meta)',
fontSize: '14px',
position: 'sticky',
top: 65,
alignSelf: 'flex-start',
maxHeight: 'calc(100vh - 65px)',
overflowY: 'auto',
}}
>
<Link
href="/"
style={{
display: 'block',
fontFamily: 'var(--font-family-masthead)',
fontSize: '22px',
fontWeight: 700,
color: 'var(--nui-text-primary)',
textDecoration: 'none',
marginBottom: '0.25rem',
letterSpacing: '0.02em',
}}
>
NewspaperUI
</Link>
<div
className="nui-small-caps"
style={{
fontSize: '11px',
color: 'var(--nui-text-muted)',
marginBottom: '2rem',
letterSpacing: '0.08em',
}}
>
Production Newspaper Components
</div>
<nav>
<ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>
{nav.map((item) => (
<li key={item.href} style={{ marginBottom: '0.75rem' }}>
<Link
href={item.href}
className={cx(pathname === item.href && 'active')}
style={{
display: 'block',
padding: '0.25rem 0',
color:
pathname === item.href
? 'var(--nui-accent-primary)'
: 'var(--nui-text-secondary)',
textDecoration: 'none',
fontWeight: pathname === item.href ? 600 : 400,
}}
>
{item.label}
</Link>
{item.children && (
<ul style={{ listStyle: 'none', padding: '0 0 0 1rem', margin: '0.25rem 0 0' }}>
{item.children.map((child) => (
<li key={child.href} style={{ marginBottom: '0.25rem' }}>
<Link
href={child.href}
style={{
display: 'block',
padding: '0.15rem 0',
fontSize: '13px',
color:
pathname === child.href
? 'var(--nui-accent-primary)'
: 'var(--nui-text-muted)',
textDecoration: 'none',
fontWeight: pathname === child.href ? 600 : 400,
}}
>
{child.label}
</Link>
</li>
))}
</ul>
)}
</li>
))}
</ul>
</nav>
</aside>
);
}
@@ -0,0 +1,67 @@
---
title: Article + Layer
description: 栅格内的文章块与浮动层组件
---
## Article
Article 是栅格内的文章块,通过 `grid-column span` 跨栏。
### Article API
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `span` | `number` | - | 跨多少列(≤ Section.columns)。缺省时占满全宽。 |
| `breakable` | `boolean` | `true` | 允许打印时跨页断开。 |
| `className` | `string` | - | 自定义 CSS 类名。 |
| `style` | `CSSProperties` | - | 自定义内联样式。 |
| `children` | `ReactNode` | **必填** | 文章内容。 |
### 跨栏示例
```tsx
<Layout columns={24}>
<Section columns={24}>
<Article span={8}>
<Headline weight="Low">短讯</Headline>
<BodyText>...</BodyText>
</Article>
<Article span={16}>
<Headline weight="Medium">主稿</Headline>
<BodyText columns={2}>...</BodyText>
</Article>
</Section>
</Layout>
```
## Layer
Layer 脱离栅格流,使用 CSS position 定位。适合浮动拉引、浮动广告、sticky 导航等场景。
### Layer API
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `position` | `'absolute' \| 'fixed' \| 'sticky'` | `'absolute'` | CSS position 属性。 |
| `top` | `string \| number` | - | 距离顶部的距离。 |
| `left` | `string \| number` | - | 距离左侧的距离。 |
| `right` | `string \| number` | - | 距离右侧的距离。 |
| `bottom` | `string \| number` | - | 距离底部的距离。 |
| `zIndex` | `number` | - | z-index 层级。 |
| `children` | `ReactNode` | **必填** | 浮动层内容。 |
### Layer 浮动拉引示例
在文章旁边添加浮动的拉引文字。父容器需要 `position: relative`。
```tsx
<div style={{ position: 'relative', minHeight: '200px' }}>
<Article span={16}>
<Headline weight="Low">主要文章</Headline>
<BodyText>...</BodyText>
</Article>
<Layer position="absolute" top="0" right="0">
<aside>浮动内容</aside>
</Layer>
</div>
```
@@ -0,0 +1,65 @@
---
title: Masthead 报头
description: 报纸门面组件,三种视觉风格
---
## 概述
报头是报纸的门面。三个 variant 覆盖经典双线居中、哥特体、现代左对齐三种风格。
## API
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `title` | `string` | **必填** | 报名文字。 |
| `variant` | `'classic' \| 'blackletter' \| 'modern'` | `'classic'` | 视觉风格。 |
| `kicker` | `string` | - | 报名上方的小标(如 "All the News That's Fit to Print")。 |
| `edition` | `string` | - | 版次(如 "Vol. CLXXII No. 59,678")。 |
| `date` | `string` | - | 日期(如 "Monday, May 19, 2026")。 |
| `price` | `string` | - | 售价(如 "$3.00")。 |
## Classic
双线居中,Cormorant Garamond 衬线。
```tsx
<Masthead
variant="classic"
title="The Daily Chronicle"
kicker="All the News That's Fit to Print"
edition="Vol. CLXXII No. 59,678"
date="Monday, May 19, 2026"
price="$3.00"
/>
```
## Blackletter
UnifrakturMaguntia 哥特体,The Times 风格。
```tsx
<Masthead
variant="blackletter"
title="The Evening Standard"
edition="Late Final Edition"
date="Monday, May 19, 2026"
price="£2.50"
/>
```
## Modern
左对齐 + accent 强调色,适合数字优先的出版物。
```tsx
<Masthead
variant="modern"
title="The Observer"
kicker="Sunday Edition"
date="May 19, 2026"
/>
```
## 使用建议
Masthead 默认 `gridColumn: 1 / -1`,在 Section 内自动全宽。通常放在 Layout 的第一个 Section 中,后接 Rule 分隔线。
@@ -0,0 +1,78 @@
---
title: 媒体组件
description: Image、Figure、Video、PullQuote — 报纸版面中的视觉元素
---
## Image
基础图片组件。`display: block; width: 100%`,在 Section 栅格内通过 span 跨栏。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `src` | `string` | **必填** | 图片 URL。 |
| `alt` | `string` | **必填** | 替代文本。 |
| `span` | `number` | - | 跨栏数。 |
## Figure
组合 Image + Caption + Credit 的语义容器。自动渲染 figcaption。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `src` | `string` | **必填** | 图片 URL。 |
| `alt` | `string` | **必填** | 替代文本。 |
| `caption` | `string` | - | 图片说明。 |
| `credit` | `string` | - | 摄影师/来源(small-caps 渲染)。 |
| `span` | `number` | - | 跨栏数。 |
```tsx
<Figure
src="https://images.unsplash.com/photo-1495020689067-958852a7765e?w=800&h=400&fit=crop"
alt="Newspapers on a wooden table"
caption="A view of the morning editions, arranged on the newsroom table."
credit="Photograph by Roman Kraft / Unsplash"
/>
```
## Video
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `src` | `string` | **必填** | 视频 URL。 |
| `poster` | `string` | - | 封面图 URL。 |
| `caption` | `string` | - | 视频说明。 |
| `credit` | `string` | - | 来源。 |
| `controls` | `boolean` | `true` | 显示播放控件。 |
| `span` | `number` | - | 跨栏数。 |
Video 组件与 Figure 结构类似,包裹 `<video>` 标签并附带 caption/credit。
## PullQuote
上下双 hairline + Display 字体的拉引组件。在多栏 BodyText 内设置 `spanAllColumns` 可跨所有栏(通过 `column-span: all`)。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `weight` | `'High' \| 'Medium'` | `'High'` | 视觉权重。 |
| `span` | `number` | - | 在 Section 栅格内跨栏。 |
| `spanAllColumns` | `boolean` | `false` | 在多栏 BodyText 内跨所有栏。 |
| `author` | `string` | - | 引用来源。 |
| `align` | `'left' \| 'center'` | `'left'` | 文本对齐。 |
### 独立使用
```tsx
<PullQuote author="Senior Cabinet Official">
We have lobbied for this kind of clarity for the better part of a decade.
</PullQuote>
```
### 在多栏 BodyText 内跨栏
```tsx
<BodyText columns={3}>
<p>...</p>
<PullQuote spanAllColumns author="...">...</PullQuote>
<p>...</p>
</BodyText>
```
@@ -0,0 +1,4 @@
{
"title": "Components",
"pages": ["article", "masthead", "rule", "media"]
}
@@ -0,0 +1,42 @@
---
title: Rule 分隔线
description: 显式分隔线组件,支持三种线型和两种方向
---
## 概述
显式分隔线组件。三个 variant 覆盖 hairline、double、thick,支持水平和垂直方向。
## API
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `variant` | `'hairline' \| 'double' \| 'thick'` | `'hairline'` | 线型。 |
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | 方向。 |
| `span` | `number` | - | 横向时占多少列。缺省时 1 / -1 全跨。 |
| `className` | `string` | - | 自定义 CSS 类名。 |
| `style` | `CSSProperties` | - | 自定义内联样式。 |
## 水平方向三种 variant
```tsx
<Rule variant="hairline" />
<Rule variant="double" />
<Rule variant="thick" />
```
## 垂直方向
在两栏之间放置垂直 Rule
```tsx
<div style={{ display: 'flex', gap: '1.5rem', height: '120px' }}>
<div>左栏内容</div>
<Rule variant="hairline" orientation="vertical" />
<div>右栏内容</div>
</div>
```
## 使用建议
Rule 在 Section 栅格内默认 `gridColumn: 1 / -1` 全跨。如果只需要跨部分列,传入 `span` prop。垂直 Rule 适合放在 flex 容器中作为栏间分隔。
@@ -0,0 +1,4 @@
{
"title": "Examples",
"pages": ["spanning", "responsive"]
}
@@ -0,0 +1,65 @@
---
title: 响应式设计
description: 基于 CSS media query + container query 的响应式策略
---
NewspaperUI 的响应式策略完全基于 CSS media query + container query,不依赖 JavaScript 运行时计算。
## 断点说明
三个核心断点覆盖移动、平板、桌面:
| 断点 | 宽度 | 栏数建议 | 说明 |
|---|---|---|---|
| Mobile | 小于 768px | 1 栏(全宽) | 单栏堆叠,BodyText columns=1 |
| Tablet | 768px - 1024px | 12 列 | 两栏布局,BodyText columns=2 |
| Desktop | 大于 1024px | 24 列 | 完整多栏布局 |
## 实施手法
NewspaperUI 不再使用 JavaScript responsive 函数。所有响应式行为通过 CSS media query 和 container query 实现,零运行时开销。
Section 的 `gridTemplateColumns` 是 inline style,因此需要在消费侧用 CSS 覆盖。推荐做法:
### Media Query 方案
```css
/* 在你的 globals.css 或组件 CSS 中 */
@media (max-width: 768px) {
.nui-section {
grid-template-columns: 1fr !important;
}
.nui-article {
grid-column: span 1 !important;
}
}
@media (min-width: 769px) and (max-width: 1024px) {
.nui-section[data-columns="24"] {
grid-template-columns: repeat(12, 1fr) !important;
}
}
```
### Container Query 方案
```css
/* 给 Section 添加 container-type */
.nui-section {
container-type: inline-size;
}
@container (max-width: 600px) {
.nui-article {
grid-column: 1 / -1 !important;
}
}
```
## 视口效果
- **Desktop (1024px+)**: 24 列完整布局,6 + 12 + 6 三栏
- **Tablet (768-1024px)**: 12 列简化,两栏并列
- **Mobile (小于 768px)**: 单栏堆叠,所有 Article 全宽
实际项目中建议使用浏览器 DevTools 的 responsive mode 进行测试。
@@ -0,0 +1,83 @@
---
title: 跨栏布局示例
description: 五种常见栏宽组合,演示 24 列栅格的灵活性
---
五种常见栏宽组合,演示 24 列栅格的灵活性。
## 8 + 16
短讯 + 主稿:
```tsx
<Layout columns={24}>
<Section columns={24}>
<Article span={8}>
<Headline weight="Low">Briefing</Headline>
<BodyText weight="Low"><p>Short news items.</p></BodyText>
</Article>
<Article span={16}>
<Headline weight="Medium">Infrastructure Review Tabled</Headline>
<BodyText><p>Main article content...</p></BodyText>
</Article>
</Section>
</Layout>
```
## 6 + 12 + 6
三栏对称布局,中间主体 + 两侧辅助:
```tsx
<Layout columns={24}>
<Section columns={24}>
<Article span={6}>Weather</Article>
<Article span={12}>Chancellor Outlines Spending Envelope</Article>
<Article span={6}>Markets</Article>
</Section>
</Layout>
```
## 12 + 12
对称双栏,适合对比报道或并列文章:
```tsx
<Layout columns={24}>
<Section columns={24}>
<Article span={12}>View from Westminster</Article>
<Article span={12}>View from the Regions</Article>
</Section>
</Layout>
```
## 4 + 16 + 4
窄侧栏 + 宽主体,适合带注释的长文:
```tsx
<Layout columns={24}>
<Section columns={24}>
<Article span={4}>Editor's note</Article>
<Article span={16}>A Standing Technical Commission</Article>
<Article span={4}>Related reading</Article>
</Section>
</Layout>
```
## 24 全宽
单篇全宽文章,适合社论或特稿:
```tsx
<Layout columns={24}>
<Section columns={24}>
<Article span={24}>
<Headline weight="High">The Quiet Revolution in Treasury Forecasting</Headline>
<BodyText columns={4} dropCap>
<p>...</p>
</BodyText>
</Article>
</Section>
</Layout>
```
@@ -0,0 +1,89 @@
---
title: 栅格系统
description: 24 列响应式栅格,CSS Grid 实现
---
## 概念
NewspaperUI 使用 24 列栅格系统。24 列由 `Layout` 提供上下文,`Section` 在栅格内分配 `grid-template-columns``Article` 用 `grid-column span` 跨栏。所有跨栏统一通过 CSS Grid 完成。
报纸排版偏好 24 列是因为它能整除 2 / 3 / 4 / 6 / 8 / 12,足够灵活地实现 1/3、2/3、1/4、3/4 这类常见栏宽组合。
## Layout API
`Layout` 是顶层容器,提供 LayoutContext。Section 通过 useLayout 读取列数上限并自动 clamp。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `columns` | `number` | `24` | 栅格总列数。决定子 Section 的最大 columns。 |
| `maxWidth` | `string` | `'1280px'` | 页面最大宽度。 |
| `padding` | `string` | `'var(--nui-space-6)'` | 页面内边距。 |
| `theme` | `'light' \| 'dark'` | - | 强制主题;省略时跟随 documentElement.dataset.theme。 |
| `children` | `ReactNode` | **必填** | 子内容(通常是若干 Section)。 |
## Section API
`Section` 是栅格容器,`display: grid; grid-template-columns: repeat(N, 1fr)`。子 Article 通过 grid-column span 占位。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `columns` | `number` | **必填** | Section 内部栅格列数(≤ Layout.columns)。 |
| `gap` | `string` | `'var(--nui-gutter)'` | 栏间间距。 |
| `breakable` | `boolean` | `true` | 允许打印时跨页断开。 |
| `divider` | `'none' \| 'top' \| 'bottom' \| 'both'` | `'none'` | 上下方向的 hairline 分隔线。 |
## Article API
`Article` 在 Section 栅格内用 `grid-column span N` 占位。span 缺省时占满 Section 全宽。
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| `span` | `number` | - | 跨多少列(0 &lt; span ≤ Section.columns)。 |
| `breakable` | `boolean` | `true` | 允许打印时跨页断开。 |
| `children` | `ReactNode` | **必填** | 文章内容。 |
## 用法示例
### 8 + 16 跨栏
左侧短讯 8 列 + 右侧主稿 16 列:
```tsx
<Layout columns={24}>
<Section columns={24}>
<Article span={8}>
<Headline weight="Low">短讯</Headline>
<BodyText>...</BodyText>
</Article>
<Article span={16}>
<Headline weight="Medium">主稿</Headline>
<BodyText>...</BodyText>
</Article>
</Section>
</Layout>
```
### 6 + 12 + 6 三栏对称
```tsx
<Layout columns={24}>
<Section columns={24}>
<Article span={6}>侧栏</Article>
<Article span={12}>主体</Article>
<Article span={6}>侧栏</Article>
</Section>
</Layout>
```
### 24 全宽
```tsx
<Layout columns={24}>
<Section columns={24}>
<Article span={24}>
<Headline weight="High">全宽文章</Headline>
<BodyText columns={4} dropCap>...</BodyText>
</Article>
</Section>
</Layout>
```
+14
View File
@@ -0,0 +1,14 @@
{
"title": "Documentation",
"pages": [
"grid-system",
"---Components---",
"...components",
"---Text---",
"...text",
"---Theme---",
"...theme",
"---Examples---",
"...examples"
]
}
+140
View File
@@ -0,0 +1,140 @@
---
title: 文本组件
description: 从 Headline 到 Caption 的完整文本谱系
---
从 Headline 到 Caption 的完整文本谱系。每个组件按视觉权重映射到字体、字号、字重、行高与颜色 token,避免硬编码样式。
## Headline
三档视觉权重:High / Medium / Low。
```tsx
<Headline weight="High" as="h1">
A Quiet Revolution in the Treasury Forecast
</Headline>
<Headline weight="Medium" as="h2">
Whitehall Confirms Infrastructure Review
</Headline>
<Headline weight="Low" as="h3">
Briefing: regional rail commitments
</Headline>
```
## Subhead
两档视觉权重:High / Medium。
```tsx
<Subhead weight="High">
A measured recalibration of regional priorities is expected to define
the chancellor's autumn agenda.
</Subhead>
<Subhead weight="Medium">
Officials emphasized that the headline figures should be read as a planning
envelope rather than a binding allocation.
</Subhead>
```
## Kicker
朱红 small-caps,挂在 Headline 上方。
```tsx
<Kicker>POLITICS · WHITEHALL</Kicker>
<Headline weight="Medium" as="h3">
A Standing Technical Commission, Quietly Proposed
</Headline>
```
## BodyText
多栏文字流组件,支持 `columns` 和 `dropCap`。
### 单栏(默认)
```tsx
<BodyText>
<p>默认形态,适合中等宽度的文章正文。</p>
</BodyText>
```
### 三栏 + 首字下沉
开启 `columns={3}` 与 `dropCap`,第一段首字母自动下沉占 2-3 行。
```tsx
<BodyText columns={3} dropCap>
<p>...</p>
<p>...</p>
</BodyText>
```
### 两栏
`columns={2}`,栏间细线由 `nui-column-rule` 提供。
```tsx
<BodyText columns={2}>
<p>...</p>
<p>...</p>
</BodyText>
```
## Quote
block / inline 两种变体。
```tsx
{/* Block quote */}
<Quote variant="block">
We have lobbied for this kind of clarity for the better part of a decade.
</Quote>
{/* Inline quote */}
<BodyText>
<p>
按一位资深内阁人士的说法,这份评估
<Quote variant="inline">是一个时代以来最连贯的战略蓝图</Quote>
,但能否落地仍取决于春季立法。
</p>
</BodyText>
```
## Byline / Dateline / Caption
元信息组件。
```tsx
<Byline>BY ALICE SMITH</Byline>
<Dateline>LONDON —</Dateline>
<figure>
<img src="..." alt="..." />
<Caption credit="Photograph by Jane Doe">
A view of the Treasury terrace at dusk; the new commission will report here from May.
</Caption>
</figure>
```
## 视觉权重映射
所有文本组件的样式由 `visualWeights` 数据驱动,修改 theme 后自动同步。映射关系:
| 组件 | 权重 | 字体 | 字号 | 字重 | 行高 |
|---|---|---|---|---|---|
| Headline | High | `--font-family-display` | 48px | 700 | 1.05 |
| Headline | Medium | `--font-family-headline` | 32px | 700 | 1.15 |
| Headline | Low | `--font-family-headline` | 22px | 600 | 1.25 |
| Subhead | High | `--font-family-headline` | 22px | 400 | 1.35 |
| Subhead | Medium | `--font-family-headline` | 18px | 400 | 1.4 |
| Kicker | - | `--font-family-meta` | 12px | 700 | 1.2 |
| BodyText | High | `--font-family-body` | 17px | 400 | 1.65 |
| BodyText | Medium | `--font-family-body` | 16px | 400 | 1.6 |
| BodyText | Low | `--font-family-body` | 15px | 400 | 1.55 |
| Byline | - | `--font-family-meta` | 12px | 600 | 1.2 |
| Dateline | - | `--font-family-meta` | 13px | 700 | 1.2 |
| Caption | - | `--font-family-meta` | 12px | 400 | 1.4 |
@@ -0,0 +1,4 @@
{
"title": "Text",
"pages": ["index"]
}
+51
View File
@@ -0,0 +1,51 @@
---
title: 主题与颜色
description: 暖灰系 + warm off-white 设计系统
---
设计哲学:暖灰系 + warm off-white。不使用纯黑(#000)也不使用纯白(#FFF),把屏幕配色带回纸面的温度。
## 主题切换
深色基调采用暖深棕黑(#14110D)而非冷蓝黑,保持报纸的纸性触感。通过 `document.documentElement.dataset.theme = 'dark'` 切换。
## 文字色 Token
| Token | 色值 | 用途 |
|---|---|---|
| `--nui-text-primary` | #1A1A1A | 标题、主文本 |
| `--nui-text-body` | #22201C | 正文 |
| `--nui-text-secondary` | #4A4742 | Subhead、次级 |
| `--nui-text-muted` | #6E6A63 | Caption、注释 |
| `--nui-text-quote` | #2E2A24 | Quote 主体 |
## 背景与分隔线 Token
| Token | 色值 | 用途 |
|---|---|---|
| `--nui-bg-page` | #F7F4ED | Warm off-white 页面底 |
| `--nui-bg-surface` | #FBF9F4 | 次级面板背景 |
| `--nui-rule-hairline` | #C9C2B2 | 细线分隔 |
| `--nui-rule-decorative` | #1A1A1A | 强调线 |
| `--nui-highlight` | #F2E9C8 | 旧报纸黄 |
## 强调色 Token
| Token | 色值 | 用途 |
|---|---|---|
| `--nui-accent-primary` | #7A1F1F | Brick red, Kicker / Masthead 强调 |
| `--nui-accent-ink-blue` | #1B2A4A | The Times 蓝 |
| `--nui-highlight` | #F2E9C8 | 高亮底色 |
## 字体家族
全部经典严肃风字体:Cormorant Garamond 承担报头与展示,Source Serif 4 贯穿正文与标题,Inter 处理 small-caps 元信息。Blackletter preset 通过 UnifrakturMaguntia 切入哥特报头。
| Token | 字体 | 用途 |
|---|---|---|
| `--font-family-masthead` | Cormorant Garamond | 报头 |
| `--font-family-blackletter` | UnifrakturMaguntia | Blackletter preset |
| `--font-family-display` | Source Serif 4 | Display 大字头条 |
| `--font-family-headline` | Source Serif 4 | Headline / Subhead |
| `--font-family-body` | Source Serif 4 | 正文 |
| `--font-family-meta` | Inter | small-caps 元信息 |
+4
View File
@@ -0,0 +1,4 @@
{
"title": "Theme",
"pages": ["index"]
}
+8
View File
@@ -0,0 +1,8 @@
import { docs, meta } from '../.source';
import { loader } from 'fumadocs-core/source';
import { createMDXSource } from 'fumadocs-mdx';
export const source = loader({
baseUrl: '/docs',
source: createMDXSource(docs, meta),
}) as ReturnType<typeof loader>;
+2
View File
@@ -1,7 +1,9 @@
import type { MDXComponents } from 'mdx/types';
import defaultMdxComponents from 'fumadocs-ui/mdx';
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...defaultMdxComponents,
...components,
};
}
+2 -17
View File
@@ -1,28 +1,13 @@
import createMDX from '@next/mdx';
import rehypePrettyCode from 'rehype-pretty-code';
import { createMDX } from 'fumadocs-mdx/next';
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
images: { unoptimized: true },
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
transpilePackages: ['newspaperui'],
};
const withMDX = createMDX({
extension: /\.mdx?$/,
options: {
rehypePlugins: [
[
rehypePrettyCode,
{
theme: 'github-dark',
keepBackground: false,
},
],
],
},
});
const withMDX = createMDX();
export default withMDX(nextConfig);
+6 -2
View File
@@ -15,13 +15,17 @@
"@mdx-js/react": "^3.1.1",
"@next/mdx": "^16.2.6",
"@types/mdx": "^2.0.13",
"fumadocs-core": "14.7.7",
"fumadocs-mdx": "10.1.0",
"fumadocs-ui": "14.7.7",
"newspaperui": "workspace:*",
"newspaperui-theme": "workspace:*",
"next": "^15.1.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rehype-pretty-code": "^0.14.3",
"shiki": "^4.1.0",
"newspaperui": "workspace:*",
"newspaperui-theme": "workspace:*"
"zod": "3"
},
"devDependencies": {
"@types/node": "^22.10.5",
+7
View File
@@ -0,0 +1,7 @@
import { defineDocs, defineConfig } from 'fumadocs-mdx/config';
export const { docs, meta } = defineDocs({
dir: 'content/docs',
});
export default defineConfig();
+3 -2
View File
@@ -5,12 +5,13 @@
"allowJs": true,
"noEmit": true,
"incremental": true,
"moduleResolution": "node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"paths": {
"@/*": ["./*"]
"@/*": ["./*"],
"@/.source": ["./.source/index.ts"]
},
"plugins": [
{
+1747
View File
File diff suppressed because it is too large Load Diff