diff --git a/src/components/LabeledPicker.module.css b/src/components/LabeledPicker.module.css new file mode 100644 index 0000000..88ac788 --- /dev/null +++ b/src/components/LabeledPicker.module.css @@ -0,0 +1,23 @@ +@layer components { + .labeled_picker { + width: 100%; + display: flex; + flex-direction: column; + align-items: stretch; + gap: var(--spacing-xs); + .title_row { + width: 100%; + display: flex; + flex-direction: row; + align-items: baseline; + label { + flex: 1; + font-size: var(--font-size-xs); + } + & > span { + text-align: right; + font-size: var(--font-size-xs); + } + } + } +} diff --git a/src/components/LabeledPicker.tsx b/src/components/LabeledPicker.tsx new file mode 100644 index 0000000..1c77e85 --- /dev/null +++ b/src/components/LabeledPicker.tsx @@ -0,0 +1,58 @@ +import cx from 'clsx'; +import { isEqual, isNil } from 'lodash-es'; +import { useEffect, useState } from 'react'; +import styles from './LabeledPicker.module.css'; + +type LabeledPickerProps = { + title?: string; + value?: number; + unit?: string; + onChange: (value: number) => void; + min?: number; + max?: number; + step?: number; +}; + +export function LabeledPicker({ + title, + value, + unit, + onChange, + min = 0, + max = 100, + step = 1, +}: LabeledPickerProps) { + const [pickerValue, setPickerValue] = useState(value ?? min); + const handlePickerChange = (event: React.ChangeEvent) => { + const value = event.target.value as number; + setPickerValue(value); + onChange?.(value); + }; + + useEffect(() => { + if (!isEqual(value, pickerValue)) { + setPickerValue(value); + } + }, [value]); + + return ( +
+
+ {!isNil(title) && } +
+ + {pickerValue} {unit} + +
+ +
+ ); +}