core-components: remove BaseTable refactoring Table logic
Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com>
This commit is contained in:
@@ -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<T extends object = {}>(
|
||||
props: TableProps<T> & {
|
||||
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<string>;
|
||||
|
||||
// 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
|
||||
```
|
||||
|
||||
@@ -48,6 +48,7 @@ import React, {
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
@@ -154,12 +155,7 @@ const useFilterStyles = makeStyles<BackstageTheme>(
|
||||
|
||||
export type TableClassKey = 'root';
|
||||
|
||||
/**
|
||||
* Style classes for the `Table` component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const tableStyles = makeStyles<BackstageTheme>(
|
||||
const tableStyles = makeStyles<BackstageTheme>(
|
||||
() => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
@@ -308,9 +304,11 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
|
||||
const {
|
||||
data,
|
||||
columns,
|
||||
emptyContent,
|
||||
options,
|
||||
title,
|
||||
subtitle,
|
||||
localization,
|
||||
filters,
|
||||
initialState,
|
||||
onStateChange,
|
||||
@@ -320,6 +318,8 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
|
||||
} = props;
|
||||
const tableClasses = tableStyles();
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const calculatedInitialState = { ...defaultInitialState, ...initialState };
|
||||
|
||||
const [filtersOpen, setFiltersOpen] = useState(
|
||||
@@ -329,8 +329,7 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
|
||||
() => 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<T extends object = {}>(props: TableProps<T>) {
|
||||
[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<T extends object = {}>(props: TableProps<T>) {
|
||||
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<any> => {
|
||||
const distinctValues = new Set<any>();
|
||||
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<SelectProps, 'onChange'> => {
|
||||
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<T extends object = {}>(props: TableProps<T>) {
|
||||
[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 (
|
||||
<Box className={tableClasses.root}>
|
||||
{filtersOpen && data && typeof data !== 'function' && filters?.length && (
|
||||
<Filters
|
||||
filters={constructFilters(filters, data as any[])}
|
||||
filters={constructFilters(filters, data as any[], columns)}
|
||||
selectedFilters={selectedFilters}
|
||||
onChangeFilters={setSelectedFilters}
|
||||
/>
|
||||
)}
|
||||
<BaseTable<T>
|
||||
<MTable<T>
|
||||
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={
|
||||
<>
|
||||
<Typography variant="h5" component="h2">
|
||||
{title}
|
||||
</Typography>
|
||||
{subtitle && (
|
||||
<Typography color="textSecondary" variant="body1">
|
||||
{subtitle}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
data={tableData}
|
||||
style={{ width: '100%' }}
|
||||
localization={{
|
||||
toolbar: { searchPlaceholder: 'Filter', searchTooltip: 'Filter' },
|
||||
...localization,
|
||||
}}
|
||||
{...restProps}
|
||||
/>
|
||||
</Box>
|
||||
@@ -491,83 +460,83 @@ export function Table<T extends object = {}>(props: TableProps<T>) {
|
||||
|
||||
Table.icons = Object.freeze(tableIcons);
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function BaseTable<T extends object = {}>(
|
||||
props: TableProps<T> & {
|
||||
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 <TableLoadingBody colSpan={columnCount} />;
|
||||
}
|
||||
|
||||
const hasNoRows = typeof data !== 'function' && data.length === 0;
|
||||
if (emptyContent && hasNoRows) {
|
||||
return (
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colSpan={columnCount}>{emptyContent}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
);
|
||||
}
|
||||
|
||||
const columnCount = columns.length;
|
||||
const Body = useCallback(
|
||||
(bodyProps: any /* no type for this in material-table */) => {
|
||||
if (loading) {
|
||||
return <TableLoadingBody colSpan={columnCount} />;
|
||||
}
|
||||
|
||||
if (emptyContent && hasNoRows) {
|
||||
return (
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colSpan={columnCount}>{emptyContent}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
);
|
||||
}
|
||||
|
||||
return <MTableBody {...bodyProps} />;
|
||||
},
|
||||
[hasNoRows, emptyContent, columnCount, loading],
|
||||
);
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<MTable<T>
|
||||
components={{
|
||||
Header: StyledMTableHeader,
|
||||
Body,
|
||||
...components,
|
||||
}}
|
||||
options={{ headerStyle: { textTransform: 'uppercase' }, ...options }}
|
||||
columns={convertColumns(columns, theme)}
|
||||
icons={tableIcons}
|
||||
title={
|
||||
<>
|
||||
<Typography variant="h5" component="h2">
|
||||
{title}
|
||||
</Typography>
|
||||
{subtitle && (
|
||||
<Typography color="textSecondary" variant="body1">
|
||||
{subtitle}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
data={data}
|
||||
style={{ width: '100%' }}
|
||||
localization={{
|
||||
...localization,
|
||||
toolbar: { searchPlaceholder: 'Filter', searchTooltip: 'Filter' },
|
||||
}}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
return <MTableBody {...bodyProps} />;
|
||||
};
|
||||
}
|
||||
|
||||
function constructFilters<T extends object>(
|
||||
filterConfig: TableFilter[],
|
||||
dataValue: any[] | undefined,
|
||||
columns: TableColumn<T>[],
|
||||
): Filter[] {
|
||||
const extractDistinctValues = (field: string | keyof T): Set<any> => {
|
||||
const distinctValues = new Set<any>();
|
||||
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<SelectProps, 'onChange'> => {
|
||||
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),
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<Box className={tableClasses.root}>
|
||||
<BaseTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
options={{
|
||||
paginationPosition: 'both',
|
||||
pageSizeOptions: [],
|
||||
showFirstLastPageButtons: false,
|
||||
pageSize: Number.MAX_SAFE_INTEGER,
|
||||
emptyRowsWhenPaging: false,
|
||||
}}
|
||||
onPageChange={page => {
|
||||
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: '' } }}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
<Table
|
||||
columns={columns}
|
||||
data={data}
|
||||
options={{
|
||||
paginationPosition: 'both',
|
||||
pageSizeOptions: [],
|
||||
showFirstLastPageButtons: false,
|
||||
pageSize: Number.MAX_SAFE_INTEGER,
|
||||
emptyRowsWhenPaging: false,
|
||||
}}
|
||||
onPageChange={page => {
|
||||
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: '' } }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user