Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion packages/@adobe/react-spectrum/src/checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const Checkbox = forwardRef(function Checkbox(props: SpectrumCheckboxProp
// This is a bit unorthodox. Typically, hooks cannot be called in a conditional,
// but since the checkbox won't move in and out of a group, it should be safe.
let groupState = useContext(CheckboxGroupContext);
let {inputProps, isInvalid, isDisabled} = groupState
let {labelProps, inputProps, isInvalid, isDisabled} = groupState
// eslint-disable-next-line react-hooks/rules-of-hooks
? useCheckboxGroupItem({
...props,
Expand Down Expand Up @@ -104,6 +104,7 @@ export const Checkbox = forwardRef(function Checkbox(props: SpectrumCheckboxProp

return (
<label
{...labelProps}
{...styleProps}
{...hoverProps}
ref={domRef}
Expand Down
3 changes: 2 additions & 1 deletion packages/@adobe/react-spectrum/src/radio/Radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,15 @@ export const Radio = forwardRef(function Radio(props: SpectrumRadioProps, ref: F
state
} = radioGroupProps;

let {inputProps} = useRadio({
let {labelProps, inputProps} = useRadio({
...props,
...radioGroupProps,
isDisabled
}, state, inputRef);

return (
<label
{...labelProps}
{...styleProps}
{...hoverProps}
ref={domRef}
Expand Down
3 changes: 2 additions & 1 deletion packages/@adobe/react-spectrum/src/switch/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@ export const Switch = forwardRef(function Switch(props: SpectrumSwitchProps, ref
let inputRef = useRef<HTMLInputElement>(null);
let domRef = useFocusableRef(ref, inputRef);
let state = useToggleState(props);
let {inputProps} = useSwitch(props, state, inputRef);
let {labelProps, inputProps} = useSwitch(props, state, inputRef);


return (
<label
{...labelProps}
{...styleProps}
{...hoverProps}
ref={domRef}
Expand Down
118 changes: 72 additions & 46 deletions packages/@react-spectrum/s2/src/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,23 @@
* governing permissions and limitations under the License.
*/

import {baseColor, focusRing, space, style} from '../style' with {type: 'macro'};
import {CenterBaseline} from './CenterBaseline';
import {
Checkbox as AriaCheckbox,
CheckboxProps as AriaCheckboxProps,
CheckboxButton,
CheckboxField,
CheckboxFieldProps,
CheckboxRenderProps
} from 'react-aria-components/Checkbox';
import {baseColor, focusRing, space, style} from '../style' with {type: 'macro'};
import {CenterBaseline} from './CenterBaseline';
import {CheckboxGroupStateContext} from 'react-aria-components/CheckboxGroup';
import CheckmarkIcon from '../ui-icons/Checkmark';
import {ContextValue, useSlottedContext} from 'react-aria-components/slots';
import {controlBorderRadius, controlFont, controlSize, getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
import {createContext, forwardRef, ReactNode, useContext, useRef} from 'react';
import DashIcon from '../ui-icons/Dash';
import {FocusableRef, FocusableRefValue, GlobalDOMAttributes} from '@react-types/shared';
import {FocusableRef, FocusableRefValue, GlobalDOMAttributes, HelpTextProps} from '@react-types/shared';
import {FormContext, useFormProps} from './Form';
import {HelpText} from './Field';
import {pressScale} from './pressScale';
import {useFocusableRef} from './useDOMRef';
import {useSpectrumContextProps} from './useSpectrumContextProps';
Expand All @@ -42,19 +44,31 @@ interface CheckboxStyleProps {

interface RenderProps extends CheckboxRenderProps, CheckboxStyleProps {}

export interface CheckboxProps extends Omit<AriaCheckboxProps, 'className' | 'style' | 'render' | 'children' | 'onHover' | 'onHoverStart' | 'onHoverEnd' | 'onHoverChange' | 'onClick' | keyof GlobalDOMAttributes>, StyleProps, CheckboxStyleProps {
export interface CheckboxProps extends Omit<CheckboxFieldProps, 'className' | 'style' | 'render' | 'children' | 'onHover' | 'onHoverStart' | 'onHoverEnd' | 'onHoverChange' | 'onClick' | keyof GlobalDOMAttributes>, HelpTextProps, StyleProps, CheckboxStyleProps {
/** The label for the element. */
children?: ReactNode
}

export const CheckboxContext = createContext<ContextValue<Partial<CheckboxProps>, FocusableRefValue<HTMLLabelElement>>>(null);
export const CheckboxContext = createContext<ContextValue<Partial<CheckboxProps>, FocusableRefValue<HTMLInputElement, HTMLDivElement>>>(null);

const field = style({
width: 'fit',
'--field-height': {
type: 'height',
value: controlSize()
},
'--field-gap': {
type: 'rowGap',
value: 'calc(var(--field-height) - 1lh)'
}
}, getAllowedOverrides());

const wrapper = style({
display: 'flex',
position: 'relative',
columnGap: 'text-to-control',
alignItems: 'baseline',
width: 'fit',
width: 'full',
font: controlFont(),
transition: 'colors',
color: {
Expand All @@ -68,7 +82,7 @@ const wrapper = style({
isInForm: 'field'
},
disableTapHighlight: true
}, getAllowedOverrides());
});

export const box = style<RenderProps>({
...focusRing(),
Expand Down Expand Up @@ -137,7 +151,7 @@ const iconSize = {
* Checkboxes allow users to select multiple items from a list of individual items,
* or to mark one individual item as selected.
*/
export const Checkbox = forwardRef(function Checkbox({children, ...props}: CheckboxProps, ref: FocusableRef<HTMLLabelElement>) {
export const Checkbox = forwardRef(function Checkbox({children, ...props}: CheckboxProps, ref: FocusableRef<HTMLInputElement, HTMLDivElement>) {
[props, ref] = useSpectrumContextProps(props, ref, CheckboxContext);
let boxRef = useRef(null);
let inputRef = useRef<HTMLInputElement | null>(null);
Expand All @@ -148,47 +162,59 @@ export const Checkbox = forwardRef(function Checkbox({children, ...props}: Check
let ctx = useSlottedContext(CheckboxContext, props.slot);

return (
<AriaCheckbox
<CheckboxField
{...props}
ref={domRef}
inputRef={inputRef}
style={props.UNSAFE_style}
className={renderProps => (props.UNSAFE_className || '') + wrapper({...renderProps, isInForm, size: props.size || 'M'}, props.styles)}>
{renderProps => {
let checkbox = (
<div
ref={boxRef}
style={pressScale(boxRef)(renderProps)}
className={box({
...renderProps,
isSelected: renderProps.isSelected || renderProps.isIndeterminate,
size: props.size || 'M',
isEmphasized: isInCheckboxGroup ? ctx?.isEmphasized : props.isEmphasized
})}>
{renderProps.isIndeterminate &&
<DashIcon size={iconSize[props.size || 'M']} className={iconStyles} />
}
{renderProps.isSelected && !renderProps.isIndeterminate &&
<CheckmarkIcon size={iconSize[props.size || 'M']} className={iconStyles} />
}
</div>
);
className={(props.UNSAFE_className || '') + field({size: props.size || 'M'}, props.styles)}>
{({isDisabled, isInvalid}) => (<>
<CheckboxButton className={renderProps => wrapper({...renderProps, isInForm, size: props.size || 'M'})}>
{renderProps => {
let checkbox = (
<div
ref={boxRef}
style={pressScale(boxRef)(renderProps)}
className={box({
...renderProps,
isSelected: renderProps.isSelected || renderProps.isIndeterminate,
size: props.size || 'M',
isEmphasized: isInCheckboxGroup ? ctx?.isEmphasized : props.isEmphasized
})}>
{renderProps.isIndeterminate &&
<DashIcon size={iconSize[props.size || 'M']} className={iconStyles} />
}
{renderProps.isSelected && !renderProps.isIndeterminate &&
<CheckmarkIcon size={iconSize[props.size || 'M']} className={iconStyles} />
}
</div>
);

// Only render checkbox without center baseline if no label.
// This avoids expanding the checkbox height to the font's line height.
if (!children) {
return checkbox;
}
// Only render checkbox without center baseline if no label.
// This avoids expanding the checkbox height to the font's line height.
if (!children) {
return checkbox;
}

return (
<>
<CenterBaseline>
{checkbox}
</CenterBaseline>
{children}
</>
);
}}
</AriaCheckbox>
return (
<>
<CenterBaseline>
{checkbox}
</CenterBaseline>
{children}
</>
);
}}
</CheckboxButton>
<HelpText
size={props.size || 'M'}
isDisabled={isDisabled}
isInvalid={isInvalid}
description={props.description}
showErrorIcon>
{props.errorMessage}
</HelpText>
</>)}
</CheckboxField>
);
});
4 changes: 2 additions & 2 deletions packages/@react-spectrum/s2/src/RadioGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export const RadioGroup = /*#__PURE__*/ forwardRef(function RadioGroup(props: Ra
);
});

export interface RadioProps extends Omit<AriaRadioProps, 'className' | 'style' | 'render' | 'children' | 'onHover' | 'onHoverStart' | 'onHoverEnd' | 'onHoverChange' | 'onClick' | keyof GlobalDOMAttributes>, StyleProps {
export interface RadioProps extends Omit<AriaRadioProps, 'description' | 'className' | 'style' | 'render' | 'children' | 'onHover' | 'onHoverStart' | 'onHoverEnd' | 'onHoverChange' | 'onClick' | keyof GlobalDOMAttributes>, StyleProps {
/**
* The label for the element.
*/
Expand Down Expand Up @@ -227,7 +227,7 @@ const circle = style<RenderProps>({
* Radio buttons allow users to select a single option from a list of mutually exclusive options.
* All possible options are exposed up front for users to compare.
*/
export const Radio = /*#__PURE__*/ forwardRef(function Radio(props: RadioProps, ref: FocusableRef<HTMLLabelElement>) {
export const Radio = /*#__PURE__*/ forwardRef(function Radio(props: RadioProps, ref: FocusableRef<HTMLInputElement, HTMLLabelElement>) {
let {children, UNSAFE_className = '', UNSAFE_style} = props;
let circleRef = useRef(null);
let inputRef = useRef<HTMLInputElement | null>(null);
Expand Down
5 changes: 2 additions & 3 deletions packages/@react-spectrum/s2/src/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
*/

import {Switch as AriaSwitch, SwitchProps as AriaSwitchProps, SwitchRenderProps} from 'react-aria-components/Switch';

import {baseColor, focusRing, fontRelative, style} from '../style' with {type: 'macro'};
import {CenterBaseline} from './CenterBaseline';
import {ContextValue} from 'react-aria-components/slots';
Expand Down Expand Up @@ -43,7 +42,7 @@ export interface SwitchProps extends Omit<AriaSwitchProps, 'className' | 'style'
children?: ReactNode
}

export const SwitchContext = createContext<ContextValue<Partial<SwitchProps>, FocusableRefValue<HTMLLabelElement>>>(null);
export const SwitchContext = createContext<ContextValue<Partial<SwitchProps>, FocusableRefValue<HTMLInputElement, HTMLLabelElement>>>(null);

const wrapper = style({
display: 'flex',
Expand Down Expand Up @@ -156,7 +155,7 @@ const transformStyle = ({isSelected, direction}: SwitchRenderProps & {direction:
* Switches allow users to turn an individual option on or off.
* They are usually used to activate or deactivate a specific setting.
*/
export const Switch = /*#__PURE__*/ forwardRef(function Switch(props: SwitchProps, ref: FocusableRef<HTMLLabelElement>) {
export const Switch = /*#__PURE__*/ forwardRef(function Switch(props: SwitchProps, ref: FocusableRef<HTMLInputElement, HTMLLabelElement>) {
[props, ref] = useSpectrumContextProps(props, ref, SwitchContext);
let {children, UNSAFE_className = '', UNSAFE_style} = props;
let inputRef = useRef<HTMLInputElement | null>(null);
Expand Down
15 changes: 8 additions & 7 deletions packages/@react-spectrum/s2/src/useDOMRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/

import {DOMRef, DOMRefValue, FocusableElement, FocusableRef, FocusableRefValue, RefObject} from '@react-types/shared';
import {DOMRef, DOMRefValue, FocusableRef, FocusableRefValue, RefObject} from '@react-types/shared';
import {useImperativeHandle, useMemo, useRef} from 'react';

export function createDOMRef<T extends HTMLElement = HTMLElement>(ref: RefObject<T | null>): DOMRefValue<T> {
Expand All @@ -21,12 +21,13 @@ export function createDOMRef<T extends HTMLElement = HTMLElement>(ref: RefObject
};
}

export function createFocusableRef<T extends HTMLElement = HTMLElement>(domRef: RefObject<T | null>, focusableRef: RefObject<FocusableElement | null> = domRef): FocusableRefValue<T> {
export function createFocusableRef<T extends HTMLElement = HTMLElement, D extends HTMLElement = T>(domRef: RefObject<D | null>, focusableRef?: RefObject<T | null>): FocusableRefValue<T, D> {
let resolvedFocusableRef = focusableRef || domRef;
return {
...createDOMRef(domRef),
focus() {
if (focusableRef.current) {
focusableRef.current.focus();
if (resolvedFocusableRef.current) {
resolvedFocusableRef.current.focus();
}
}
};
Expand All @@ -38,9 +39,9 @@ export function useDOMRef<T extends HTMLElement = HTMLElement>(ref: DOMRef<T>):
return domRef;
}

export function useFocusableRef<T extends HTMLElement = HTMLElement>(ref: FocusableRef<T>, focusableRef?: RefObject<FocusableElement | null>): RefObject<T | null> {
let domRef = useRef<T>(null);
useImperativeHandle(ref, () => createFocusableRef(domRef, focusableRef));
export function useFocusableRef<T extends HTMLElement = HTMLElement, D extends HTMLElement = T>(ref: FocusableRef<T, D>, focusableRef?: RefObject<T | null>): RefObject<D | null> {
let domRef = useRef<D>(null);
useImperativeHandle(ref, () => createFocusableRef<T, D>(domRef, focusableRef));
return domRef;
}

Expand Down
3 changes: 3 additions & 0 deletions packages/@react-types/shared/src/dom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
PointerEventHandler,
DOMAttributes as ReactDOMAttributes,
ReactEventHandler,
RefAttributes,
TouchEventHandler,
TransitionEventHandler,
UIEventHandler,
Expand Down Expand Up @@ -237,6 +238,8 @@ export interface DOMAttributes<T = FocusableElement> extends AriaAttributes, Rea
className?: string | undefined
}

export interface DOMAttributesWithRef<T = Element> extends DOMAttributes<T>, RefAttributes<T> {}

export interface GroupDOMAttributes extends Omit<DOMAttributes<HTMLElement>, 'role'> {
role?: 'group' | 'region' | 'presentation'
}
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-types/shared/src/refs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface FocusableRefValue<T extends HTMLElement = HTMLElement, D extend
}

export type DOMRef<T extends HTMLElement = HTMLElement> = Ref<DOMRefValue<T>>;
export type FocusableRef<T extends HTMLElement = HTMLElement> = Ref<FocusableRefValue<T>>;
export type FocusableRef<T extends HTMLElement = HTMLElement, D extends HTMLElement = T> = Ref<FocusableRefValue<T, D>>;

export interface RefObject<T> {
current: T
Expand Down
Loading
Loading