Skip to content

Commit

Permalink
feat: implement :focus-visible for the Tree component with targeted…
Browse files Browse the repository at this point in the history
… focus style for non-pointer devices
  • Loading branch information
cheton committed Aug 30, 2023
1 parent a80d4cc commit f6f61ad
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 23 deletions.
25 changes: 7 additions & 18 deletions packages/react/src/tree/TreeItemContent.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useMergeRefs } from '@tonic-ui/react-hooks';
import { callEventHandlers } from '@tonic-ui/utils';
import { ensureArray } from 'ensure-type';
import React, { forwardRef, useCallback, useMemo } from 'react';
import React, { forwardRef, useCallback } from 'react';
import { Box } from '../box';
import { useTheme } from '../theme';
import { useTreeItemContentStyle } from './styles';
Expand All @@ -12,7 +11,7 @@ const TreeItemContent = forwardRef((
{
onClick: onClickProp,
onMouseDown: onMouseDownProp,
sx: sxProp,
style: styleProp,
...rest
},
ref,
Expand Down Expand Up @@ -62,30 +61,20 @@ const TreeItemContent = forwardRef((
}
}, [isDisabled]);

const style = {
paddingLeft: `calc(${nodeDepth} * ${sizes['6x']} + ${sizes['3x']})`,
...styleProp,
};
const tabIndex = -1;
const styleProps = useTreeItemContentStyle({ isDisabled, isSelected, tabIndex });
const sxProps = useMemo(() => ([
{
pl: `calc(${nodeDepth} * ${sizes['6x']} + ${sizes['3x']})`,
pr: `calc(${sizes['3x']})`,
':focus-visible': {
borderStyle: 'solid',
borderWidth: '1h',
pl: `calc(${nodeDepth} * ${sizes['6x']} + ${sizes['3x']} - ${sizes['1h']})`,
pr: `calc(${sizes['3x']} - ${sizes['1h']})`,
py: `calc(${sizes['2x']} - ${sizes['1h']})`,
},
},
...ensureArray(sxProp),
]), [nodeDepth, sizes, sxProp]);

return (
<Box
ref={combinedRef}
onClick={callEventHandlers(onClickProp, onClick)}
onMouseDown={callEventHandlers(onMouseDownProp, onMouseDown)}
style={style}
tabIndex={tabIndex}
sx={sxProps}
{...styleProps}
{...rest}
/>
Expand Down
2 changes: 2 additions & 0 deletions packages/react/src/tree/__tests__/Tree.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ describe('Tree', () => {

expect(screen.getByRole('tree')).toHaveAttribute('aria-multiselectable', 'true');

expect(container).toMatchSnapshot();

await testA11y(container);
});

Expand Down
289 changes: 289 additions & 0 deletions packages/react/src/tree/__tests__/__snapshots__/Tree.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Tree should render correctly 1`] = `
.emotion-0 {
outline: 0;
}
.emotion-4 {
color: var(--tonic-colors-white-primary);
cursor: pointer;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-text-decoration: none;
text-decoration: none;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
outline: 0;
padding-left: var(--tonic-space-3x);
padding-right: var(--tonic-space-3x);
padding-top: var(--tonic-space-2x);
padding-bottom: var(--tonic-space-2x);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
width: 100%;
}
.emotion-4:focus-visible {
box-shadow: inset 0 0 0 .125rem #1e5ede;
}
.emotion-4:hover,
.emotion-4[data-hover] {
background-color: rgba(255, 255, 255, 0.12);
}
.emotion-4:hover+[role="group"] {
position: relative;
}
.emotion-4:hover+[role="group"]::before {
background-color: rgba(255, 255, 255, 0.12);
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 19.5px;
width: 1px;
}
.emotion-6 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: none;
-ms-flex: none;
flex: none;
width: var(--tonic-sizes-6x);
}
.emotion-8 {
-webkit-appearance: none;
-moz-appearance: none;
-ms-appearance: none;
appearance: none;
background-color: var(--tonic-colors-transparent);
border: none;
color: inherit;
cursor: pointer;
outline: 0;
padding: 0;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
}
.emotion-10 {
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
color: var(--tonic-colors-white-secondary);
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
-webkit-transition: -webkit-transform 93ms cubic-bezier(0.0, 0, 0.2, 1);
transition: transform 93ms cubic-bezier(0.0, 0, 0.2, 1);
}
.emotion-10:hover,
.emotion-10[data-hover] {
color: var(--tonic-colors-white-primary);
}
.emotion-12 {
width: var(--tonic-sizes-4x);
height: var(--tonic-sizes-4x);
fill: currentColor;
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
vertical-align: middle;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
flex-shrink: 0;
}
.emotion-12:not(:root) {
overflow: hidden;
}
.emotion-14 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
-webkit-flex: auto;
-ms-flex: auto;
flex: auto;
font-weight: var(--tonic-fontWeights-normal);
}
<div>
<div
aria-label="tree"
aria-multiselectable="true"
class="emotion-0 emotion-1"
id="4"
role="tree"
tabindex="0"
>
<div
class="emotion-2 emotion-1"
data-testid="node-1"
id="4-1"
role="treeitem"
>
<div
class="emotion-4 emotion-1"
data-testid="node-1-content"
tabindex="-1"
>
<div
class="emotion-6 emotion-1"
>
<button
aria-label="toggle"
class="emotion-8 emotion-1"
data-testid="node-1-toggle"
role="button"
tabindex="0"
type="button"
>
<div
class="emotion-10 emotion-1"
>
<svg
aria-hidden="true"
class="emotion-12 emotion-13"
focusable="false"
role="presentation"
viewBox="0 0 16 16"
>
<g>
<path
d="M6.501 12.5l-1-1 3.501-3.506-3.49-3.494 1-1 4.488 4.494z"
/>
</g>
</svg>
</div>
</button>
</div>
<div
class="emotion-14 emotion-1"
id="@tonic-ui/react:TooltipTrigger-1"
>
Node 1
</div>
</div>
</div>
<div
class="emotion-2 emotion-1"
data-testid="node-7"
id="4-7"
role="treeitem"
>
<div
class="emotion-4 emotion-1"
data-testid="node-7-content"
tabindex="-1"
>
<div
class="emotion-6 emotion-1"
>
<button
aria-label="toggle"
class="emotion-8 emotion-1"
data-testid="node-7-toggle"
role="button"
tabindex="0"
type="button"
>
<div
class="emotion-10 emotion-1"
>
<svg
aria-hidden="true"
class="emotion-12 emotion-13"
focusable="false"
role="presentation"
viewBox="0 0 16 16"
>
<g>
<path
d="M6.501 12.5l-1-1 3.501-3.506-3.49-3.494 1-1 4.488 4.494z"
/>
</g>
</svg>
</div>
</button>
</div>
<div
class="emotion-14 emotion-1"
id="@tonic-ui/react:TooltipTrigger-2"
>
Node 7
</div>
</div>
</div>
<div
class="emotion-2 emotion-1"
data-testid="node-9"
id="4-9"
role="treeitem"
>
<div
class="emotion-4 emotion-1"
data-testid="node-9-content"
tabindex="-1"
>
<div
class="emotion-6 emotion-1"
>
<button
aria-label="toggle"
class="emotion-8 emotion-1"
data-testid="node-9-toggle"
role="button"
tabindex="0"
type="button"
>
<div
class="emotion-10 emotion-1"
>
<svg
aria-hidden="true"
class="emotion-12 emotion-13"
focusable="false"
role="presentation"
viewBox="0 0 16 16"
>
<g>
<path
d="M6.501 12.5l-1-1 3.501-3.506-3.49-3.494 1-1 4.488 4.494z"
/>
</g>
</svg>
</div>
</button>
</div>
<div
class="emotion-14 emotion-1"
id="@tonic-ui/react:TooltipTrigger-3"
>
Node 9
</div>
</div>
</div>
</div>
</div>
`;
10 changes: 5 additions & 5 deletions packages/react/src/tree/styles.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useColorMode } from '../color-mode';
import { useTheme } from '../theme';

const useTreeItemStyle = () => {
return {
Expand All @@ -10,6 +11,7 @@ const useTreeItemContentStyle = ({
isSelected,
tabIndex,
}) => {
const { colors, sizes } = useTheme();
const [colorMode] = useColorMode();
const color = {
dark: 'white:primary',
Expand All @@ -19,7 +21,7 @@ const useTreeItemContentStyle = ({
dark: 'rgba(255, 255, 255, 0.12)',
light: 'rgba(0, 0, 0, 0.12)',
}[colorMode];
const focusBorderColor = {
const focusBoxShadowColor = {
dark: 'blue:60',
light: 'blue:60',
}[colorMode];
Expand Down Expand Up @@ -48,10 +50,8 @@ const useTreeItemContentStyle = ({
_hover: {
backgroundColor: !isDisabled ? hoverBackgroundColor : undefined,
},
_focus: {
borderColor: focusBorderColor,

// Note: The border will be added in TreeItemContent
_focusVisible: {
boxShadow: `inset 0 0 0 ${sizes?.['1h']} ${colors?.[focusBoxShadowColor]}`,
},
};
};
Expand Down

0 comments on commit f6f61ad

Please sign in to comment.