diff --git a/src/explorer/Explorer.test.tsx b/src/explorer/Explorer.test.tsx index e3d50c8c..a8e0de88 100644 --- a/src/explorer/Explorer.test.tsx +++ b/src/explorer/Explorer.test.tsx @@ -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(); - // 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(); - // 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(); - // 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(); - // 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); diff --git a/src/explorer/Explorer.tsx b/src/explorer/Explorer.tsx index 83b29451..20162779 100644 --- a/src/explorer/Explorer.tsx +++ b/src/explorer/Explorer.tsx @@ -111,7 +111,6 @@ const FileActionButtonGroup: React.VoidFunctionComponent return ( diff --git a/src/explorer/explorer.scss b/src/explorer/explorer.scss index 538c6fac..6ca44dea 100644 --- a/src/explorer/explorer.scss +++ b/src/explorer/explorer.scss @@ -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; diff --git a/src/utils/tree-renderer.tsx b/src/utils/tree-renderer.tsx index b5d5db26..399b335f 100644 --- a/src/utils/tree-renderer.tsx +++ b/src/utils/tree-renderer.tsx @@ -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 => @@ -62,48 +63,58 @@ export const renderers: Omit< ), - renderItem: (props) => ( - -
  • e.stopPropagation()} - {...props.context.itemContainerWithChildrenProps} - {...props.context.interactiveElementProps} - > -
    { + const { + value: isHover, + setTrue: setIsHoverTrue, + setFalse: setIsHoverFalse, + } = useBoolean(false); + + return ( + +
  • e.stopPropagation()} + onMouseEnter={setIsHoverTrue} + onMouseLeave={setIsHoverFalse} + {...props.context.itemContainerWithChildrenProps} + {...props.context.interactiveElementProps} > - {props.item.hasChildren ? ( - props.arrow - ) : ( - - )} - - {props.title} - {props.item.data.secondaryLabel && ( - - {props.item.data.secondaryLabel} - - )} - - {props.context.isExpanded && props.children} -
  • -
    - ), +
    + {props.item.hasChildren ? ( + props.arrow + ) : ( + + )} + + {props.title} + {props.item.data.secondaryLabel && isHover && ( + + {props.item.data.secondaryLabel} + + )} +
    + {props.context.isExpanded && props.children} + + + ); + }, renderItemArrow: (props) => (