diff --git a/web/src/components/llm-setting-items/slider.tsx b/web/src/components/llm-setting-items/slider.tsx index 58d7a1ddc8..6fd447650b 100644 --- a/web/src/components/llm-setting-items/slider.tsx +++ b/web/src/components/llm-setting-items/slider.tsx @@ -2,6 +2,7 @@ import { useTranslate } from '@/hooks/common-hooks'; import { cn } from '@/lib/utils'; import { forwardRef } from 'react'; import { useFormContext } from 'react-hook-form'; +import NumberInput from '../originui/number-input'; import { SingleFormSlider } from '../ui/dual-range-slider'; import { FormControl, @@ -10,7 +11,6 @@ import { FormLabel, FormMessage, } from '../ui/form'; -import { NumberInput } from '../ui/input'; import { Switch } from '../ui/switch'; type SliderInputSwitchFormFieldProps = { @@ -95,14 +95,12 @@ export const SliderInputSwitchFormField = forwardRef< { onChange?.(value); field.onChange(value); diff --git a/web/src/components/originui/number-input.tsx b/web/src/components/originui/number-input.tsx index 8dd329681d..d5dc6c6075 100644 --- a/web/src/components/originui/number-input.tsx +++ b/web/src/components/originui/number-input.tsx @@ -1,4 +1,5 @@ -import { isNumber, trim } from 'lodash'; +import { cn } from '@/lib/utils'; +import { isNumber, omit, trim } from 'lodash'; import { MinusIcon, PlusIcon } from 'lucide-react'; import React, { FocusEventHandler, @@ -18,121 +19,155 @@ interface NumberInputProps { height?: number | string; min?: number; max?: number; + hideIcons?: boolean; + inputClassName?: string; } -const NumberInput = forwardRef( - function NumberInput( - { - className, - value: initialValue, - onChange, - height, - min = 0, - max = Infinity, - }, - ref, - ) { - const [value, setValue] = useState(() => { - return initialValue ?? 0; - }); +const NumberInput = forwardRef< + HTMLInputElement, + Omit & NumberInputProps +>(function NumberInput( + { + className, + value: initialValue, + onChange, + height, + min = 0, + max = Infinity, + hideIcons = false, + inputClassName, + ...props + }, + ref, +) { + const [value, setValue] = useState(() => { + return initialValue ?? 0; + }); - const valueRef = useRef(); + const valueRef = useRef(); - useEffect(() => { - if (initialValue !== undefined) { - setValue(initialValue); - } - }, [initialValue]); + useEffect(() => { + if (initialValue !== undefined) { + setValue(initialValue); + } + }, [initialValue]); - const handleDecrement = () => { - if (isNumber(value) && value > min) { - setValue(value - 1); - onChange?.(value - 1); - } - }; + const handleDecrement = () => { + if (isNumber(value) && value > min) { + setValue(value - 1); + onChange?.(value - 1); + } + }; - const handleIncrement = () => { - if (!isNumber(value)) { - return; - } - if (value > max - 1) { - return; - } - setValue(value + 1); - onChange?.(value + 1); - }; + const handleIncrement = () => { + if (!isNumber(value)) { + return; + } + if (value > max - 1) { + return; + } + setValue(value + 1); + onChange?.(value + 1); + }; - const handleChange = (e: React.ChangeEvent) => { - const currentValue = e.target.value; - const newValue = Number(currentValue); + const handleChange = (e: React.ChangeEvent) => { + const currentValue = e.target.value; + const newValue = Number(currentValue); - if (trim(currentValue) === '') { - if (isNumber(value)) { - valueRef.current = value; - } - setValue(''); - return; - } - - if (!isNaN(newValue)) { - if (newValue > max || newValue < min) { - return; - } - setValue(newValue); - onChange?.(newValue); - } - }; - - const handleBlur: FocusEventHandler = useCallback(() => { + if (trim(currentValue) === '') { if (isNumber(value)) { - onChange?.(value); - } else { - const previousValue = valueRef.current ?? min; - setValue(previousValue); - onChange?.(previousValue); + valueRef.current = value; } - }, [min, onChange, value]); + setValue(''); + return; + } - const style = useMemo( - () => ({ - height: height ? `${height.toString().replace('px', '')}px` : 'auto', - }), - [height], - ); - return ( + if (!isNaN(newValue)) { + if (newValue > max || newValue < min) { + return; + } + setValue(newValue); + onChange?.(newValue); + } + }; + + const handleBlur: FocusEventHandler = useCallback(() => { + if (isNumber(value)) { + onChange?.(value); + } else { + const previousValue = valueRef.current ?? min; + setValue(previousValue); + onChange?.(previousValue); + } + }, [min, onChange, value]); + + const style = useMemo( + () => ({ + height: height ? `${height.toString().replace('px', '')}px` : 'auto', + }), + [height], + ); + return ( + <> +
- + {hideIcons || ( + + )} - + {hideIcons || ( + + )}
- ); - }, -); + + ); +}); export default NumberInput; diff --git a/web/src/components/slider-input-form-field.tsx b/web/src/components/slider-input-form-field.tsx index f45290eb07..c7834a1544 100644 --- a/web/src/components/slider-input-form-field.tsx +++ b/web/src/components/slider-input-form-field.tsx @@ -2,6 +2,7 @@ import { FormLayout } from '@/constants/form'; import { cn } from '@/lib/utils'; import { forwardRef, ReactNode, useMemo } from 'react'; import { useFormContext } from 'react-hook-form'; +import NumberInput from './originui/number-input'; import { SingleFormSlider } from './ui/dual-range-slider'; import { FormControl, @@ -10,7 +11,6 @@ import { FormLabel, FormMessage, } from './ui/form'; -import { NumberInput } from './ui/input'; export type FormLayoutType = { layout?: FormLayout; @@ -105,13 +105,14 @@ export const SliderInputFormField = forwardRef< console.log('Value changed:', value)} +/> +\`\`\` + `, + }, + }, + }, + tags: ['!dev'], +}; + +export const HideIcons: Story = { + args: { + value: 7, + hideIcons: true, + }, + parameters: { + docs: { + description: { + story: ` +### Without Icons + +Shows the number input with increment/decrement buttons hidden, leaving only the text input field. + +\`\`\`tsx + console.log('Value changed:', value)} +/> +\`\`\` + `, + }, + }, + }, + tags: ['!dev'], +}; + +export const WithInputClassName: Story = { + args: { + value: 4, + inputClassName: 'text-red-500 font-bold', + }, + parameters: { + docs: { + description: { + story: ` +### With Input Class Name + +Shows the number input with custom CSS classes applied directly to the input element. + +\`\`\`tsx + console.log('Value changed:', value)} +/> +\`\`\` + `, + }, + }, + }, + tags: ['!dev'], +};