This commit is contained in:
sunzhongyi
2026-05-20 01:30:41 +08:00
parent f3e6b95be9
commit 7dded89537
44 changed files with 1166 additions and 3699 deletions
+1
View File
@@ -12,6 +12,7 @@
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./dist/style.css": "./dist/style.css",
"./variables.css": "./src/variables.css",
"./tailwind.config": "./src/tailwind.config.js"
},
+4 -19
View File
@@ -1,21 +1,6 @@
/**
* @newspaperui/theme
* Theme tokens and CSS variables for newspaperui
*/
// Import CSS variables
import './fonts.css';
import './variables.css';
import './typography.css';
// Export visual weight types and configuration
export type {
VisualWeight,
ComponentType,
VisualWeightConfig,
} from './visual-weights';
export { visualWeights, resolveFontSize } from './visual-weights';
// Export Tailwind config for consumers
export { default as tailwindConfig } from './tailwind.config.js';
export const version = '0.0.0';
export { visualWeights, resolveFontSize, resolveSpan } from './visual-weights';
export type { VisualWeight, ComponentType, VisualWeightConfig } from './visual-weights';
+23 -95
View File
@@ -1,103 +1,31 @@
/**
* Tailwind CSS Configuration for NewspaperUI
* Extends Tailwind with newspaper-specific utilities and 24-column grid
*/
const config = {
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
extend: {
colors: {
nui: {
text: {
primary: 'var(--nui-color-text-primary)',
secondary: 'var(--nui-color-text-secondary)',
tertiary: 'var(--nui-color-text-tertiary)',
quaternary: 'var(--nui-color-text-quaternary)',
muted: 'var(--nui-color-text-muted)',
},
gray: {
50: 'var(--nui-color-gray-50)',
100: 'var(--nui-color-gray-100)',
200: 'var(--nui-color-gray-200)',
300: 'var(--nui-color-gray-300)',
400: 'var(--nui-color-gray-400)',
500: 'var(--nui-color-gray-500)',
600: 'var(--nui-color-gray-600)',
700: 'var(--nui-color-gray-700)',
800: 'var(--nui-color-gray-800)',
900: 'var(--nui-color-gray-900)',
},
accent: {
primary: 'var(--nui-color-accent-primary)',
secondary: 'var(--nui-color-accent-secondary)',
tertiary: 'var(--nui-color-accent-tertiary)',
},
},
},
spacing: {
'nui-gutter': 'var(--nui-gutter)',
'nui-gutter-sm': 'var(--nui-gutter-sm)',
'nui-gutter-lg': 'var(--nui-gutter-lg)',
},
fontFamily: {
'nui-serif': 'var(--nui-font-serif)',
'nui-serif-display': 'var(--nui-font-serif-display)',
'nui-sans': 'var(--nui-font-sans)',
'nui-sans-condensed': 'var(--nui-font-sans-condensed)',
'nui-mono': 'var(--nui-font-mono)',
masthead: ['var(--font-family-masthead)'],
blackletter: ['var(--font-family-blackletter)'],
display: ['var(--font-family-display)'],
headline: ['var(--font-family-headline)'],
body: ['var(--font-family-body)'],
meta: ['var(--font-family-meta)'],
},
fontSize: {
'nui-xs': 'var(--nui-font-size-xs)',
'nui-sm': 'var(--nui-font-size-sm)',
'nui-base': 'var(--nui-font-size-base)',
'nui-lg': 'var(--nui-font-size-lg)',
'nui-xl': 'var(--nui-font-size-xl)',
'nui-2xl': 'var(--nui-font-size-2xl)',
'nui-3xl': 'var(--nui-font-size-3xl)',
'nui-4xl': 'var(--nui-font-size-4xl)',
'nui-5xl': 'var(--nui-font-size-5xl)',
'nui-6xl': 'var(--nui-font-size-6xl)',
},
fontWeight: {
'nui-normal': 'var(--nui-font-weight-normal)',
'nui-medium': 'var(--nui-font-weight-medium)',
'nui-semibold': 'var(--nui-font-weight-semibold)',
'nui-bold': 'var(--nui-font-weight-bold)',
},
lineHeight: {
'nui-tight': 'var(--nui-line-height-tight)',
'nui-snug': 'var(--nui-line-height-snug)',
'nui-normal': 'var(--nui-line-height-normal)',
'nui-relaxed': 'var(--nui-line-height-relaxed)',
'nui-loose': 'var(--nui-line-height-loose)',
'nui-body': 'var(--nui-line-height-body)',
},
maxWidth: {
'nui-grid': 'var(--nui-grid-max-width)',
},
// 24-column grid utilities
gridTemplateColumns: {
'nui-24': 'repeat(24, minmax(0, 1fr))',
'nui-16': 'repeat(16, minmax(0, 1fr))',
'nui-12': 'repeat(12, minmax(0, 1fr))',
},
gridColumn: {
'span-13': 'span 13 / span 13',
'span-14': 'span 14 / span 14',
'span-15': 'span 15 / span 15',
'span-16': 'span 16 / span 16',
'span-17': 'span 17 / span 17',
'span-18': 'span 18 / span 18',
'span-19': 'span 19 / span 19',
'span-20': 'span 20 / span 20',
'span-21': 'span 21 / span 21',
'span-22': 'span 22 / span 22',
'span-23': 'span 23 / span 23',
'span-24': 'span 24 / span 24',
colors: {
page: 'var(--nui-bg-page)',
surface: 'var(--nui-bg-surface)',
primary: 'var(--nui-text-primary)',
body: 'var(--nui-text-body)',
secondary: 'var(--nui-text-secondary)',
muted: 'var(--nui-text-muted)',
quote: 'var(--nui-text-quote)',
hairline: 'var(--nui-rule-hairline)',
decorative: 'var(--nui-rule-decorative)',
accent: {
primary: 'var(--nui-accent-primary)',
'ink-blue': 'var(--nui-accent-ink-blue)',
},
highlight: 'var(--nui-highlight)',
},
},
},
plugins: [],
};
export default config;
+45 -117
View File
@@ -1,127 +1,55 @@
/**
* NewspaperUI Theme Variables
* Global CSS variables for newspaper layout system
*/
:root {
/* ========== Color System ========== */
/* Primary text colors */
--nui-color-text-primary: #111111;
--nui-color-text-secondary: #222222;
--nui-color-text-tertiary: #333333;
--nui-color-text-quaternary: #444444;
--nui-color-text-muted: #555555;
/* font families */
--font-family-masthead: "Cormorant Garamond", "Playfair Display", Georgia, serif;
--font-family-blackletter: "UnifrakturMaguntia", "Cormorant Garamond", serif;
--font-family-display: "Source Serif 4", Georgia, "Times New Roman", serif;
--font-family-headline: "Source Serif 4", Georgia, "Times New Roman", serif;
--font-family-body: "Source Serif 4", Georgia, "Times New Roman", serif;
--font-family-meta: "Inter", system-ui, sans-serif;
/* Gray scale */
--nui-color-gray-50: #fafafa;
--nui-color-gray-100: #f5f5f5;
--nui-color-gray-200: #e5e5e5;
--nui-color-gray-300: #d4d4d4;
--nui-color-gray-400: #a3a3a3;
--nui-color-gray-500: #737373;
--nui-color-gray-600: #525252;
--nui-color-gray-700: #404040;
--nui-color-gray-800: #262626;
--nui-color-gray-900: #171717;
/* page background */
--nui-bg-page: #F7F4ED;
--nui-bg-surface: #FBF9F4;
/* Accent colors */
--nui-color-accent-primary: #0066cc;
--nui-color-accent-secondary: #cc0000;
--nui-color-accent-tertiary: #006600;
/* text */
--nui-text-primary: #1A1A1A;
--nui-text-body: #22201C;
--nui-text-secondary: #4A4742;
--nui-text-muted: #6E6A63;
--nui-text-quote: #2E2A24;
/* ========== Spacing System ========== */
/* Gutter (column gap) */
--nui-gutter: 1rem;
--nui-gutter-sm: 0.75rem;
--nui-gutter-lg: 1.5rem;
/* rules */
--nui-rule-hairline: #C9C2B2;
--nui-rule-decorative: #1A1A1A;
/* Margin */
--nui-margin-xs: 0.25rem;
--nui-margin-sm: 0.5rem;
--nui-margin-md: 0.75rem;
--nui-margin-lg: 1rem;
--nui-margin-xl: 1.5rem;
/* accents */
--nui-accent-primary: #7A1F1F;
--nui-accent-ink-blue: #1B2A4A;
--nui-highlight: #F2E9C8;
/* Padding */
--nui-padding-xs: 0.25rem;
--nui-padding-sm: 0.5rem;
--nui-padding-md: 0.75rem;
--nui-padding-lg: 1rem;
--nui-padding-xl: 1.5rem;
/* ========== Font System ========== */
/* Serif fonts for headlines and body text */
--nui-font-serif: 'Georgia', 'Times New Roman', serif;
--nui-font-serif-display: 'Playfair Display', 'Georgia', serif;
/* Sans-serif fonts for UI elements */
--nui-font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
--nui-font-sans-condensed: 'Arial Narrow', 'Helvetica Condensed', sans-serif;
/* Monospace for code */
--nui-font-mono: 'Courier New', Courier, monospace;
/* ========== Grid System ========== */
/* 24-column grid */
--nui-grid-columns: 24;
--nui-grid-max-width: 1440px;
--nui-grid-container-padding: 2rem;
/* Responsive breakpoints */
--nui-breakpoint-sm: 768px;
--nui-breakpoint-md: 1024px;
--nui-breakpoint-lg: 1440px;
/* ========== Typography Scale ========== */
/* Font sizes */
--nui-font-size-xs: 0.75rem; /* 12px */
--nui-font-size-sm: 0.875rem; /* 14px */
--nui-font-size-base: 1rem; /* 16px */
--nui-font-size-lg: 1.125rem; /* 18px */
--nui-font-size-xl: 1.25rem; /* 20px */
--nui-font-size-2xl: 1.5rem; /* 24px */
--nui-font-size-3xl: 1.75rem; /* 28px */
--nui-font-size-4xl: 2rem; /* 32px */
--nui-font-size-5xl: 2.25rem; /* 36px */
--nui-font-size-6xl: 3rem; /* 48px */
/* Font weights */
--nui-font-weight-normal: 400;
--nui-font-weight-medium: 500;
--nui-font-weight-semibold: 600;
--nui-font-weight-bold: 700;
/* Line heights */
--nui-line-height-tight: 1.1;
--nui-line-height-snug: 1.2;
--nui-line-height-normal: 1.25;
--nui-line-height-relaxed: 1.3;
--nui-line-height-loose: 1.4;
--nui-line-height-body: 1.5;
/* spacing */
--nui-gutter: 1.5rem;
--nui-space-1: 0.25rem;
--nui-space-2: 0.5rem;
--nui-space-3: 0.75rem;
--nui-space-4: 1rem;
--nui-space-6: 1.5rem;
--nui-space-8: 2rem;
}
[data-theme="dark"] {
/* Primary text colors */
--nui-color-text-primary: #eeeeee;
--nui-color-text-secondary: #dddddd;
--nui-color-text-tertiary: #cccccc;
--nui-color-text-quaternary: #bbbbbb;
--nui-color-text-muted: #aaaaaa;
/* Gray scale */
--nui-color-gray-50: #171717;
--nui-color-gray-100: #262626;
--nui-color-gray-200: #404040;
--nui-color-gray-300: #525252;
--nui-color-gray-400: #737373;
--nui-color-gray-500: #a3a3a3;
--nui-color-gray-600: #d4d4d4;
--nui-color-gray-700: #e5e5e5;
--nui-color-gray-800: #f5f5f5;
--nui-color-gray-900: #fafafa;
/* Accent colors */
--nui-color-accent-primary: #4da6ff;
--nui-color-accent-secondary: #ff6666;
--nui-color-accent-tertiary: #66cc66;
--nui-bg-page: #14110D;
--nui-bg-surface: #1C1812;
--nui-text-primary: #EDE6D6;
--nui-text-body: #DCD4C2;
--nui-text-secondary: #A89F8C;
--nui-text-muted: #857C6A;
--nui-text-quote: #DCD4C2;
--nui-rule-hairline: #3A352C;
--nui-rule-decorative: #EDE6D6;
--nui-accent-primary: #C97A6E;
--nui-accent-ink-blue: #7E94C2;
--nui-highlight: #3A2F18;
}
html, body { background: var(--nui-bg-page); color: var(--nui-text-body); }
+157 -74
View File
@@ -1,164 +1,247 @@
/**
* Resolves a fontSize value to a single CSS string.
* For range tuples [min, max], returns the lower bound (min).
* Visual weight mapping for newspaper components.
* Reference: design.md §5 + classic-style design audit revision.
*
* fontFamily / color use CSS variable NAMES (e.g. '--font-family-headline').
* Components do `var(${config.fontFamily})` at usage site.
*/
export function resolveFontSize(fontSize: string | [string, string]): string {
return Array.isArray(fontSize) ? fontSize[0] : fontSize;
}
export type VisualWeight = 'High' | 'Medium' | 'Low' | 'Standard';
export type ComponentType =
| 'Masthead'
| 'Headline'
| 'Subhead'
| 'Kicker'
| 'BodyText'
| 'Quote'
| 'PullQuote'
| 'Byline'
| 'Dateline'
| 'Caption';
export interface VisualWeightConfig {
fontFamily: string; // CSS variable name, e.g. '--font-family-headline'
fontSize: string | [string, string];
fontWeight: number;
lineHeight: number;
color: string;
letterSpacing?: string;
fontStyle?: 'normal' | 'italic';
fontVariant?: string; // 'small-caps' | 'oldstyle-nums proportional-nums' …
color: string; // CSS variable name, e.g. '--nui-text-primary'
span: number | [number, number];
margin: string;
}
/**
* Visual weight configuration mapping
* Maps component types and their visual weights to specific styling configurations
*/
export const visualWeights: Record<
ComponentType,
Partial<Record<VisualWeight, VisualWeightConfig>>
> = {
export const visualWeights: Record<ComponentType, Partial<Record<VisualWeight, VisualWeightConfig>>> = {
Masthead: {
Standard: {
fontFamily: '--font-family-masthead',
fontSize: ['56px', '96px'],
fontWeight: 700,
lineHeight: 1.0,
letterSpacing: '0.02em',
fontVariant: 'lining-nums',
color: '--nui-text-primary',
span: 24,
margin: '0',
},
},
Headline: {
High: {
fontSize: ['36px', '48px'],
fontWeight: 700,
lineHeight: 1.1,
color: '#111',
span: [6, 8],
fontFamily: '--font-family-display',
fontSize: ['48px', '72px'],
fontWeight: 600,
lineHeight: 1.05,
letterSpacing: '-0.01em',
fontVariant: 'lining-nums',
color: '--nui-text-primary',
span: [8, 16],
margin: '0 0 1rem 0',
},
Medium: {
fontSize: ['28px', '34px'],
fontFamily: '--font-family-headline',
fontSize: ['32px', '40px'],
fontWeight: 600,
lineHeight: 1.2,
color: '#111',
span: [4, 6],
lineHeight: 1.1,
letterSpacing: '-0.005em',
fontVariant: 'lining-nums',
color: '--nui-text-primary',
span: [6, 10],
margin: '0 0 0.75rem 0',
},
Low: {
fontFamily: '--font-family-headline',
fontSize: ['22px', '26px'],
fontWeight: 500,
lineHeight: 1.3,
color: '#222',
span: [2, 4],
lineHeight: 1.2,
fontVariant: 'lining-nums',
color: '--nui-text-body',
span: [4, 6],
margin: '0 0 0.5rem 0',
},
},
Subhead: {
High: {
fontSize: ['20px', '24px'],
fontWeight: 600,
lineHeight: 1.25,
color: '#222',
span: [2, 3],
margin: '0 0 0.5rem 0',
fontFamily: '--font-family-headline',
fontSize: ['18px', '22px'],
fontWeight: 500,
fontStyle: 'italic',
lineHeight: 1.3,
fontVariant: 'oldstyle-nums',
color: '--nui-text-secondary',
span: [4, 8],
margin: '0 0 0.75rem 0',
},
Medium: {
fontFamily: '--font-family-headline',
fontSize: ['16px', '18px'],
fontWeight: 500,
lineHeight: 1.3,
color: '#333',
span: [1, 2],
fontWeight: 400,
fontStyle: 'italic',
lineHeight: 1.35,
fontVariant: 'oldstyle-nums',
color: '--nui-text-secondary',
span: [2, 4],
margin: '0 0 0.5rem 0',
},
},
Kicker: {
Standard: {
fontFamily: '--font-family-meta',
fontSize: ['12px', '13px'],
fontWeight: 600,
lineHeight: 1.2,
letterSpacing: '0.08em',
fontVariant: 'small-caps',
color: '--nui-accent-primary',
span: [1, 4],
margin: '0 0 0.25rem 0',
},
},
BodyText: {
High: {
fontSize: '16px',
fontFamily: '--font-family-body',
fontSize: ['16px', '17px'],
fontWeight: 400,
lineHeight: 1.5,
color: '#333',
span: 1,
margin: '0 0 1rem 0',
},
Medium: {
fontSize: ['14px', '15px'],
fontWeight: 400,
lineHeight: 1.5,
color: '#444',
lineHeight: 1.6,
fontVariant: 'oldstyle-nums',
color: '--nui-text-body',
span: 1,
margin: '0 0 0.75rem 0',
},
Low: {
fontSize: ['12px', '14px'],
Medium: {
fontFamily: '--font-family-body',
fontSize: '15px',
fontWeight: 400,
lineHeight: 1.4,
color: '#555',
lineHeight: 1.55,
fontVariant: 'oldstyle-nums',
color: '--nui-text-body',
span: 1,
margin: '0 0 0.5rem 0',
},
Low: {
fontFamily: '--font-family-body',
fontSize: ['13px', '14px'],
fontWeight: 400,
lineHeight: 1.5,
fontVariant: 'oldstyle-nums',
color: '--nui-text-secondary',
span: 1,
margin: '0 0 0.5rem 0',
},
},
Quote: {
High: {
fontSize: ['20px', '24px'],
fontWeight: 500,
lineHeight: 1.4,
color: '#222',
fontFamily: '--font-family-body',
fontSize: ['17px', '19px'],
fontWeight: 400,
lineHeight: 1.55,
fontVariant: 'oldstyle-nums',
color: '--nui-text-quote',
span: 2,
margin: '0 0 0.75rem 0',
margin: '1rem 0 1rem 1.5em',
},
Medium: {
fontSize: ['16px', '18px'],
fontFamily: '--font-family-body',
fontSize: ['15px', '17px'],
fontWeight: 400,
lineHeight: 1.4,
color: '#333',
lineHeight: 1.5,
fontVariant: 'oldstyle-nums',
color: '--nui-text-quote',
span: 1,
margin: '0 0 0.5rem 0',
margin: '0.75rem 0',
},
},
PullQuote: {
High: {
fontSize: ['24px', '28px'],
fontFamily: '--font-family-display',
fontSize: ['26px', '32px'],
fontWeight: 600,
lineHeight: 1.2,
color: '#111',
letterSpacing: '-0.005em',
fontVariant: 'lining-nums',
color: '--nui-text-primary',
span: [2, 3],
margin: '0 0 0.5rem 0',
margin: '1.5rem 0',
},
Medium: {
fontSize: ['18px', '20px'],
fontFamily: '--font-family-display',
fontSize: ['20px', '24px'],
fontWeight: 500,
lineHeight: 1.25,
color: '#222',
fontVariant: 'lining-nums',
color: '--nui-text-body',
span: [1, 2],
margin: '0 0 0.25rem 0',
margin: '1rem 0',
},
},
Byline: {
Standard: {
fontSize: ['12px', '14px'],
fontWeight: 400,
fontFamily: '--font-family-meta',
fontSize: ['12px', '13px'],
fontWeight: 500,
lineHeight: 1.3,
color: '#555',
letterSpacing: '0.06em',
fontVariant: 'small-caps',
color: '--nui-text-secondary',
span: 1,
margin: '0 0 0.25rem 0',
},
},
Dateline: {
Standard: {
fontFamily: '--font-family-meta',
fontSize: ['12px', '13px'],
fontWeight: 600,
lineHeight: 1.3,
letterSpacing: '0.08em',
fontVariant: 'small-caps',
color: '--nui-text-primary',
span: 1,
margin: '0',
},
},
Caption: {
Standard: {
fontSize: ['12px', '14px'],
fontFamily: '--font-family-body',
fontSize: '13px',
fontWeight: 400,
lineHeight: 1.3,
color: '#555',
fontStyle: 'italic',
lineHeight: 1.4,
fontVariant: 'oldstyle-nums',
color: '--nui-text-secondary',
span: 1,
margin: '0.25rem 0',
margin: '0.5rem 0 0 0',
},
},
};
/** Resolve fontSize: tuple → first value (lower bound used by default). */
export function resolveFontSize(value: string | [string, string]): string {
return Array.isArray(value) ? value[0] : value;
}
/** Resolve span: tuple → first value (lower bound). */
export function resolveSpan(value: number | [number, number]): number {
return Array.isArray(value) ? value[0] : value;
}