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 (
{renderProps.children}
); }, ); Field.displayName = 'Field'; export const FieldWrapper = (props: FieldWrapperProps) => { const { wrapped = true, children, label, description, errorMessage } = props; return wrapped ? ( {label && } {children} {description && {description}} {errorMessage} ) : ( <>{children} ); };