Fix: Fixed some errors in the console (#13317)

### What problem does this PR solve?

Fix: Fixed some errors in the console
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2026-03-02 19:19:15 +08:00
committed by GitHub
parent a806f7b707
commit ef264b52c7
5 changed files with 322 additions and 270 deletions

View File

@ -1,5 +1,6 @@
import { useTranslate } from '@/hooks/common-hooks';
import { cn } from '@/lib/utils';
import { forwardRef } from 'react';
import { useFormContext } from 'react-hook-form';
import { SingleFormSlider } from '../ui/dual-range-slider';
import {
@ -25,82 +26,96 @@ type SliderInputSwitchFormFieldProps = {
numberInputClassName?: string;
};
export function SliderInputSwitchFormField({
max,
min,
step,
label,
name,
defaultValue,
onChange,
className,
checkName,
numberInputClassName,
}: SliderInputSwitchFormFieldProps) {
const form = useFormContext();
const disabled = !form.watch(checkName);
const { t } = useTranslate('chat');
export const SliderInputSwitchFormField = forwardRef<
HTMLDivElement,
SliderInputSwitchFormFieldProps
>(
(
{
max,
min,
step,
label,
name,
defaultValue,
onChange,
className,
checkName,
numberInputClassName,
},
ref,
) => {
const form = useFormContext();
const disabled = !form.watch(checkName);
const { t } = useTranslate('chat');
return (
<FormField
control={form.control}
name={name}
defaultValue={defaultValue}
render={({ field }) => (
<FormItem>
<FormLabel tooltip={t(`${label}Tip`)}>{t(label)}</FormLabel>
<div
className={cn('flex items-center gap-4 justify-between', className)}
>
<FormField
control={form.control}
name={checkName}
render={({ field }) => (
<FormItem>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<FormMessage />
</FormItem>
return (
<FormField
control={form.control}
name={name}
defaultValue={defaultValue}
render={({ field }) => (
<FormItem>
<FormLabel tooltip={t(`${label}Tip`)}>{t(label)}</FormLabel>
<div
ref={ref}
className={cn(
'flex items-center gap-4 justify-between',
className,
)}
/>
<FormControl>
<SingleFormSlider
{...field}
onChange={(value: number) => {
onChange?.(value);
field.onChange(value);
}}
max={max}
min={min}
step={step}
disabled={disabled}
></SingleFormSlider>
</FormControl>
<FormControl>
<NumberInput
disabled={disabled}
className={cn(
'h-6 w-10 p-1 border border-border-button rounded-sm',
numberInputClassName,
>
<FormField
control={form.control}
name={checkName}
render={({ field }) => (
<FormItem>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
max={max}
min={min}
step={step}
{...field}
onChange={(value: number) => {
onChange?.(value);
field.onChange(value);
}}
></NumberInput>
</FormControl>
</div>
<FormMessage />
</FormItem>
)}
/>
);
}
/>
<FormControl>
<SingleFormSlider
{...field}
onChange={(value: number) => {
onChange?.(value);
field.onChange(value);
}}
max={max}
min={min}
step={step}
disabled={disabled}
></SingleFormSlider>
</FormControl>
<FormControl>
<NumberInput
disabled={disabled}
className={cn(
'h-6 w-10 p-1 border border-border-button rounded-sm',
numberInputClassName,
)}
max={max}
min={min}
step={step}
{...field}
onChange={(value: number) => {
onChange?.(value);
field.onChange(value);
}}
></NumberInput>
</FormControl>
</div>
<FormMessage />
</FormItem>
)}
/>
);
},
);
SliderInputSwitchFormField.displayName = 'SliderInputSwitchFormField';

View File

@ -1,6 +1,6 @@
import { FormLayout } from '@/constants/form';
import { cn } from '@/lib/utils';
import { ReactNode, useMemo } from 'react';
import { forwardRef, ReactNode, useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import { SingleFormSlider } from './ui/dual-range-slider';
import {
@ -29,88 +29,104 @@ type SliderInputFormFieldProps = {
percentage?: boolean;
} & FormLayoutType;
export function SliderInputFormField({
max,
min,
step,
label,
name,
tooltip,
defaultValue,
className,
numberInputClassName,
layout = FormLayout.Horizontal,
percentage = false,
}: SliderInputFormFieldProps) {
const form = useFormContext();
export const SliderInputFormField = forwardRef<
HTMLDivElement,
SliderInputFormFieldProps
>(
(
{
max,
min,
step,
label,
name,
tooltip,
defaultValue,
className,
numberInputClassName,
layout = FormLayout.Horizontal,
percentage = false,
},
ref,
) => {
const form = useFormContext();
const isHorizontal = useMemo(() => layout !== FormLayout.Vertical, [layout]);
const displayMax = percentage ? (max || 1) * 100 : max;
const displayMin = percentage ? (min || 0) * 100 : min;
const displayStep = percentage ? (step || 0.01) * 100 : step;
return (
<FormField
control={form.control}
name={name}
defaultValue={defaultValue || 0}
render={({ field }) => (
<FormItem
className={cn({ 'flex items-center gap-1 space-y-0': isHorizontal })}
>
<FormLabel
tooltip={tooltip}
const isHorizontal = useMemo(
() => layout !== FormLayout.Vertical,
[layout],
);
const displayMax = percentage ? (max || 1) * 100 : max;
const displayMin = percentage ? (min || 0) * 100 : min;
const displayStep = percentage ? (step || 0.01) * 100 : step;
return (
<FormField
control={form.control}
name={name}
defaultValue={defaultValue || 0}
render={({ field }) => (
<FormItem
ref={ref}
className={cn({
'text-sm whitespace-break-spaces w-1/4': isHorizontal,
'flex items-center gap-1 space-y-0': isHorizontal,
})}
>
{label}
</FormLabel>
<div
className={cn(
'flex items-center gap-4 justify-between',
{ 'w-3/4': isHorizontal },
className,
)}
>
<FormControl>
<SingleFormSlider
{...field}
value={percentage ? field.value * 100 : field.value}
onChange={(value) =>
field.onChange(percentage ? value / 100 : value)
}
max={displayMax}
min={displayMin}
step={displayStep}
></SingleFormSlider>
</FormControl>
<FormControl>
<NumberInput
className={cn(
'h-6 w-10 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}
value={
percentage ? (field.value * 100).toFixed(0) : field.value
}
onChange={(val) => {
const value = Number(val || 0);
if (!isNaN(value)) {
field.onChange(
percentage ? (value / 100).toFixed(0) : value,
);
<FormLabel
tooltip={tooltip}
className={cn({
'text-sm whitespace-break-spaces w-1/4': isHorizontal,
})}
>
{label}
</FormLabel>
<div
className={cn(
'flex items-center gap-4 justify-between',
{ 'w-3/4': isHorizontal },
className,
)}
>
<FormControl>
<SingleFormSlider
{...field}
value={percentage ? field.value * 100 : field.value}
onChange={(value) =>
field.onChange(percentage ? value / 100 : value)
}
}}
></NumberInput>
</FormControl>
</div>
<FormMessage />
</FormItem>
)}
/>
);
}
max={displayMax}
min={displayMin}
step={displayStep}
></SingleFormSlider>
</FormControl>
<FormControl>
<NumberInput
className={cn(
'h-6 w-10 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}
value={
percentage ? (field.value * 100).toFixed(0) : field.value
}
onChange={(val) => {
const value = Number(val || 0);
if (!isNaN(value)) {
field.onChange(
percentage ? (value / 100).toFixed(0) : value,
);
}
}}
></NumberInput>
</FormControl>
</div>
<FormMessage />
</FormItem>
)}
/>
);
},
);
SliderInputFormField.displayName = 'SliderInputFormField';

View File

@ -6,25 +6,29 @@ import * as React from 'react';
import { cn } from '@/lib/utils';
function RadioGroup({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentProps<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
ref={ref}
data-slot="radio-group"
className={cn('grid gap-3', className)}
{...props}
/>
);
}
});
function RadioGroupItem({
className,
...props
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
RadioGroup.displayName = 'RadioGroup';
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentProps<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
data-slot="radio-group-item"
className={cn(
'text-primary aspect-square size-4 shrink-0 rounded-full',
@ -45,6 +49,8 @@ function RadioGroupItem({
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
}
});
RadioGroupItem.displayName = 'RadioGroupItem';
export { RadioGroup, RadioGroupItem };

View File

@ -74,59 +74,66 @@ type RadioGroupProps = {
direction?: 'horizontal' | 'vertical';
};
function Group({
value,
defaultValue,
onChange,
disabled,
children,
className,
direction = 'horizontal',
}: RadioGroupProps) {
const [internalValue, setInternalValue] = useState(defaultValue || '');
const Group = React.forwardRef<HTMLDivElement, RadioGroupProps>(
(
{
value,
defaultValue,
onChange,
disabled,
children,
className,
direction = 'horizontal',
},
ref,
) => {
const [internalValue, setInternalValue] = useState(defaultValue || '');
const isControlled = value !== undefined;
const mergedValue = isControlled ? value : internalValue;
const isControlled = value !== undefined;
const mergedValue = isControlled ? value : internalValue;
const handleChange = (val: string | number) => {
if (disabled) return;
const handleChange = (val: string | number) => {
if (disabled) return;
if (!isControlled) {
setInternalValue(val);
}
if (!isControlled) {
setInternalValue(val);
}
if (onChange) {
onChange(val);
}
};
if (onChange) {
onChange(val);
}
};
return (
<RadioGroupContext.Provider
value={{
value: mergedValue,
onChange: handleChange,
disabled,
}}
>
<div
className={cn(
'flex gap-4',
direction === 'vertical' ? 'flex-col' : 'flex-row',
className,
)}
return (
<RadioGroupContext.Provider
value={{
value: mergedValue,
onChange: handleChange,
disabled,
}}
>
{React.Children.map(children, (child) =>
React.cloneElement(child as React.ReactElement, {
disabled: disabled || child?.props?.disabled,
}),
)}
</div>
</RadioGroupContext.Provider>
);
}
<div
ref={ref}
className={cn(
'flex gap-4',
direction === 'vertical' ? 'flex-col' : 'flex-row',
className,
)}
>
{React.Children.map(children, (child) =>
React.cloneElement(child as React.ReactElement, {
disabled: disabled || child?.props?.disabled,
}),
)}
</div>
</RadioGroupContext.Provider>
);
},
);
const RadioComponent = Object.assign(Radio, {
Group,
});
Group.displayName = 'RadioGroup';
export { RadioComponent as Radio };

View File

@ -59,63 +59,71 @@ export interface SegmentedProps extends Omit<
buttonSize?: keyof typeof segmentedVariants.buttonSize;
}
export function Segmented({
options,
value,
onChange,
className,
activeClassName,
itemClassName,
rounded = 'default',
sizeType = 'default',
buttonSize = 'default',
}: SegmentedProps) {
const [selectedValue, setSelectedValue] = React.useState<
SegmentedValue | undefined
>(value);
React.useEffect(() => {
setSelectedValue(value);
}, [value]);
const handleOnChange = (e: SegmentedValue) => {
if (onChange) {
onChange(e);
}
setSelectedValue(e);
};
return (
<div
className={cn(
'flex items-center p-1 gap-2 bg-bg-card',
segmentedVariants.round[rounded],
segmentedVariants.size[sizeType],
className,
)}
>
{options.map((option) => {
const isObject = typeof option === 'object';
const actualValue = isObject ? option.value : option;
export const Segmented = React.forwardRef<HTMLDivElement, SegmentedProps>(
(
{
options,
value,
onChange,
className,
activeClassName,
itemClassName,
rounded = 'default',
sizeType = 'default',
buttonSize = 'default',
},
ref,
) => {
const [selectedValue, setSelectedValue] = React.useState<
SegmentedValue | undefined
>(value);
React.useEffect(() => {
setSelectedValue(value);
}, [value]);
const handleOnChange = (e: SegmentedValue) => {
if (onChange) {
onChange(e);
}
setSelectedValue(e);
};
return (
<div
ref={ref}
className={cn(
'flex items-center p-1 gap-2 bg-bg-card',
segmentedVariants.round[rounded],
segmentedVariants.size[sizeType],
className,
)}
>
{options.map((option) => {
const isObject = typeof option === 'object';
const actualValue = isObject ? option.value : option;
return (
<div
key={actualValue}
className={cn(
'inline-flex items-center text-base font-normal cursor-pointer',
segmentedVariants.round[rounded],
segmentedVariants.buttonSize[buttonSize],
{
'text-text-primary bg-bg-base': selectedValue === actualValue,
},
itemClassName,
activeClassName && selectedValue === actualValue
? activeClassName
: '',
)}
onClick={() => handleOnChange(actualValue)}
>
{isObject ? option.label : option}
</div>
);
})}
</div>
);
}
return (
<div
key={actualValue}
className={cn(
'inline-flex items-center text-base font-normal cursor-pointer',
segmentedVariants.round[rounded],
segmentedVariants.buttonSize[buttonSize],
{
'text-text-primary bg-bg-base': selectedValue === actualValue,
},
itemClassName,
activeClassName && selectedValue === actualValue
? activeClassName
: '',
)}
onClick={() => handleOnChange(actualValue)}
>
{isObject ? option.label : option}
</div>
);
})}
</div>
);
},
);
Segmented.displayName = 'Segmented';