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 { 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 = {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user