feat: responsive system, engineering infra, new components, performance

- Section: responsive prop with media query injection
- visual-weights: fontSize clamp() for responsive sizing
- variables.css: add border-radius/shadow/transition/z-index tokens
- ESLint flat config + Prettier + Changeset init
- New components: Footer, NewsSidebar, BreakingNewsBanner
- Image/Figure: loading=lazy, aspectRatio, sizes props
This commit is contained in:
sunzhongyi
2026-05-21 10:04:35 +08:00
parent 184353cfb0
commit 5f65d741ed
54 changed files with 2759 additions and 101 deletions
+24 -6
View File
@@ -1,7 +1,7 @@
{
"name": "@newspaperui/components",
"version": "0.0.0",
"description": "React components for newspaperui",
"name": "newspaperui-components",
"version": "0.1.0",
"description": "Production-grade newspaper layout React components",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
@@ -29,8 +29,8 @@
"react-dom": "^18.3.1"
},
"dependencies": {
"@newspaperui/theme": "workspace:*",
"@newspaperui/utils": "workspace:*"
"newspaperui-theme": "workspace:*",
"newspaperui-utils": "workspace:*"
},
"devDependencies": {
"@testing-library/react": "^16.1.0",
@@ -45,5 +45,23 @@
"vite": "^5.4.11",
"vite-plugin-dts": "^4.3.0",
"vitest": "^2.1.8"
},
"license": "MIT",
"author": "sunzhongyi",
"repository": {
"type": "git",
"url": "https://github.com/joisun/newspaperui.git"
},
"keywords": [
"newspaper",
"react",
"components",
"layout",
"typography",
"css-grid",
"multi-column"
],
"publishConfig": {
"access": "public"
}
}
}
+7 -1
View File
@@ -1,4 +1,4 @@
import '@newspaperui/theme';
import 'newspaperui-theme';
// layout
export { Layout, useLayout } from './layout/Layout';
@@ -13,6 +13,12 @@ export { Masthead } from './layout/Masthead';
export type { MastheadProps } from './layout/Masthead';
export { Rule } from './layout/Rule';
export type { RuleProps } from './layout/Rule';
export { Footer } from './layout/Footer';
export type { FooterProps } from './layout/Footer';
export { Sidebar as NewsSidebar } from './layout/Sidebar';
export type { SidebarProps as NewsSidebarProps } from './layout/Sidebar';
export { BreakingNewsBanner } from './layout/BreakingNewsBanner';
export type { BreakingNewsBannerProps } from './layout/BreakingNewsBanner';
// text
export { Headline } from './text/Headline';
+1 -1
View File
@@ -1,6 +1,6 @@
'use client';
import React, { ReactNode, CSSProperties } from 'react';
import { clampSpan, cx } from '@newspaperui/utils';
import { clampSpan, cx } from 'newspaperui-utils';
import { useSection } from './Section';
export interface ArticleProps {
@@ -0,0 +1,48 @@
'use client';
import React, { ReactNode, CSSProperties } from 'react';
import { cx } from 'newspaperui-utils';
export interface BreakingNewsBannerProps {
label?: string;
className?: string;
style?: CSSProperties;
children: ReactNode;
}
/**
* BreakingNewsBanner — 突发新闻横幅
*
* @example
* <BreakingNewsBanner label="BREAKING">
* Major earthquake strikes coastal region
* </BreakingNewsBanner>
*/
export const BreakingNewsBanner: React.FC<BreakingNewsBannerProps> = ({
label = 'BREAKING', className, style, children,
}) => (
<div
className={cx('nui-breaking', className)}
role="alert"
style={{
gridColumn: '1 / -1',
background: 'var(--nui-accent-primary)',
color: 'var(--nui-bg-page)',
padding: 'var(--nui-space-3) var(--nui-space-6)',
fontFamily: 'var(--font-family-meta)',
fontSize: '13px',
fontWeight: 600,
display: 'flex',
alignItems: 'center',
gap: 'var(--nui-space-4)',
...style,
}}
>
<span style={{
fontVariantCaps: 'small-caps',
letterSpacing: '0.1em',
fontWeight: 700,
flexShrink: 0,
}}>{label}</span>
<span style={{ flex: 1 }}>{children}</span>
</div>
);
+57
View File
@@ -0,0 +1,57 @@
'use client';
import React, { ReactNode, CSSProperties } from 'react';
import { cx } from 'newspaperui-utils';
export interface FooterProps {
copyright?: string;
edition?: string;
links?: Array<{ label: string; href: string }>;
className?: string;
style?: CSSProperties;
children?: ReactNode;
}
/**
* Footer — 报纸页脚/版权信息区
*
* @example
* <Footer copyright="© 2026 The Daily Chronicle" edition="Vol. CXLIX" />
*/
export const Footer: React.FC<FooterProps> = ({
copyright, edition, links, className, style, children,
}) => (
<footer
className={cx('nui-footer nui-small-caps', className)}
style={{
gridColumn: '1 / -1',
borderTop: '2px solid var(--nui-rule-decorative)',
paddingTop: 'var(--nui-space-4)',
marginTop: 'var(--nui-space-8)',
fontFamily: 'var(--font-family-meta)',
fontSize: '11px',
letterSpacing: '0.06em',
color: 'var(--nui-text-muted)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexWrap: 'wrap',
gap: 'var(--nui-space-4)',
...style,
}}
>
<div>
{copyright && <span>{copyright}</span>}
{edition && <span style={{ marginLeft: 'var(--nui-space-4)' }}>{edition}</span>}
</div>
{links && (
<nav style={{ display: 'flex', gap: 'var(--nui-space-4)' }}>
{links.map(link => (
<a key={link.href} href={link.href} style={{ color: 'var(--nui-text-muted)', textDecoration: 'none' }}>
{link.label}
</a>
))}
</nav>
)}
{children}
</footer>
);
+1 -1
View File
@@ -1,6 +1,6 @@
'use client';
import React from 'react';
import { cx } from '@newspaperui/utils';
import { cx } from 'newspaperui-utils';
export interface MastheadProps {
title: string;
+1 -1
View File
@@ -1,6 +1,6 @@
'use client';
import React, { CSSProperties } from 'react';
import { cx } from '@newspaperui/utils';
import { cx } from 'newspaperui-utils';
export interface RuleProps {
variant?: 'hairline' | 'double' | 'thick';
+13 -4
View File
@@ -1,6 +1,6 @@
'use client';
import React, { createContext, useContext, ReactNode, CSSProperties } from 'react';
import { clampSpan, cx } from '@newspaperui/utils';
import React, { createContext, useContext, useId, ReactNode, CSSProperties } from 'react';
import { clampSpan, cx } from 'newspaperui-utils';
import { useLayout } from './Layout';
export interface SectionProps {
@@ -8,6 +8,7 @@ export interface SectionProps {
gap?: string; // 默认 'var(--nui-gutter)'
breakable?: boolean; // 是否允许 print 分页断开,默认 true
divider?: 'none' | 'top' | 'bottom' | 'both'; // hairline 分隔
responsive?: { sm?: number; md?: number; lg?: number }; // 响应式列数覆盖
className?: string;
style?: CSSProperties;
children: ReactNode;
@@ -32,10 +33,17 @@ export const useSection = () => useContext(SectionContext);
*/
export const Section: React.FC<SectionProps> = ({
columns, gap = 'var(--nui-gutter)', breakable = true, divider = 'none',
className, style, children,
responsive, className, style, children,
}) => {
const layout = useLayout();
const cols = clampSpan(columns, layout.columns);
const id = useId().replace(/:/g, '');
const sectionId = `nui-s-${id}`;
const responsiveCSS = responsive ? `
@media (max-width: 768px) { .${sectionId} { grid-template-columns: repeat(${responsive.sm ?? 12}, 1fr) !important; } }
@media (min-width: 769px) and (max-width: 1024px) { .${sectionId} { grid-template-columns: repeat(${responsive.md ?? 16}, 1fr) !important; } }
` : '';
const dividerStyle: CSSProperties = {};
if (divider === 'top' || divider === 'both')
@@ -45,8 +53,9 @@ export const Section: React.FC<SectionProps> = ({
return (
<SectionContext.Provider value={{ columns: cols }}>
{responsiveCSS && <style dangerouslySetInnerHTML={{ __html: responsiveCSS }} />}
<section
className={cx('nui-section', className)}
className={cx('nui-section', sectionId, className)}
style={{
display: 'grid',
gridTemplateColumns: `repeat(${cols}, 1fr)`,
@@ -0,0 +1,47 @@
'use client';
import React, { ReactNode, CSSProperties } from 'react';
import { clampSpan, cx } from 'newspaperui-utils';
import { useSection } from './Section';
export interface SidebarProps {
span?: number;
position?: 'left' | 'right';
divider?: boolean;
className?: string;
style?: CSSProperties;
children: ReactNode;
}
/**
* Sidebar — 侧边栏内容区(天气、股票、快讯摘要等)
*
* @example
* <Section columns={24}>
* <Article span={18}>...</Article>
* <Sidebar span={6} position="right" divider>...</Sidebar>
* </Section>
*/
export const Sidebar: React.FC<SidebarProps> = ({
span, position = 'right', divider = false, className, style, children,
}) => {
const section = useSection();
const cols = span ? clampSpan(span, section.columns) : 6;
const borderStyle: CSSProperties = divider
? position === 'left'
? { borderRight: '1px solid var(--nui-rule-hairline)', paddingRight: 'var(--nui-space-4)' }
: { borderLeft: '1px solid var(--nui-rule-hairline)', paddingLeft: 'var(--nui-space-4)' }
: {};
return (
<aside
className={cx('nui-sidebar', className)}
style={{
gridColumn: `span ${cols}`,
...borderStyle,
...style,
}}
>
{children}
</aside>
);
};
+21 -6
View File
@@ -1,17 +1,21 @@
'use client';
import React, { CSSProperties } from 'react';
import { clampSpan, cx } from '@newspaperui/utils';
import React, { ReactNode, CSSProperties } from 'react';
import { clampSpan, cx } from 'newspaperui-utils';
import { useSection } from '../layout/Section';
import { Caption } from '../text/Caption';
export interface FigureProps {
src: string;
alt: string;
src?: string;
alt?: string;
caption?: string;
credit?: string;
span?: number;
loading?: 'lazy' | 'eager';
aspectRatio?: string;
sizes?: string;
className?: string;
style?: CSSProperties;
children?: ReactNode;
}
/**
@@ -20,6 +24,7 @@ export interface FigureProps {
* - 将图片与 Caption 组合为语义化 figure 元素
* - 支持 caption 文字说明和 credit 来源标注
* - 自动避免 print 分页断开(break-inside: avoid
* - 支持 children 自定义内容替代 img
*
* @example
* <Figure
@@ -28,10 +33,12 @@ export interface FigureProps {
* caption="Downtown at dusk"
* credit="Photo by John"
* span={10}
* aspectRatio="16/9"
* />
*/
export const Figure: React.FC<FigureProps> = ({
src, alt, caption, credit, span, className, style,
src, alt, caption, credit, span, loading, aspectRatio, sizes,
className, style, children,
}) => {
const section = useSection();
const cols = span ? clampSpan(span, section.columns) : undefined;
@@ -44,7 +51,15 @@ export const Figure: React.FC<FigureProps> = ({
...style,
}}
>
<img src={src} alt={alt} style={{ display: 'block', width: '100%', height: 'auto' }} />
{src ? (
<img
src={src}
alt={alt ?? ''}
loading={loading ?? 'lazy'}
sizes={sizes}
style={{ display: 'block', width: '100%', height: 'auto', aspectRatio }}
/>
) : children}
{(caption || credit) && <Caption credit={credit}>{caption}</Caption>}
</figure>
);
+12 -3
View File
@@ -1,12 +1,15 @@
'use client';
import React, { CSSProperties } from 'react';
import { clampSpan, cx } from '@newspaperui/utils';
import { clampSpan, cx } from 'newspaperui-utils';
import { useSection } from '../layout/Section';
export interface ImageProps {
src: string;
alt: string;
span?: number;
loading?: 'lazy' | 'eager';
aspectRatio?: string;
sizes?: string;
className?: string;
style?: CSSProperties;
}
@@ -16,23 +19,29 @@ export interface ImageProps {
*
* - 响应式全宽图片,自动适配栅格列宽
* - 通过 span 控制在 Section 中占据的列数
* - 默认 lazy loading 优化性能
* - 必须提供 alt 属性以确保无障碍访问
*
* @example
* <Image src="/photo.jpg" alt="City skyline" span={12} />
* <Image src="/photo.jpg" alt="City skyline" span={12} aspectRatio="16/9" />
*/
export const Image: React.FC<ImageProps> = ({ src, alt, span, className, style }) => {
export const Image: React.FC<ImageProps> = ({
src, alt, span, loading, aspectRatio, sizes, className, style,
}) => {
const section = useSection();
const cols = span ? clampSpan(span, section.columns) : undefined;
return (
<img
src={src}
alt={alt}
loading={loading ?? 'lazy'}
sizes={sizes}
className={cx('nui-image', className)}
style={{
display: 'block',
width: '100%',
height: 'auto',
aspectRatio,
gridColumn: cols ? `span ${cols}` : undefined,
...style,
}}
+2 -2
View File
@@ -1,7 +1,7 @@
'use client';
import React, { ReactNode, CSSProperties } from 'react';
import { visualWeights, resolveFontSize } from '@newspaperui/theme';
import { clampSpan, cx } from '@newspaperui/utils';
import { visualWeights, resolveFontSize } from 'newspaperui-theme';
import { clampSpan, cx } from 'newspaperui-utils';
import { useSection } from '../layout/Section';
export interface PullQuoteProps {
+1 -1
View File
@@ -1,6 +1,6 @@
'use client';
import React, { CSSProperties } from 'react';
import { clampSpan, cx } from '@newspaperui/utils';
import { clampSpan, cx } from 'newspaperui-utils';
import { useSection } from '../layout/Section';
import { Caption } from '../text/Caption';
+2 -2
View File
@@ -1,7 +1,7 @@
'use client';
import React, { ReactNode, CSSProperties } from 'react';
import { visualWeights, resolveFontSize } from '@newspaperui/theme';
import { clampSpan, cx } from '@newspaperui/utils';
import { visualWeights, resolveFontSize } from 'newspaperui-theme';
import { clampSpan, cx } from 'newspaperui-utils';
import { useSection } from '../layout/Section';
export interface BodyTextProps {
+2 -2
View File
@@ -1,7 +1,7 @@
'use client';
import React, { ReactNode, CSSProperties } from 'react';
import { visualWeights, resolveFontSize } from '@newspaperui/theme';
import { cx } from '@newspaperui/utils';
import { visualWeights, resolveFontSize } from 'newspaperui-theme';
import { cx } from 'newspaperui-utils';
export interface BylineProps {
className?: string;
+2 -2
View File
@@ -1,7 +1,7 @@
'use client';
import React, { ReactNode, CSSProperties } from 'react';
import { visualWeights, resolveFontSize } from '@newspaperui/theme';
import { cx } from '@newspaperui/utils';
import { visualWeights, resolveFontSize } from 'newspaperui-theme';
import { cx } from 'newspaperui-utils';
export interface CaptionProps {
credit?: string; // e.g. "Photograph by Jane Doe"
+2 -2
View File
@@ -1,7 +1,7 @@
'use client';
import React, { ReactNode, CSSProperties } from 'react';
import { visualWeights, resolveFontSize } from '@newspaperui/theme';
import { cx } from '@newspaperui/utils';
import { visualWeights, resolveFontSize } from 'newspaperui-theme';
import { cx } from 'newspaperui-utils';
export interface DatelineProps {
className?: string;
+2 -2
View File
@@ -1,7 +1,7 @@
'use client';
import React, { ReactNode, CSSProperties } from 'react';
import { visualWeights, resolveFontSize } from '@newspaperui/theme';
import { clampSpan, cx } from '@newspaperui/utils';
import { visualWeights, resolveFontSize } from 'newspaperui-theme';
import { clampSpan, cx } from 'newspaperui-utils';
import { useSection } from '../layout/Section';
const weightToTag: Record<'High' | 'Medium' | 'Low', 'h1' | 'h2' | 'h3'> = {
+2 -2
View File
@@ -1,7 +1,7 @@
'use client';
import React, { ReactNode, CSSProperties } from 'react';
import { visualWeights, resolveFontSize } from '@newspaperui/theme';
import { cx } from '@newspaperui/utils';
import { visualWeights, resolveFontSize } from 'newspaperui-theme';
import { cx } from 'newspaperui-utils';
export interface KickerProps {
className?: string;
+2 -2
View File
@@ -1,7 +1,7 @@
'use client';
import React, { ReactNode, CSSProperties } from 'react';
import { visualWeights, resolveFontSize } from '@newspaperui/theme';
import { clampSpan, cx } from '@newspaperui/utils';
import { visualWeights, resolveFontSize } from 'newspaperui-theme';
import { clampSpan, cx } from 'newspaperui-utils';
import { useSection } from '../layout/Section';
export interface QuoteProps {
+2 -2
View File
@@ -1,7 +1,7 @@
'use client';
import React, { ReactNode, CSSProperties } from 'react';
import { visualWeights, resolveFontSize } from '@newspaperui/theme';
import { clampSpan, cx } from '@newspaperui/utils';
import { visualWeights, resolveFontSize } from 'newspaperui-theme';
import { clampSpan, cx } from 'newspaperui-utils';
import { useSection } from '../layout/Section';
export interface SubheadProps {
@@ -7,7 +7,7 @@ import {
Headline,
Subhead,
BodyText,
} from '@newspaperui/components';
} from 'newspaperui-components';
import { Demo } from '@/components/Demo';
import { PropsTable } from '@/components/PropsTable';
@@ -7,7 +7,7 @@ import {
Subhead,
BodyText,
Masthead,
} from '@newspaperui/components';
} from 'newspaperui-components';
import { Demo } from '@/components/Demo';
import { PropsTable } from '@/components/PropsTable';
@@ -8,7 +8,7 @@ import {
BodyText,
Figure,
PullQuote,
} from '@newspaperui/components';
} from 'newspaperui-components';
import { Demo } from '@/components/Demo';
import { PropsTable } from '@/components/PropsTable';
@@ -7,7 +7,7 @@ import {
Subhead,
BodyText,
Rule,
} from '@newspaperui/components';
} from 'newspaperui-components';
import { Demo } from '@/components/Demo';
import { PropsTable } from '@/components/PropsTable';
@@ -7,7 +7,7 @@ import {
Subhead,
BodyText,
Rule,
} from '@newspaperui/components';
} from 'newspaperui-components';
import { CodeBlock } from '@/components/CodeBlock';
export default function ResponsivePage() {
@@ -7,7 +7,7 @@ import {
Subhead,
BodyText,
Rule,
} from '@newspaperui/components';
} from 'newspaperui-components';
import { Demo } from '@/components/Demo';
export default function SpanningPage() {
@@ -6,7 +6,7 @@ import {
Headline,
Subhead,
BodyText,
} from '@newspaperui/components';
} from 'newspaperui-components';
import { Demo } from '@/components/Demo';
import { PropsTable } from '@/components/PropsTable';
+2 -2
View File
@@ -11,8 +11,8 @@ import {
Byline,
Dateline,
Caption,
} from '@newspaperui/components';
import { visualWeights, resolveFontSize } from '@newspaperui/theme';
} from 'newspaperui-components';
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.`;
+1 -1
View File
@@ -6,7 +6,7 @@ import {
Headline,
Subhead,
BodyText,
} from '@newspaperui/components';
} from 'newspaperui-components';
import { ThemeToggle } from '@/components/ThemeToggle';
interface Swatch {
+1 -1
View File
@@ -1,5 +1,5 @@
'use client';
import { Layout, Section, Article, Rule, Headline, Subhead, Kicker, BodyText, Byline, Dateline, Figure, PullQuote } from '@newspaperui/components';
import { Layout, Section, Article, Rule, Headline, Subhead, Kicker, BodyText, Byline, Dateline, Figure, PullQuote } from 'newspaperui-components';
export default function EnFeature() {
return (
@@ -1,5 +1,5 @@
'use client';
import { Layout, Section, Article, Rule, BodyText, Figure } from '@newspaperui/components';
import { Layout, Section, Article, Rule, BodyText, Figure } from 'newspaperui-components';
const jp = { fontFamily: 'var(--font-family-cjk-jp)' };
const jpAccent = { color: 'var(--nui-accent-ink-blue)' };
+1 -1
View File
@@ -1,6 +1,6 @@
'use client';
import Link from 'next/link';
import { Layout, Section, Article, Headline, Subhead, Kicker } from '@newspaperui/components';
import { Layout, Section, Article, Headline, Subhead, Kicker } from 'newspaperui-components';
const blocks = [
{
@@ -1,5 +1,5 @@
'use client';
import { Layout, Section, Article, Rule, BodyText, Quote } from '@newspaperui/components';
import { Layout, Section, Article, Rule, BodyText, Quote } from 'newspaperui-components';
const cn = { fontFamily: 'var(--font-family-cjk-serif)' };
const cnRed = { color: 'var(--nui-accent-cjk-red)' };
+1 -1
View File
@@ -1,5 +1,5 @@
'use client';
import { Layout, Section, Article, BodyText, Quote, Figure, PullQuote } from '@newspaperui/components';
import { Layout, Section, Article, BodyText, Quote, Figure, PullQuote } from 'newspaperui-components';
const cn = { fontFamily: 'var(--font-family-cjk-serif)' };
const cnRed = { color: 'var(--nui-accent-cjk-red)' };
@@ -1,5 +1,5 @@
'use client';
import { Layout, Section, Article, Rule, BodyText, Figure } from '@newspaperui/components';
import { Layout, Section, Article, Rule, BodyText, Figure } from 'newspaperui-components';
const cn = { fontFamily: 'var(--font-family-cjk-serif)' };
const cnRed = { color: 'var(--nui-accent-cjk-red)' };
@@ -4,7 +4,7 @@ import {
Layout, Section, Article, Masthead,
Headline, Subhead, Kicker, BodyText, Byline, Dateline,
Figure, PullQuote,
} from '@newspaperui/components';
} from 'newspaperui-components';
export default function BlackletterFrontPage() {
return (
@@ -4,7 +4,7 @@ import {
Layout, Section, Article, Masthead, Rule,
Headline, Subhead, Kicker, BodyText, Byline, Dateline,
Figure, PullQuote,
} from '@newspaperui/components';
} from 'newspaperui-components';
export default function FrontPage() {
return (
+1 -1
View File
@@ -1,4 +1,4 @@
@import "@newspaperui/theme/dist/style.css";
@import "newspaperui-theme/dist/style.css";
@tailwind base;
@tailwind components;
+4 -4
View File
@@ -9,7 +9,7 @@ import {
Subhead,
Kicker,
BodyText,
} from '@newspaperui/components';
} from 'newspaperui-components';
import Link from 'next/link';
const demos = [
@@ -108,7 +108,7 @@ export default function LandingPage() {
</Headline>
<BodyText weight="Medium">
<p style={{ fontFamily: 'var(--font-family-meta)', fontSize: '13px' }}>
<code>pnpm add @newspaperui/components @newspaperui/theme</code>
<code>pnpm add newspaperui-components newspaperui-theme</code>
</p>
</BodyText>
<div style={{ marginTop: '1rem' }}>
@@ -267,8 +267,8 @@ export default function LandingPage() {
</Headline>
<BodyText weight="Low">
<p>
4 packages<code>@newspaperui/theme</code><code>@newspaperui/utils</code>
<code>@newspaperui/components</code>18 components<code>@newspaperui/docs</code>
4 packages<code>newspaperui-theme</code><code>newspaperui-utils</code>
<code>newspaperui-components</code>18 components<code>@newspaperui/docs</code>
</p>
<p>Built with pnpm workspaces + Turborepo. Vite for libraries, Next.js 15 for docs.</p>
</BodyText>
+1 -1
View File
@@ -1,7 +1,7 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { cx } from '@newspaperui/utils';
import { cx } from 'newspaperui-utils';
interface NavItem {
label: string;
+1 -1
View File
@@ -7,7 +7,7 @@ const nextConfig = {
basePath: process.env.NEXT_PUBLIC_BASE_PATH || '',
images: { unoptimized: true },
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
transpilePackages: ['@newspaperui/components', '@newspaperui/theme', '@newspaperui/utils'],
transpilePackages: ['newspaperui-components', 'newspaperui-theme', 'newspaperui-utils'],
};
const withMDX = createMDX({
+5 -5
View File
@@ -13,16 +13,16 @@
"dependencies": {
"@mdx-js/loader": "^3.1.1",
"@mdx-js/react": "^3.1.1",
"@newspaperui/components": "workspace:*",
"@newspaperui/theme": "workspace:*",
"@newspaperui/utils": "workspace:*",
"@next/mdx": "^16.2.6",
"@types/mdx": "^2.0.13",
"next": "^15.1.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rehype-pretty-code": "^0.14.3",
"shiki": "^4.1.0"
"shiki": "^4.1.0",
"newspaperui-components": "workspace:*",
"newspaperui-theme": "workspace:*",
"newspaperui-utils": "workspace:*"
},
"devDependencies": {
"@types/node": "^22.10.5",
@@ -33,4 +33,4 @@
"tailwindcss": "^3.4.17",
"typescript": "^5.7.2"
}
}
}
+20 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@newspaperui/theme",
"version": "0.0.0",
"description": "Theme tokens and CSS variables for newspaperui",
"name": "newspaperui-theme",
"version": "0.1.0",
"description": "CSS variables, visual weights, and typography utilities for NewspaperUI",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
@@ -31,5 +31,21 @@
"typescript": "^5.7.2",
"vite": "^5.4.11",
"vite-plugin-dts": "^4.3.0"
},
"license": "MIT",
"author": "sunzhongyi",
"repository": {
"type": "git",
"url": "https://github.com/joisun/newspaperui.git"
},
"keywords": [
"newspaper",
"typography",
"css-variables",
"theme",
"design-tokens"
],
"publishConfig": {
"access": "public"
}
}
}
+22
View File
@@ -42,6 +42,26 @@
--nui-space-4: 1rem;
--nui-space-6: 1.5rem;
--nui-space-8: 2rem;
/* border-radius */
--nui-radius-none: 0;
--nui-radius-sm: 2px;
--nui-radius-md: 4px;
/* shadow */
--nui-shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--nui-shadow-md: 0 2px 8px rgba(0,0,0,0.08);
/* transition */
--nui-transition-fast: 150ms ease;
--nui-transition-normal: 250ms ease;
/* z-index layers */
--nui-z-base: 0;
--nui-z-sticky: 10;
--nui-z-header: 50;
--nui-z-modal: 100;
--nui-z-tooltip: 150;
}
[data-theme="dark"] {
@@ -57,6 +77,8 @@
--nui-accent-primary: #C97A6E;
--nui-accent-ink-blue: #7E94C2;
--nui-highlight: #3A2F18;
--nui-shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
--nui-shadow-md: 0 2px 8px rgba(0,0,0,0.4);
}
html, body { background: var(--nui-bg-page); color: var(--nui-text-body); }
+10 -2
View File
@@ -236,9 +236,17 @@ export const visualWeights: Record<ComponentType, Partial<Record<VisualWeight, V
},
};
/** Resolve fontSize: tuple → first value (lower bound used by default). */
/** Resolve fontSize: tuple → clamp(min, preferred, max) for responsive sizing. */
export function resolveFontSize(value: string | [string, string]): string {
return Array.isArray(value) ? value[0] : value;
if (Array.isArray(value)) {
const min = value[0];
const max = value[1];
const minNum = parseFloat(min);
const maxNum = parseFloat(max);
const vw = ((minNum + maxNum) / 2 / 16 * 1.5).toFixed(2);
return `clamp(${min}, ${vw}vw, ${max})`;
}
return value;
}
/** Resolve span: tuple → first value (lower bound). */
+18 -4
View File
@@ -1,7 +1,7 @@
{
"name": "@newspaperui/utils",
"version": "0.0.0",
"description": "Utility functions for newspaperui",
"name": "newspaperui-utils",
"version": "0.1.0",
"description": "Grid validation and utility functions for NewspaperUI",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
@@ -29,5 +29,19 @@
"vite": "^5.4.11",
"vite-plugin-dts": "^4.3.0",
"vitest": "^2.1.8"
},
"license": "MIT",
"author": "sunzhongyi",
"repository": {
"type": "git",
"url": "https://github.com/joisun/newspaperui.git"
},
"keywords": [
"newspaper",
"grid",
"utilities"
],
"publishConfig": {
"access": "public"
}
}
}