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

[Emotion] Convert EuiTreeView #7513

Merged
merged 18 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 16 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 changelogs/upcoming/7513.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
**CSS-in-JS conversions**

- Converted `EuiTreeView` to Emotion. Updates as part of the conversion:
- Removed `.euiTreeView__wrapper` div node
- Enforced consistent `icon` size based on `display` size
9 changes: 6 additions & 3 deletions src-docs/src/views/tree_view/playground.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { EuiTreeView, EuiIcon } from '../../../../src/components';
import { EuiTreeViewClass } from '../../../../src/components/tree_view/tree_view';
import {
propUtilityForPlayground,
generateCustomProps,
} from '../../services/playground';

export const TreeViewConfig = () => {
const docgenInfo = Array.isArray(EuiTreeView.__docgenInfo)
? EuiTreeView.__docgenInfo[0]
: EuiTreeView.__docgenInfo;
const docgenInfo = Array.isArray(EuiTreeViewClass.__docgenInfo)
? EuiTreeViewClass.__docgenInfo[0]
: EuiTreeViewClass.__docgenInfo;
const propsToUse = propUtilityForPlayground(docgenInfo.props);

delete propsToUse.theme;

propsToUse.display = {
...propsToUse.display,
defaultValue: 'default',
Expand Down
1 change: 0 additions & 1 deletion src/components/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
@import 'datagrid/index';
@import 'form/index';
@import 'markdown_editor/index';
@import 'tree_view/index';
@import 'side_nav/index';
@import 'selectable/index';
@import 'table/index';
173 changes: 79 additions & 94 deletions src/components/tree_view/__snapshots__/tree_view.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`EuiTreeView is rendered 1`] = `
<div
class="euiText euiTreeView__wrapper emotion-euiText-m"
>
<div>
<p
class="emotion-euiScreenReaderOnly"
id="euiTreeView_generated-id--instruction"
Expand All @@ -13,147 +11,134 @@ exports[`EuiTreeView is rendered 1`] = `
<ul
aria-describedby="euiTreeView_generated-id--instruction"
aria-label="aria-label"
class="euiTreeView testClass1 testClass2 emotion-euiTestCss"
class="euiTreeView testClass1 testClass2 emotion-euiTreeView-default-euiTestCss"
data-test-subj="test subject string"
id="euiTreeView_generated-id"
>
<li
class="euiTreeView__node euiTreeView__node--expanded"
class="euiTreeView__node euiTreeView__node--expanded emotion-euiTreeView__node-default-expanded"
>
<button
aria-controls="euiTreeView_generated-id_item_one"
aria-expanded="true"
class="euiTreeView__nodeInner"
class="euiTreeView__nodeInner emotion-euiTreeView__nodeInner-default"
data-test-subj="euiTreeViewButton-euiTreeView_generated-id"
id="item_one"
>
<span
class="euiTreeView__iconWrapper"
class="euiTreeView__iconWrapper emotion-euiTreeView__iconWrapper-default"
>
<span
data-euiicon-type="folderOpen"
/>
</span>
<span
class="euiTreeView__nodeLabel"
class="euiTreeView__nodeLabel eui-textTruncate"
>
Item One
</span>
</button>
<div
id="euiTreeView_generated-id_item_one"
>
<div
class="euiText euiTreeView__wrapper emotion-euiText-m"
<ul
class="euiTreeView emotion-euiTreeView-default"
>
<ul
aria-label="Item One child of aria-label"
class="euiTreeView"
Comment on lines -49 to -51
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@1Copenut I just wanted to let you know that I made the opinionated decision to remove these aria-labels from nested EuiTreeViews. Could you compare staging vs. prod and let me know how that feels a11y-wise?

I made the decision to remove the labels because I wasn't sure they were necessary, and WCAG's example doesn't appear to have them. WCAG uses very different roles instead, which I briefly experimented with trying to implement, but it broke a bunch of stuff so I opted to not do it as part of the Emotion conversion.

Copy link
Contributor

@1Copenut 1Copenut Feb 7, 2024

Choose a reason for hiding this comment

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

TBH I like this implementation better than the W3C version, at least for VoiceOver for all three screen readers. The ARIA W3C is using structures the tree as a table with collapsed cells that can be expanded. That's a valid approach, but I think of a tree view more as nested lists, as you've got it structured. It was more clear to me being able to traverse through nested lists in all three screen readers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gotcha - just to confirm, were you okay with removing the aria labels? Do you think it's an improvement or degradation over production behavior?

Copy link
Contributor

@1Copenut 1Copenut Feb 7, 2024

Choose a reason for hiding this comment

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

I went back and listened to the staging version and production again and felt there wasn't enough evidence to warrant keeping the aria-labels for each list. This tree view is structured as a navigational element, would likely have headings around it and/or be in a <nav> landmark, and felt a little more natural hearing List, number of items being read out. Button names are a big key; if users have a good context when they expand a button, that's near-term memory for what they're likely to find inside that expanded button.

After some more digging, Scott O'Hara comes through in the clutch for a second time on this PR:
https://www.scottohara.me/blog/2020/05/02/labelled-lists.html#naming-lists-situationally-helpful

tl;dr Accessible labels on UL/OL make sense sometimes if there's a lot of lists that could be confused for one another. The use case for this component has me leaning toward A-OK to remove them in favor of native semantics.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fantastic! Thank you so much Trevor!

<li
class="euiTreeView__node emotion-euiTreeView__node-default"
>
<li
class="euiTreeView__node"
<button
aria-expanded="false"
class="euiTreeView__nodeInner emotion-euiTreeView__nodeInner-default"
data-test-subj="euiTreeViewButton-euiTreeView_generated-id"
id="item_a"
>
<button
aria-controls="euiTreeView_generated-id_item_a"
aria-expanded="false"
class="euiTreeView__nodeInner"
data-test-subj="euiTreeViewButton-euiTreeView_generated-id"
id="item_a"
<span
class="euiTreeView__iconWrapper emotion-euiTreeView__iconWrapper-default"
>
<span
class="euiTreeView__iconWrapper"
>
<span
data-euiicon-type="document"
/>
</span>
<span
class="euiTreeView__nodeLabel"
>
Item A
</span>
</button>
<div
id="euiTreeView_generated-id_item_a"
/>
</li>
<li
class="euiTreeView__node"
data-euiicon-type="document"
/>
</span>
<span
class="euiTreeView__nodeLabel eui-textTruncate"
>
Item A
</span>
</button>
</li>
<li
class="euiTreeView__node emotion-euiTreeView__node-default"
>
<button
aria-controls="euiTreeView_generated-id_item_b"
aria-expanded="false"
class="euiTreeView__nodeInner emotion-euiTreeView__nodeInner-default"
data-test-subj="euiTreeViewButton-euiTreeView_generated-id"
id="item_b"
>
<button
aria-controls="euiTreeView_generated-id_item_b"
aria-expanded="false"
class="euiTreeView__nodeInner"
data-test-subj="euiTreeViewButton-euiTreeView_generated-id"
id="item_b"
<span
class="euiTreeView__iconWrapper emotion-euiTreeView__iconWrapper-default"
>
<span
class="euiTreeView__iconWrapper"
>
<span
data-euiicon-type="arrowRight"
/>
</span>
<span
class="euiTreeView__nodeLabel"
>
Item B
</span>
</button>
<div
id="euiTreeView_generated-id_item_b"
/>
</li>
<li
class="euiTreeView__node"
data-euiicon-type="arrowRight"
/>
</span>
<span
class="euiTreeView__nodeLabel eui-textTruncate"
>
Item B
</span>
</button>
<div
id="euiTreeView_generated-id_item_b"
/>
</li>
<li
class="euiTreeView__node emotion-euiTreeView__node-default"
>
<button
aria-controls="euiTreeView_generated-id_item_c"
aria-expanded="false"
class="euiTreeView__nodeInner emotion-euiTreeView__nodeInner-default"
data-test-subj="euiTreeViewButton-euiTreeView_generated-id"
id="item_c"
>
<button
aria-controls="euiTreeView_generated-id_item_c"
aria-expanded="false"
class="euiTreeView__nodeInner"
data-test-subj="euiTreeViewButton-euiTreeView_generated-id"
id="item_c"
<span
class="euiTreeView__iconWrapper emotion-euiTreeView__iconWrapper-default"
>
<span
class="euiTreeView__iconWrapper"
>
<span
data-euiicon-type="arrowRight"
/>
</span>
<span
class="euiTreeView__nodeLabel"
>
Item C
</span>
</button>
<div
id="euiTreeView_generated-id_item_c"
/>
</li>
</ul>
</div>
data-euiicon-type="arrowRight"
/>
</span>
<span
class="euiTreeView__nodeLabel eui-textTruncate"
>
Item C
</span>
</button>
<div
id="euiTreeView_generated-id_item_c"
/>
</li>
</ul>
</div>
</li>
<li
class="euiTreeView__node"
class="euiTreeView__node emotion-euiTreeView__node-default"
>
<button
aria-controls="euiTreeView_generated-id_item_two"
aria-expanded="false"
class="euiTreeView__nodeInner"
class="euiTreeView__nodeInner emotion-euiTreeView__nodeInner-default"
data-test-subj="euiTreeViewButton-euiTreeView_generated-id"
id="item_two"
>
<span
class="euiTreeView__nodeLabel"
class="euiTreeView__nodeLabel eui-textTruncate"
>
Item Two
</span>
</button>
<div
id="euiTreeView_generated-id_item_two"
/>
</li>
</ul>
</div>
Expand Down
1 change: 0 additions & 1 deletion src/components/tree_view/_index.scss

This file was deleted.

102 changes: 102 additions & 0 deletions src/components/tree_view/_tree_view_item.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { css } from '@emotion/react';

import { UseEuiTheme, transparentize } from '../../services';
import { euiFocusRing, logicalCSS, mathWithUnits } from '../../global_styling';

export const euiTreeViewItemStyles = (euiThemeContext: UseEuiTheme) => {
const { euiTheme } = euiThemeContext;

const defaultSize = euiTheme.size.xl;
const compressedSize = euiTheme.size.l;

return {
li: {
euiTreeView__node: css``,
default: css`
${logicalCSS('max-height', defaultSize)}
line-height: ${defaultSize};
`,
compressed: css`
${logicalCSS('max-height', compressedSize)}
line-height: ${compressedSize};
`,
expanded: css`
${logicalCSS('max-height', '100vh')}
`,
},

button: {
euiTreeView__nodeInner: css`
${logicalCSS('width', '100%')}
${logicalCSS('padding-left', euiTheme.size.s)}
${logicalCSS('padding-right', euiTheme.size.xxs)}
display: flex;
align-items: center;

&:focus {
${euiFocusRing(euiThemeContext, 'inset')}
}

&:hover,
&:active,
&:focus {
background-color: ${transparentize(
euiTheme.colors.text,
euiTheme.focus.transparency
)};
}
`,
default: css`
${logicalCSS('height', defaultSize)}
gap: ${euiTheme.size.s};
border-radius: ${euiTheme.border.radius.medium};
`,
compressed: css`
${logicalCSS('height', compressedSize)}
gap: ${euiTheme.size.xs};
border-radius: ${euiTheme.border.radius.small};
`,
},

icon: {
euiTreeView__iconWrapper: css`
flex-shrink: 0;
line-height: 0; /* Vertically centers the icon */

/* Handle smaller icons in compressed mode */
& > * {
${logicalCSS('max-width', '100%')}
}

& > .euiToken {
${logicalCSS('max-height', '100%')}
${logicalCSS('height', 'auto')}

svg {
${logicalCSS('width', '100%')}
}
}
`,
default: css`
${logicalCSS(
'width',
mathWithUnits(defaultSize, (x) => x / 2)
)}
`,
compressed: css`
${logicalCSS(
'width',
mathWithUnits(compressedSize, (x) => x / 2)
)}
`,
},
};
};
Loading
Loading