mantinedev / mantine

A fully featured React components library
https://mantine.dev
MIT License
26.86k stars 1.9k forks source link

a11y: Tree with checkboxes expands node instead of checking checkbox for `SPACE` key #7132

Open jnachtigall opened 5 days ago

jnachtigall commented 5 days ago

Dependencies check up

What version of @mantine/* packages do you have in package.json?

7.14.1

What package has an issue?

@mantine/core

What framework do you use?

Next.js

In which browsers you can reproduce the issue?

All

Describe the bug

Steps to reproduce:

  1. Go to https://mantine.dev/core/tree/#checked-state and use this example
  2. Press TAB until you focus any of the tree`s checkboxes
  3. Hit SPACE bar

Expected result:

The checkbox should be checked or unchecked

Actual result:

The checkbox is not affected at all but instead the tree is expanded.

I have this problem locally too but I guess it is easier for reproduction if you just use the official example.

If possible, include a link to a codesandbox with a minimal reproduction

No response

Possible fix

No response

Self-service

jnachtigall commented 4 days ago

Seems to be caused by a missing expandOnSpace={false}

rtivital commented 4 days ago

Have you fixed your issue?

jnachtigall commented 3 days ago

Yes, I've fixed it locally for my tree, but from accessibility perspective the two examples from https://mantine.dev/core/tree/#checked-state are wrong:

image First node is focussed, but SPACE cannot check checkbox

Here's a quick outline on what I did to fix this:

The remains kind of a problem, that Tree component still also focusses the li which does not make any sense anymore and I cannot disable this.

In the end the code looks something like this:

import { IconChevronDown } from "@tabler/icons-react";
import {
  Button,
  Checkbox,
  Group,
  RenderTreeNodePayload,
  Tree,
} from "@mantine/core";
import { data } from "./data";

const renderTreeNode = ({
  node,
  expanded,
  hasChildren,
  elementProps,
  tree,
}: RenderTreeNodePayload) => {
  const checked = tree.isNodeChecked(node.value);
  const indeterminate = tree.isNodeIndeterminate(node.value);

  return (
    <Group gap="xs" {...elementProps}>
      <Checkbox
        checked={checked}
        indeterminate={indeterminate}
        onChange={() =>
          !checked ? tree.checkNode(node.value) : tree.uncheckNode(node.value)
        }
        label={node.label}
      />
      {hasChildren && (
        <Button
          title={expanded ? "Close" : "Open"}
          onClick={() => tree.toggleExpanded(node.value)}
        >
          <IconChevronDown
            size={14}
            style={{
              transform: expanded ? "rotate(180deg)" : "rotate(0deg)",
            }}
          />
        </Button>
      )}
    </Group>
  );
};

function Demo() {
  return (
    <Tree
      data={data}
      levelOffset={23}
      expandOnClick={false}
      expandOnSpace={false}
      renderNode={renderTreeNode}
    />
  );
}