Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(preview): Close color palette when tabbed out of the palette focus #1435

Merged
merged 4 commits into from
Nov 4, 2021

Conversation

JChan106
Copy link
Contributor

@JChan106 JChan106 commented Nov 3, 2021

Fixes the bug where focusing out of the color palette using keyboard navigation didn't close the palette. The palette now closes when a user tabs out of the color palette, or if the user is tabbed in the palette and the user clicks out of the palette.

@JChan106 JChan106 requested a review from a team as a code owner November 3, 2021 18:52
@CLAassistant
Copy link

CLAassistant commented Nov 3, 2021

CLA assistant check
All committers have signed the CLA.

const { current: paletteEl } = paletteRef;

const handlePaletteBlur = ({ relatedTarget }: React.FocusEvent<HTMLButtonElement>): void => {
const targetElement = relatedTarget as HTMLButtonElement;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think IE11 supports relatedTarget, so we may need to fall back to document.activeElement here as in the handleToggleBlur function below.


const handlePaletteBlur = ({ relatedTarget }: React.FocusEvent<HTMLButtonElement>): void => {
const targetElement = relatedTarget as HTMLButtonElement;
if (targetElement.parentElement && targetElement.parentElement.parentElement !== paletteEl) {
Copy link
Collaborator

@jstoffan jstoffan Nov 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parentElement calls here make some assumptions about the DOM that may eventually change and break unexpectedly. Can we use !paletteEl.contains(targetElement), instead?

}
};

const [isPaletteActive, handlers] = useAttention({ onBlur: handlePaletteBlur });
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add onBlur to <ColorPickerPalette> to avoid having to modify useAttention with separate callbacks?


const handleSelect = (color: string): void => {
setIsColorPickerToggled(false);
onColorSelect(color);
};

const handleBlur = ({ relatedTarget }: React.FocusEvent<HTMLButtonElement>): void => {
const { current: paletteEl } = paletteRef;
const handleToggleBlur = ({ relatedTarget }: React.FocusEvent<HTMLButtonElement>): void => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at it again, it seems like both of the blur handlers are doing the same thing, just in different ways. Can we use the same callback for both the toggle and the palette?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah just realized I can use the same handler if I take out the isPaletteActive conditional. I didn't see any focus issues in taking that conditional out.

@JChan106 JChan106 requested a review from jstoffan November 3, 2021 23:54
const [isColorPickerToggled, setIsColorPickerToggled] = React.useState(false);
const [isPaletteActive, handlers] = useAttention();
const [handlers] = useAttention();
const { current: paletteEl } = paletteRef;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we revert this change? We typically destructure these inside the event handler where they're used, since the value could theoretically change before they're invoked.

// IE11 does not have relatedTarget but update activeElement before blur
const nextTarget = relatedTarget || document.activeElement;

if (isPaletteActive || (nextTarget && paletteEl && paletteEl.contains(nextTarget as Node))) {
if ((nextTarget && paletteEl && paletteEl.contains(nextTarget as Node)) || toggleEl === (nextTarget as Node)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a bit more readable by using some additional variables?

const { current: paletteEl } = paletteRef;
const { current: toggleEl } = toggleRef;

// IE11 does not have relatedTarget but update activeElement before blur
const nextTarget = (relatedTarget || document.activeElement) as Node;
const isNextInPalette = paletteEl && paletteEl.contains(nextTarget);
const isNextToggle = toggleEl && toggleEl === nextTarget;

if (isNextInPalette || isNextToggle) {
    return;
}

setIsColorPickerToggled(false);

return;
}

setIsColorPickerToggled(false);
};

const handleClick = (): void => setIsColorPickerToggled(!isColorPickerToggled);
const handleClick = (): void => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change needed or can we revert it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forgot that, thanks

onSelect: (color: string) => void;
};

export default function ColorPickerPalette({ colors, onSelect }: Props): JSX.Element {
export default function ColorPickerPalette({ colors, onSelect, onBlur }: Props): JSX.Element {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We typically alphabetize our props, attributes, etc. when possible.

test('should close the palette when focus is outside palette', () => {
const wrapper = getWrapper();
const toggleButton = getToggleButton(wrapper);
const divNode = document.createElement('div');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would typically refer to this as divEl or divElement, since we know it's an Element object rather than a generic Node.

toggleButton.simulate('click');
expect(getColorPickerPalette(wrapper).hasClass('bp-is-open')).toBe(true);

toggleButton.simulate('blur', {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be testing the palette instead of the button? Do we also need a test for when the nextTarget after blur is the toggle button?

<ColorPickerPalette colors={colors} data-testid="bp-ColorPickerPalette" onSelect={handleSelect} />
<ColorPickerPalette
colors={colors}
data-testid="bp-ColorPickerPalette"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't related to your change, but does this get applied to the palette? It's not a listed prop and we don't have a ...rest operator anywhere. Can we remove it safely?

const [isColorPickerToggled, setIsColorPickerToggled] = React.useState(false);
const [isPaletteActive, handlers] = useAttention();
const [handlers] = useAttention();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need useAttention if we're not using the state it returns?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, will remove

@JChan106 JChan106 requested a review from jstoffan November 4, 2021 19:57
@JChan106 JChan106 merged commit 0a2b990 into box:master Nov 4, 2021
@JChan106 JChan106 deleted the fix-palette-focus branch November 4, 2021 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants