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