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(Indeterminate Checkbox): Fixed issue with voice over not recognizing the mixed state when using keyboard navigation. #1652

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
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
5 changes: 5 additions & 0 deletions .changeset/fix-IndeterminateCheckbox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'react-magma-dom': patch
---

fix(Indeterminate Checkbox): Fixed issue with voice over not recognizing the mixed state when using keyboard navigation.
Original file line number Diff line number Diff line change
@@ -1,127 +1,141 @@
import React from 'react';
import { Card, CardBody } from '../Card';
import { Container } from '../Container';
import { Checkbox } from '../Checkbox';
import { FormGroup } from '../FormGroup';
import {
IndeterminateCheckbox,
IndeterminateCheckboxStatus,
} from '../IndeterminateCheckbox';
import { magma } from '../../theme/magma';
import { Meta } from '@storybook/react/types-6-0';
import { Story, Meta } from '@storybook/react/types-6-0';
import { IndeterminateCheckboxProps } from '../../../dist';

const Template: Story<IndeterminateCheckboxProps> = args => (
<FormGroup labelText="Indeterminate Checkbox Examples">
<IndeterminateCheckbox
{...args}
color={magma.colors.primary}
defaultChecked={true}
labelText="Indeterminate checkbox"
id="0"
/>
<IndeterminateCheckbox
{...args}
disabled
defaultChecked={true}
labelText="Disabled indeterminate checkbox"
id="1"
/>
<IndeterminateCheckbox
{...args}
defaultChecked={true}
labelText="Error indeterminate checkbox"
id="2"
errorMessage="Error"
/>
</FormGroup>
);

export default {
component: IndeterminateCheckbox,
title: 'Indeterminate Checkbox',
argTypes: {
status: {
control: { type: 'select' },
options: IndeterminateCheckboxStatus,
},
isInverse: {
control: {
type: 'boolean',
},
},
},
decorators: [
(Story, context) => (
<Container isInverse={context.args.isInverse} style={{ padding: '20px' }}>
<Story />
</Container>
),
],
} as Meta;

export const Default = () => {
return (
<FormGroup labelText="Indeterminate Checkbox Examples">
<IndeterminateCheckbox
color={magma.colors.primary}
defaultChecked={true}
status={IndeterminateCheckboxStatus.indeterminate}
labelText="Indeterminate checkbox"
id="0"
/>
<IndeterminateCheckbox
disabled
defaultChecked={true}
status={IndeterminateCheckboxStatus.indeterminate}
labelText="Disabled indeterminate checkbox"
id="1"
/>
<IndeterminateCheckbox
defaultChecked={true}
status={IndeterminateCheckboxStatus.indeterminate}
labelText="Error indeterminate checkbox"
id="2"
errorMessage="Error"
/>
</FormGroup>
);
};

export const Inverse = () => {
return (
<Card isInverse>
<CardBody>
<FormGroup
labelText="Inverse Indeterminate Checkbox Examples"
isInverse
>
<IndeterminateCheckbox
isInverse
labelText="Indeterminate checkbox inverse"
status={IndeterminateCheckboxStatus.indeterminate}
id="3"
/>
<IndeterminateCheckbox
disabled
isInverse
labelText="Disabled indeterminate checkbox inverse"
status={IndeterminateCheckboxStatus.indeterminate}
id="4"
/>
<IndeterminateCheckbox
isInverse
labelText="Error indeterminate checkbox"
status={IndeterminateCheckboxStatus.indeterminate}
id="5"
errorMessage="Error"
/>
</FormGroup>
</CardBody>
</Card>
);
};
export const Default = Template.bind({});
Default.args = {};

