mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-06-29 23:41:12 +08:00
Fix: Fix: The minimum value for the "Suggested text block size" input box is set to 1. (#14246)
### What problem does this PR solve? Fix: The minimum value for the "Suggested text block size" input box is set to 1. ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
@@ -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<
|
||||
<FormControl>
|
||||
<NumberInput
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'h-6 w-10 p-1 border border-border-button rounded-sm',
|
||||
numberInputClassName,
|
||||
)}
|
||||
className={cn('h-6 w-16', numberInputClassName)}
|
||||
max={max}
|
||||
min={min}
|
||||
step={step}
|
||||
{...field}
|
||||
hideIcons
|
||||
onChange={(value: number) => {
|
||||
onChange?.(value);
|
||||
field.onChange(value);
|
||||
|
||||
@@ -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<HTMLInputElement, InputProps & NumberInputProps>(
|
||||
function NumberInput(
|
||||
{
|
||||
className,
|
||||
value: initialValue,
|
||||
onChange,
|
||||
height,
|
||||
min = 0,
|
||||
max = Infinity,
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const [value, setValue] = useState<number | ''>(() => {
|
||||
return initialValue ?? 0;
|
||||
});
|
||||
const NumberInput = forwardRef<
|
||||
HTMLInputElement,
|
||||
Omit<InputProps, 'onChange' | 'value'> & NumberInputProps
|
||||
>(function NumberInput(
|
||||
{
|
||||
className,
|
||||
value: initialValue,
|
||||
onChange,
|
||||
height,
|
||||
min = 0,
|
||||
max = Infinity,
|
||||
hideIcons = false,
|
||||
inputClassName,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const [value, setValue] = useState<number | ''>(() => {
|
||||
return initialValue ?? 0;
|
||||
});
|
||||
|
||||
const valueRef = useRef<number>();
|
||||
const valueRef = useRef<number>();
|
||||
|
||||
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<HTMLInputElement>) => {
|
||||
const currentValue = e.target.value;
|
||||
const newValue = Number(currentValue);
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
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<HTMLInputElement> = 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<HTMLInputElement> = 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 (
|
||||
<>
|
||||
<style>{`
|
||||
.number-input-hide-spin::-webkit-inner-spin-button,
|
||||
.number-input-hide-spin::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
.number-input-hide-spin[type='number'] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
`}</style>
|
||||
<div
|
||||
className={`flex h-10 items-center space-x-2 border-[1px] rounded-lg w-[150px] ${className || ''}`}
|
||||
className={cn(
|
||||
`flex h-10 items-center space-x-2 border-[1px] rounded-lg w-[150px]`,
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
ref={ref}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="w-10 p-2 focus:outline-none border-r-[1px]"
|
||||
onClick={handleDecrement}
|
||||
style={style}
|
||||
>
|
||||
<MinusIcon size={16} aria-hidden="true" />
|
||||
</button>
|
||||
{hideIcons || (
|
||||
<button
|
||||
type="button"
|
||||
className="w-10 p-2 focus:outline-none border-r-[1px]"
|
||||
onClick={handleDecrement}
|
||||
style={style}
|
||||
>
|
||||
<MinusIcon size={16} aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
<input
|
||||
type="text"
|
||||
type="number"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
className="w-full flex-1 text-center bg-transparent focus:outline-none"
|
||||
className={cn(
|
||||
'w-full flex-1 text-center bg-transparent focus-visible:outline-none number-input-hide-spin',
|
||||
'disabled:cursor-not-allowed disabled:opacity-50 transition-colors',
|
||||
{
|
||||
'focus-visible:ring-1 focus-visible:ring-accent-primary rounded-lg':
|
||||
hideIcons,
|
||||
},
|
||||
inputClassName,
|
||||
)}
|
||||
style={style}
|
||||
min={min}
|
||||
{...omit(props, ['prefix', 'suffix'])}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="w-10 p-2 focus:outline-none border-l-[1px]"
|
||||
onClick={handleIncrement}
|
||||
style={style}
|
||||
>
|
||||
<PlusIcon size={16} aria-hidden="true" />
|
||||
</button>
|
||||
{hideIcons || (
|
||||
<button
|
||||
type="button"
|
||||
className="w-10 p-2 focus:outline-none border-l-[1px]"
|
||||
onClick={handleIncrement}
|
||||
style={style}
|
||||
>
|
||||
<PlusIcon size={16} aria-hidden="true" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default NumberInput;
|
||||
|
||||
@@ -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<
|
||||
<FormControl>
|
||||
<NumberInput
|
||||
className={cn(
|
||||
'h-6 w-10 p-0 text-center bg-bg-input border border-border-button text-text-secondary',
|
||||
'h-6 w-16 p-0 text-center bg-bg-input border border-border-button text-text-secondary',
|
||||
'[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none',
|
||||
numberInputClassName,
|
||||
)}
|
||||
max={displayMax}
|
||||
min={displayMin}
|
||||
step={displayStep}
|
||||
hideIcons
|
||||
value={
|
||||
percentage ? (field.value * 100).toFixed(0) : field.value
|
||||
}
|
||||
|
||||
@@ -43,8 +43,9 @@ function MyComponent() {
|
||||
### Features
|
||||
- Increment/decrement buttons for easy value adjustment
|
||||
- Keyboard input validation (only allows numeric input)
|
||||
- Customizable height and styling
|
||||
- Non-negative number validation
|
||||
- Customizable height and styling (wrapper and input)
|
||||
- Min/max value constraints
|
||||
- Option to hide increment/decrement buttons
|
||||
- Responsive design with Tailwind CSS
|
||||
`,
|
||||
},
|
||||
@@ -63,13 +64,30 @@ function MyComponent() {
|
||||
control: false,
|
||||
},
|
||||
height: {
|
||||
description: 'Custom height for the input component',
|
||||
description:
|
||||
'Custom height for the input component (number or string with px)',
|
||||
control: { type: 'text' },
|
||||
},
|
||||
className: {
|
||||
description: 'Additional CSS classes for styling',
|
||||
description: 'Additional CSS classes for the wrapper',
|
||||
control: { type: 'text' },
|
||||
},
|
||||
inputClassName: {
|
||||
description: 'Additional CSS classes for the input element',
|
||||
control: { type: 'text' },
|
||||
},
|
||||
min: {
|
||||
description: 'Minimum allowed value',
|
||||
control: { type: 'number' },
|
||||
},
|
||||
max: {
|
||||
description: 'Maximum allowed value',
|
||||
control: { type: 'number' },
|
||||
},
|
||||
hideIcons: {
|
||||
description: 'Hide the increment/decrement buttons',
|
||||
control: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
// Use `fn` to spy on the onChange arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
|
||||
args: { onChange: fn() },
|
||||
@@ -182,3 +200,86 @@ Shows the number input with custom CSS classes for styling.
|
||||
},
|
||||
tags: ['!dev'],
|
||||
};
|
||||
|
||||
export const WithMinMax: Story = {
|
||||
args: {
|
||||
value: 5,
|
||||
min: 0,
|
||||
max: 10,
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: `
|
||||
### With Min/Max Constraints
|
||||
|
||||
Shows the number input with minimum and maximum value constraints. Values outside the range are rejected.
|
||||
|
||||
\`\`\`tsx
|
||||
<NumberInput
|
||||
value={5}
|
||||
min={0}
|
||||
max={10}
|
||||
onChange={(value) => 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
|
||||
<NumberInput
|
||||
value={7}
|
||||
hideIcons
|
||||
onChange={(value) => 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
|
||||
<NumberInput
|
||||
value={4}
|
||||
inputClassName="text-red-500 font-bold"
|
||||
onChange={(value) => console.log('Value changed:', value)}
|
||||
/>
|
||||
\`\`\`
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['!dev'],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user