Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/react-aria-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@react-aria/interactions": "^3.19.1",
"@react-aria/toolbar": "3.0.0-alpha.1",
"@react-aria/utils": "^3.21.1",
"@react-stately/form": "3.0.0-alpha.1",
"@react-stately/table": "^3.11.2",
"@react-types/calendar": "^3.4.1",
"@react-types/grid": "^3.2.2",
Expand Down
33 changes: 22 additions & 11 deletions packages/react-aria-components/src/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
*/
import {AriaCheckboxGroupProps, AriaCheckboxProps, mergeProps, useCheckbox, useCheckboxGroup, useCheckboxGroupItem, useFocusRing, useHover, usePress, VisuallyHidden} from 'react-aria';
import {CheckboxGroupState, useCheckboxGroupState, useToggleState} from 'react-stately';
import {ContextValue, forwardRefType, Provider, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
import {ContextValue, forwardRefType, Provider, RACValidation, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
import {FieldErrorContext} from './FieldError';
import {filterDOMProps} from '@react-aria/utils';
import {LabelContext} from './Label';
import React, {createContext, ForwardedRef, forwardRef, useContext, useState} from 'react';
import {TextContext} from './Text';

export interface CheckboxGroupProps extends Omit<AriaCheckboxGroupProps, 'children' | 'label' | 'description' | 'errorMessage' | 'validationState'>, RenderProps<CheckboxGroupRenderProps>, SlotProps {}
export interface CheckboxProps extends Omit<AriaCheckboxProps, 'children' | 'validationState'>, RenderProps<CheckboxRenderProps>, SlotProps {}
export interface CheckboxGroupProps extends Omit<AriaCheckboxGroupProps, 'children' | 'label' | 'description' | 'errorMessage' | 'validationState' | 'validationBehavior'>, RACValidation, RenderProps<CheckboxGroupRenderProps>, SlotProps {}
export interface CheckboxProps extends Omit<AriaCheckboxProps, 'children' | 'validationState'>, RACValidation, RenderProps<CheckboxRenderProps>, SlotProps {}

export interface CheckboxGroupRenderProps {
/**
Expand Down Expand Up @@ -105,11 +106,15 @@ export const CheckboxGroupStateContext = createContext<CheckboxGroupState | null

function CheckboxGroup(props: CheckboxGroupProps, ref: ForwardedRef<HTMLDivElement>) {
[props, ref] = useContextProps(props, ref, CheckboxGroupContext);
let state = useCheckboxGroupState(props);
let state = useCheckboxGroupState({
...props,
validationBehavior: props.validationBehavior ?? 'native'
});
let [labelRef, label] = useSlot();
let {groupProps, labelProps, descriptionProps, errorMessageProps} = useCheckboxGroup({
let {groupProps, labelProps, descriptionProps, errorMessageProps, ...validation} = useCheckboxGroup({
...props,
label
label,
validationBehavior: props.validationBehavior ?? 'native'
}, state);

let renderProps = useRenderProps({
Expand Down Expand Up @@ -143,7 +148,8 @@ function CheckboxGroup(props: CheckboxGroupProps, ref: ForwardedRef<HTMLDivEleme
description: descriptionProps,
errorMessage: errorMessageProps
}
}]
}],
[FieldErrorContext, validation]
]}>
{renderProps.children}
</Provider>
Expand All @@ -156,7 +162,7 @@ export const CheckboxContext = createContext<ContextValue<CheckboxProps, HTMLInp
function Checkbox(props: CheckboxProps, ref: ForwardedRef<HTMLInputElement>) {
[props, ref] = useContextProps(props, ref, CheckboxContext);
let groupState = useContext(CheckboxGroupStateContext);
let {inputProps, isSelected, isDisabled, isReadOnly, isPressed: isPressedKeyboard} = groupState
let {inputProps, isSelected, isDisabled, isReadOnly, isPressed: isPressedKeyboard, isInvalid} = groupState
// eslint-disable-next-line react-hooks/rules-of-hooks
? useCheckboxGroupItem({
...props,
Expand All @@ -168,7 +174,12 @@ function Checkbox(props: CheckboxProps, ref: ForwardedRef<HTMLInputElement>) {
children: typeof props.children === 'function' ? true : props.children
}, groupState, ref)
// eslint-disable-next-line react-hooks/rules-of-hooks
: useCheckbox({...props, children: typeof props.children === 'function' ? true : props.children}, useToggleState(props), ref);
: useCheckbox({
...props,
children: typeof props.children === 'function' ? true : props.children,
validationBehavior: props.validationBehavior ?? 'native'
// eslint-disable-next-line react-hooks/rules-of-hooks
}, useToggleState(props), ref);
let {isFocused, isFocusVisible, focusProps} = useFocusRing();
let isInteractionDisabled = isDisabled || isReadOnly;

Expand Down Expand Up @@ -208,7 +219,7 @@ function Checkbox(props: CheckboxProps, ref: ForwardedRef<HTMLInputElement>) {
isFocusVisible,
isDisabled,
isReadOnly,
isInvalid: props.isInvalid || groupState?.isInvalid || false,
isInvalid,
isRequired: props.isRequired || false
}
});
Expand All @@ -228,7 +239,7 @@ function Checkbox(props: CheckboxProps, ref: ForwardedRef<HTMLInputElement>) {
data-focus-visible={isFocusVisible || undefined}
data-disabled={isDisabled || undefined}
data-readonly={isReadOnly || undefined}
data-invalid={props.isInvalid || groupState?.isInvalid || undefined}
data-invalid={isInvalid || undefined}
data-required={props.isRequired || undefined}>
<VisuallyHidden elementType="span">
<input {...inputProps} {...focusProps} ref={ref} />
Expand Down
34 changes: 20 additions & 14 deletions packages/react-aria-components/src/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {AriaComboBoxProps, useComboBox, useFilter} from 'react-aria';
import {ButtonContext} from './Button';
import {Collection, ComboBoxState, Node, useComboBoxState} from 'react-stately';
import {CollectionDocumentContext, useCollectionDocument} from './Collection';
import {ContextValue, forwardRefType, Hidden, Provider, removeDataAttributes, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
import {ContextValue, forwardRefType, Hidden, Provider, RACValidation, removeDataAttributes, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
import {FieldErrorContext} from './FieldError';
import {filterDOMProps, useResizeObserver} from '@react-aria/utils';
import {InputContext} from './Input';
import {LabelContext} from './Label';
Expand Down Expand Up @@ -46,7 +47,7 @@ export interface ComboBoxRenderProps {
isRequired: boolean
}

export interface ComboBoxProps<T extends object> extends Omit<AriaComboBoxProps<T>, 'children' | 'placeholder' | 'label' | 'description' | 'errorMessage' | 'validationState'>, RenderProps<ComboBoxRenderProps>, SlotProps {
export interface ComboBoxProps<T extends object> extends Omit<AriaComboBoxProps<T>, 'children' | 'placeholder' | 'label' | 'description' | 'errorMessage' | 'validationState' | 'validationBehavior'>, RACValidation, RenderProps<ComboBoxRenderProps>, SlotProps {
/** The filter function used to determine if a option should be included in the combo box list. */
defaultFilter?: (textValue: string, inputValue: string) => boolean,
/**
Expand Down Expand Up @@ -116,16 +117,10 @@ function ComboBoxInner<T extends object>({props, collection, comboBoxRef: ref}:
// If props.items isn't provided, rely on collection filtering (aka listbox.items is provided or defaultItems provided to Combobox)
items: props.items,
children: undefined,
collection
collection,
validationBehavior: props.validationBehavior ?? 'native'
});

// Only expose a subset of state to renderProps function to avoid infinite render loop
let renderPropsState = useMemo(() => ({
isOpen: state.isOpen,
isDisabled: props.isDisabled || false,
isInvalid: props.isInvalid || false,
isRequired: props.isRequired || false
}), [state.isOpen, props.isDisabled, props.isInvalid, props.isRequired]);
let buttonRef = useRef<HTMLButtonElement>(null);
let inputRef = useRef<HTMLInputElement>(null);
let listBoxRef = useRef<HTMLDivElement>(null);
Expand All @@ -137,15 +132,17 @@ function ComboBoxInner<T extends object>({props, collection, comboBoxRef: ref}:
listBoxProps,
labelProps,
descriptionProps,
errorMessageProps
errorMessageProps,
...validation
} = useComboBox({
...removeDataAttributes(props),
label,
inputRef,
buttonRef,
listBoxRef,
popoverRef,
name: formValue === 'text' ? name : undefined
name: formValue === 'text' ? name : undefined,
validationBehavior: props.validationBehavior ?? 'native'
}, state);

// Make menu width match input + button
Expand All @@ -165,6 +162,14 @@ function ComboBoxInner<T extends object>({props, collection, comboBoxRef: ref}:
onResize: onResize
});

// Only expose a subset of state to renderProps function to avoid infinite render loop
let renderPropsState = useMemo(() => ({
isOpen: state.isOpen,
isDisabled: props.isDisabled || false,
isInvalid: validation.isInvalid || false,
isRequired: props.isRequired || false
}), [state.isOpen, props.isDisabled, validation.isInvalid, props.isRequired]);

let renderProps = useRenderProps({
...props,
values: renderPropsState,
Expand Down Expand Up @@ -196,7 +201,8 @@ function ComboBoxInner<T extends object>({props, collection, comboBoxRef: ref}:
description: descriptionProps,
errorMessage: errorMessageProps
}
}]
}],
[FieldErrorContext, validation]
]}>
<div
{...DOMProps}
Expand All @@ -206,7 +212,7 @@ function ComboBoxInner<T extends object>({props, collection, comboBoxRef: ref}:
data-focused={state.isFocused || undefined}
data-open={state.isOpen || undefined}
data-disabled={props.isDisabled || undefined}
data-invalid={props.isInvalid || undefined}
data-invalid={validation.isInvalid || undefined}
data-required={props.isRequired || undefined} />
{name && formValue === 'key' && <input type="hidden" name={name} value={state.selectedKey} />}
</Provider>
Expand Down
33 changes: 24 additions & 9 deletions packages/react-aria-components/src/DateField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
* governing permissions and limitations under the License.
*/
import {AriaDateFieldProps, AriaTimeFieldProps, DateValue, mergeProps, TimeValue, useDateField, useDateSegment, useFocusRing, useHover, useLocale, useTimeField} from 'react-aria';
import {ContextValue, forwardRefType, Provider, removeDataAttributes, RenderProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps, useSlot} from './utils';
import {ContextValue, forwardRefType, Provider, RACValidation, removeDataAttributes, RenderProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps, useSlot} from './utils';
import {createCalendar} from '@internationalized/date';
import {DateFieldState, DateSegmentType, DateSegment as IDateSegment, TimeFieldState, useDateFieldState, useTimeFieldState} from 'react-stately';
import {FieldErrorContext} from './FieldError';
import {filterDOMProps, useObjectRef} from '@react-aria/utils';
import {Group, GroupContext} from './Group';
import {Input, InputContext} from './Input';
Expand All @@ -36,8 +37,8 @@ export interface DateFieldRenderProps {
*/
isDisabled: boolean
}
export interface DateFieldProps<T extends DateValue> extends Omit<AriaDateFieldProps<T>, 'label' | 'description' | 'errorMessage' | 'validationState'>, RenderProps<DateFieldRenderProps>, SlotProps {}
export interface TimeFieldProps<T extends TimeValue> extends Omit<AriaTimeFieldProps<T>, 'label' | 'description' | 'errorMessage' | 'validationState'>, RenderProps<DateFieldRenderProps>, SlotProps {}
export interface DateFieldProps<T extends DateValue> extends Omit<AriaDateFieldProps<T>, 'label' | 'description' | 'errorMessage' | 'validationState' | 'validationBehavior'>, RACValidation, RenderProps<DateFieldRenderProps>, SlotProps {}
export interface TimeFieldProps<T extends TimeValue> extends Omit<AriaTimeFieldProps<T>, 'label' | 'description' | 'errorMessage' | 'validationState' | 'validationBehavior'>, RACValidation, RenderProps<DateFieldRenderProps>, SlotProps {}

export const DateFieldContext = createContext<ContextValue<DateFieldProps<any>, HTMLDivElement>>(null);
export const TimeFieldContext = createContext<ContextValue<TimeFieldProps<any>, HTMLDivElement>>(null);
Expand All @@ -50,13 +51,19 @@ function DateField<T extends DateValue>(props: DateFieldProps<T>, ref: Forwarded
let state = useDateFieldState({
...props,
locale,
createCalendar
createCalendar,
validationBehavior: props.validationBehavior ?? 'native'
});

let fieldRef = useRef<HTMLDivElement>(null);
let [labelRef, label] = useSlot();
let inputRef = useRef<HTMLInputElement>(null);
let {labelProps, fieldProps, inputProps, descriptionProps, errorMessageProps} = useDateField({...removeDataAttributes(props), label, inputRef}, state, fieldRef);
let {labelProps, fieldProps, inputProps, descriptionProps, errorMessageProps, ...validation} = useDateField({
...removeDataAttributes(props),
label,
inputRef,
validationBehavior: props.validationBehavior ?? 'native'
}, state, fieldRef);

let renderProps = useRenderProps({
...removeDataAttributes(props),
Expand All @@ -83,7 +90,8 @@ function DateField<T extends DateValue>(props: DateFieldProps<T>, ref: Forwarded
description: descriptionProps,
errorMessage: errorMessageProps
}
}]
}],
[FieldErrorContext, validation]
]}>
<div
{...DOMProps}
Expand All @@ -107,13 +115,19 @@ function TimeField<T extends TimeValue>(props: TimeFieldProps<T>, ref: Forwarded
let {locale} = useLocale();
let state = useTimeFieldState({
...props,
locale
locale,
validationBehavior: props.validationBehavior ?? 'native'
});

let fieldRef = useRef<HTMLDivElement>(null);
let [labelRef, label] = useSlot();
let inputRef = useRef<HTMLInputElement>(null);
let {labelProps, fieldProps, inputProps, descriptionProps, errorMessageProps} = useTimeField({...removeDataAttributes(props), label, inputRef}, state, fieldRef);
let {labelProps, fieldProps, inputProps, descriptionProps, errorMessageProps, ...validation} = useTimeField({
...removeDataAttributes(props),
label,
inputRef,
validationBehavior: props.validationBehavior ?? 'native'
}, state, fieldRef);

let renderProps = useRenderProps({
...props,
Expand All @@ -140,7 +154,8 @@ function TimeField<T extends TimeValue>(props: TimeFieldProps<T>, ref: Forwarded
description: descriptionProps,
errorMessage: errorMessageProps
}
}]
}],
[FieldErrorContext, validation]
]}>
<div
{...DOMProps}
Expand Down
43 changes: 32 additions & 11 deletions packages/react-aria-components/src/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
import {AriaDatePickerProps, AriaDateRangePickerProps, DateValue, useDatePicker, useDateRangePicker, useFocusRing} from 'react-aria';
import {ButtonContext} from './Button';
import {CalendarContext, RangeCalendarContext} from './Calendar';
import {ContextValue, forwardRefType, Provider, removeDataAttributes, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
import {ContextValue, forwardRefType, Provider, RACValidation, removeDataAttributes, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
import {DateFieldContext} from './DateField';
import {DatePickerState, DateRangePickerState, useDatePickerState, useDateRangePickerState} from 'react-stately';
import {DialogContext, OverlayTriggerStateContext} from './Dialog';
import {FieldErrorContext} from './FieldError';
import {filterDOMProps} from '@react-aria/utils';
import {GroupContext} from './Group';
import {LabelContext} from './Label';
Expand Down Expand Up @@ -61,8 +62,8 @@ export interface DateRangePickerRenderProps extends Omit<DatePickerRenderProps,
state: DateRangePickerState
}

export interface DatePickerProps<T extends DateValue> extends Omit<AriaDatePickerProps<T>, 'label' | 'description' | 'errorMessage' | 'validationState'>, RenderProps<DatePickerRenderProps>, SlotProps {}
export interface DateRangePickerProps<T extends DateValue> extends Omit<AriaDateRangePickerProps<T>, 'label' | 'description' | 'errorMessage' | 'validationState'>, RenderProps<DateRangePickerRenderProps>, SlotProps {}
export interface DatePickerProps<T extends DateValue> extends Omit<AriaDatePickerProps<T>, 'label' | 'description' | 'errorMessage' | 'validationState' | 'validationBehavior'>, RACValidation, RenderProps<DatePickerRenderProps>, SlotProps {}
export interface DateRangePickerProps<T extends DateValue> extends Omit<AriaDateRangePickerProps<T>, 'label' | 'description' | 'errorMessage' | 'validationState' | 'validationBehavior'>, RACValidation, RenderProps<DateRangePickerRenderProps>, SlotProps {}

export const DatePickerContext = createContext<ContextValue<DatePickerProps<any>, HTMLDivElement>>(null);
export const DateRangePickerContext = createContext<ContextValue<DateRangePickerProps<any>, HTMLDivElement>>(null);
Expand All @@ -71,7 +72,11 @@ export const DateRangePickerStateContext = createContext<DateRangePickerState |

function DatePicker<T extends DateValue>(props: DatePickerProps<T>, ref: ForwardedRef<HTMLDivElement>) {
[props, ref] = useContextProps(props, ref, DatePickerContext);
let state = useDatePickerState(props);
let state = useDatePickerState({
...props,
validationBehavior: props.validationBehavior ?? 'native'
});

let groupRef = useRef<HTMLDivElement>(null);
let [labelRef, label] = useSlot();
let {
Expand All @@ -82,8 +87,13 @@ function DatePicker<T extends DateValue>(props: DatePickerProps<T>, ref: Forward
dialogProps,
calendarProps,
descriptionProps,
errorMessageProps
} = useDatePicker({...removeDataAttributes(props), label}, state, groupRef);
errorMessageProps,
...validation
} = useDatePicker({
...removeDataAttributes(props),
label,
validationBehavior: props.validationBehavior ?? 'native'
}, state, groupRef);

let {focusProps, isFocused, isFocusVisible} = useFocusRing({within: true});
let renderProps = useRenderProps({
Expand Down Expand Up @@ -119,7 +129,8 @@ function DatePicker<T extends DateValue>(props: DatePickerProps<T>, ref: Forward
description: descriptionProps,
errorMessage: errorMessageProps
}
}]
}],
[FieldErrorContext, validation]
]}>
<div
{...focusProps}
Expand All @@ -144,7 +155,11 @@ export {_DatePicker as DatePicker};

