import type { Rule } from '../types'; import { getVar, resolveColor, withUnit } from '../utils'; export const typographyRules: Rule[] = [ // Text Alignment, Balance, Antialiasing [ (parsed) => parsed.nodes[0]?.type === 'literal' && parsed.nodes[0].value === 'text' && parsed.nodes.length === 2 && ['left', 'center', 'right', 'justify', 'start', 'end', 'balance', 'nowrap'].includes( (parsed.nodes[1] as any).value, ), (_, { parsed }) => { const val = (parsed.nodes[1] as any).value; if (['left', 'center', 'right', 'justify', 'start', 'end'].includes(val)) return { 'text-align': val }; if (val === 'balance') return { 'text-wrap': 'balance' }; if (val === 'nowrap') return { 'white-space': 'nowrap' }; }, ], [ (parsed) => parsed.nodes[0]?.type === 'literal' && parsed.nodes[0].value === 'text' && parsed.nodes.length >= 2, (_, { tokens, parsed, unit }) => { const node1 = parsed.nodes[1]; // 1. 尝试从 tokens.typography.fontSize 中寻找 (优先支持 2xl, 4xl 等) const key = (node1 as any).raw || (node1 as any).value; const tokenValue = node1.type === 'literal' || node1.type === 'numeric' ? tokens.typography.fontSize[key] : undefined; if (tokenValue) { return { 'font-size': getVar(`typography-fontSize-${key}`, tokenValue) }; } // 2. 如果没找到 token,且是 numeric,则作为原始值 if (node1.type === 'numeric') { return { 'font-size': (node1 as any).raw.match(/^\d+$/) ? withUnit((node1 as any).raw, unit) : (node1 as any).raw, }; } else if ( node1.type === 'arbitrary' && (node1.value.endsWith('px') || node1.value.endsWith('rem') || node1.value.endsWith('em') || !node1.value.startsWith('#')) ) { // 如果是 arbitrary 且看起来像长度,或者是非颜色值 return { 'font-size': node1.value }; } // 2. 匹配颜色,优先从 tokens.color.text 中寻找 const res = resolveColor(parsed, tokens, { subGroup: 'text' }) || resolveColor(parsed, tokens); if (res) { const color = getVar(res.varPath, res.color, res.opacity); // 如果 color 已经是 color-mix (即带有透明度),直接返回 color if (color.startsWith('color-mix')) { return { color }; } return { '--sxo-text-opacity': '100%', '--sxo-text-color': color, color: `color-mix(in srgb, var(--sxo-text-color), transparent calc(100% - var(--sxo-text-opacity)))`, }; } }, ], // Text Decoration ['underline', () => ({ 'text-decoration': 'underline' })], ['no-underline', () => ({ 'text-decoration': 'none' })], ['line-through', () => ({ 'text-decoration': 'line-through' })], ['overline', () => ({ 'text-decoration': 'overline' })], // Text Transform ['uppercase', () => ({ 'text-transform': 'uppercase' })], ['lowercase', () => ({ 'text-transform': 'lowercase' })], ['capitalize', () => ({ 'text-transform': 'capitalize' })], ['normal-case', () => ({ 'text-transform': 'none' })], // Text Opacity [ (parsed) => parsed.nodes[0]?.type === 'literal' && parsed.nodes[0].value === 'text' && parsed.nodes[1]?.type === 'literal' && parsed.nodes[1].value === 'opacity' && parsed.nodes.length === 3, (_, { parsed }) => { const node = parsed.nodes[2]; let val = ''; if (node.type === 'numeric') val = `${node.value}%`; else if (node.type === 'arbitrary') val = node.value; if (val) return { '--sxo-text-opacity': val }; }, ], [ (parsed) => parsed.nodes[0]?.type === 'literal' && parsed.nodes[0].value === 'antialiased', () => ({ '-webkit-font-smoothing': 'antialiased', '-moz-osx-font-smoothing': 'grayscale', }), ], // White Space ['outline-none', () => ({ outline: '2px solid transparent', 'outline-offset': '2px' })], [ (parsed) => parsed.nodes[0]?.type === 'literal' && parsed.nodes[0].value === 'whitespace' && parsed.nodes.length >= 2, (_, { parsed }) => { // 将 whitespace- 之后的所有部分连起来,处理 pre-wrap 等带有横杠的值 const val = parsed.nodes .slice(1) .map((n) => (n as any).value || (n as any).raw) .join('-'); if (['normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap', 'break-spaces'].includes(val)) return { 'white-space': val }; }, ], // Fill ['fill-current', () => ({ fill: 'currentColor' })], // Font Weight & Family [ (parsed) => parsed.nodes[0]?.type === 'literal' && parsed.nodes[0].value === 'font' && parsed.nodes.length >= 2, (_, { tokens, parsed }) => { const node = parsed.nodes[1]; if (node.type === 'literal') { const weight = (tokens.typography as any).fontWeight?.[node.value]; if (weight) return { 'font-weight': getVar(`typography-fontWeight-${node.value}`, weight) }; const family = (tokens.typography as any).fontFamily?.[node.value]; if (family) return { 'font-family': getVar(`typography-fontFamily-${node.value}`, family) }; } else if (node.type === 'numeric') { return { 'font-weight': node.value }; } else if (node.type === 'arbitrary') { return { 'font-weight': node.value }; } }, ], ];