feat(q-2-scheme): 添加颜色方案预览组件并优化类型定义
新增 Q2SchemePreview 组件用于展示颜色方案的预览效果 将 Map 类型改为 Record 以简化数据结构
This commit is contained in:
parent
a7ef8eb576
commit
a77fb3f18b
@ -6,6 +6,7 @@ import { Q2SchemeStorage } from '../../q-2-scheme';
|
||||
import { isNilOrEmpty } from '../../utls';
|
||||
import { SchemeExport } from './Export';
|
||||
import { Q2SchemeBuilder } from './q-2-scheme/Builder';
|
||||
import Q2SchemePreview from './q-2-scheme/Preview';
|
||||
|
||||
const tabOptions = [
|
||||
{ title: 'Overview', id: 'overview' },
|
||||
@ -33,6 +34,7 @@ export function Q2Scheme({ scheme }: Q2SchemeProps) {
|
||||
export: isNilOrEmpty(scheme.schemeStorage?.cssVariables),
|
||||
}}
|
||||
/>
|
||||
{isEqual(activeTab, 'overview') && <Q2SchemePreview scheme={scheme} />}
|
||||
{isEqual(activeTab, 'builder') && (
|
||||
<Q2SchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
|
||||
)}
|
||||
|
58
src/page-components/scheme/q-2-scheme/Preview.module.css
Normal file
58
src/page-components/scheme/q-2-scheme/Preview.module.css
Normal file
@ -0,0 +1,58 @@
|
||||
@layer pages {
|
||||
.preview_layout {
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
.preview_block {
|
||||
width: inherit;
|
||||
padding: var(--spacing-xl) var(--spacing-m);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-xs);
|
||||
h2 {
|
||||
font-size: var(--font-size-xl);
|
||||
font-weight: bold;
|
||||
line-height: 1.7em;
|
||||
}
|
||||
}
|
||||
.preview_unit {
|
||||
width: inherit;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
.preview_indi_block {
|
||||
width: inherit;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
.preview_swatch {
|
||||
width: inherit;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(16, 1fr);
|
||||
gap: var(--spacing-xs);
|
||||
.preview_swatch_cell {
|
||||
height: 1em;
|
||||
}
|
||||
}
|
||||
.preview_cell {
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-xxs);
|
||||
font-size: var(--font-size-s);
|
||||
line-height: 1.5em;
|
||||
.wacg {
|
||||
font-size: var(--font-size-xxs);
|
||||
line-height: 1em;
|
||||
}
|
||||
}
|
||||
}
|
169
src/page-components/scheme/q-2-scheme/Preview.tsx
Normal file
169
src/page-components/scheme/q-2-scheme/Preview.tsx
Normal file
@ -0,0 +1,169 @@
|
||||
import { capitalize, keys } from 'lodash-es';
|
||||
import { FC, ReactNode, useMemo } from 'react';
|
||||
import { useColorFunction } from '../../../ColorFunctionContext';
|
||||
import { ScrollArea } from '../../../components/ScrollArea';
|
||||
import { SchemeContent } from '../../../models';
|
||||
import { Q2Baseline, Q2ColorSet, Q2ColorUnit, Q2SchemeStorage } from '../../../q-2-scheme';
|
||||
import styles from './Preview.module.css';
|
||||
|
||||
interface PreviewCellProps {
|
||||
bg: string;
|
||||
fg: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const PreviewCell: FC<PreviewCellProps> = ({ bg, fg, children }) => {
|
||||
const { colorFn } = useColorFunction();
|
||||
const wacgRatio = useMemo(() => {
|
||||
try {
|
||||
if (!colorFn) return null;
|
||||
return colorFn.wacg_relative_contrast(fg, bg);
|
||||
} catch (e) {
|
||||
console.error('[Error on calc WACG Ratio]', e);
|
||||
}
|
||||
return null;
|
||||
}, [bg, fg]);
|
||||
|
||||
return (
|
||||
<div className={styles.preview_cell} style={{ backgroundColor: `#${bg}`, color: `#${fg}` }}>
|
||||
<span>{children}</span>
|
||||
{wacgRatio && <span className={styles.wacg}>WACG {wacgRatio?.toFixed(2)}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface PreviewLineProps {
|
||||
name: string;
|
||||
unit: Q2ColorSet;
|
||||
}
|
||||
|
||||
const PreviewLine: FC<PreviewLineProps> = ({ name, unit }) => {
|
||||
return (
|
||||
<div className={styles.preview_unit}>
|
||||
<PreviewCell bg={unit.root} fg={unit.onRoot}>
|
||||
{name}
|
||||
</PreviewCell>
|
||||
<PreviewCell bg={unit.hover} fg={unit.onRoot}>
|
||||
{name} Hover
|
||||
</PreviewCell>
|
||||
<PreviewCell bg={unit.active} fg={unit.onRoot}>
|
||||
{name} Active
|
||||
</PreviewCell>
|
||||
<PreviewCell bg={unit.focus} fg={unit.onRoot}>
|
||||
{name} Focus
|
||||
</PreviewCell>
|
||||
<PreviewCell bg={unit.disabled} fg={unit.onDisabled}>
|
||||
{name} Disabled
|
||||
</PreviewCell>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface PreviewSwatchLineProps {
|
||||
swatch: Record<string, string>;
|
||||
}
|
||||
|
||||
const PreviewSwatchLine: FC<PreviewSwatchLineProps> = ({ swatch }) => {
|
||||
const cells = useMemo(() => {
|
||||
const collection: ReactNode[] = [];
|
||||
for (const key of keys(swatch)) {
|
||||
const color = swatch[key];
|
||||
collection.push(
|
||||
<div className={styles.preview_swatch_cell} style={{ backgroundColor: `#${color}` }} />,
|
||||
);
|
||||
}
|
||||
return collection;
|
||||
}, [swatch]);
|
||||
|
||||
return <div className={styles.preview_swatch}>{cells}</div>;
|
||||
};
|
||||
|
||||
interface PreviewSetProps {
|
||||
name: string;
|
||||
colorUnit: Q2ColorUnit;
|
||||
}
|
||||
|
||||
const PreviewSet: FC<PreviewSetProps> = ({ name, colorUnit }) => {
|
||||
return (
|
||||
<>
|
||||
<PreviewLine name={name} unit={colorUnit.root} />
|
||||
<PreviewLine name={`${name} Surface`} unit={colorUnit.surface} />
|
||||
<PreviewSwatchLine name={name} swatch={colorUnit.swatch} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface PreviewBlockProps {
|
||||
baseline: Q2Baseline;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const PreviewBlock: FC<PreviewBlockProps> = ({ baseline, title }) => {
|
||||
const customSets = useMemo(() => {
|
||||
const colors = keys(baseline.custom);
|
||||
const elements: ReactNode[] = [];
|
||||
|
||||
for (const key of colors) {
|
||||
const color = baseline.custom[key];
|
||||
elements.push(<PreviewSet name={capitalize(key)} colorUnit={color} />);
|
||||
}
|
||||
|
||||
return elements;
|
||||
}, [baseline.custom]);
|
||||
|
||||
return (
|
||||
<div className={styles.preview_block} style={{ backgroundColor: `#${baseline.surface.root}` }}>
|
||||
<h2 style={{ color: `#${baseline.surface.onRoot}` }}>{title}</h2>
|
||||
<PreviewSet name="Primary" colorUnit={baseline.primary} />
|
||||
<PreviewSet name="Secondary" colorUnit={baseline.secondary} />
|
||||
<PreviewSet name="Tertiary" colorUnit={baseline.tertiary} />
|
||||
<PreviewSet name="Accent" colorUnit={baseline.accent} />
|
||||
<PreviewSet name="Danger" colorUnit={baseline.danger} />
|
||||
<PreviewSet name="Success" colorUnit={baseline.success} />
|
||||
<PreviewSet name="Warn" colorUnit={baseline.warn} />
|
||||
<PreviewSet name="Info" colorUnit={baseline.info} />
|
||||
<PreviewLine name="Neutral" unit={baseline.neutral} />
|
||||
<PreviewLine name="Neutral Variant" unit={baseline.neutralVariant} />
|
||||
<PreviewLine name="Surface" unit={baseline.surface} />
|
||||
<PreviewLine name="Surface Variant" unit={baseline.surfaceVariant} />
|
||||
<div className={styles.preview_indi_block}>
|
||||
<PreviewCell bg={baseline.shadow} fg={baseline.surface.root.onRoot}>
|
||||
Shadow
|
||||
</PreviewCell>
|
||||
<PreviewCell bg={baseline.overlay} fg={baseline.surface.root.onRoot}>
|
||||
Overlay
|
||||
</PreviewCell>
|
||||
<PreviewCell bg={baseline.outline} fg={baseline.surface.root.onRoot}>
|
||||
Outline
|
||||
</PreviewCell>
|
||||
<PreviewCell bg={baseline.outlineVariant} fg={baseline.surface.root.onRoot}>
|
||||
Outline Variant
|
||||
</PreviewCell>
|
||||
</div>
|
||||
{customSets}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface PreviewProps {
|
||||
scheme: SchemeContent<Q2SchemeStorage>;
|
||||
}
|
||||
|
||||
const Q2SchemePreview: FC<PreviewProps> = ({ scheme }) => {
|
||||
return (
|
||||
<ScrollArea enableY>
|
||||
<div className={styles.preview_layout}>
|
||||
<div className={styles.preview_layout}>
|
||||
{scheme.schemeStorage.scheme?.light && (
|
||||
<PreviewBlock baseline={scheme.schemeStorage.scheme.light} title="Light Scheme" />
|
||||
)}
|
||||
{scheme.schemeStorage.scheme?.dark && (
|
||||
<PreviewBlock baseline={scheme.schemeStorage.scheme.dark} title="Dark Scheme" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
};
|
||||
|
||||
export default Q2SchemePreview;
|
@ -13,7 +13,7 @@ export type Q2ColorSet = {
|
||||
export type Q2ColorUnit = {
|
||||
root: Q2ColorSet;
|
||||
surface: Q2ColorSet;
|
||||
swatch: Map<string, string>;
|
||||
swatch: Record<string, string>;
|
||||
};
|
||||
|
||||
export type Q2Baseline = {
|
||||
@ -33,7 +33,7 @@ export type Q2Baseline = {
|
||||
overlay: string;
|
||||
outline: string;
|
||||
outlineVariant: string;
|
||||
custom: Map<string, Q2ColorUnit>;
|
||||
custom: Record<string, Q2ColorUnit>;
|
||||
};
|
||||
|
||||
export type Q2Scheme = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user