mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-03-29 03:29:13 +00:00
feat: better number inputs
This commit is contained in:
parent
114526221b
commit
8378b85167
1 changed files with 76 additions and 108 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Icon } from "@iconify/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -5,120 +6,87 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NumberInputs({ target }: Props) {
|
export default function NumberInputs({ target }: Props) {
|
||||||
const [height, setHeight] = useState(0);
|
const [values, setValues] = useState<Record<string, number>>({
|
||||||
const [distance, setDistance] = useState(0);
|
height: 0,
|
||||||
const [rotation, setRotation] = useState(0);
|
distance: 0,
|
||||||
const [size, setSize] = useState(0);
|
rotation: 0,
|
||||||
const [stretch, setStretch] = useState(0);
|
size: 0,
|
||||||
|
stretch: 0,
|
||||||
|
});
|
||||||
|
|
||||||
if (!target) return null;
|
if (!target) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 gap-x-4 h-min w-fit">
|
<div className="grid grid-cols-2 gap-x-4 h-min w-fit">
|
||||||
{target.height !== undefined && (
|
{["Height", "Distance", "Rotation", "Size", "Stretch"].map(
|
||||||
<div>
|
(label) =>
|
||||||
<label htmlFor="height" className="text-xs">
|
target[label.toLowerCase()] !== undefined && (
|
||||||
Height
|
<NumberField
|
||||||
</label>
|
key={label}
|
||||||
<input
|
label={label}
|
||||||
type="number"
|
value={values[label.toLowerCase()]}
|
||||||
id="height"
|
onChange={(value) => {
|
||||||
min={-15}
|
const field = label.toLowerCase();
|
||||||
max={15}
|
setValues((prev) => ({ ...prev, [field]: value }));
|
||||||
value={height}
|
target[field] = value;
|
||||||
onChange={(e) => {
|
|
||||||
const value = Number(e.target.value);
|
|
||||||
setHeight(value);
|
|
||||||
target.height = value;
|
|
||||||
}}
|
}}
|
||||||
className="pill input text-sm py-1! px-3! w-full"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
),
|
||||||
)}
|
|
||||||
|
|
||||||
{target.distance !== undefined && (
|
|
||||||
<div>
|
|
||||||
<label htmlFor="distance" className="text-xs">
|
|
||||||
Distance
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
id="distance"
|
|
||||||
min={-15}
|
|
||||||
max={15}
|
|
||||||
value={distance}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = Number(e.target.value);
|
|
||||||
setDistance(value);
|
|
||||||
target.distance = value;
|
|
||||||
}}
|
|
||||||
className="pill input text-sm py-1! px-3! w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{target.rotation !== undefined && (
|
|
||||||
<div>
|
|
||||||
<label htmlFor="rotation" className="text-xs">
|
|
||||||
Rotation
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
id="rotation"
|
|
||||||
min={-15}
|
|
||||||
max={15}
|
|
||||||
value={rotation}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = Number(e.target.value);
|
|
||||||
setRotation(value);
|
|
||||||
target.rotation = value;
|
|
||||||
}}
|
|
||||||
className="pill input text-sm py-1! px-3! w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{target.size !== undefined && (
|
|
||||||
<div>
|
|
||||||
<label htmlFor="size" className="text-xs">
|
|
||||||
Size
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
id="size"
|
|
||||||
min={-15}
|
|
||||||
max={15}
|
|
||||||
value={size}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = Number(e.target.value);
|
|
||||||
setSize(value);
|
|
||||||
target.size = value;
|
|
||||||
}}
|
|
||||||
className="pill input text-sm py-1! px-3! w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{target.stretch !== undefined && (
|
|
||||||
<div>
|
|
||||||
<label htmlFor="stretch" className="text-xs">
|
|
||||||
Stretch
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
id="stretch"
|
|
||||||
min={-15}
|
|
||||||
max={15}
|
|
||||||
value={stretch}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = Number(e.target.value);
|
|
||||||
setStretch(value);
|
|
||||||
target.stretch = value;
|
|
||||||
}}
|
|
||||||
className="pill input text-sm py-1! px-3! w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NumberFieldProps {
|
||||||
|
label: string;
|
||||||
|
value: number;
|
||||||
|
onChange: (value: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function NumberField({ label, value, onChange }: NumberFieldProps) {
|
||||||
|
const MIN = -15;
|
||||||
|
const MAX = 15;
|
||||||
|
|
||||||
|
const decrement = () => onChange(Math.max(MIN, value - 1));
|
||||||
|
const increment = () => onChange(Math.min(MAX, value + 1));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<label htmlFor={label} className="text-xs">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
<div className="pill input text-sm py-1! px-2! w-full flex items-center gap-1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={decrement}
|
||||||
|
disabled={value <= MIN}
|
||||||
|
className="cursor-pointer flex items-center justify-center shrink-0 disabled:opacity-30"
|
||||||
|
aria-label={`Decrease ${label}`}
|
||||||
|
>
|
||||||
|
<Icon icon="mdi:minus" width="16" height="16" />
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id={label}
|
||||||
|
min={MIN}
|
||||||
|
max={MAX}
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val = Math.min(MAX, Math.max(MIN, Number(e.target.value)));
|
||||||
|
onChange(val);
|
||||||
|
}}
|
||||||
|
className="w-full text-center bg-transparent outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={increment}
|
||||||
|
disabled={value >= MAX}
|
||||||
|
className="cursor-pointer flex items-center justify-center shrink-0 disabled:opacity-30"
|
||||||
|
aria-label={`Increase ${label}`}
|
||||||
|
>
|
||||||
|
<Icon icon="mdi:plus" width="16" height="16" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue