add range sliders
This commit is contained in:
parent
59459019fc
commit
e854f77bdc
6
tank-frontend/src/ThemeChooser.css
Normal file
6
tank-frontend/src/ThemeChooser.css
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
.HslEditor-Inputs {
|
||||||
|
display: grid;
|
||||||
|
column-gap: var(--padding-normal);
|
||||||
|
grid-template-columns: auto auto auto;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
}
|
|
@ -2,40 +2,37 @@ import Button from './components/Button.tsx';
|
||||||
import {getRandomTheme, HSL, hslToString, useHslTheme} from './theme.tsx';
|
import {getRandomTheme, HSL, hslToString, useHslTheme} from './theme.tsx';
|
||||||
import Dialog from './components/Dialog.tsx';
|
import Dialog from './components/Dialog.tsx';
|
||||||
import {useState} from 'react';
|
import {useState} from 'react';
|
||||||
import {NumberInput} from './components/Input.tsx';
|
import {NumberInput, RangeInput} from './components/Input.tsx';
|
||||||
import Row from './components/Row.tsx';
|
import Row from './components/Row.tsx';
|
||||||
import Column from './components/Column.tsx';
|
import Column from './components/Column.tsx';
|
||||||
|
import './ThemeChooser.css';
|
||||||
|
|
||||||
function HslEditor({name, value, setValue}: {
|
function HslEditor({name, value, setValue}: {
|
||||||
name: string;
|
name: string;
|
||||||
value: HSL;
|
value: HSL;
|
||||||
setValue: (value: HSL) => void
|
setValue: (value: HSL) => void
|
||||||
}) {
|
}) {
|
||||||
|
const setH = (h: number) => setValue({...value, h});
|
||||||
|
const setS = (s: number) => setValue({...value, s});
|
||||||
|
const setL = (l: number) => setValue({...value, l});
|
||||||
|
|
||||||
return <Column>
|
return <Column>
|
||||||
<Row>
|
<Row>
|
||||||
<div className="" style={{background: hslToString(value), border: '1px solid white', aspectRatio: '1'}}/>
|
<div className="" style={{background: hslToString(value), border: '1px solid white', aspectRatio: '1'}}/>
|
||||||
<p>{name}</p>
|
<p>{name}</p>
|
||||||
</Row>
|
</Row>
|
||||||
<div style={{
|
<div className='HslEditor-Inputs'>
|
||||||
display: 'grid',
|
|
||||||
columnGap: 'var(--padding-normal)',
|
|
||||||
gridTemplateColumns: 'auto auto',
|
|
||||||
gridTemplateRows: 'auto'
|
|
||||||
}}>
|
|
||||||
<p>Hue</p>
|
<p>Hue</p>
|
||||||
<NumberInput value={Math.round(value.h)} placeholder="Hue" onChange={event => {
|
<NumberInput value={Math.round(value.h)} onChange={setH}/>
|
||||||
setValue({...value, h: parseInt(event.target.value)});
|
<RangeInput value={Math.round(value.h)} min={0} max={360} onChange={setH}/>
|
||||||
}}/>
|
|
||||||
|
|
||||||
<p>Saturation</p>
|
<p>Saturation</p>
|
||||||
<NumberInput value={Math.round(value.s)} placeholder="Saturation" onChange={event => {
|
<NumberInput value={Math.round(value.s)} onChange={setS}/>
|
||||||
setValue({...value, s: parseInt(event.target.value)});
|
<RangeInput value={Math.round(value.s)} min={0} max={100} onChange={setS}/>
|
||||||
}}/>
|
|
||||||
|
|
||||||
<p>Lightness</p>
|
<p>Lightness</p>
|
||||||
<NumberInput value={Math.round(value.l)} placeholder="Lightness" onChange={event => {
|
<NumberInput value={Math.round(value.l)} onChange={setL}/>
|
||||||
setValue({...value, l: parseInt(event.target.value)});
|
<RangeInput value={Math.round(value.l)} min={0} max={100} onChange={setL}/>
|
||||||
}}/>
|
|
||||||
</div>
|
</div>
|
||||||
</Column>;
|
</Column>;
|
||||||
}
|
}
|
||||||
|
@ -45,25 +42,30 @@ function ThemeChooserDialog({onClose}: {
|
||||||
}) {
|
}) {
|
||||||
const {hslTheme, setHslTheme} = useHslTheme();
|
const {hslTheme, setHslTheme} = useHslTheme();
|
||||||
return <Dialog title="Theme editor" onClose={onClose}>
|
return <Dialog title="Theme editor" onClose={onClose}>
|
||||||
<Button
|
<Row>
|
||||||
text="surprise me"
|
<Button
|
||||||
onClick={() => setHslTheme(_ => getRandomTheme())}/>
|
text="surprise me"
|
||||||
<HslEditor
|
className='flex-grow'
|
||||||
name="background"
|
onClick={() => setHslTheme(_ => getRandomTheme())}/>
|
||||||
value={hslTheme.background}
|
</Row>
|
||||||
setValue={value => setHslTheme(old => ({...old, background: value}))}/>
|
<Column className='overflow-scroll'>
|
||||||
<HslEditor
|
<HslEditor
|
||||||
name="primary"
|
name="background"
|
||||||
value={hslTheme.primary}
|
value={hslTheme.background}
|
||||||
setValue={value => setHslTheme(old => ({...old, primary: value}))}/>
|
setValue={value => setHslTheme(old => ({...old, background: value}))}/>
|
||||||
<HslEditor
|
<HslEditor
|
||||||
name="secondary"
|
name="primary"
|
||||||
value={hslTheme.secondary}
|
value={hslTheme.primary}
|
||||||
setValue={value => setHslTheme(old => ({...old, secondary: value}))}/>
|
setValue={value => setHslTheme(old => ({...old, primary: value}))}/>
|
||||||
<HslEditor
|
<HslEditor
|
||||||
name="background"
|
name="secondary"
|
||||||
value={hslTheme.tertiary}
|
value={hslTheme.secondary}
|
||||||
setValue={value => setHslTheme(old => ({...old, tertiary: value}))}/>
|
setValue={value => setHslTheme(old => ({...old, secondary: value}))}/>
|
||||||
|
<HslEditor
|
||||||
|
name="tertiary"
|
||||||
|
value={hslTheme.tertiary}
|
||||||
|
setValue={value => setHslTheme(old => ({...old, tertiary: value}))}/>
|
||||||
|
</Column>
|
||||||
</Dialog>;
|
</Dialog>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
.Input {
|
.Input {
|
||||||
|
background-color: var(--color-background);
|
||||||
|
color: var(--color-text);
|
||||||
padding: var(--padding-normal);
|
padding: var(--padding-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.RangeInput {
|
||||||
|
appearance: auto;
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {ChangeEventHandler} from "react";
|
import {ChangeEventHandler} from 'react';
|
||||||
import './Input.css';
|
import './Input.css';
|
||||||
|
|
||||||
export function TextInput( {onChange, className, value, placeholder, onEnter }: {
|
export function TextInput({onChange, className, value, placeholder, onEnter}: {
|
||||||
onChange?: ChangeEventHandler<HTMLInputElement> | undefined;
|
onChange?: ChangeEventHandler<HTMLInputElement> | undefined;
|
||||||
className?: string;
|
className?: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
@ -10,7 +10,7 @@ export function TextInput( {onChange, className, value, placeholder, onEnter }:
|
||||||
}) {
|
}) {
|
||||||
return <input
|
return <input
|
||||||
type="text"
|
type="text"
|
||||||
className={'Input ' + (className?? '')}
|
className={'Input ' + (className ?? '')}
|
||||||
value={value}
|
value={value}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
@ -21,8 +21,8 @@ export function TextInput( {onChange, className, value, placeholder, onEnter }:
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NumberInput( {onChange, className, value, placeholder, onEnter }: {
|
export function NumberInput({onChange, className, value, placeholder, onEnter}: {
|
||||||
onChange?: ChangeEventHandler<HTMLInputElement> | undefined;
|
onChange?: (value: number) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
value: number;
|
value: number;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
@ -30,16 +30,47 @@ export function NumberInput( {onChange, className, value, placeholder, onEnter }
|
||||||
}) {
|
}) {
|
||||||
return <input
|
return <input
|
||||||
type="number"
|
type="number"
|
||||||
className={'Input ' + (className?? '')}
|
className={'Input ' + (className ?? '')}
|
||||||
value={value}
|
value={value}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
onChange={onChange}
|
onChange={event => {
|
||||||
|
if (!onChange)
|
||||||
|
return;
|
||||||
|
onChange(parseFloat(event.target.value));
|
||||||
|
}}
|
||||||
onKeyUp={event => {
|
onKeyUp={event => {
|
||||||
if (onEnter && event.key === 'Enter')
|
if (!onEnter || event.key !== 'Enter')
|
||||||
onEnter();
|
return;
|
||||||
|
onEnter();
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RangeInput({onChange, className, value, placeholder, onEnter, min, max}: {
|
||||||
|
onChange?: (value: number) => void;
|
||||||
|
className?: string;
|
||||||
|
value: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
placeholder?: string;
|
||||||
|
onEnter?: () => void;
|
||||||
|
}) {
|
||||||
|
return <input
|
||||||
|
type="range"
|
||||||
|
className={'Input RangeInput ' + (className ?? '')}
|
||||||
|
value={value} min={min} max={max}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChange={event => {
|
||||||
|
if (!onChange)
|
||||||
|
return;
|
||||||
|
onChange(parseFloat(event.target.value));
|
||||||
|
}}
|
||||||
|
onKeyUp={event => {
|
||||||
|
if (!onEnter || event.key !== 'Enter')
|
||||||
|
return;
|
||||||
|
onEnter();
|
||||||
}}
|
}}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue