完成Lighten & Darken功能。
This commit is contained in:
		| @@ -3,6 +3,7 @@ import { ColorFunctionProvider } from './ColorFunctionContext'; | ||||
| import { Notifications } from './components/Notifications'; | ||||
| import { Harmonies } from './pages/Harmonies'; | ||||
| import { Home } from './pages/Home'; | ||||
| import { LightenDarken } from './pages/LightenDarken'; | ||||
| import { MainLayout } from './pages/MainLayout'; | ||||
| import { NewScheme } from './pages/NewScheme'; | ||||
| import { SchemeDetail } from './pages/SchemeDetail'; | ||||
| @@ -31,6 +32,7 @@ const routes = createBrowserRouter([ | ||||
|       { path: 'wheels', element: <Wheels /> }, | ||||
|       { path: 'tones', element: <Tones /> }, | ||||
|       { path: 'tints-shades', element: <TintsShades /> }, | ||||
|       { path: 'lighten-darken', element: <LightenDarken /> }, | ||||
|     ], | ||||
|   }, | ||||
| ]); | ||||
|   | ||||
							
								
								
									
										59
									
								
								src/page-components/lighten-darken/darkens.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/page-components/lighten-darken/darkens.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 DarkensProps = { | ||||
|   color: string; | ||||
|   darkens: number; | ||||
|   mix: 'progressive' | 'linear' | 'average'; | ||||
|   step?: number; | ||||
|   maximum?: number; | ||||
|   copyMode: 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'; | ||||
| }; | ||||
|  | ||||
| export function Darkens({ color, darkens, mix, step, maximum, copyMode }: DarkensProps) { | ||||
|   const { colorFn } = useColorFunction(); | ||||
|   const colors = useMemo(() => { | ||||
|     try { | ||||
|       if (!colorFn) { | ||||
|         return Array.from({ length: darkens + 1 }, () => color); | ||||
|       } | ||||
|       const darkenColors = [color]; | ||||
|       switch (mix) { | ||||
|         case 'progressive': | ||||
|           for (let i = 1; i <= darkens; i++) { | ||||
|             const darkenColor = colorFn.darken(last(darkenColors), step); | ||||
|             darkenColors.push(darkenColor); | ||||
|           } | ||||
|           break; | ||||
|         case 'linear': | ||||
|           for (let i = 1; i <= darkens; i++) { | ||||
|             const darkenColor = colorFn.darken(color, step * i); | ||||
|             darkenColors.push(darkenColor); | ||||
|           } | ||||
|           break; | ||||
|         case 'average': { | ||||
|           const interval = maximum / darkens / 100; | ||||
|           for (let i = 1; i <= darkens; i++) { | ||||
|             const darkenColor = colorFn.darken(color, interval * i); | ||||
|             darkenColors.push(darkenColor); | ||||
|           } | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|       return darkenColors.reverse(); | ||||
|     } catch (e) { | ||||
|       console.error('[Generate Lighten]', e); | ||||
|     } | ||||
|     return Array.from({ length: darkens + 1 }, () => color); | ||||
|   }, [color, darkens, mix, step, maximum]); | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       {colors.map((c, index) => ( | ||||
|         <FlexColorStand key={`${c}-${index}`} color={c} valueMode={copyMode} /> | ||||
|       ))} | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										59
									
								
								src/page-components/lighten-darken/lightens.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/page-components/lighten-darken/lightens.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 LightensProps = { | ||||
|   color: string; | ||||
|   lightens: number; | ||||
|   mix: 'progressive' | 'linear' | 'average'; | ||||
|   step?: number; | ||||
|   maximum?: number; | ||||
|   copyMode: 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'; | ||||
| }; | ||||
|  | ||||
| export function Lightens({ color, lightens, mix, step, maximum, copyMode }: LightensProps) { | ||||
|   const { colorFn } = useColorFunction(); | ||||
|   const colors = useMemo(() => { | ||||
|     try { | ||||
|       if (!colorFn) { | ||||
|         return Array.from({ length: lightens + 1 }, () => color); | ||||
|       } | ||||
|       const lightenColors = [color]; | ||||
|       switch (mix) { | ||||
|         case 'progressive': | ||||
|           for (let i = 1; i <= lightens; i++) { | ||||
|             const lightenColor = colorFn.lighten(last(lightenColors), step); | ||||
|             lightenColors.push(lightenColor); | ||||
|           } | ||||
|           break; | ||||
|         case 'linear': | ||||
|           for (let i = 1; i <= lightens; i++) { | ||||
|             const lightenColor = colorFn.lighten(color, step * i); | ||||
|             lightenColors.push(lightenColor); | ||||
|           } | ||||
|           break; | ||||
|         case 'average': { | ||||
|           const interval = maximum / lightens / 100; | ||||
|           for (let i = 1; i <= lightens; i++) { | ||||
|             const lightenColor = colorFn.lighten(color, interval * i); | ||||
|             lightenColors.push(lightenColor); | ||||
|           } | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|       return lightenColors; | ||||
|     } catch (e) { | ||||
|       console.error('[Generate Lighten]', e); | ||||
|     } | ||||
|     return Array.from({ length: lightens + 1 }, () => color); | ||||
|   }, [color, lightens, mix, step, maximum]); | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       {colors.map((c, index) => ( | ||||
|         <FlexColorStand key={`${c}-${index}`} color={c} valueMode={copyMode} /> | ||||
|       ))} | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										55
									
								
								src/pages/LightenDarken.module.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/pages/LightenDarken.module.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| @layer pages { | ||||
|   .lighten_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); | ||||
|     } | ||||
|   } | ||||
|   .lights_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; | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										135
									
								
								src/pages/LightenDarken.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/pages/LightenDarken.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| 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 { Darkens } from '../page-components/lighten-darken/darkens'; | ||||
| import { Lightens } from '../page-components/lighten-darken/lightens'; | ||||
| import { currentPickedColor } from '../stores/colors'; | ||||
| import styles from './LightenDarken.module.css'; | ||||
|  | ||||
| export function LightenDarken() { | ||||
|   const [selectedColor, setSelectedColor] = useAtom(currentPickedColor); | ||||
|   const [lighten, setLighten] = useState(3); | ||||
|   const [darken, setDarken] = useState(3); | ||||
|   const [steps, setSteps] = useState(10); | ||||
|   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.lighten_workspace)}> | ||||
|       {' '} | ||||
|       <header> | ||||
|         <h3>Lighten & Darken</h3> | ||||
|         <p>By varying the brightness of a specified color, produced a series of 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="Lighten" | ||||
|                 min={1} | ||||
|                 max={10} | ||||
|                 step={1} | ||||
|                 value={lighten} | ||||
|                 onChange={setLighten} | ||||
|               /> | ||||
|               <LabeledPicker | ||||
|                 title="Darken" | ||||
|                 min={1} | ||||
|                 max={10} | ||||
|                 step={1} | ||||
|                 value={darken} | ||||
|                 onChange={setDarken} | ||||
|               /> | ||||
|               <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.lights_content}> | ||||
|             <h5>Lighten Series</h5> | ||||
|             <div className={styles.colors_booth}> | ||||
|               <Lightens | ||||
|                 color={selectedColor} | ||||
|                 step={steps / 100} | ||||
|                 lightens={lighten} | ||||
|                 mix={mixMode} | ||||
|                 maximum={maximum} | ||||
|                 copyMode={mode} | ||||
|               /> | ||||
|             </div> | ||||
|             <h5>Darken Series</h5> | ||||
|             <div className={styles.colors_booth}> | ||||
|               <Darkens | ||||
|                 color={selectedColor} | ||||
|                 step={steps / 100} | ||||
|                 darkens={darken} | ||||
|                 mix={mixMode} | ||||
|                 maximum={maximum} | ||||
|                 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