完成Mixer功能的设计。
This commit is contained in:
		@@ -5,6 +5,7 @@ import { Harmonies } from './pages/Harmonies';
 | 
				
			|||||||
import { Home } from './pages/Home';
 | 
					import { Home } from './pages/Home';
 | 
				
			||||||
import { LightenDarken } from './pages/LightenDarken';
 | 
					import { LightenDarken } from './pages/LightenDarken';
 | 
				
			||||||
import { MainLayout } from './pages/MainLayout';
 | 
					import { MainLayout } from './pages/MainLayout';
 | 
				
			||||||
 | 
					import { Mixer } from './pages/Mixer';
 | 
				
			||||||
import { NewScheme } from './pages/NewScheme';
 | 
					import { NewScheme } from './pages/NewScheme';
 | 
				
			||||||
import { SchemeDetail } from './pages/SchemeDetail';
 | 
					import { SchemeDetail } from './pages/SchemeDetail';
 | 
				
			||||||
import { SchemeNotFound } from './pages/SchemeNotFound';
 | 
					import { SchemeNotFound } from './pages/SchemeNotFound';
 | 
				
			||||||
@@ -33,6 +34,7 @@ const routes = createBrowserRouter([
 | 
				
			|||||||
      { path: 'tones', element: <Tones /> },
 | 
					      { path: 'tones', element: <Tones /> },
 | 
				
			||||||
      { path: 'tints-shades', element: <TintsShades /> },
 | 
					      { path: 'tints-shades', element: <TintsShades /> },
 | 
				
			||||||
      { path: 'lighten-darken', element: <LightenDarken /> },
 | 
					      { path: 'lighten-darken', element: <LightenDarken /> },
 | 
				
			||||||
 | 
					      { path: 'mixer', element: <Mixer /> },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										34
									
								
								src/pages/Mixer.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/pages/Mixer.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					@layer pages {
 | 
				
			||||||
 | 
					  .mixer_workspace {
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .mixer_content {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: row;
 | 
				
			||||||
 | 
					    justify-content: space-around;
 | 
				
			||||||
 | 
					    align-items: flex-start;
 | 
				
			||||||
 | 
					    gap: var(--spacing-m);
 | 
				
			||||||
 | 
					    .mixer_column {
 | 
				
			||||||
 | 
					      display: flex;
 | 
				
			||||||
 | 
					      flex-direction: column;
 | 
				
			||||||
 | 
					      gap: var(--spacing-s);
 | 
				
			||||||
 | 
					      font-size: var(--font-size-s);
 | 
				
			||||||
 | 
					      .color_value_mode {
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        flex-direction: row;
 | 
				
			||||||
 | 
					        align-items: center;
 | 
				
			||||||
 | 
					        gap: var(--spacing-s);
 | 
				
			||||||
 | 
					        font-size: var(--font-size-s);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .colors_booth {
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        flex-direction: row;
 | 
				
			||||||
 | 
					        justify-content: center;
 | 
				
			||||||
 | 
					        align-items: stretch;
 | 
				
			||||||
 | 
					        gap: var(--spacing-xs);
 | 
				
			||||||
 | 
					        min-height: 12em;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										79
									
								
								src/pages/Mixer.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/pages/Mixer.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					import cx from 'clsx';
 | 
				
			||||||
 | 
					import { useMemo, useState } from 'react';
 | 
				
			||||||
 | 
					import { useColorFunction } from '../ColorFunctionContext';
 | 
				
			||||||
 | 
					import { ColorPicker } from '../components/ColorPicker';
 | 
				
			||||||
 | 
					import { FlexColorStand } from '../components/FlexColorStand';
 | 
				
			||||||
 | 
					import { HSegmentedControl } from '../components/HSegmentedControl';
 | 
				
			||||||
 | 
					import { LabeledPicker } from '../components/LabeledPicker';
 | 
				
			||||||
 | 
					import { ScrollArea } from '../components/ScrollArea';
 | 
				
			||||||
 | 
					import styles from './Mixer.module.css';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function Mixer() {
 | 
				
			||||||
 | 
					  const { colorFn } = useColorFunction();
 | 
				
			||||||
 | 
					  const [basicColor, setBasicColor] = useState('000000');
 | 
				
			||||||
 | 
					  const [mixColor, setMixColor] = useState('000000');
 | 
				
			||||||
 | 
					  const [mixRatio, setMixRatio] = useState(0);
 | 
				
			||||||
 | 
					  const [mode, setMode] = useState<'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'>('hex');
 | 
				
			||||||
 | 
					  const mixedColor = useMemo(() => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      if (!colorFn) {
 | 
				
			||||||
 | 
					        return '000000';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const mixed = colorFn.mix(basicColor, mixColor, mixRatio / 100);
 | 
				
			||||||
 | 
					      return mixed;
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      console.error('[Mix Color]', e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return '000000';
 | 
				
			||||||
 | 
					  }, [basicColor, mixColor, mixRatio]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className={cx('workspace', styles.mixer_workspace)}>
 | 
				
			||||||
 | 
					      <header>
 | 
				
			||||||
 | 
					        <h3>Color Mixer</h3>
 | 
				
			||||||
 | 
					        <p>Make a new color by mixing two colors.</p>
 | 
				
			||||||
 | 
					      </header>
 | 
				
			||||||
 | 
					      <ScrollArea enableY>
 | 
				
			||||||
 | 
					        <div className={styles.mixer_content}>
 | 
				
			||||||
 | 
					          <div className={styles.mixer_column}>
 | 
				
			||||||
 | 
					            <h5>Basic Color</h5>
 | 
				
			||||||
 | 
					            <ColorPicker color={basicColor} onSelect={setBasicColor} />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div className={styles.mixer_column}>
 | 
				
			||||||
 | 
					            <h5>Mix Color</h5>
 | 
				
			||||||
 | 
					            <ColorPicker color={mixColor} onSelect={setMixColor} />
 | 
				
			||||||
 | 
					            <LabeledPicker
 | 
				
			||||||
 | 
					              title="Mix Ratio"
 | 
				
			||||||
 | 
					              value={mixRatio}
 | 
				
			||||||
 | 
					              onChange={setMixRatio}
 | 
				
			||||||
 | 
					              min={0}
 | 
				
			||||||
 | 
					              max={100}
 | 
				
			||||||
 | 
					              step={1}
 | 
				
			||||||
 | 
					              unit="%"
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div className={styles.mixer_column}>
 | 
				
			||||||
 | 
					            <h5>Mix Result</h5>
 | 
				
			||||||
 | 
					            <div className={styles.colors_booth}>
 | 
				
			||||||
 | 
					              <FlexColorStand color={mixedColor} valueMode={mode} />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div className={styles.color_value_mode}>
 | 
				
			||||||
 | 
					              <label>Copy color value in</label>
 | 
				
			||||||
 | 
					              <HSegmentedControl
 | 
				
			||||||
 | 
					                options={[
 | 
				
			||||||
 | 
					                  { label: 'HEX', value: 'hex' },
 | 
				
			||||||
 | 
					                  { label: 'RGB', value: 'rgb' },
 | 
				
			||||||
 | 
					                  { label: 'HSL', value: 'hsl' },
 | 
				
			||||||
 | 
					                  { label: 'LAB', value: 'lab' },
 | 
				
			||||||
 | 
					                  { label: 'OKLCH', value: 'oklch' },
 | 
				
			||||||
 | 
					                ]}
 | 
				
			||||||
 | 
					                value={mode}
 | 
				
			||||||
 | 
					                onChange={setMode}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </ScrollArea>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user