import React from 'react';
import {
type FieldErrorProps,
Group,
type GroupProps,
type InputProps,
type LabelProps,
FieldError as RACFieldError,
Input as RACInput,
Label as RACLabel,
Text,
type TextProps,
composeRenderProps,
useRenderProps,
FieldErrorContext,
LabelContext,
Provider,
GroupContext,
TextContext,
} from 'react-aria-components';
import { twMerge } from 'tailwind-merge';
import { tv } from 'tailwind-variants';
import { composeTailwindRenderProps, focusRing, useSlot } from '../utils';
import { useField } from 'react-aria';
import { filterDOMProps } from '@react-aria/utils';
export function Label(props: LabelProps) {
return (
);
}
export function Description(props: TextProps) {
return (
);
}
export function FieldError(props: FieldErrorProps) {
return (
);
}
export const fieldBorderStyles = tv({
variants: {
isFocusWithin: {
false: `forced-colors:border-[ButtonBorder]`,
true: `
bg-quanta-air inset-ring-quanta-sapphire outline-2
group-data-readonly:inset-ring-0
hover:bg-quanta-air
forced-colors:border-[Highlight]
`,
},
isInvalid: {
true: `
bg-quanta-ballet outline-2
hover:bg-quanta-flamingo
focus:inset-ring-quanta-candy
forced-colors:border-[Mark]
`,
},
isDisabled: {
true: `
bg-quanta-air text-quanta-silver
hover:bg-quanta-air
forced-colors:border-[GrayText]
`,
},
},
});
export const fieldGroupStyles = tv({
extend: focusRing,
base: `
group flex h-11 items-center overflow-hidden rounded-lg bg-quanta-snow text-quanta-space
hover:bg-quanta-smoke
read-only:hover:bg-quanta-air
focus:bg-quanta-air
active:bg-quanta-air
forced-colors:bg-[Field]
`,
variants: {
...fieldBorderStyles.variants,
isFocusWithin: {
...fieldBorderStyles.variants.isFocusWithin,
true: twMerge(
fieldBorderStyles.variants.isFocusWithin.true,
`inset-ring-2`,
),
},
isInvalid: {
...fieldBorderStyles.variants.isInvalid,
true: twMerge(
fieldBorderStyles.variants.isInvalid.true,
`focus:inset-ring-2`,
),
},
},
});
export function FieldGroup(props: GroupProps) {
return (
fieldGroupStyles({ ...renderProps, className }),
)}
/>
);
}
export const inputStyles = tv({
extend: focusRing,
base: 'rounded-md p-3',
variants: {
isFocused: {
...fieldBorderStyles.variants.isFocusWithin,
true: twMerge(
fieldBorderStyles.variants.isFocusWithin.true,
`inset-ring-2`,
),
},
isInvalid: {
...fieldBorderStyles.variants.isInvalid,
true: twMerge(
fieldBorderStyles.variants.isInvalid.true,
`focus:inset-ring-2`,
),
},
isDisabled: fieldBorderStyles.variants.isDisabled,
},
});
export function Input(props: InputProps) {
return (
);
}
type FieldWrapperProps = {
wrapped?: boolean;
children: React.ReactNode;
className?: string;
isRequired?: boolean;
label?: string;
description?: string;
errorMessage?: string;
placeholder?: string;
isFocused?: boolean;
isInvalid?: boolean;
isDisabled?: boolean;
isReadOnly?: boolean;
'aria-label'?: string;
'aria-labelledby'?: string;
slot?: string;
} & React.HTMLAttributes;
export const Field = React.forwardRef(
(props: FieldWrapperProps, ref) => {
// TODO: Determine if the use case of handling the case where both 'aria-label'
// and 'aria-labelledby' are provided, like the TextField component does.
// Probably not needed here, but worth considering for consistency.
const [labelRef] = useSlot(
!props['aria-label'] && !props['aria-labelledby'],
);
// TODO: FieldProps is not used in the current implementation, but it can be
// useful in the future to pass down additional props to the wrapped field.
const { labelProps, descriptionProps, errorMessageProps } = useField({
...props,
isInvalid: props.isInvalid || false,
errorMessage: props.errorMessage,
});
const renderProps = useRenderProps({
...props,
values: {
isDisabled: props.isDisabled || false,
isInvalid: props.isInvalid || false,
isReadOnly: props.isReadOnly || false,
isRequired: props.isRequired || false,
},
defaultClassName: 'react-aria-Field',
});
const DOMProps = filterDOMProps(props);
return (
);
},
);
Field.displayName = 'Field';
export const FieldWrapper = (props: FieldWrapperProps) => {
const { wrapped = true, children, label, description, errorMessage } = props;
return wrapped ? (
{label && }
{children}
{description && {description}}
{errorMessage}
) : (
<>{children}>
);
};