basic theme editor

This commit is contained in:
Vinzenz Schroeter 2024-05-07 13:20:33 +02:00
parent 4960df370c
commit bacd1777be
9 changed files with 138 additions and 38 deletions

View file

@ -3,7 +3,7 @@ import {useMutation} from '@tanstack/react-query';
import {makeApiUrl} from './serverCalls'; import {makeApiUrl} from './serverCalls';
import Button from './components/Button.tsx'; import Button from './components/Button.tsx';
import TextInput from './components/TextInput.tsx'; import {TextInput} from './components/Input.tsx';
import Dialog from './components/Dialog.tsx'; import Dialog from './components/Dialog.tsx';
import './JoinForm.css'; import './JoinForm.css';
@ -30,8 +30,7 @@ export default function JoinForm({onDone}: {
const confirm = () => postPlayer.mutate({name}); const confirm = () => postPlayer.mutate({name});
return <Dialog className="JoinForm"> return <Dialog className="JoinForm" title="Enter your name to play">
<h3> Enter your name to play </h3>
<TextInput <TextInput
value={name} value={name}
placeholder="player name" placeholder="player name"

View file

@ -57,8 +57,7 @@ function MapChooserDialog({mapNames, onClose, onConfirm}: {
readonly onClose: () => void; readonly onClose: () => void;
}) { }) {
const [chosenMap, setChosenMap] = useState<string>(); const [chosenMap, setChosenMap] = useState<string>();
return <Dialog> return <Dialog title='Choose a map' onClose={onClose}>
<h3>Choose a map</h3>
<Row className="MapChooser-Row overflow-scroll"> <Row className="MapChooser-Row overflow-scroll">
{mapNames.map(name => <MapPreview {mapNames.map(name => <MapPreview
key={name} key={name}

View file

@ -1,11 +1,79 @@
import Button from './components/Button.tsx'; import Button from './components/Button.tsx';
import {getRandomTheme, useHslTheme} from './theme.tsx'; import {getRandomTheme, HSL, hslToString, useHslTheme} from './theme.tsx';
import Dialog from './components/Dialog.tsx';
import {useState} from 'react';
import {NumberInput} from './components/Input.tsx';
import Row from './components/Row.tsx';
import Column from './components/Column.tsx';
function HslEditor({name, value, setValue}: {
name: string;
value: HSL;
setValue: (value: HSL) => void
}) {
return <Column>
<Row>
<div className="" style={{background: hslToString(value), border: '1px solid white', aspectRatio: '1'}}/>
<p>{name}</p>
</Row>
<div style={{
display: 'grid',
columnGap: 'var(--padding-normal)',
gridTemplateColumns: 'auto auto',
gridTemplateRows: 'auto'
}}>
<p>Hue</p>
<NumberInput value={Math.round(value.h)} placeholder="Hue" onChange={event => {
setValue({...value, h: parseInt(event.target.value)});
}}/>
<p>Saturation</p>
<NumberInput value={Math.round(value.s)} placeholder="Saturation" onChange={event => {
setValue({...value, s: parseInt(event.target.value)});
}}/>
<p>Lightness</p>
<NumberInput value={Math.round(value.l)} placeholder="Lightness" onChange={event => {
setValue({...value, l: parseInt(event.target.value)});
}}/>
</div>
</Column>;
}
function ThemeChooserDialog({onClose}: {
onClose: () => void;
}) {
const {hslTheme, setHslTheme} = useHslTheme();
return <Dialog title="Theme editor" onClose={onClose}>
<Button
text="surprise me"
onClick={() => setHslTheme(_ => getRandomTheme())}/>
<HslEditor
name="background"
value={hslTheme.background}
setValue={value => setHslTheme(old => ({...old, background: value}))}/>
<HslEditor
name="primary"
value={hslTheme.primary}
setValue={value => setHslTheme(old => ({...old, primary: value}))}/>
<HslEditor
name="secondary"
value={hslTheme.secondary}
setValue={value => setHslTheme(old => ({...old, secondary: value}))}/>
<HslEditor
name="background"
value={hslTheme.tertiary}
setValue={value => setHslTheme(old => ({...old, tertiary: value}))}/>
</Dialog>;
}
export default function ThemeChooser({}: {}) { export default function ThemeChooser({}: {}) {
const {setHslTheme} = useHslTheme(); const [open, setOpen] = useState(false);
return <> return <>
<Button <Button
text="☼ change colors" text="☼ change colors"
onClick={() => setHslTheme(_ => getRandomTheme())}/> onClick={() => setOpen(true)}/>
{open && <ThemeChooserDialog onClose={() => setOpen(false)}/>}
</>; </>;
} }

View file

@ -1,12 +1,20 @@
import {ReactNode} from 'react'; import {ReactNode} from 'react';
import Column from './Column.tsx'; import Column from './Column.tsx';
import Row from './Row.tsx';
import Button from './Button.tsx';
import './Dialog.css'; import './Dialog.css';
export default function Dialog({children, className}: { export default function Dialog({children, className, title, onClose}: {
children: ReactNode; title?: string;
children?: ReactNode;
className?: string; className?: string;
onClose?: () => void;
}) { }) {
return <Column className={'Dialog overflow-scroll ' + (className ?? '')}> return <Column className={'Dialog overflow-scroll ' + (className ?? '')}>
<Row>
<h3 className='flex-grow'>{title}</h3>
{onClose && <Button text='x' onClick={onClose} />}
</Row>
{children} {children}
</Column> </Column>
} }

View file

@ -1,3 +1,3 @@
.TextInput { .Input {
padding: var(--padding-normal); padding: var(--padding-normal);
} }

View file

@ -0,0 +1,45 @@
import {ChangeEventHandler} from "react";
import './Input.css';
export function TextInput( {onChange, className, value, placeholder, onEnter }: {
onChange?: ChangeEventHandler<HTMLInputElement> | undefined;
className?: string;
value: string;
placeholder: string;
onEnter?: () => void;
}) {
return <input
type="text"
className={'Input ' + (className?? '')}
value={value}
placeholder={placeholder}
onChange={onChange}
onKeyUp={event => {
if (onEnter && event.key === 'Enter')
onEnter();
}}
/>;
}
export function NumberInput( {onChange, className, value, placeholder, onEnter }: {
onChange?: ChangeEventHandler<HTMLInputElement> | undefined;
className?: string;
value: number;
placeholder: string;
onEnter?: () => void;
}) {
return <input
type="number"
className={'Input ' + (className?? '')}
value={value}
placeholder={placeholder}
onChange={onChange}
onKeyUp={event => {
if (onEnter && event.key === 'Enter')
onEnter();
}}
/>;
}

View file

@ -1,9 +1,12 @@
import {ReactNode} from "react"; import {ReactNode} from 'react';
import './Row.css'; import './Row.css';
export default function Row({children, className}: { children: ReactNode, className?: string }) { export default function Row({children, className}: {
children?: ReactNode;
className?: string;
}) {
return <div className={'Row flex-row ' + (className ?? '')}> return <div className={'Row flex-row ' + (className ?? '')}>
{children} {children}
</div> </div>;
} }

View file

@ -1,22 +0,0 @@
import {ChangeEventHandler} from "react";
import './TextInput.css';
export default function TextInput( {onChange, className, value, placeholder, onEnter }: {
onChange?: ChangeEventHandler<HTMLInputElement> | undefined;
className?: string;
value: string;
placeholder: string;
onEnter?: () => void;
}) {
return <input
type="text"
className={'TextInput ' + (className?? '')}
value={value}
placeholder={placeholder}
onChange={onChange}
onKeyUp={event => {
if (onEnter && event.key === 'Enter')
onEnter();
}}
/>;
}

View file

@ -1,7 +1,7 @@
import {useStoredObjectState} from './useStoredState.ts'; import {useStoredObjectState} from './useStoredState.ts';
import {createContext, ReactNode, useContext, useMemo, useRef, useState} from 'react'; import {createContext, ReactNode, useContext, useMemo, useRef, useState} from 'react';
type HSL = { export type HSL = {
h: number; h: number;
s: number; s: number;
l: number; l: number;
@ -53,7 +53,7 @@ function getRandomHsl(params: {
return {h, s, l}; return {h, s, l};
} }
function hslToString({h, s, l}: HSL) { export function hslToString({h, s, l}: HSL) {
return `hsl(${h},${s}%,${l}%)`; return `hsl(${h},${s}%,${l}%)`;
} }