mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
FormNumberInput: add number|undefined input
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Box, FormControl, IconButton, Input, Tooltip } from '@mui/joy';
|
||||
import ClearIcon from '@mui/icons-material/Clear';
|
||||
|
||||
import { FormLabelStart } from './FormLabelStart';
|
||||
|
||||
|
||||
const _style = {
|
||||
control: {
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
inputRow: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.5,
|
||||
},
|
||||
} as const satisfies Record<string, SxProps>;
|
||||
|
||||
|
||||
/**
|
||||
* Compact numeric input field with label and optional description.
|
||||
* Supports undefined (unset/default) state with a toggle button:
|
||||
* - undefined: input disabled, X button pressed (soft) — input shows empty
|
||||
* - number: input active, X button flat (plain) — pressing X clears to undefined
|
||||
*/
|
||||
export function FormNumberInput(props: {
|
||||
title: string,
|
||||
description?: string,
|
||||
tooltip?: string,
|
||||
disabled?: boolean,
|
||||
size?: 'sm' | 'md' | 'lg',
|
||||
min?: number,
|
||||
max?: number,
|
||||
initialValue: number,
|
||||
value: undefined | number,
|
||||
onChange: (value: number | undefined) => void,
|
||||
inputSx?: SxProps,
|
||||
}) {
|
||||
|
||||
const { onChange, min, max, initialValue } = props;
|
||||
const isSet = props.value !== undefined;
|
||||
|
||||
const handleInputChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const num = parseInt(event.target.value, 10);
|
||||
if (!isNaN(num)) {
|
||||
const clamped = Math.max(min ?? -Infinity, Math.min(max ?? Infinity, num));
|
||||
onChange(clamped);
|
||||
}
|
||||
}, [max, min, onChange]);
|
||||
|
||||
const handleToggle = React.useCallback(() => {
|
||||
if (isSet)
|
||||
onChange(undefined);
|
||||
else
|
||||
onChange(initialValue);
|
||||
}, [isSet, onChange, initialValue]);
|
||||
|
||||
return (
|
||||
<FormControl size={props.size} disabled={props.disabled} orientation='horizontal' sx={_style.control}>
|
||||
<FormLabelStart title={props.title} description={props.description} tooltip={props.tooltip} />
|
||||
<Box sx={_style.inputRow}>
|
||||
<Input
|
||||
type='number'
|
||||
size={props.size}
|
||||
variant={isSet ? 'outlined' : 'plain'}
|
||||
disabled={!isSet}
|
||||
placeholder='unset'
|
||||
slotProps={{
|
||||
input: {
|
||||
...(min !== undefined ? { min } : {}),
|
||||
...(max !== undefined ? { max } : {}),
|
||||
},
|
||||
}}
|
||||
value={isSet ? props.value : ''}
|
||||
onChange={handleInputChange}
|
||||
sx={props.inputSx}
|
||||
/>
|
||||
<Tooltip arrow disableInteractive title={isSet ? 'Reset to default' : 'Customize'}>
|
||||
<IconButton
|
||||
size={props.size}
|
||||
variant={isSet ? 'plain' : 'soft'}
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<ClearIcon sx={{ fontSize: props.size === 'sm' ? 'md' : 'lg' }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user