增加色调调色功能。

This commit is contained in:
徐涛 2025-01-06 15:30:17 +08:00
parent 4effdb0847
commit e3b3151bba
4 changed files with 217 additions and 0 deletions

View File

@ -8,6 +8,7 @@ import { NewScheme } from './pages/NewScheme';
import { SchemeDetail } from './pages/SchemeDetail';
import { SchemeNotFound } from './pages/SchemeNotFound';
import { Schemes } from './pages/Schemes';
import { Tones } from './pages/Tones';
import { Wheels } from './pages/Wheels';
const routes = createBrowserRouter([
@ -27,6 +28,7 @@ const routes = createBrowserRouter([
},
{ path: 'harmonies', element: <Harmonies /> },
{ path: 'wheels', element: <Wheels /> },
{ path: 'tones', element: <Tones /> },
],
},
]);

View File

@ -0,0 +1,62 @@
import { useMemo } from 'react';
import { useColorFunction } from '../../ColorFunctionContext';
import { FlexColorStand } from '../../components/FlexColorStand';
type ToneSeresProps = {
color?: string;
expand?: number;
step?: number;
valueMode?: 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
};
export function LightenSeries({
color = '000000',
expand = 3,
step = 10,
valueMode = 'hex',
}: ToneSeresProps) {
const { colorFn } = useColorFunction();
const colors = useMemo(() => {
try {
const lightenColors = colorFn!.tonal_lighten_series(color, expand, step / 100);
return lightenColors;
} catch (e) {
console.error('[Expand lighten colors]', e);
}
return Array.from({ length: expand }, () => color);
}, [color, expand, step]);
return (
<>
{colors.map((c, index) => (
<FlexColorStand key={`${c}-${index}`} color={c} valueMode={valueMode} />
))}
</>
);
}
export function DarkenSeries({
color = '000000',
expand = 3,
step = 10,
valueMode = 'hex',
}: ToneSeresProps) {
const { colorFn } = useColorFunction();
const colors = useMemo(() => {
try {
const darkenColors = colorFn!.tonal_darken_series(color, expand, step / 100);
return darkenColors;
} catch (e) {
console.error('[Expand darken colors]', e);
}
return Array.from({ length: expand }, () => color);
}, [color, expand, step]);
return (
<>
{colors.map((c, index) => (
<FlexColorStand key={`${c}-${index}`} color={c} valueMode={valueMode} />
))}
</>
);
}

View File

@ -0,0 +1,55 @@
@layer pages {
.tone_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);
}
}
.tones_content {
flex: 1;
padding: 0 var(--spacing-m);
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
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: 15em;
}
}
}

98
src/pages/Tones.tsx Normal file
View File

@ -0,0 +1,98 @@
import cx from 'clsx';
import { useAtom } from 'jotai';
import { toInteger } from 'lodash-es';
import { useState } from 'react';
import { ColorPicker } from '../components/ColorPicker';
import { FlexColorStand } from '../components/FlexColorStand';
import { HSegmentedControl } from '../components/HSegmentedControl';
import { LabeledPicker } from '../components/LabeledPicker';
import { DarkenSeries, LightenSeries } from '../page-components/tones/ToneSeries';
import { currentPickedColor } from '../stores/colors';
import styles from './Tones.module.css';
export function Tones() {
const [selectedColor, setSelectedColor] = useAtom(currentPickedColor);
const [steps, setSteps] = useState(10);
const [tones, setTones] = useState(3);
const [seedBias, setSeedBias] = useState(0);
const [mode, setMode] = useState<'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch'>('hex');
return (
<div className={cx('workspace', styles.tone_workspace)}>
<header>
<h3>Color Tones</h3>
<p>By regularly changing the color hue to generate a series of light and dark colors.</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>
<h5>Series Setting</h5>
<LabeledPicker
title="Expand Tones"
min={1}
max={5}
step={1}
value={tones}
onChange={(value) => {
setTones(toInteger(value));
setSeedBias(0);
}}
/>
<LabeledPicker
title="Root bias"
min={-(tones - 1)}
max={tones - 1}
value={seedBias}
onChange={(value) => setSeedBias(toInteger(value))}
/>
<LabeledPicker
title="Tone Step"
min={10}
max={25}
step={1}
unit="%"
value={steps}
onChange={setSteps}
/>
</div>
</aside>
<div className={styles.tones_content}>
<h5>Color Tones</h5>
<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 className={styles.colors_booth}>
<DarkenSeries
color={selectedColor}
expand={tones + seedBias}
step={steps}
valueMode={mode}
/>
<FlexColorStand color={selectedColor} valueMode={mode} />
<LightenSeries
color={selectedColor}
expand={tones - seedBias}
step={steps}
valueMode={mode}
/>
</div>
</div>
</section>
</div>
);
}