first commit
Some checks failed
Build / run (push) Has been cancelled

This commit is contained in:
maher
2025-10-29 11:42:25 +01:00
commit 703f50a09d
4595 changed files with 385164 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
import {
Children,
cloneElement,
forwardRef,
isValidElement,
ReactNode,
useId,
} from 'react';
import clsx from 'clsx';
import {useController} from 'react-hook-form';
import {Orientation} from '../orientation';
import {RadioProps} from './radio';
import {getInputFieldClassNames} from '../input-field/get-input-field-class-names';
export interface RadioGroupProps {
children: ReactNode;
orientation?: Orientation;
size?: 'xs' | 'sm' | 'md' | 'lg';
className?: string;
label?: ReactNode;
disabled?: boolean;
name?: string;
errorMessage?: ReactNode;
description?: ReactNode;
invalid?: boolean;
required?: boolean;
}
export const RadioGroup = forwardRef<HTMLFieldSetElement, RadioGroupProps>(
(props, ref) => {
const style = getInputFieldClassNames(props);
const {
label,
children,
size,
className,
orientation = 'horizontal',
disabled,
required,
invalid,
errorMessage,
description,
} = props;
const labelProps = {};
const id = useId();
const name = props.name || id;
return (
<fieldset
aria-describedby={description ? `${id}-description` : undefined}
ref={ref}
className={clsx('text-left', className)}
>
{label && (
<legend className={style.label} {...labelProps}>
{label}
</legend>
)}
<div
className={clsx(
'flex',
label ? 'mt-6' : 'mt-0',
orientation === 'vertical' ? 'flex-col gap-10' : 'flex-row gap-16'
)}
>
{Children.map(children, child => {
if (isValidElement<RadioProps>(child)) {
return cloneElement<RadioProps>(child, {
name,
size,
invalid: child.props.invalid || invalid || undefined,
disabled: child.props.disabled || disabled,
required: child.props.required || required,
});
}
})}
</div>
{description && !errorMessage && (
<div className={style.description} id={`${id}-description`}>
{description}
</div>
)}
{errorMessage && <div className={style.error}>{errorMessage}</div>}
</fieldset>
);
}
);
interface FormRadioGroupProps extends RadioGroupProps {
name: string;
}
export function FormRadioGroup({children, ...props}: FormRadioGroupProps) {
const {
fieldState: {error},
} = useController({
name: props.name!,
});
return (
<RadioGroup errorMessage={error?.message} {...props}>
{children}
</RadioGroup>
);
}

View File

@@ -0,0 +1,85 @@
import React, {ComponentPropsWithoutRef, forwardRef} from 'react';
import clsx from 'clsx';
import {mergeProps, useObjectRef} from '@react-aria/utils';
import {useController} from 'react-hook-form';
import {AutoFocusProps, useAutoFocus} from '../../focus/use-auto-focus';
type RadioSize = 'xs' | 'sm' | 'md' | 'lg' | undefined;
export interface RadioProps
extends AutoFocusProps,
Omit<ComponentPropsWithoutRef<'input'>, 'size'> {
size?: RadioSize;
value: string;
invalid?: boolean;
isFirst?: boolean;
}
export const Radio = forwardRef<HTMLInputElement, RadioProps>((props, ref) => {
const {children, autoFocus, size, invalid, isFirst, ...domProps} = props;
const inputRef = useObjectRef(ref);
useAutoFocus({autoFocus}, inputRef);
const sizeClassNames = getSizeClassNames(size);
return (
<label
className={clsx(
'inline-flex gap-8 select-none items-center whitespace-nowrap align-middle',
sizeClassNames.label,
props.disabled && 'text-disabled pointer-events-none',
props.invalid && 'text-danger'
)}
>
<input
type="radio"
className={clsx(
'focus-visible:ring outline-none',
'rounded-full transition-button border-2 appearance-none',
'border-text-muted disabled:border-disabled-fg checked:border-primary checked:hover:border-primary-dark',
'before:bg-primary disabled:before:bg-disabled-fg before:hover:bg-primary-dark',
'before:h-full before:w-full before:block before:rounded-full before:scale-10 before:opacity-0 before:transition before:duration-200',
'checked:before:scale-[.65] checked:before:opacity-100',
sizeClassNames.circle
)}
ref={inputRef}
{...domProps}
/>
{children && <span>{children}</span>}
</label>
);
});
export function FormRadio(props: RadioProps) {
const {
field: {onChange, onBlur, value, ref},
fieldState: {invalid},
} = useController({
name: props.name!,
});
const formProps: Partial<RadioProps> = {
onChange,
onBlur,
checked: props.value === value,
invalid: props.invalid || invalid,
};
return <Radio ref={ref} {...mergeProps(formProps, props)} />;
}
function getSizeClassNames(size?: RadioSize): {
circle: string;
label: string;
} {
switch (size) {
case 'xs':
return {circle: 'h-12 w-12', label: 'text-xs'};
case 'sm':
return {circle: 'h-16 w-16', label: 'text-sm'};
case 'lg':
return {circle: 'h-24 w-24', label: 'text-lg'};
default:
return {circle: 'h-20 w-20', label: 'text-base'};
}
}