export const Behavior = () => {
const [checkedItems, setCheckedItems] = React.useState<Array<boolean>>([
true,
false,
false,
false,
]);

const status: IndeterminateCheckboxStatus = checkedItems.every(Boolean)
? IndeterminateCheckboxStatus.checked
: checkedItems.some(Boolean)
? IndeterminateCheckboxStatus.indeterminate
: IndeterminateCheckboxStatus.unchecked;
const [status, setStatus] = React.useState<IndeterminateCheckboxStatus>(
IndeterminateCheckboxStatus.indeterminate
);

function getStatus(items: Array<boolean>) {
return items.every(Boolean)
? IndeterminateCheckboxStatus.checked
: items.some(Boolean)
? IndeterminateCheckboxStatus.indeterminate
: IndeterminateCheckboxStatus.unchecked;
}

function handleUpdateIndeterminateChecked(
event: React.ChangeEvent<HTMLInputElement>
) {
setCheckedItems([event.target.checked, event.target.checked]);
const updatedCheckedItems = Array(4).fill(event.target.checked);
setCheckedItems(updatedCheckedItems);
setStatus(getStatus(updatedCheckedItems));
}

function handleUpdateRedChecked(event: React.ChangeEvent<HTMLInputElement>) {
setCheckedItems([event.target.checked, checkedItems[1]]);
}

function handleUpdateBlueChecked(event: React.ChangeEvent<HTMLInputElement>) {
setCheckedItems([checkedItems[0], event.target.checked]);
function handleColorChecked(
index: number,
event: React.ChangeEvent<HTMLInputElement>
) {
const updatedCheckedItems = [...checkedItems];
updatedCheckedItems[index] = event.target.checked;
setCheckedItems(updatedCheckedItems);
setStatus(getStatus(updatedCheckedItems));
}

return (
<>
<IndeterminateCheckbox
onChange={handleUpdateIndeterminateChecked}
status={status}
labelText="Colors"
id="5"
/>
<div style={{ marginLeft: magma.spaceScale.spacing08 }}>
<Checkbox
checked={checkedItems[0]}
onChange={handleUpdateRedChecked}
labelText="Red"
/>
<Checkbox
checked={checkedItems[1]}
onChange={handleUpdateBlueChecked}
labelText="Blue"
<FormGroup labelText="Colors group" isTextVisuallyHidden>
<IndeterminateCheckbox
onChange={handleUpdateIndeterminateChecked}
status={status}
labelText="Colors"
id="indeterminateCheckbox"
/>
</div>
<div style={{ marginLeft: magma.spaceScale.spacing08 }}>
<Checkbox
checked={checkedItems[0]}
onChange={e => handleColorChecked(0, e)}
labelText="Red"
id="Red"
/>
<Checkbox
checked={checkedItems[1]}
onChange={e => handleColorChecked(1, e)}
labelText="Blue"
id="Blue"
/>
<Checkbox
checked={checkedItems[2]}
onChange={e => handleColorChecked(2, e)}
labelText="Green"
id="Green"
/>
<Checkbox
checked={checkedItems[3]}
onChange={e => handleColorChecked(3, e)}
labelText="Yellow"
id="Yellow"
/>
</div>
</FormGroup>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ export const IndeterminateCheckbox = React.forwardRef<
}
}

function handleOnKeyDown(event: React.KeyboardEvent) {
if (event.key === ' ' && !disabled) {
event.preventDefault();
const syntheticEvent = {
...event,
target: { checked: !isChecked },
} as unknown as React.ChangeEvent<HTMLInputElement>;
handleChange(syntheticEvent);
}
}

const theme = React.useContext(ThemeContext);
const i18n = React.useContext(I18nContext);
const context = React.useContext(FormGroupContext);
Expand Down Expand Up @@ -159,7 +170,9 @@ export const IndeterminateCheckbox = React.forwardRef<
ref={ref}
type="checkbox"
onChange={handleChange}
tabIndex={-1}
/>

<StyledLabel htmlFor={id} isInverse={isInverse} style={labelStyle}>
<StyledFakeInput
isChecked={isChecked}
Expand All @@ -172,6 +185,10 @@ export const IndeterminateCheckbox = React.forwardRef<
style={inputStyle}
theme={theme}
aria-hidden="true"
tabIndex={0}
role="checkbox"
aria-checked={isIndeterminate ? 'mixed' : isChecked}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it okay to have aria-checked twice? If it is, we can copy line 153 aria-checked={ariaCheckedValue}.
We have type="checkbox" on the hidden input so I worry this is repetitive??

onKeyDown={handleOnKeyDown}
>
{isIndeterminate ? (
<IndeterminateCheckBoxIcon
Expand All @@ -190,6 +207,7 @@ export const IndeterminateCheckbox = React.forwardRef<
labelText
)}
</StyledLabel>

<Announce>
{showAnnounce && <VisuallyHidden>{announceText}</VisuallyHidden>}
</Announce>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1507,10 +1507,13 @@ exports[`TreeView tree with hidden items can uncheck all items by clicking on th
style="padding: 0px;"
>
<span
aria-checked="false"
aria-hidden="true"
class="emotion-42 emotion-27"
color="#3942B0"
role="checkbox"
style="margin-right: 8px;"
tabindex="0"
>
<svg
class="icon"
Expand Down Expand Up @@ -3183,10 +3186,13 @@ exports[`TreeView tree with hidden items clicking show all displays the rest of
style="padding: 0px;"
>
<span
aria-checked="false"
aria-hidden="true"
class="emotion-58 emotion-27"
color="#3942B0"
role="checkbox"
style="margin-right: 8px;"
tabindex="0"
>
<svg
class="icon"
Expand Down Expand Up @@ -4925,10 +4931,13 @@ exports[`TreeView tree with hidden items renders tree with some items preselecte
style="padding: 0px;"
>
<span
aria-checked="false"
aria-hidden="true"
class="emotion-58 emotion-27"
color="#3942B0"
role="checkbox"
style="margin-right: 8px;"
tabindex="0"
>
<svg
class="icon"
Expand Down Expand Up @@ -6667,10 +6676,13 @@ exports[`TreeView tree with hidden items renders tree with some items preselecte
style="padding: 0px;"
>
<span
aria-checked="false"
aria-hidden="true"
class="emotion-58 emotion-27"
color="#3942B0"
role="checkbox"
style="margin-right: 8px;"
tabindex="0"
>
<svg
class="icon"
Expand Down Expand Up @@ -8258,10 +8270,13 @@ exports[`TreeView tree with hidden items renders tree with some items, and click
style="padding: 0px;"
>
<span
aria-checked="false"
aria-hidden="true"
class="emotion-42 emotion-27"
color="#3942B0"
role="checkbox"
style="margin-right: 8px;"
tabindex="0"
>
<svg
class="icon"
Expand Down
Loading