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
7 changes: 6 additions & 1 deletion packages/@react-aria/tooltip/src/useTooltipTrigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export interface TooltipTriggerAria {
export function useTooltipTrigger(props: TooltipTriggerProps, state: TooltipTriggerState, ref: RefObject<FocusableElement | null>) : TooltipTriggerAria {
let {
isDisabled,
trigger
trigger,
closeOnPress = true
} = props;

let tooltipId = useId();
Expand Down Expand Up @@ -102,6 +103,10 @@ export function useTooltipTrigger(props: TooltipTriggerProps, state: TooltipTrig
};

let onPressStart = () => {
// if closeOnPress is false, we should not close the tooltip
if (!closeOnPress) {
return;
}
// no matter how the trigger is pressed, we should close the tooltip
isFocused.current = false;
isHovered.current = false;
Expand Down
7 changes: 5 additions & 2 deletions packages/@react-spectrum/tooltip/src/TooltipTrigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ import {useTooltipTriggerState} from '@react-stately/tooltip';

const DEFAULT_OFFSET = -1; // Offset needed to reach 4px/5px (med/large) distance between tooltip and trigger button
const DEFAULT_CROSS_OFFSET = 0;
const DEFAULT_CLOSE_ON_PRESS = true; // Whether the tooltip should close when the trigger is pressed

function TooltipTrigger(props: SpectrumTooltipTriggerProps) {
let {
children,
crossOffset = DEFAULT_CROSS_OFFSET,
isDisabled,
offset = DEFAULT_OFFSET,
trigger: triggerAction
trigger: triggerAction,
closeOnPress = DEFAULT_CLOSE_ON_PRESS
} = props;

let [trigger, tooltip] = React.Children.toArray(children) as [ReactElement, ReactElement];
Expand All @@ -40,7 +42,8 @@ function TooltipTrigger(props: SpectrumTooltipTriggerProps) {

let {triggerProps, tooltipProps} = useTooltipTrigger({
isDisabled,
trigger: triggerAction
trigger: triggerAction,
closeOnPress
}, state, tooltipTriggerRef);

let [borderRadius, setBorderRadius] = useState(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ const argTypes = {
},
children: {
control: {disable: true}
},
closeOnPress: {
control: 'boolean'
}
};

Expand Down Expand Up @@ -113,7 +116,8 @@ export default {
<ActionButton aria-label="Edit Name"><Edit /></ActionButton>,
<Tooltip>Change Name</Tooltip>
],
onOpenChange: action('openChange')
onOpenChange: action('openChange'),
closeOnPress: true
},
argTypes: argTypes
} as Meta<typeof TooltipTrigger>;
Expand Down
42 changes: 42 additions & 0 deletions packages/@react-spectrum/tooltip/test/TooltipTrigger.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,48 @@ describe('TooltipTrigger', function () {
expect(queryByRole('tooltip')).toBeNull();
});

it('does not close if the trigger is clicked when closeOnPress is false', async () => {
let {getByRole, getByLabelText} = render(
<Provider theme={theme}>
<TooltipTrigger onOpenChange={onOpenChange} delay={0} closeOnPress={false}>
<ActionButton aria-label="trigger" />
<Tooltip>Helpful information.</Tooltip>
</TooltipTrigger>
</Provider>
);
await user.click(document.body);

let button = getByLabelText('trigger');
await user.hover(button);
expect(onOpenChange).toHaveBeenCalledWith(true);
let tooltip = getByRole('tooltip');
expect(tooltip).toBeVisible();
await user.click(button);
expect(onOpenChange).toHaveBeenCalledTimes(1);
expect(tooltip).toBeVisible();
});

it('does not close if the trigger is clicked with the keyboard when closeOnPress is false', async () => {
let {getByRole, getByLabelText} = render(
<Provider theme={theme}>
<TooltipTrigger onOpenChange={onOpenChange} delay={0} closeOnPress={false}>
<ActionButton aria-label="trigger" />
<Tooltip>Helpful information.</Tooltip>
</TooltipTrigger>
</Provider>
);

let button = getByLabelText('trigger');
await user.tab();
expect(onOpenChange).toHaveBeenCalledWith(true);
let tooltip = getByRole('tooltip');
expect(tooltip).toBeVisible();
fireEvent.keyDown(button, {key: 'Enter'});
fireEvent.keyUp(button, {key: 'Enter'});
expect(onOpenChange).toHaveBeenCalledTimes(1);
expect(tooltip).toBeVisible();
});

describe('delay', () => {
it('opens immediately for focus', () => {
let {getByRole, getByLabelText} = render(
Expand Down
8 changes: 7 additions & 1 deletion packages/@react-types/tooltip/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ export interface TooltipTriggerProps extends OverlayTriggerProps {
* By default, opens for both focus and hover. Can be made to open only for focus.
* @default 'hover'
*/
trigger?: 'hover' | 'focus'
trigger?: 'hover' | 'focus',

/**
* Whether the tooltip should close when the trigger is pressed.
* @default true
*/
closeOnPress?: boolean
}

export interface SpectrumTooltipTriggerProps extends Omit<TooltipTriggerProps, 'closeDelay'>, PositionProps {
Expand Down