diff --git a/extension/src/experiments/index.ts b/extension/src/experiments/index.ts index 34ddf474a8..fc60f3fcb9 100644 --- a/extension/src/experiments/index.ts +++ b/extension/src/experiments/index.ts @@ -440,6 +440,7 @@ export class Experiments extends BaseRepository { columnOrder: this.columns.getColumnOrder(), columnWidths: this.columns.getColumnWidths(), columns: this.columns.getSelected(), + filters: this.experiments.getFilterPaths(), hasCheckpoints: this.hasCheckpoints(), hasColumns: this.columns.hasColumns(), hasRunningExperiment: this.experiments.hasRunningExperiment(), diff --git a/extension/src/experiments/model/index.ts b/extension/src/experiments/model/index.ts index d191bb0d45..22d3c30861 100644 --- a/extension/src/experiments/model/index.ts +++ b/extension/src/experiments/model/index.ts @@ -162,6 +162,10 @@ export class ExperimentsModel extends ModelWithPersistence { return [...this.filters.values()] } + public getFilterPaths() { + return this.getFilters().map(({ path }) => path) + } + public canAutoApplyFilters(...filterIdsToRemove: string[]): boolean { if (!this.useFiltersForSelection) { return true diff --git a/extension/src/experiments/webview/contract.ts b/extension/src/experiments/webview/contract.ts index 47804cc362..98d05047d6 100644 --- a/extension/src/experiments/webview/contract.ts +++ b/extension/src/experiments/webview/contract.ts @@ -60,6 +60,7 @@ export type TableData = { hasRunningExperiment: boolean rows: Row[] sorts: SortDefinition[] + filters: string[] } export type InitiallyUndefinedTableData = TableData | undefined diff --git a/extension/src/test/fixtures/expShow/deeplyNested.ts b/extension/src/test/fixtures/expShow/deeplyNested.ts index 4fe2131f31..f35f4913ed 100644 --- a/extension/src/test/fixtures/expShow/deeplyNested.ts +++ b/extension/src/test/fixtures/expShow/deeplyNested.ts @@ -281,6 +281,10 @@ const deeplyNestedTableData: TableData = { changes: [], columnOrder: [], columnWidths: {}, + filters: [ + 'params:params.yaml:nested1.doubled', + 'params:params.yaml:nested1%2Enested2%2Enested3.nested4.nested5b.doubled' + ], hasCheckpoints: false, hasRunningExperiment: false, sorts: [ diff --git a/extension/src/test/fixtures/expShow/tableData.ts b/extension/src/test/fixtures/expShow/tableData.ts index b3f2f6be02..7007fddec4 100644 --- a/extension/src/test/fixtures/expShow/tableData.ts +++ b/extension/src/test/fixtures/expShow/tableData.ts @@ -5,6 +5,7 @@ import columnsFixture from './columns' const tableDataFixture: TableData = { rows: rowsFixture, columns: columnsFixture, + filters: [], hasCheckpoints: true, hasRunningExperiment: true, hasColumns: true, diff --git a/extension/src/test/suite/experiments/index.test.ts b/extension/src/test/suite/experiments/index.test.ts index ec617dc378..64f315cff4 100644 --- a/extension/src/test/suite/experiments/index.test.ts +++ b/extension/src/test/suite/experiments/index.test.ts @@ -125,6 +125,7 @@ suite('Experiments Test Suite', () => { columnOrder: [], columnWidths: {}, columns: columnsFixture, + filters: [], hasCheckpoints: true, hasColumns: true, hasRunningExperiment: true, @@ -683,6 +684,7 @@ suite('Experiments Test Suite', () => { columnOrder: [], columnWidths: {}, columns: [], + filters: [], hasCheckpoints: true, hasColumns: true, hasRunningExperiment: true, diff --git a/extension/src/test/suite/experiments/model/filterBy/tree.test.ts b/extension/src/test/suite/experiments/model/filterBy/tree.test.ts index b46814b50c..006674acca 100644 --- a/extension/src/test/suite/experiments/model/filterBy/tree.test.ts +++ b/extension/src/test/suite/experiments/model/filterBy/tree.test.ts @@ -109,6 +109,7 @@ suite('Experiments Filter By Tree Test Suite', () => { columnOrder: [], columnWidths: {}, columns: columnsFixture, + filters: [accuracyPath], hasCheckpoints: true, hasColumns: true, hasRunningExperiment: true, @@ -138,6 +139,7 @@ suite('Experiments Filter By Tree Test Suite', () => { columnOrder: [], columnWidths: {}, columns: columnsFixture, + filters: [], hasCheckpoints: true, hasColumns: true, hasRunningExperiment: true, diff --git a/webview/src/experiments/components/Experiments.tsx b/webview/src/experiments/components/Experiments.tsx index 285f09dae9..db0c2d81e5 100644 --- a/webview/src/experiments/components/Experiments.tsx +++ b/webview/src/experiments/components/Experiments.tsx @@ -28,8 +28,8 @@ import { GetStarted } from '../../shared/components/getStarted/GetStarted' import { DragDropProvider } from '../../shared/components/dragDrop/DragDropContext' import { EmptyState } from '../../shared/components/emptyState/EmptyState' -const DEFAULT_COLUMN_WIDTH = 75 -const MINIMUM_COLUMN_WIDTH = 50 +const DEFAULT_COLUMN_WIDTH = 90 +const MINIMUM_COLUMN_WIDTH = 90 const timeFormatter = new Intl.DateTimeFormat([], { hour: '2-digit', @@ -138,6 +138,7 @@ export const ExperimentsTable: React.FC<{ columnOrder: [], columnWidths: {}, columns: [], + filters: [], hasCheckpoints: false, hasColumns: false, hasRunningExperiment: false, diff --git a/webview/src/experiments/components/table/MergeHeaderGroups.tsx b/webview/src/experiments/components/table/MergeHeaderGroups.tsx index fcbffd3869..35ac703c6e 100644 --- a/webview/src/experiments/components/table/MergeHeaderGroups.tsx +++ b/webview/src/experiments/components/table/MergeHeaderGroups.tsx @@ -15,6 +15,7 @@ export const MergedHeaderGroups: React.FC<{ headerGroup: HeaderGroup columns: HeaderGroup[] sorts: SortDefinition[] + filters: string[] orderedColumns: Column[] onDragUpdate: OnDragOver onDragStart: OnDragStart @@ -22,6 +23,7 @@ export const MergedHeaderGroups: React.FC<{ }> = ({ headerGroup, sorts, + filters, columns, orderedColumns, onDragUpdate, @@ -41,6 +43,7 @@ export const MergedHeaderGroups: React.FC<{ column={column} columns={columns} sorts={sorts} + filters={filters} onDragOver={onDragUpdate} onDragStart={onDragStart} onDrop={onDragEnd} diff --git a/webview/src/experiments/components/table/SortPicker.tsx b/webview/src/experiments/components/table/SortPicker.tsx deleted file mode 100644 index 8ab9057c17..0000000000 --- a/webview/src/experiments/components/table/SortPicker.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react' -import { SelectMenu } from '../../../shared/components/selectMenu/SelectMenu' -import { SelectMenuOptionProps } from '../../../shared/components/selectMenu/SelectMenuOption' - -export enum SortOrder { - ASCENDING = 'ascending', - DESCENDING = 'descending', - NONE = 'none' -} - -export enum SortOrderLabel { - ASCENDING = 'Sort Ascending', - DESCENDING = 'Sort Descending', - NONE = 'Remove Sort' -} - -export const SortPicker: React.FC<{ - sortOrder: SortOrder - setSelectedOrder: (order: SortOrder) => void -}> = ({ sortOrder, setSelectedOrder }) => { - const options: SelectMenuOptionProps[] = [ - { - id: SortOrder.ASCENDING, - isSelected: sortOrder === SortOrder.ASCENDING, - label: SortOrderLabel.ASCENDING - }, - { - id: SortOrder.DESCENDING, - isSelected: sortOrder === SortOrder.DESCENDING, - label: SortOrderLabel.DESCENDING - }, - { - id: SortOrder.NONE, - isSelected: !sortOrder || sortOrder === SortOrder.NONE, - label: SortOrderLabel.NONE - } - ] - return ( - { - const order = (id as SortOrder) || SortOrder.NONE - - if (order !== sortOrder) { - setSelectedOrder(order) - } - }} - /> - ) -} diff --git a/webview/src/experiments/components/table/Table.test.tsx b/webview/src/experiments/components/table/Table.test.tsx index 14f63b0a37..c3e6d8d90a 100644 --- a/webview/src/experiments/components/table/Table.test.tsx +++ b/webview/src/experiments/components/table/Table.test.tsx @@ -3,19 +3,13 @@ */ /* eslint jest/expect-expect: ["error", { "assertFunctionNames": ["expect", "expectHeaders"] }] */ import '@testing-library/jest-dom/extend-expect' -import { - cleanup, - fireEvent, - render, - screen, - within -} from '@testing-library/react' +import { cleanup, fireEvent, render, screen } from '@testing-library/react' import { Experiment, TableData } from 'dvc/src/experiments/webview/contract' import { MessageFromWebviewType } from 'dvc/src/webview/contract' import React from 'react' import { TableInstance } from 'react-table' import tableDataFixture from 'dvc/src/test/fixtures/expShow/tableData' -import { SortOrderLabel } from './SortPicker' +import { SortOrder } from './TableHeader' import { Table } from './Table' import styles from './styles.module.scss' import { ExperimentsTable } from '../Experiments' @@ -112,6 +106,7 @@ describe('Table', () => { columnOrder: [], columnWidths: {}, columns: [], + filters: [], hasCheckpoints: false, hasColumns: true, hasRunningExperiment: false, @@ -144,54 +139,41 @@ describe('Table', () => { const mockColumnName = 'C' const mockColumnPath = 'params:C' - const findSortableColumn = async (headerText = mockColumnName) => - await screen.findByText(headerText) + const findSortableColumn = async () => + await screen.findByTestId(`header-${mockColumnPath}`) - const clickOnSortOption = async (optionLabel: SortOrderLabel) => { - const column = await findSortableColumn() + const clickOnSortOption = async (optionLabel: SortOrder) => { + const column = await screen.findByText(mockColumnName) fireEvent.contextMenu(column, { bubbles: true }) - const columnMenu = await screen.findAllByRole('menu') - const sortOption = await within(columnMenu[0]).findByText(optionLabel) + const sortOption = await screen.findByText(optionLabel) fireEvent.click(sortOption) } - describe('Given no sorting is present yet', () => { - it('should add an ascending sort to the column path when the user clicks on the Ascending option', async () => { + describe('Sortable column', () => { + it('should not not have a sorting indicator if it is not sorted yet', () => { renderExperimentsTable() - await clickOnSortOption(SortOrderLabel.ASCENDING) - expect(mockedPostMessage).toBeCalledWith({ - payload: { - descending: false, - path: mockColumnPath - }, - type: MessageFromWebviewType.SORT_COLUMN - }) + const sortIcons = screen.queryAllByTestId('sorting-indicator') + + expect(sortIcons.length).toBe(0) }) - it('should add a descending sort to the column path when the user clicks on the Descending option', async () => { + it('should be able to add an ascending sort to the column, if it is not sorted yet', async () => { renderExperimentsTable() - await clickOnSortOption(SortOrderLabel.DESCENDING) + await clickOnSortOption(SortOrder.ASCENDING) + expect(mockedPostMessage).toBeCalledWith({ payload: { - descending: true, + descending: false, path: mockColumnPath }, type: MessageFromWebviewType.SORT_COLUMN }) }) - it('should not do anything when the user clicks on the None option', async () => { - renderExperimentsTable() - await clickOnSortOption(SortOrderLabel.NONE) - expect(mockedPostMessage).not.toHaveBeenCalled() - }) - }) - - describe('Given an initial ascending column sort', () => { - it('should add a descending sort to the column path when the user clicks on the Descending option', async () => { + it('should add a descending sort to the column, when clicking on the descending option', async () => { renderExperimentsTable({ ...sortingTableDataFixture, sorts: [ @@ -201,7 +183,12 @@ describe('Table', () => { } ] }) - await clickOnSortOption(SortOrderLabel.DESCENDING) + + const column = await findSortableColumn() + expect(column).toHaveClass('sortingHeaderCellAsc') + + await clickOnSortOption(SortOrder.DESCENDING) + expect(mockedPostMessage).toBeCalledWith({ payload: { descending: true, @@ -211,31 +198,22 @@ describe('Table', () => { }) }) - it('should not do anything when the user clicks on the Ascending option', async () => { + it('should remove the column sort if the remove option is selected', async () => { renderExperimentsTable({ ...sortingTableDataFixture, sorts: [ { - descending: false, + descending: true, path: mockColumnPath } ] }) - await clickOnSortOption(SortOrderLabel.ASCENDING) - expect(mockedPostMessage).not.toHaveBeenCalled() - }) - it('should remove the column sort when the user clicks on the None option', async () => { - renderExperimentsTable({ - ...sortingTableDataFixture, - sorts: [ - { - descending: false, - path: mockColumnPath - } - ] - }) - await clickOnSortOption(SortOrderLabel.NONE) + const column = await findSortableColumn() + expect(column).toHaveClass('sortingHeaderCellDesc') + + await clickOnSortOption(SortOrder.NONE) + expect(mockedPostMessage).toBeCalledWith({ payload: mockColumnPath, type: MessageFromWebviewType.REMOVE_COLUMN_SORT diff --git a/webview/src/experiments/components/table/Table.tsx b/webview/src/experiments/components/table/Table.tsx index eb294032bb..d5cb13d4e5 100644 --- a/webview/src/experiments/components/table/Table.tsx +++ b/webview/src/experiments/components/table/Table.tsx @@ -101,13 +101,24 @@ export const Table: React.FC = ({ tableData }) => { const { getTableProps, rows } = instance - const { sorts, columns, changes, hasCheckpoints, hasRunningExperiment } = - tableData + const { + filters, + sorts, + columns, + changes, + hasCheckpoints, + hasRunningExperiment + } = tableData return (
- + {rows.map(row => ( columns: Column[] sorts: SortDefinition[] + filters: string[] } export const TableHead: React.FC = ({ + filters, instance: { headerGroups, setColumnOrder, @@ -90,6 +92,7 @@ export const TableHead: React.FC = ({ headerGroup={headerGroup} columns={allHeaders} sorts={sorts} + filters={filters} onDragStart={onDragStart} onDragUpdate={onDragUpdate} onDragEnd={onDragEnd} diff --git a/webview/src/experiments/components/table/TableHeader.tsx b/webview/src/experiments/components/table/TableHeader.tsx index 0fec2f73ad..ceacb1577a 100644 --- a/webview/src/experiments/components/table/TableHeader.tsx +++ b/webview/src/experiments/components/table/TableHeader.tsx @@ -10,9 +10,7 @@ import cx from 'classnames' import { MessageFromWebviewType } from 'dvc/src/webview/contract' import { VSCodeDivider } from '@vscode/webview-ui-toolkit/react' import styles from './styles.module.scss' -import { SortOrder, SortPicker } from './SortPicker' import { countUpperLevels, isFirstLevelHeader } from '../../util/columns' -import { sendMessage } from '../../../shared/vscode' import { ContextMenu } from '../../../shared/components/contextMenu/ContextMenu' import { Draggable, @@ -22,6 +20,20 @@ import { } from '../../../shared/components/dragDrop/DragDropWorkbench' import { MessagesMenu } from '../../../shared/components/messagesMenu/MessagesMenu' import { MessagesMenuOptionProps } from '../../../shared/components/messagesMenu/MessagesMenuOption' +import { IconMenu } from '../../../shared/components/iconMenu/IconMenu' +import { AllIcons } from '../../../shared/components/Icon' + +export enum SortOrder { + ASCENDING = 'Sort Ascending', + DESCENDING = 'Sort Descending', + NONE = 'Remove Sort' +} + +const possibleOrders = { + false: SortOrder.ASCENDING, + true: SortOrder.DESCENDING, + undefined: SortOrder.NONE +} as const export const ColumnDragHandle: React.FC<{ disabled: boolean @@ -60,13 +72,71 @@ export const ColumnDragHandle: React.FC<{ ) } +const calcResizerHeight = ( + isPlaceholder: boolean, + orderedColumns: Column[], + column: HeaderGroup, + columns: HeaderGroup[] +) => { + const nbUpperLevels = isPlaceholder + ? 0 + : countUpperLevels(orderedColumns, column, columns, 0) + return 100 + nbUpperLevels * 92 + '%' +} + +const getHeaderPropsArgs = ( + column: HeaderGroup, + sortEnabled: boolean, + sortOrder: SortOrder +) => { + return { + className: cx( + styles.th, + column.placeholderOf ? styles.placeholderHeaderCell : styles.headerCell, + { + [styles.paramHeaderCell]: column.group === ColumnType.PARAMS, + [styles.metricHeaderCell]: column.group === ColumnType.METRICS, + [styles.depHeaderCell]: column.group === ColumnType.DEPS, + [styles.firstLevelHeader]: isFirstLevelHeader(column.id), + [styles.leafHeader]: column.headers === undefined, + [styles.menuEnabled]: sortEnabled, + [styles.sortingHeaderCellAsc]: + sortOrder === SortOrder.ASCENDING && !column.parent?.placeholderOf, + [styles.sortingHeaderCellDesc]: + sortOrder === SortOrder.DESCENDING && !column.placeholderOf + } + ) + } +} + +const getIconMenuItems = ( + sortEnabled: boolean, + sortOrder: SortOrder, + hasFilter: boolean +) => [ + { + hidden: !sortEnabled || sortOrder === SortOrder.NONE, + icon: + (sortOrder === SortOrder.DESCENDING && AllIcons.DOWN_ARROW) || + AllIcons.UP_ARROW, + tooltip: 'Table Sorted By' + }, + { + hidden: !hasFilter, + icon: AllIcons.LINES, + tooltip: 'Table Filtered By' + } +] + const TableHeaderCell: React.FC<{ column: HeaderGroup columns: HeaderGroup[] + hasFilter: boolean orderedColumns: Column[] sortOrder: SortOrder - menuDisabled: boolean - menuContent: React.ReactNode + sortEnabled: boolean + menuDisabled?: boolean + menuContent?: React.ReactNode onDragOver: OnDragOver onDragStart: OnDragStart onDrop: OnDrop @@ -74,56 +144,51 @@ const TableHeaderCell: React.FC<{ column, columns, orderedColumns, + hasFilter, sortOrder, + sortEnabled, menuContent, menuDisabled, onDragOver, onDragStart, onDrop }) => { - const isPlaceholder = !!column.placeholderOf - const canResize = column.canResize && !isPlaceholder - const nbUpperLevels = isPlaceholder - ? 0 - : countUpperLevels(orderedColumns, column, columns, 0) - const resizerHeight = 100 + nbUpperLevels * 92 + '%' - - const sortingClasses = () => ({ - [styles.sortingHeaderCellAsc]: - sortOrder === 'ascending' && !column.parent?.placeholderOf, - [styles.sortingHeaderCellDesc]: - sortOrder === 'descending' && !column.placeholderOf - }) + const [menuSuppressed, setMenuSuppressed] = React.useState(false) - const headerPropsArgs = () => { - return { - className: cx( - styles.th, - column.placeholderOf ? styles.placeholderHeaderCell : styles.headerCell, - { - [styles.paramHeaderCell]: column.group === ColumnType.PARAMS, - [styles.metricHeaderCell]: column.group === ColumnType.METRICS, - [styles.depHeaderCell]: column.group === ColumnType.DEPS, - [styles.firstLevelHeader]: isFirstLevelHeader(column.id), - [styles.leafHeader]: column.headers === undefined, - ...sortingClasses() - } - ) - } - } const isDraggable = !column.placeholderOf && !['id', 'timestamp'].includes(column.id) + const isPlaceholder = !!column.placeholderOf + const canResize = column.canResize && !isPlaceholder + const resizerHeight = calcResizerHeight( + isPlaceholder, + orderedColumns, + column, + columns + ) + return ( - +
+
+ +
setMenuSuppressed(true)} + onMouseLeave={() => setMenuSuppressed(false)} className={styles.columnResizer} style={{ height: resizerHeight }} /> @@ -144,6 +211,7 @@ interface TableHeaderProps { column: HeaderGroup columns: HeaderGroup[] sorts: SortDefinition[] + filters: string[] orderedColumns: Column[] onDragOver: OnDragOver onDragStart: OnDragStart @@ -153,6 +221,7 @@ interface TableHeaderProps { export const TableHeader: React.FC = ({ column, columns, + filters, sorts, orderedColumns, onDragOver, @@ -161,44 +230,14 @@ export const TableHeader: React.FC = ({ }) => { const baseColumn = column.placeholderOf || column const sort = sorts.find(sort => sort.path === baseColumn.id) + + const hasFilter = !!(column.id && filters.includes(column.id)) const isSortable = !column.placeholderOf && !['id', 'timestamp'].includes(column.id) && !column.columns - const sortOrder: SortOrder = (() => { - const possibleOrders = { - false: SortOrder.ASCENDING, - true: SortOrder.DESCENDING, - undefined: SortOrder.NONE - } - - return possibleOrders[`${sort?.descending}`] - })() - - const removeColumnSort = () => { - sendMessage({ - payload: column.id, - type: MessageFromWebviewType.REMOVE_COLUMN_SORT - }) - } - - const setColumnSort = (selectedSort: SortOrder) => { - if (selectedSort === SortOrder.NONE) { - removeColumnSort() - return - } - - const payload: SortDefinition = { - descending: selectedSort === SortOrder.DESCENDING, - path: column.id - } - - sendMessage({ - payload, - type: MessageFromWebviewType.SORT_COLUMN - }) - } + const sortOrder: SortOrder = possibleOrders[`${sort?.descending}`] const contextMenuOptions: MessagesMenuOptionProps[] = React.useMemo(() => { const menuOptions: MessagesMenuOptionProps[] = [ @@ -231,24 +270,53 @@ export const TableHeader: React.FC = ({ columns={columns} orderedColumns={orderedColumns} sortOrder={sortOrder} + sortEnabled={isSortable} + hasFilter={hasFilter} onDragOver={onDragOver} onDragStart={onDragStart} onDrop={onDrop} menuDisabled={!isSortable && column.group !== ColumnType.PARAMS} menuContent={
- {isSortable && ( -
- { - setColumnSort(order) - }} - /> - -
- )} + +
} /> diff --git a/webview/src/experiments/components/table/styles.module.scss b/webview/src/experiments/components/table/styles.module.scss index e96f90c5b5..0943dc2c80 100644 --- a/webview/src/experiments/components/table/styles.module.scss +++ b/webview/src/experiments/components/table/styles.module.scss @@ -27,7 +27,6 @@ $workspace-row-edge-margin: $edge-padding - $cell-padding; %truncateLeftParent { overflow: hidden; text-overflow: ellipsis; - direction: rtl; } %truncateLeftChild { @@ -55,6 +54,29 @@ $workspace-row-edge-margin: $edge-padding - $cell-padding; } } +.iconMenu { + position: absolute; + left: 0; + bottom: 0; + padding-bottom: 3.5px; + + ul[role='menu'] { + background-color: $header-bg-color; + padding-left: 2px; + margin: 0; + border: none; + + button { + width: 13px; + height: 11px; + svg { + fill: currentColor; + transform: scale(0.7); + } + } + } +} + // Concrete selectors @keyframes spin { @@ -260,6 +282,7 @@ $workspace-row-edge-margin: $edge-padding - $cell-padding; span[draggable='true'] { display: block; + cursor: grab; } } @@ -282,7 +305,7 @@ $workspace-row-edge-margin: $edge-padding - $cell-padding; } &:last-child { - font-size: 0.9rem; + font-size: 0.7rem; .headerCell { text-align: right; @@ -408,14 +431,6 @@ $workspace-row-edge-margin: $edge-padding - $cell-padding; border-bottom: none; } } - - .sortingHeaderCellAsc { - border-top: 2px solid $fg-color; - } - - .sortingHeaderCellDesc { - border-bottom: 2px solid $fg-color; - } } .webviewHeader { diff --git a/webview/src/shared/components/contextMenu/ContextMenu.tsx b/webview/src/shared/components/contextMenu/ContextMenu.tsx index b689871477..2a47972c53 100644 --- a/webview/src/shared/components/contextMenu/ContextMenu.tsx +++ b/webview/src/shared/components/contextMenu/ContextMenu.tsx @@ -52,6 +52,7 @@ export const ContextMenu: React.FC = ({ = ({ items }) => { }} >
    - {items.map(item => ( - - ))} + {items + .filter(({ hidden }) => !hidden) + .map(item => ( + + ))}
diff --git a/webview/src/shared/components/iconMenu/IconMenuItem.tsx b/webview/src/shared/components/iconMenu/IconMenuItem.tsx index 2cd163c1d1..42737bc04e 100644 --- a/webview/src/shared/components/iconMenu/IconMenuItem.tsx +++ b/webview/src/shared/components/iconMenu/IconMenuItem.tsx @@ -1,4 +1,5 @@ import React from 'react' +import cx from 'classnames' import { TippyProps } from '@tippyjs/react' import styles from './styles.module.scss' import { Icon, IconValues } from '../Icon' @@ -9,6 +10,7 @@ export interface IconMenuItemProps { onClick?: () => void onClickNode?: React.ReactNode tooltip: string + hidden?: boolean } export interface IconMenuItemAllProps extends IconMenuItemProps { @@ -28,7 +30,7 @@ export const IconMenuItem: React.FC = ({