feat(q-2-scheme): 添加颜色方案预览组件并优化类型定义
新增 Q2SchemePreview 组件用于展示颜色方案的预览效果 将 Map 类型改为 Record 以简化数据结构
This commit is contained in:
		@@ -6,6 +6,7 @@ import { Q2SchemeStorage } from '../../q-2-scheme';
 | 
				
			|||||||
import { isNilOrEmpty } from '../../utls';
 | 
					import { isNilOrEmpty } from '../../utls';
 | 
				
			||||||
import { SchemeExport } from './Export';
 | 
					import { SchemeExport } from './Export';
 | 
				
			||||||
import { Q2SchemeBuilder } from './q-2-scheme/Builder';
 | 
					import { Q2SchemeBuilder } from './q-2-scheme/Builder';
 | 
				
			||||||
 | 
					import Q2SchemePreview from './q-2-scheme/Preview';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tabOptions = [
 | 
					const tabOptions = [
 | 
				
			||||||
  { title: 'Overview', id: 'overview' },
 | 
					  { title: 'Overview', id: 'overview' },
 | 
				
			||||||
@@ -33,6 +34,7 @@ export function Q2Scheme({ scheme }: Q2SchemeProps) {
 | 
				
			|||||||
          export: isNilOrEmpty(scheme.schemeStorage?.cssVariables),
 | 
					          export: isNilOrEmpty(scheme.schemeStorage?.cssVariables),
 | 
				
			||||||
        }}
 | 
					        }}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					      {isEqual(activeTab, 'overview') && <Q2SchemePreview scheme={scheme} />}
 | 
				
			||||||
      {isEqual(activeTab, 'builder') && (
 | 
					      {isEqual(activeTab, 'builder') && (
 | 
				
			||||||
        <Q2SchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
 | 
					        <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 = {
 | 
					export type Q2ColorUnit = {
 | 
				
			||||||
  root: Q2ColorSet;
 | 
					  root: Q2ColorSet;
 | 
				
			||||||
  surface: Q2ColorSet;
 | 
					  surface: Q2ColorSet;
 | 
				
			||||||
  swatch: Map<string, string>;
 | 
					  swatch: Record<string, string>;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Q2Baseline = {
 | 
					export type Q2Baseline = {
 | 
				
			||||||
@@ -33,7 +33,7 @@ export type Q2Baseline = {
 | 
				
			|||||||
  overlay: string;
 | 
					  overlay: string;
 | 
				
			||||||
  outline: string;
 | 
					  outline: string;
 | 
				
			||||||
  outlineVariant: string;
 | 
					  outlineVariant: string;
 | 
				
			||||||
  custom: Map<string, Q2ColorUnit>;
 | 
					  custom: Record<string, Q2ColorUnit>;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type Q2Scheme = {
 | 
					export type Q2Scheme = {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user