function DateRangePicker<T extends DateValue>(props: DateRangePickerProps<T>, ref: ForwardedRef<HTMLDivElement>) {
[props, ref] = useContextProps(props, ref, DateRangePickerContext);
let state = useDateRangePickerState(props);
let state = useDateRangePickerState({
...props,
validationBehavior: props.validationBehavior ?? 'native'
});

let groupRef = useRef<HTMLDivElement>(null);
let [labelRef, label] = useSlot();
let {
Expand All @@ -156,8 +171,13 @@ function DateRangePicker<T extends DateValue>(props: DateRangePickerProps<T>, re
dialogProps,
calendarProps,
descriptionProps,
errorMessageProps
} = useDateRangePicker({...removeDataAttributes(props), label}, state, groupRef);
errorMessageProps,
...validation
} = useDateRangePicker({
...removeDataAttributes(props),
label,
validationBehavior: props.validationBehavior ?? 'native'
}, state, groupRef);

let {focusProps, isFocused, isFocusVisible} = useFocusRing({within: true});
let renderProps = useRenderProps({
Expand Down Expand Up @@ -198,7 +218,8 @@ function DateRangePicker<T extends DateValue>(props: DateRangePickerProps<T>, re
description: descriptionProps,
errorMessage: errorMessageProps
}
}]
}],
[FieldErrorContext, validation]
]}>
<div
{...focusProps}
Expand Down
Loading