完成色轮选色的基础功能。
This commit is contained in:
		@@ -8,6 +8,7 @@ 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';
 | 
				
			||||||
import { Schemes } from './pages/Schemes';
 | 
					import { Schemes } from './pages/Schemes';
 | 
				
			||||||
 | 
					import { Wheels } from './pages/Wheels';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const routes = createBrowserRouter([
 | 
					const routes = createBrowserRouter([
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
@@ -25,6 +26,7 @@ const routes = createBrowserRouter([
 | 
				
			|||||||
        ],
 | 
					        ],
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      { path: 'harmonies', element: <Harmonies /> },
 | 
					      { path: 'harmonies', element: <Harmonies /> },
 | 
				
			||||||
 | 
					      { path: 'wheels', element: <Wheels /> },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										46
									
								
								src/page-components/wheels/ColorColumn.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/page-components/wheels/ColorColumn.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					@layer pages {
 | 
				
			||||||
 | 
					  @keyframes current_blink {
 | 
				
			||||||
 | 
					    0% {
 | 
				
			||||||
 | 
					      opacity: 0.2;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    50% {
 | 
				
			||||||
 | 
					      opacity: 0.5;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    100% {
 | 
				
			||||||
 | 
					      opacity: 0.2;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .color_column {
 | 
				
			||||||
 | 
					    clip-path: polygon(38.5% 0px, 50% 100%, 61.25% 0px);
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    top: 0;
 | 
				
			||||||
 | 
					    left: 0;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    transform-origin: center bottom;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .color_block {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    height: 1.5em;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    .bg {
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      inset: 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .dim_overlay {
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      inset: 0;
 | 
				
			||||||
 | 
					      background: rgba(0, 0, 0, 70%);
 | 
				
			||||||
 | 
					      opacity: 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .show_overlay {
 | 
				
			||||||
 | 
					      opacity: 0.8;
 | 
				
			||||||
 | 
					      transition: opacity 500ms ease-in-out;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .active {
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      inset: 0;
 | 
				
			||||||
 | 
					      background-color: var(--color-white);
 | 
				
			||||||
 | 
					      animation: current_blink 2s linear infinite;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										56
									
								
								src/page-components/wheels/ColorColumn.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/page-components/wheels/ColorColumn.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					import cx from 'clsx';
 | 
				
			||||||
 | 
					import { isEqual } from 'lodash-es';
 | 
				
			||||||
 | 
					import { useMemo } from 'react';
 | 
				
			||||||
 | 
					import { useColorFunction } from '../../ColorFunctionContext';
 | 
				
			||||||
 | 
					import { useCopyColor } from '../../hooks/useCopyColor';
 | 
				
			||||||
 | 
					import styles from './ColorColumn.module.css';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ColorBlockProps = {
 | 
				
			||||||
 | 
					  dimmed: boolean;
 | 
				
			||||||
 | 
					  color: string;
 | 
				
			||||||
 | 
					  isRoot?: boolean;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function ColorBlock({ dimmed, color, isRoot = false }: ColorBlockProps) {
 | 
				
			||||||
 | 
					  const copyColor = useCopyColor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className={styles.color_block} onClick={() => copyColor(color)}>
 | 
				
			||||||
 | 
					      <div className={styles.bg} style={{ backgroundColor: `#${color}` }} />
 | 
				
			||||||
 | 
					      <div className={cx(styles.dim_overlay, dimmed && styles.show_overlay)} />
 | 
				
			||||||
 | 
					      <div className={cx(isRoot && styles.active)} />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ColorWheelProps = {
 | 
				
			||||||
 | 
					  actived: boolean;
 | 
				
			||||||
 | 
					  rotate: number;
 | 
				
			||||||
 | 
					  rootColor: string;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function ColorColumn({ actived, rotate, rootColor }: ColorWheelProps) {
 | 
				
			||||||
 | 
					  const { colorFn } = useColorFunction();
 | 
				
			||||||
 | 
					  const colorSeries = useMemo(() => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const colors = colorFn?.series(rootColor, 4, 0.16);
 | 
				
			||||||
 | 
					      return (colors ?? Array.from({ length: 9 }, () => rootColor)).reverse();
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      console.error('[Generate Color Series]', e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return Array.from({ length: 9 }, () => rootColor);
 | 
				
			||||||
 | 
					  }, [rootColor]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className={styles.color_column} style={{ transform: `rotate(${rotate}deg)` }}>
 | 
				
			||||||
 | 
					      {colorSeries.map((c, index) => (
 | 
				
			||||||
 | 
					        <ColorBlock
 | 
				
			||||||
 | 
					          key={`${c}-${index}`}
 | 
				
			||||||
 | 
					          dimmed={!actived}
 | 
				
			||||||
 | 
					          color={c}
 | 
				
			||||||
 | 
					          isRoot={isEqual(c, rootColor) && isEqual(rotate, 0)}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      ))}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/page-components/wheels/ColorWheel.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/page-components/wheels/ColorWheel.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					@layer pages {
 | 
				
			||||||
 | 
					  .wheel_view {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    padding: 0 var(--spacing-m);
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    h5 {
 | 
				
			||||||
 | 
					      padding-block: var(--spacing-m);
 | 
				
			||||||
 | 
					      font-size: var(--font-size-m);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .wheel_place {
 | 
				
			||||||
 | 
					      flex: 1;
 | 
				
			||||||
 | 
					      padding: var(--spacing-m) 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .wheel_container {
 | 
				
			||||||
 | 
					      position: relative;
 | 
				
			||||||
 | 
					      aspect-ratio: 1 / 1;
 | 
				
			||||||
 | 
					      height: 100%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										67
									
								
								src/page-components/wheels/ColorWheel.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/page-components/wheels/ColorWheel.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					import cx from 'clsx';
 | 
				
			||||||
 | 
					import { includes } from 'lodash-es';
 | 
				
			||||||
 | 
					import { useMemo } from 'react';
 | 
				
			||||||
 | 
					import { useColorFunction } from '../../ColorFunctionContext';
 | 
				
			||||||
 | 
					import { ColorColumn } from './ColorColumn';
 | 
				
			||||||
 | 
					import styles from './ColorWheel.module.css';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ColorWheelProps = {
 | 
				
			||||||
 | 
					  originColor?: string;
 | 
				
			||||||
 | 
					  highlightMode?:
 | 
				
			||||||
 | 
					    | 'complementary'
 | 
				
			||||||
 | 
					    | 'analogous'
 | 
				
			||||||
 | 
					    | 'analogous_with_complementary'
 | 
				
			||||||
 | 
					    | 'triadic'
 | 
				
			||||||
 | 
					    | 'split_complementary'
 | 
				
			||||||
 | 
					    | 'tetradic'
 | 
				
			||||||
 | 
					    | 'flip_tetradic'
 | 
				
			||||||
 | 
					    | 'square';
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const wheelRotates = [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330];
 | 
				
			||||||
 | 
					const highlightSeries = {
 | 
				
			||||||
 | 
					  complementary: [0, 180],
 | 
				
			||||||
 | 
					  analogous: [0, 30, 330],
 | 
				
			||||||
 | 
					  analogous_with_complementary: [0, 30, 180, 330],
 | 
				
			||||||
 | 
					  triadic: [0, 120, 240],
 | 
				
			||||||
 | 
					  split_complementary: [0, 150, 210],
 | 
				
			||||||
 | 
					  tetradic: [0, 60, 180, 240],
 | 
				
			||||||
 | 
					  flip_tetradic: [0, 120, 180, 300],
 | 
				
			||||||
 | 
					  square: [0, 90, 180, 270],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function ColorWheel({
 | 
				
			||||||
 | 
					  originColor = '000000',
 | 
				
			||||||
 | 
					  highlightMode = 'complementary',
 | 
				
			||||||
 | 
					}: ColorWheelProps) {
 | 
				
			||||||
 | 
					  const { colorFn } = useColorFunction();
 | 
				
			||||||
 | 
					  const wheelColors = useMemo(() => {
 | 
				
			||||||
 | 
					    return wheelRotates.map((rotate) => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const color = colorFn?.shift_hue(originColor, rotate);
 | 
				
			||||||
 | 
					        return { color: color ?? '000000', rotate };
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        console.error('[Generate color wheel]', e);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return { color: '000000', rotate };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }, [originColor]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className={styles.wheel_view}>
 | 
				
			||||||
 | 
					      <h5>Color Wheel</h5>
 | 
				
			||||||
 | 
					      <div className={cx('center', styles.wheel_place)}>
 | 
				
			||||||
 | 
					        <div className={styles.wheel_container}>
 | 
				
			||||||
 | 
					          {wheelColors.map(({ color, rotate }) => (
 | 
				
			||||||
 | 
					            <ColorColumn
 | 
				
			||||||
 | 
					              key={`${color}-${rotate}`}
 | 
				
			||||||
 | 
					              rootColor={color}
 | 
				
			||||||
 | 
					              rotate={rotate}
 | 
				
			||||||
 | 
					              actived={includes(highlightSeries[highlightMode], rotate)}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          ))}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										32
									
								
								src/pages/Wheels.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/pages/Wheels.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					@layer pages {
 | 
				
			||||||
 | 
					  .wheels_workspace {
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .explore_section {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: row;
 | 
				
			||||||
 | 
					    align-items: stretch;
 | 
				
			||||||
 | 
					    gap: var(--spacing-m);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .function_side {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    gap: var(--spacing-m);
 | 
				
			||||||
 | 
					    font-size: var(--font-size-s);
 | 
				
			||||||
 | 
					    .mode_navigation {
 | 
				
			||||||
 | 
					      display: flex;
 | 
				
			||||||
 | 
					      flex-direction: column;
 | 
				
			||||||
 | 
					      align-items: stretch;
 | 
				
			||||||
 | 
					      gap: var(--spacing-s);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    h5 {
 | 
				
			||||||
 | 
					      padding-block: var(--spacing-m);
 | 
				
			||||||
 | 
					      font-size: var(--font-size-m);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .wheel_side {
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										49
									
								
								src/pages/Wheels.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/pages/Wheels.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					import cx from 'clsx';
 | 
				
			||||||
 | 
					import { useAtom } from 'jotai';
 | 
				
			||||||
 | 
					import { useState } from 'react';
 | 
				
			||||||
 | 
					import { ColorPicker } from '../components/ColorPicker';
 | 
				
			||||||
 | 
					import { VSegmentedControl } from '../components/VSegmentedControl';
 | 
				
			||||||
 | 
					import { ColorWheel } from '../page-components/wheels/ColorWheel';
 | 
				
			||||||
 | 
					import { currentPickedColor } from '../stores/colors';
 | 
				
			||||||
 | 
					import styles from './Wheels.module.css';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function Wheels() {
 | 
				
			||||||
 | 
					  const [selectedColor, setSelectedColor] = useAtom(currentPickedColor);
 | 
				
			||||||
 | 
					  const [selectedMode, setSelectedMode] = useState('complementary');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className={cx('workspace', styles.wheels_workspace)}>
 | 
				
			||||||
 | 
					      <header>
 | 
				
			||||||
 | 
					        <h3>Color Wheels</h3>
 | 
				
			||||||
 | 
					        <p>Choose the required color on the color wheel according to color theory.</p>
 | 
				
			||||||
 | 
					      </header>
 | 
				
			||||||
 | 
					      <section className={styles.explore_section}>
 | 
				
			||||||
 | 
					        <aside className={styles.function_side}>
 | 
				
			||||||
 | 
					          <div>
 | 
				
			||||||
 | 
					            <h5>Basic color</h5>
 | 
				
			||||||
 | 
					            <ColorPicker color={selectedColor} onSelect={(color) => setSelectedColor(color)} />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					          <div className={styles.mode_navigation}>
 | 
				
			||||||
 | 
					            <h5>Color selection method</h5>
 | 
				
			||||||
 | 
					            <VSegmentedControl
 | 
				
			||||||
 | 
					              onChange={setSelectedMode}
 | 
				
			||||||
 | 
					              options={[
 | 
				
			||||||
 | 
					                { label: 'Complementary', value: 'complementary' },
 | 
				
			||||||
 | 
					                { label: 'Analogous', value: 'analogous' },
 | 
				
			||||||
 | 
					                { label: 'Analogous with Complementary', value: 'analogous_with_complementary' },
 | 
				
			||||||
 | 
					                { label: 'Triadic', value: 'triadic' },
 | 
				
			||||||
 | 
					                { label: 'Split Complementary', value: 'split_complementary' },
 | 
				
			||||||
 | 
					                { label: 'Tetradic', value: 'tetradic' },
 | 
				
			||||||
 | 
					                { label: 'Flip Tetradic', value: 'flip_tetradic' },
 | 
				
			||||||
 | 
					                { label: 'Square', value: 'square' },
 | 
				
			||||||
 | 
					              ]}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </aside>
 | 
				
			||||||
 | 
					        <div className={styles.wheel_side}>
 | 
				
			||||||
 | 
					          <ColorWheel originColor={selectedColor} highlightMode={selectedMode} />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </section>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user