完成Lighten & Darken功能。
This commit is contained in:
		| @@ -3,6 +3,7 @@ import { ColorFunctionProvider } from './ColorFunctionContext'; | |||||||
| import { Notifications } from './components/Notifications'; | import { Notifications } from './components/Notifications'; | ||||||
| import { Harmonies } from './pages/Harmonies'; | import { Harmonies } from './pages/Harmonies'; | ||||||
| import { Home } from './pages/Home'; | import { Home } from './pages/Home'; | ||||||
|  | import { LightenDarken } from './pages/LightenDarken'; | ||||||
| import { MainLayout } from './pages/MainLayout'; | import { MainLayout } from './pages/MainLayout'; | ||||||
| import { NewScheme } from './pages/NewScheme'; | import { NewScheme } from './pages/NewScheme'; | ||||||
| import { SchemeDetail } from './pages/SchemeDetail'; | import { SchemeDetail } from './pages/SchemeDetail'; | ||||||
| @@ -31,6 +32,7 @@ const routes = createBrowserRouter([ | |||||||
|       { path: 'wheels', element: <Wheels /> }, |       { path: 'wheels', element: <Wheels /> }, | ||||||
|       { path: 'tones', element: <Tones /> }, |       { path: 'tones', element: <Tones /> }, | ||||||
|       { path: 'tints-shades', element: <TintsShades /> }, |       { 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