完成Tints & Shades功能。
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 { TintsShades } from './pages/TintsShades';
 | 
				
			||||||
import { Tones } from './pages/Tones';
 | 
					import { Tones } from './pages/Tones';
 | 
				
			||||||
import { Wheels } from './pages/Wheels';
 | 
					import { Wheels } from './pages/Wheels';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,6 +30,7 @@ const routes = createBrowserRouter([
 | 
				
			|||||||
      { path: 'harmonies', element: <Harmonies /> },
 | 
					      { path: 'harmonies', element: <Harmonies /> },
 | 
				
			||||||
      { path: 'wheels', element: <Wheels /> },
 | 
					      { path: 'wheels', element: <Wheels /> },
 | 
				
			||||||
      { path: 'tones', element: <Tones /> },
 | 
					      { path: 'tones', element: <Tones /> },
 | 
				
			||||||
 | 
					      { path: 'tints-shades', element: <TintsShades /> },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
]);
 | 
					]);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										59
									
								
								src/page-components/TintsShades/shades.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/page-components/TintsShades/shades.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					import { last } from 'lodash-es';
 | 
				
			||||||
 | 
					import { useMemo } from 'react';
 | 
				
			||||||
 | 
					import { useColorFunction } from '../../ColorFunctionContext';
 | 
				
			||||||
 | 
					import { FlexColorStand } from '../../components/FlexColorStand';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ShadesListProps = {
 | 
				
			||||||
 | 
					  color: string;
 | 
				
			||||||
 | 
					  shades: number;
 | 
				
			||||||
 | 
					  mix: 'progressive' | 'linear' | 'average';
 | 
				
			||||||
 | 
					  step?: number;
 | 
				
			||||||
 | 
					  maximum?: number;
 | 
				
			||||||
 | 
					  copyMode: 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function Shades({ color, shades, mix, step, maximum, copyMode }: ShadesListProps) {
 | 
				
			||||||
 | 
					  const { colorFn } = useColorFunction();
 | 
				
			||||||
 | 
					  const colors = useMemo(() => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      if (!colorFn) {
 | 
				
			||||||
 | 
					        return Array.from({ length: shades + 1 }, () => color);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const genColors = [color];
 | 
				
			||||||
 | 
					      switch (mix) {
 | 
				
			||||||
 | 
					        case 'progressive':
 | 
				
			||||||
 | 
					          for (let i = 1; i <= shades; i++) {
 | 
				
			||||||
 | 
					            const shade = colorFn!.shade(last(genColors), step);
 | 
				
			||||||
 | 
					            genColors.push(shade);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'linear':
 | 
				
			||||||
 | 
					          for (let i = 1; i <= shades; i++) {
 | 
				
			||||||
 | 
					            const shade = colorFn!.shade(color, step * i);
 | 
				
			||||||
 | 
					            genColors.push(shade);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'average': {
 | 
				
			||||||
 | 
					          const interval = maximum / shades / 100;
 | 
				
			||||||
 | 
					          for (let i = 1; i <= shades; i++) {
 | 
				
			||||||
 | 
					            const shade = colorFn!.shade(color, interval * i);
 | 
				
			||||||
 | 
					            genColors.push(shade);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return genColors.reverse();
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      console.error('[Generate Shades]', e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return Array.from({ length: shades + 1 }, () => color);
 | 
				
			||||||
 | 
					  }, [color, shades, mix, step, maximum]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      {colors.map((c, index) => (
 | 
				
			||||||
 | 
					        <FlexColorStand key={`${c}-${index}`} color={c} valueMode={copyMode} />
 | 
				
			||||||
 | 
					      ))}
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										59
									
								
								src/page-components/TintsShades/tints.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/page-components/TintsShades/tints.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					import { last } from 'lodash-es';
 | 
				
			||||||
 | 
					import { useMemo } from 'react';
 | 
				
			||||||
 | 
					import { useColorFunction } from '../../ColorFunctionContext';
 | 
				
			||||||
 | 
					import { FlexColorStand } from '../../components/FlexColorStand';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type TintsListProps = {
 | 
				
			||||||
 | 
					  color: string;
 | 
				
			||||||
 | 
					  tints: number;
 | 
				
			||||||
 | 
					  mix: 'progressive' | 'linear' | 'average';
 | 
				
			||||||
 | 
					  step?: number;
 | 
				
			||||||
 | 
					  maximum?: number;
 | 
				
			||||||
 | 
					  copyMode: 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function Tints({ color, tints, mix, step, maximum, copyMode }: TintsListProps) {
 | 
				
			||||||
 | 
					  const { colorFn } = useColorFunction();
 | 
				
			||||||
 | 
					  const colors = useMemo(() => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      if (!colorFn) {
 | 
				
			||||||
 | 
					        return Array.from({ length: tints + 1 }, () => color);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const genColors = [color];
 | 
				
			||||||
 | 
					      switch (mix) {
 | 
				
			||||||
 | 
					        case 'progressive':
 | 
				
			||||||
 | 
					          for (let i = 1; i <= tints; i++) {
 | 
				
			||||||
 | 
					            const tint = colorFn!.tint(last(genColors), step);
 | 
				
			||||||
 | 
					            genColors.push(tint);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'linear':
 | 
				
			||||||
 | 
					          for (let i = 1; i <= tints; i++) {
 | 
				
			||||||
 | 
					            const tint = colorFn!.tint(color, step * i);
 | 
				
			||||||
 | 
					            genColors.push(tint);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case 'average': {
 | 
				
			||||||
 | 
					          const interval = maximum / tints / 100;
 | 
				
			||||||
 | 
					          for (let i = 1; i <= tints; i++) {
 | 
				
			||||||
 | 
					            const tint = colorFn!.tint(color, interval * i);
 | 
				
			||||||
 | 
					            genColors.push(tint);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return genColors;
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      console.error('[Generate Tints]', e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return Array.from({ length: tints + 1 }, () => color);
 | 
				
			||||||
 | 
					  }, [color, tints, mix, step, maximum]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      {colors.map((c, index) => (
 | 
				
			||||||
 | 
					        <FlexColorStand key={`${c}-${index}`} color={c} valueMode={copyMode} />
 | 
				
			||||||
 | 
					      ))}
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										55
									
								
								src/pages/TintsShades.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/pages/TintsShades.module.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					@layer pages {
 | 
				
			||||||
 | 
					  .tints_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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  .tints_content {
 | 
				
			||||||
 | 
					    flex: 1;
 | 
				
			||||||
 | 
					    padding: 0 var(--spacing-m);
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    gap: var(--spacing-s);
 | 
				
			||||||
 | 
					    h5 {
 | 
				
			||||||
 | 
					      padding-block: var(--spacing-m);
 | 
				
			||||||
 | 
					      font-size: var(--font-size-m);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										134
									
								
								src/pages/TintsShades.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								src/pages/TintsShades.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,134 @@
 | 
				
			|||||||
 | 
					import cx from 'clsx';
 | 
				
			||||||
 | 
					import { useAtom } from 'jotai';
 | 
				
			||||||
 | 
					import { isEqual } from 'lodash-es';
 | 
				
			||||||
 | 
					import { useState } from 'react';
 | 
				
			||||||
 | 
					import { ColorPicker } from '../components/ColorPicker';
 | 
				
			||||||
 | 
					import { HSegmentedControl } from '../components/HSegmentedControl';
 | 
				
			||||||
 | 
					import { Labeled } from '../components/Labeled';
 | 
				
			||||||
 | 
					import { LabeledPicker } from '../components/LabeledPicker';
 | 
				
			||||||
 | 
					import { ScrollArea } from '../components/ScrollArea';
 | 
				
			||||||
 | 
					import { VSegmentedControl } from '../components/VSegmentedControl';
 | 
				
			||||||
 | 
					import { Shades } from '../page-components/TintsShades/shades';
 | 
				
			||||||
 | 
					import { Tints } from '../page-components/TintsShades/tints';
 | 
				
			||||||
 | 
					import { currentPickedColor } from '../stores/colors';
 | 
				
			||||||
 | 
					import styles from './TintsShades.module.css';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function TintsShades() {
 | 
				
			||||||
 | 
					  const [selectedColor, setSelectedColor] = useAtom(currentPickedColor);
 | 
				
			||||||
 | 
					  const [steps, setSteps] = useState(10);
 | 
				
			||||||
 | 
					  const [tints, setTints] = useState(3);
 | 
				
			||||||
 | 
					  const [shades, setShades] = useState(3);
 | 
				
			||||||
 | 
					  const [maximum, setMaximum] = useState(90);
 | 
				
			||||||
 | 
					  const [mixMode, setMixMode] = useState<'progressive' | 'average'>('progressive');
 | 
				
			||||||
 | 
					  const [mode, setMode] = useState<'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'>('hex');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className={cx('workspace', styles.tints_workspace)}>
 | 
				
			||||||
 | 
					      <header>
 | 
				
			||||||
 | 
					        <h3>Tints & Shades</h3>
 | 
				
			||||||
 | 
					        <p>By proportionally mixing black and white, generating a series of varying colors.</p>
 | 
				
			||||||
 | 
					      </header>
 | 
				
			||||||
 | 
					      <ScrollArea enableY>
 | 
				
			||||||
 | 
					        <section className={styles.explore_section}>
 | 
				
			||||||
 | 
					          <aside className={styles.function_side}>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					              <h5>Basic color</h5>
 | 
				
			||||||
 | 
					              <ColorPicker color={selectedColor} onSelect={(color) => setSelectedColor(color)} />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					              <h5>Series Setting</h5>
 | 
				
			||||||
 | 
					              <LabeledPicker
 | 
				
			||||||
 | 
					                title="Tints"
 | 
				
			||||||
 | 
					                min={1}
 | 
				
			||||||
 | 
					                max={10}
 | 
				
			||||||
 | 
					                step={1}
 | 
				
			||||||
 | 
					                value={tints}
 | 
				
			||||||
 | 
					                onChange={setTints}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <LabeledPicker
 | 
				
			||||||
 | 
					                title="Shades"
 | 
				
			||||||
 | 
					                min={1}
 | 
				
			||||||
 | 
					                max={10}
 | 
				
			||||||
 | 
					                step={1}
 | 
				
			||||||
 | 
					                value={shades}
 | 
				
			||||||
 | 
					                onChange={setShades}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					              <Labeled label="Generate Mode">
 | 
				
			||||||
 | 
					                <VSegmentedControl
 | 
				
			||||||
 | 
					                  options={[
 | 
				
			||||||
 | 
					                    { label: 'Progressive', value: 'progressive' },
 | 
				
			||||||
 | 
					                    { label: 'Linear', value: 'linear' },
 | 
				
			||||||
 | 
					                    { label: 'Average', value: 'average' },
 | 
				
			||||||
 | 
					                  ]}
 | 
				
			||||||
 | 
					                  value={mixMode}
 | 
				
			||||||
 | 
					                  onChange={(value) => {
 | 
				
			||||||
 | 
					                    setMixMode(value as 'progressive' | 'average');
 | 
				
			||||||
 | 
					                    console.debug('[Mix Mode Switch]', value);
 | 
				
			||||||
 | 
					                  }}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              </Labeled>
 | 
				
			||||||
 | 
					              {(isEqual(mixMode, 'progressive') || isEqual(mixMode, 'linear')) && (
 | 
				
			||||||
 | 
					                <LabeledPicker
 | 
				
			||||||
 | 
					                  title="Step"
 | 
				
			||||||
 | 
					                  min={10}
 | 
				
			||||||
 | 
					                  max={25}
 | 
				
			||||||
 | 
					                  step={1}
 | 
				
			||||||
 | 
					                  value={steps}
 | 
				
			||||||
 | 
					                  onChange={setSteps}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					              {isEqual(mixMode, 'average') && (
 | 
				
			||||||
 | 
					                <LabeledPicker
 | 
				
			||||||
 | 
					                  title="Maximum"
 | 
				
			||||||
 | 
					                  min={10}
 | 
				
			||||||
 | 
					                  max={100}
 | 
				
			||||||
 | 
					                  step={1}
 | 
				
			||||||
 | 
					                  value={maximum}
 | 
				
			||||||
 | 
					                  onChange={setMaximum}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              )}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </aside>
 | 
				
			||||||
 | 
					          <div className={styles.tints_content}>
 | 
				
			||||||
 | 
					            <h5>Tints</h5>
 | 
				
			||||||
 | 
					            <div className={styles.colors_booth}>
 | 
				
			||||||
 | 
					              <Tints
 | 
				
			||||||
 | 
					                color={selectedColor}
 | 
				
			||||||
 | 
					                tints={tints}
 | 
				
			||||||
 | 
					                step={steps / 100}
 | 
				
			||||||
 | 
					                maximum={maximum}
 | 
				
			||||||
 | 
					                mix={mixMode}
 | 
				
			||||||
 | 
					                copyMode={mode}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <h5>Shades</h5>
 | 
				
			||||||
 | 
					            <div className={styles.colors_booth}>
 | 
				
			||||||
 | 
					              <Shades
 | 
				
			||||||
 | 
					                color={selectedColor}
 | 
				
			||||||
 | 
					                shades={shades}
 | 
				
			||||||
 | 
					                step={steps / 100}
 | 
				
			||||||
 | 
					                maximum={maximum}
 | 
				
			||||||
 | 
					                mix={mixMode}
 | 
				
			||||||
 | 
					                copyMode={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' },
 | 
				
			||||||
 | 
					                ]}
 | 
				
			||||||
 | 
					                valu={mode}
 | 
				
			||||||
 | 
					                onChange={setMode}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </section>
 | 
				
			||||||
 | 
					      </ScrollArea>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user