完成Tints & Shades功能。
This commit is contained in:
parent
8e12f9f74a
commit
996906488e
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user