定制化指南
“主题和样式系统是编辑器的核心基础设施之一。通过深入理解其工作原理,我们可以构建出既美观又实用的编辑体验。”
样式隔离机制
JSON Editor 采用多层次的样式隔离机制,确保编辑器的样式不会受到外部环境的影响,同时也不会污染全局样式。
数据属性隔离
每个主要组件都使用特定的 data-bkjson-* 属性来标识:
// 编辑器容器
<div data-bkjson-root>
// 工具栏
<div data-bkjson-toolbar>...</div>
// 编辑器主体
<div data-bkjson-editor>...</div>
// 状态栏
<div data-bkjson-status>...</div>
// Schema 面板
<div data-bkjson-schema>...</div>
</div>
这种机制确保:
- 样式选择器具有足够的特异性
- 避免与页面其他元素的样式冲突
- 便于调试和样式覆盖
CSS 模块化
组件样式采用 CSS Modules 方案:
// Toolbar.module.css
.toolbar {
display: flex;
align-items: center;
padding: 8px;
}
// Toolbar.tsx
import styles from './Toolbar.module.css';
export const Toolbar = () => (
<div className={styles.toolbar}>
...
</div>
);
优势:
- 样式作用域完全隔离
- 类名自动唯一化
- 支持组合和继承
主题样式隔离
主题相关的样式通过特定的选择器组合来实现隔离:
const darkTheme = {
// 编辑器范围内的样式
"[data-bkjson-root][data-theme='dark']": {
// 基础样式
backgroundColor: "#282c34",
color: "#abb2bf",
// 组件样式
"[data-bkjson-toolbar]": {
backgroundColor: "#21252b"
},
// CodeMirror 样式
".cm-content": {
caretColor: "#abb2bf"
}
}
};
这种方式确保:
- 主题样式不会泄露
- 支持嵌套的主题定义
- 便于主题切换
动态样式注入
对于需要动态计算的样式,使用 CSS-in-JS 方案:
const dynamicStyle = {
height: expanded ? 'auto' : getCollapsedHeight(),
transition: 'height 0.3s ease-in-out',
overflow: 'hidden'
};
// 使用 style 属性注入
<div style={dynamicStyle}>...</div>
这种方式适用于:
- 需要基于状态计算的样式
- 需要动态主题变量的场景
- 需要性能优化的场景
主题系统
主题实现原理
JSON Editor 的主题系统基于 CodeMirror 的主题机制构建,同时扩展了对 JSON 特定语法的支持。主题系统包含以下核心部分:
- 基础主题定义
- 编辑器背景色和前景色
- 字体和字号设置
- 光标和选区样式
- 行号和装订线样式
- 语法高亮规则
- JSON 属性名称
- 字符串、数字、布尔值
- 括号和标点符号
- 错误标记
- 组件主题
- 工具栏样式
- 状态栏样式
- Schema 面板样式
内置主题
暗色主题
// 暗色主题的颜色定义
const darkColors = {
chalky: "#e5c07b", // 用于类名和属性名
coral: "#e06c75", // 用于关键字和标签
cyan: "#56b6c2", // 用于内置函数
ivory: "#abb2bf", // 基础文本颜色
stone: "#7d8799", // 用于注释
malibu: "#61afef", // 用于函数和属性
sage: "#98c379", // 用于字符串
whiskey: "#d19a66", // 用于数字
violet: "#c678dd" // 用于特殊关键字
};
// 语法高亮规则
const darkHighlightStyle = HighlightStyle.define([
// JSON 特定语法
{ tag: t.propertyName, color: darkColors.malibu, fontWeight: "500" },
{ tag: t.string, color: darkColors.sage },
{ tag: t.number, color: darkColors.whiskey },
{ tag: t.bool, color: darkColors.violet, fontWeight: "500" },
{ tag: t.null, color: darkColors.violet, fontWeight: "500" },
{ tag: t.keyword, color: darkColors.coral },
{ tag: t.operator, color: darkColors.ivory },
{ tag: t.bracket, color: darkColors.ivory },
{ tag: t.punctuation, color: darkColors.ivory },
{ tag: t.invalid, color: darkColors.invalid, backgroundColor: darkColors.coral }
]);
// 编辑器主题
const darkTheme = EditorView.theme({
"&": {
backgroundColor: "#282c34",
color: darkColors.ivory
},
".cm-content": {
caretColor: darkColors.ivory,
fontFamily: 'ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace',
fontSize: "14px"
},
"&.cm-focused .cm-cursor": {
borderLeftColor: darkColors.ivory
},
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
backgroundColor: "#3E4451"
},
".cm-activeLine": {
backgroundColor: "#2c313a"
},
".cm-gutters": {
backgroundColor: "#282c34",
color: darkColors.stone,
border: "none",
borderRight: "1px solid #3E4451"
}
}, { dark: true });
亮色主题
// 亮色主题的颜色定义
const lightColors = {
chalky: "#e5c07b", // 用于类名和属性名
coral: "#e06c75", // 用于关键字和标签
cyan: "#56b6c2", // 用于内置函数
ivory: "#24292e", // 基础文本颜色
stone: "#7d8799", // 用于注释
malibu: "#0366d6", // 用于函数和属性
sage: "#98c379", // 用于字符串
whiskey: "#d19a66", // 用于数字
violet: "#6f42c1" // 用于特殊关键字
};
// 语法高亮规则
const lightHighlightStyle = HighlightStyle.define([
// JSON 特定语法
{ tag: t.propertyName, color: lightColors.malibu, fontWeight: "500" },
{ tag: t.string, color: lightColors.sage },
{ tag: t.number, color: lightColors.whiskey },
{ tag: t.bool, color: lightColors.violet, fontWeight: "500" },
{ tag: t.null, color: lightColors.violet, fontWeight: "500" },
{ tag: t.keyword, color: lightColors.coral },
{ tag: t.operator, color: lightColors.ivory },
{ tag: t.bracket, color: lightColors.ivory },
{ tag: t.punctuation, color: lightColors.ivory },
{ tag: t.invalid, color: lightColors.invalid, backgroundColor: lightColors.coral }
]);
// 编辑器主题
const lightTheme = EditorView.theme({
"&": {
backgroundColor: "#ffffff",
color: lightColors.ivory
},
".cm-content": {
caretColor: lightColors.ivory,
fontFamily: 'ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace',
fontSize: "14px"
},
"&.cm-focused .cm-cursor": {
borderLeftColor: lightColors.ivory
},
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
backgroundColor: "#0366d625"
},
".cm-activeLine": {
backgroundColor: "#f6f8fa"
},
".cm-gutters": {
backgroundColor: "#ffffff",
color: lightColors.stone,
border: "none",
borderRight: "1px solid #e1e4e8"
}
}, { dark: false });
自定义主题
你可以通过 themeConfig 属性来自定义主题:
const customTheme = {
theme: 'dark',
vars: {
// 基础颜色
primary: '#1890ff',
secondary: '#52c41a',
error: '#f5222d',
// 编辑器颜色
editorBg: '#282c34',
editorFg: '#abb2bf',
selectionBg: '#3E4451',
// 语法高亮
propertyColor: '#61afef',
stringColor: '#98c379',
numberColor: '#d19a66'
}
};
// 使用自定义主题
<JsonEditor themeConfig={customTheme} />
主题配置支持以下选项:
- 基础设置
-
theme: ‘light’‘dark’ - 基础主题 vars: 主题变量对象
-
- 主题变量
- 基础颜色
primary: 主色调secondary: 次要色调error: 错误色
- 编辑器颜色
editorBg: 编辑器背景色editorFg: 编辑器前景色selectionBg: 选中背景色gutterBg: 行号区域背景色gutterFg: 行号颜色
- 语法高亮
propertyColor: 属性名颜色stringColor: 字符串颜色numberColor: 数字颜色booleanColor: 布尔值颜色nullColor: null 值颜色bracketColor: 括号颜色punctuationColor: 标点符号颜色
- 基础颜色
- 组件主题
- 工具栏
toolbarBg: 工具栏背景色toolbarBorder: 工具栏边框色buttonHoverBg: 按钮悬停背景色
- 状态栏
statusBg: 状态栏背景色statusText: 状态文本颜色
- Schema 面板
schemaBg: Schema 面板背景色schemaText: Schema 文本颜色schemaBorder: Schema 面板边框色
- 工具栏
最佳实践
- 主题继承
// 继承暗色主题 const customDarkTheme = { theme: 'dark', vars: { ...darkTheme.vars, primary: '#1890ff' } }; -
响应式主题
// 根据系统主题切换 const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; <JsonEditor themeConfig={{ theme: systemTheme }} /> - 主题变量复用
const brandColors = { primary: '#1890ff', success: '#52c41a', warning: '#faad14', error: '#f5222d' }; const editorTheme = { theme: 'light', vars: { primary: brandColors.primary, error: brandColors.error } }; - 性能优化
- 避免频繁切换主题
- 使用
useMemo缓存主题配置 - 提取公共主题变量
组件定制
工具栏定制
工具栏支持以下定制选项:
- 按钮配置
interface ToolbarButtonConfig { icon?: React.ReactNode; tooltip?: string; disabled?: boolean; hidden?: boolean; onClick?: () => void; style?: React.CSSProperties; } - 按钮组
interface ToolbarButtonGroup { key: string; buttons: string[]; divider?: boolean; style?: React.CSSProperties; } -
示例配置
<JsonEditor toolbarConfig={{ // 按钮样式 buttonStyles: { base: { height: '28px', padding: '0 8px', borderRadius: '4px', transition: 'all 0.3s' }, hover: { background: 'rgba(255,255,255,0.1)' }, active: { background: 'rgba(255,255,255,0.2)' } }, // 按钮分组 buttonGroups: [ { key: 'edit', buttons: ['undo', 'redo'], divider: true }, { key: 'format', buttons: ['format', 'validate'] } ], // 自定义按钮 customButtons: [ { key: 'save', icon: <SaveIcon />, tooltip: '保存', onClick: () => {/* ... */} } ] }} />
状态栏定制
状态栏支持以下定制选项:
- 显示项配置
interface StatusBarFeatures { showError?: boolean; showCursor?: boolean; showSize?: boolean; showValid?: boolean; } - 格式化配置
interface StatusBarFormat { cursor?: (line: number, col: number) => string; size?: (lines: number, bytes: number) => string; valid?: (isValid: boolean) => StatusIndicator; } -
示例配置
<JsonEditor statusBarConfig={{ // 显示项 features: { showError: true, showCursor: true, showSize: true, showValid: true }, // 布局 layout: { order: ['error', 'cursor', 'size', 'valid'], divider: '|', style: { padding: '0 8px' } }, // 格式化 format: { cursor: (line, col) => `Ln ${line}, Col ${col}`, size: (lines, bytes) => { const kb = (bytes / 1024).toFixed(1); return `${lines} 行 (${kb}KB)`; }, valid: (isValid) => ({ icon: isValid ? <CheckIcon /> : <ErrorIcon />, text: isValid ? '有效' : '无效', color: isValid ? '#52c41a' : '#f5222d' }) } }} />
Schema 面板定制
Schema 面板支持以下定制选项:
- 布局配置
interface SchemaInfoLayout { showDescription?: boolean; showPath?: boolean; showType?: boolean; showRequired?: boolean; order?: string[]; } - 格式化配置
interface SchemaInfoFormat { type?: (type: string, format?: string) => string; description?: (desc: string) => React.ReactNode; path?: (path: string[]) => string; required?: () => React.ReactNode; } -
示例配置
<JsonEditor schemaInfoConfig={{ // 布局 layout: { showDescription: true, showPath: true, showType: true, showRequired: true, order: ['description', 'type', 'required'] }, // 格式化 format: { type: (type, format) => { if (format) return `${type} (${format})`; return type; }, description: (desc) => <ReactMarkdown>{desc}</ReactMarkdown>, path: (path) => path.join(' > '), required: () => <Tag color="error">必填</Tag> }, // 样式 style: { padding: '16px', borderLeft: '1px solid #e8e8e8' } }} />
最佳实践
- 主题定制
- 保持颜色系统的一致性
- 注意深色/浅色主题的对比度
- 使用语义化的颜色变量
- 考虑无障碍设计
- 组件定制
- 遵循组件的设计规范
- 保持交互的一致性
- 提供合适的视觉反馈
- 注意性能影响
- 性能优化
- 避免过度的样式计算
- 合理使用 React.memo
- 优化重渲染逻辑
- 使用性能分析工具
常见问题
1. 主题切换问题
问题: 主题切换后样式未完全更新 解决方案:
- 确保所有组件都正确响应主题变化
- 检查是否有硬编码的样式值
- 使用 React.useEffect 处理副作用
2. 样式冲突
问题: 自定义样式被覆盖 解决方案:
- 使用更高优先级的选择器
- 采用 CSS Modules
- 避免全局样式污染
3. 性能问题
问题: 样式变更导致性能下降 解决方案:
- 减少样式计算
- 使用 CSS-in-JS 的缓存机制
- 优化选择器性能
💡 提示: 定制化是一个渐进的过程。建议从基础主题开始,逐步添加自定义样式,并时刻关注性能指标。合理的定制化可以提升用户体验,过度的定制化反而可能带来维护负担。