diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index ed7fd0c765..0361b81f0d 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -13,7 +13,6 @@ import { BackstageUserIdentity } from '@backstage/core-plugin-api'; import { BottomNavigationActionProps } from '@material-ui/core/BottomNavigationAction'; import { ButtonProps as ButtonProps_2 } from '@material-ui/core/Button'; import { CardHeaderProps } from '@material-ui/core/CardHeader'; -import { ClassNameMap } from '@material-ui/styles'; import { Column } from '@material-table/core'; import { ComponentClass } from 'react'; import { ComponentProps } from 'react'; @@ -105,15 +104,6 @@ export type BackstageOverrides = Overrides & { >; }; -// @public (undocumented) -export function BaseTable( - props: TableProps & { - loading?: boolean; - emptyContent?: ReactNode; - subtitle?: string; - }, -): React_2.JSX.Element; - // @public (undocumented) export type BoldHeaderClassKey = 'root' | 'title' | 'subheader'; @@ -1448,9 +1438,6 @@ export type TableState = { filters?: SelectedFilters; }; -// @public -export const tableStyles: (props?: any) => ClassNameMap; - // Warning: (ae-missing-release-tag) "TableToolbarClassKey" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1551,6 +1538,6 @@ export type WarningPanelClassKey = // src/components/DependencyGraph/types.d.ts:22:9 - (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/core-components" does not have an export "DependencyNode" // src/components/DependencyGraph/types.d.ts:26:9 - (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/core-components" does not have an export "DependencyNode" // src/components/TabbedLayout/RoutedTabs.d.ts:9:5 - (ae-forgotten-export) The symbol "SubRoute_2" needs to be exported by the entry point index.d.ts -// src/components/Table/Table.d.ts:26:5 - (ae-forgotten-export) The symbol "SelectedFilters" needs to be exported by the entry point index.d.ts +// src/components/Table/Table.d.ts:20:5 - (ae-forgotten-export) The symbol "SelectedFilters" needs to be exported by the entry point index.d.ts // src/layout/ErrorBoundary/ErrorBoundary.d.ts:8:5 - (ae-forgotten-export) The symbol "SlackChannel" needs to be exported by the entry point index.d.ts ``` diff --git a/packages/core-components/src/components/Table/Table.tsx b/packages/core-components/src/components/Table/Table.tsx index 09957e2e03..c49c953264 100644 --- a/packages/core-components/src/components/Table/Table.tsx +++ b/packages/core-components/src/components/Table/Table.tsx @@ -48,6 +48,7 @@ import React, { ReactNode, useCallback, useEffect, + useMemo, useState, } from 'react'; @@ -154,12 +155,7 @@ const useFilterStyles = makeStyles( export type TableClassKey = 'root'; -/** - * Style classes for the `Table` component. - * - * @public - */ -export const tableStyles = makeStyles( +const tableStyles = makeStyles( () => ({ root: { display: 'flex', @@ -308,9 +304,11 @@ export function Table(props: TableProps) { const { data, columns, + emptyContent, options, title, subtitle, + localization, filters, initialState, onStateChange, @@ -320,6 +318,8 @@ export function Table(props: TableProps) { } = props; const tableClasses = tableStyles(); + const theme = useTheme(); + const calculatedInitialState = { ...defaultInitialState, ...initialState }; const [filtersOpen, setFiltersOpen] = useState( @@ -329,8 +329,7 @@ export function Table(props: TableProps) { () => setFiltersOpen(v => !v), [setFiltersOpen], ); - const [selectedFiltersLength, setSelectedFiltersLength] = useState(0); - const [tableData, setTableData] = useState(data as any[]); + const [selectedFilters, setSelectedFilters] = useState( calculatedInitialState.filters, ); @@ -358,13 +357,9 @@ export function Table(props: TableProps) { [columns], ); - useEffect(() => { - if (typeof data === 'function') { - return; - } - if (!selectedFilters) { - setTableData(data as any[]); - return; + const tableData = useMemo(() => { + if (typeof data === 'function' || !selectedFilters) { + return data; } const selectedFiltersArray = Object.values(selectedFilters); @@ -390,62 +385,12 @@ export function Table(props: TableProps) { return fieldValue === filterValue; }), ); - setTableData(newData); - } else { - setTableData(data as any[]); + return newData; } - setSelectedFiltersLength(selectedFiltersArray.flat().length); + return data; }, [data, selectedFilters, getFieldByTitle]); - const constructFilters = ( - filterConfig: TableFilter[], - dataValue: any[] | undefined, - ): Filter[] => { - const extractDistinctValues = (field: string | keyof T): Set => { - const distinctValues = new Set(); - const addValue = (value: any) => { - if (value !== undefined && value !== null) { - distinctValues.add(value); - } - }; - - if (dataValue) { - dataValue.forEach(el => { - const value = extractValueByField( - el, - getFieldByTitle(field) as string, - ); - - if (Array.isArray(value)) { - (value as []).forEach(addValue); - } else { - addValue(value); - } - }); - } - - return distinctValues; - }; - - const constructSelect = ( - filter: TableFilter, - ): Without => { - return { - placeholder: 'All results', - label: filter.column, - multiple: filter.type === 'multiple-select', - items: [...extractDistinctValues(filter.column)].sort().map(value => ({ - label: value, - value, - })), - }; - }; - - return filterConfig.map(filter => ({ - type: filter.type, - element: constructSelect(filter), - })); - }; + const selectedFiltersLength = Object.values(selectedFilters).flat().length; const hasFilters = !!filters?.length; const Toolbar = useCallback( @@ -463,26 +408,50 @@ export function Table(props: TableProps) { [toggleFilters, hasFilters, selectedFiltersLength, setSearch], ); + const hasNoRows = typeof data !== 'function' && data.length === 0; + const columnCount = columns.length; + const Body = useMemo( + () => makeBody({ hasNoRows, emptyContent, columnCount, loading }), + [hasNoRows, emptyContent, columnCount, loading], + ); + return ( {filtersOpen && data && typeof data !== 'function' && filters?.length && ( )} - + components={{ + Header: StyledMTableHeader, + Body, Toolbar, ...components, }} - options={options} - columns={columns} - title={title} - subtitle={subtitle} - loading={loading} - data={typeof data === 'function' ? data : tableData} + options={{ headerStyle: { textTransform: 'uppercase' }, ...options }} + columns={convertColumns(columns, theme)} + icons={tableIcons} + title={ + <> + + {title} + + {subtitle && ( + + {subtitle} + + )} + + } + data={tableData} + style={{ width: '100%' }} + localization={{ + toolbar: { searchPlaceholder: 'Filter', searchTooltip: 'Filter' }, + ...localization, + }} {...restProps} /> @@ -491,83 +460,83 @@ export function Table(props: TableProps) { Table.icons = Object.freeze(tableIcons); -/** - * @public - */ -export function BaseTable( - props: TableProps & { - loading?: boolean; - emptyContent?: ReactNode; - subtitle?: string; - }, -) { - const { - columns, - components, - data, - emptyContent, - loading, - options, - title, - subtitle, - localization, - ...restProps - } = props; +function makeBody({ + columnCount, + emptyContent, + hasNoRows, + loading, +}: { + hasNoRows: boolean; + emptyContent: ReactNode; + columnCount: number; + loading?: boolean; +}) { + return (bodyProps: any /* no type for this in material-table */) => { + if (loading) { + return ; + } - const hasNoRows = typeof data !== 'function' && data.length === 0; + if (emptyContent && hasNoRows) { + return ( + + + {emptyContent} + + + ); + } - const columnCount = columns.length; - const Body = useCallback( - (bodyProps: any /* no type for this in material-table */) => { - if (loading) { - return ; - } - - if (emptyContent && hasNoRows) { - return ( - - - {emptyContent} - - - ); - } - - return ; - }, - [hasNoRows, emptyContent, columnCount, loading], - ); - const theme = useTheme(); - - return ( - - components={{ - Header: StyledMTableHeader, - Body, - ...components, - }} - options={{ headerStyle: { textTransform: 'uppercase' }, ...options }} - columns={convertColumns(columns, theme)} - icons={tableIcons} - title={ - <> - - {title} - - {subtitle && ( - - {subtitle} - - )} - - } - data={data} - style={{ width: '100%' }} - localization={{ - ...localization, - toolbar: { searchPlaceholder: 'Filter', searchTooltip: 'Filter' }, - }} - {...restProps} - /> - ); + return ; + }; +} + +function constructFilters( + filterConfig: TableFilter[], + dataValue: any[] | undefined, + columns: TableColumn[], +): Filter[] { + const extractDistinctValues = (field: string | keyof T): Set => { + const distinctValues = new Set(); + const addValue = (value: any) => { + if (value !== undefined && value !== null) { + distinctValues.add(value); + } + }; + + if (dataValue) { + dataValue.forEach(el => { + const value = extractValueByField( + el, + columns.find(c => c.title === field)?.field as string, + ); + + if (Array.isArray(value)) { + (value as []).forEach(addValue); + } else { + addValue(value); + } + }); + } + + return distinctValues; + }; + + const constructSelect = ( + filter: TableFilter, + ): Without => { + return { + placeholder: 'All results', + label: filter.column, + multiple: filter.type === 'multiple-select', + items: [...extractDistinctValues(filter.column)].sort().map(value => ({ + label: value, + value, + })), + }; + }; + + return filterConfig.map(filter => ({ + type: filter.type, + element: constructSelect(filter), + })); } diff --git a/packages/core-components/src/components/Table/index.ts b/packages/core-components/src/components/Table/index.ts index 2ddfb52ab3..00e1ae9de5 100644 --- a/packages/core-components/src/components/Table/index.ts +++ b/packages/core-components/src/components/Table/index.ts @@ -17,7 +17,7 @@ export type { TableFiltersClassKey } from './Filters'; export { SubvalueCell } from './SubvalueCell'; export type { SubvalueCellClassKey } from './SubvalueCell'; -export { Table, BaseTable, tableStyles } from './Table'; +export { Table } from './Table'; export type { TableColumn, TableFilter, diff --git a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx index b592ec01d2..771878b65c 100644 --- a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.tsx @@ -15,9 +15,8 @@ */ import React from 'react'; -import Box from '@material-ui/core/Box'; -import { BaseTable, TableProps, tableStyles } from '@backstage/core-components'; +import { Table, TableProps } from '@backstage/core-components'; import { CatalogTableRow } from './types'; type PaginatedCatalogTableProps = { @@ -30,35 +29,30 @@ type PaginatedCatalogTableProps = { */ export function PaginatedCatalogTable(props: PaginatedCatalogTableProps) { const { columns, data, next, prev } = props; - const tableClasses = tableStyles(); return ( - <> - - { - if (page > 0) { - next?.(); - } else { - prev?.(); - } - }} - /* this will enable the prev button accordingly */ - page={prev ? 1 : 0} - /* this will enable the next button accordingly */ - totalCount={next ? Number.MAX_VALUE : Number.MAX_SAFE_INTEGER} - localization={{ pagination: { labelDisplayedRows: '' } }} - /> - - + { + if (page > 0) { + next?.(); + } else { + prev?.(); + } + }} + /* this will enable the prev button accordingly */ + page={prev ? 1 : 0} + /* this will enable the next button accordingly */ + totalCount={next ? Number.MAX_VALUE : Number.MAX_SAFE_INTEGER} + localization={{ pagination: { labelDisplayedRows: '' } }} + /> ); }