mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 09:36:27 +00:00
explorer: render file toolbar on hover
This improves performance by only rendering the toolbar for each file when it is actually shown.
This commit is contained in:
committed by
David Lechner
parent
d891d349f6
commit
a835934028
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { cleanup } from '@testing-library/react';
|
||||
import { cleanup, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { testRender, uuid } from '../../test';
|
||||
import { FileMetadata } from '../fileStorage';
|
||||
@@ -99,9 +99,12 @@ describe('tree item', () => {
|
||||
jest.mocked(useFileStorageMetadata).mockReturnValue([testFile]);
|
||||
const [user, explorer, dispatch] = testRender(<Explorer />);
|
||||
|
||||
// NB: this button is intentionally not accessible (by role) since
|
||||
// there is a keyboard shortcut.
|
||||
const button = explorer.getByTitle('Duplicate test.file');
|
||||
const treeItem = explorer.getByRole('treeitem', { name: 'test.file' });
|
||||
await user.hover(treeItem);
|
||||
|
||||
const button = await waitFor(() =>
|
||||
explorer.getByRole('button', { name: 'Duplicate test.file' }),
|
||||
);
|
||||
|
||||
await user.click(button);
|
||||
|
||||
@@ -129,9 +132,12 @@ describe('tree item', () => {
|
||||
jest.mocked(useFileStorageMetadata).mockReturnValue([testFile]);
|
||||
const [user, explorer, dispatch] = testRender(<Explorer />);
|
||||
|
||||
// NB: this button is intentionally not accessible (by role) since
|
||||
// there is a keyboard shortcut.
|
||||
const button = explorer.getByTitle('Rename test.file');
|
||||
const treeItem = explorer.getByRole('treeitem', { name: 'test.file' });
|
||||
await user.hover(treeItem);
|
||||
|
||||
const button = await waitFor(() =>
|
||||
explorer.getByRole('button', { name: 'Rename test.file' }),
|
||||
);
|
||||
|
||||
await user.click(button);
|
||||
|
||||
@@ -159,9 +165,12 @@ describe('tree item', () => {
|
||||
jest.mocked(useFileStorageMetadata).mockReturnValue([testFile]);
|
||||
const [user, explorer, dispatch] = testRender(<Explorer />);
|
||||
|
||||
// NB: this button is intentionally not accessible (by role) since
|
||||
// there is a keyboard shortcut.
|
||||
const button = explorer.getByTitle('Export test.file');
|
||||
const treeItem = explorer.getByRole('treeitem', { name: 'test.file' });
|
||||
await user.hover(treeItem);
|
||||
|
||||
const button = await waitFor(() =>
|
||||
explorer.getByRole('button', { name: 'Export test.file' }),
|
||||
);
|
||||
|
||||
await user.click(button);
|
||||
|
||||
@@ -189,9 +198,12 @@ describe('tree item', () => {
|
||||
jest.mocked(useFileStorageMetadata).mockReturnValue([testFile]);
|
||||
const [user, explorer, dispatch] = testRender(<Explorer />);
|
||||
|
||||
// NB: this button is intentionally not accessible (by role) since
|
||||
// there is a keyboard shortcut.
|
||||
const button = explorer.getByTitle('Delete test.file');
|
||||
const treeItem = explorer.getByRole('treeitem', { name: 'test.file' });
|
||||
await user.hover(treeItem);
|
||||
|
||||
const button = await waitFor(() =>
|
||||
explorer.getByRole('button', { name: 'Delete test.file' }),
|
||||
);
|
||||
|
||||
await user.click(button);
|
||||
|
||||
|
||||
@@ -111,7 +111,6 @@ const FileActionButtonGroup: React.VoidFunctionComponent<ActionButtonGroupProps>
|
||||
|
||||
return (
|
||||
<ButtonGroup
|
||||
aria-hidden={true}
|
||||
className="pb-explorer-file-tree-action-button-group"
|
||||
minimal={true}
|
||||
>
|
||||
|
||||
@@ -15,11 +15,6 @@
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
// reveal file action toolbar on hover
|
||||
.#{bp.$ns}-tree-node-content:not(:hover) &-action-button-group {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// fix layout since we are nesting toolbar in button group
|
||||
&-action-toolbar {
|
||||
display: inline-flex;
|
||||
|
||||
+50
-39
@@ -17,6 +17,7 @@ import {
|
||||
} from '@blueprintjs/core';
|
||||
import React, { createContext } from 'react';
|
||||
import { TreeItem, TreeRenderProps } from 'react-complex-tree';
|
||||
import { useBoolean } from 'usehooks-ts';
|
||||
|
||||
/** Combines class names into a string. */
|
||||
const cx = (...classNames: Array<string | undefined | false>): string =>
|
||||
@@ -62,48 +63,58 @@ export const renderers: Omit<
|
||||
</ul>
|
||||
),
|
||||
|
||||
renderItem: (props) => (
|
||||
<TreeItemContext.Provider value={props.item}>
|
||||
<li
|
||||
className={cx(
|
||||
Classes.TREE_NODE,
|
||||
// TODO: include Classes.DISABLED if disabled
|
||||
props.context.isExpanded && Classes.TREE_NODE_EXPANDED,
|
||||
(props.context.isSelected || props.context.isDraggingOver) &&
|
||||
Classes.TREE_NODE_SELECTED,
|
||||
)}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
{...props.context.itemContainerWithChildrenProps}
|
||||
{...props.context.interactiveElementProps}
|
||||
>
|
||||
<div
|
||||
renderItem: (props) => {
|
||||
const {
|
||||
value: isHover,
|
||||
setTrue: setIsHoverTrue,
|
||||
setFalse: setIsHoverFalse,
|
||||
} = useBoolean(false);
|
||||
|
||||
return (
|
||||
<TreeItemContext.Provider value={props.item}>
|
||||
<li
|
||||
className={cx(
|
||||
Classes.TREE_NODE_CONTENT,
|
||||
`${Classes.TREE_NODE_CONTENT}-${props.depth}`,
|
||||
Classes.TREE_NODE,
|
||||
// TODO: include Classes.DISABLED if disabled
|
||||
props.context.isExpanded && Classes.TREE_NODE_EXPANDED,
|
||||
(props.context.isSelected || props.context.isDraggingOver) &&
|
||||
Classes.TREE_NODE_SELECTED,
|
||||
)}
|
||||
{...props.context.itemContainerWithoutChildrenProps}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
onMouseEnter={setIsHoverTrue}
|
||||
onMouseLeave={setIsHoverFalse}
|
||||
{...props.context.itemContainerWithChildrenProps}
|
||||
{...props.context.interactiveElementProps}
|
||||
>
|
||||
{props.item.hasChildren ? (
|
||||
props.arrow
|
||||
) : (
|
||||
<span className={Classes.TREE_NODE_CARET_NONE} />
|
||||
)}
|
||||
<Icon
|
||||
className={Classes.TREE_NODE_ICON}
|
||||
icon={props.item.data.icon}
|
||||
aria-hidden={true}
|
||||
/>
|
||||
{props.title}
|
||||
{props.item.data.secondaryLabel && (
|
||||
<span className={Classes.TREE_NODE_SECONDARY_LABEL}>
|
||||
{props.item.data.secondaryLabel}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{props.context.isExpanded && props.children}
|
||||
</li>
|
||||
</TreeItemContext.Provider>
|
||||
),
|
||||
<div
|
||||
className={cx(
|
||||
Classes.TREE_NODE_CONTENT,
|
||||
`${Classes.TREE_NODE_CONTENT}-${props.depth}`,
|
||||
)}
|
||||
{...props.context.itemContainerWithoutChildrenProps}
|
||||
>
|
||||
{props.item.hasChildren ? (
|
||||
props.arrow
|
||||
) : (
|
||||
<span className={Classes.TREE_NODE_CARET_NONE} />
|
||||
)}
|
||||
<Icon
|
||||
className={Classes.TREE_NODE_ICON}
|
||||
icon={props.item.data.icon}
|
||||
aria-hidden={true}
|
||||
/>
|
||||
{props.title}
|
||||
{props.item.data.secondaryLabel && isHover && (
|
||||
<span className={Classes.TREE_NODE_SECONDARY_LABEL}>
|
||||
{props.item.data.secondaryLabel}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{props.context.isExpanded && props.children}
|
||||
</li>
|
||||
</TreeItemContext.Provider>
|
||||
);
|
||||
},
|
||||
|
||||
renderItemArrow: (props) => (
|
||||
<Icon
|
||||
|
||||
Reference in New Issue
Block a user