diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Skeleton/Skeleton.md b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Skeleton/Skeleton.md new file mode 100644 index 00000000..1286c024 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Skeleton/Skeleton.md @@ -0,0 +1,38 @@ +--- +# Sidenav top-level section +# should be the same for all markdown files +section: extensions +subsection: Component groups +# Sidenav secondary level section +# should be the same for all markdown files +id: Skeleton table +# Tab (react | react-demos | html | html-demos | design-guidelines | accessibility) +source: react +# If you use typescript, the name of the interface to display props for +# These are found through the sourceProps function provided in patternfly-docs.source.js +propComponents: ['SkeletonTable'] +--- +import SkeletonTable from '@patternfly/react-component-groups/dist/dynamic/SkeletonTable'; + +The **skeleton table** component is used to display placeholder "skeletons" within a table as its contents load. + +## Examples + +### Basic skeleton table + +To indicate that a table's cells are still loading, a basic skeleton table uses the [skeleton](https://www.patternfly.org/components/skeleton) component to place a placeholder skeleton in each cell. Once the data is loaded, the skeleton table is replaced with a table containing the real data. + +```js file="./SkeletonTableExample.tsx" + +``` + +### Full loading simulation + +The following example demonstrates the typical behavior of a skeleton table transitioning to a normal table as the data becomes available. + +To simulate this loading process, select the `Reload table` button and wait for the data to populate. + + +```js file="./SkeletonTableLoadingExample.tsx" + +``` diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Skeleton/SkeletonTableExample.tsx b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Skeleton/SkeletonTableExample.tsx new file mode 100644 index 00000000..2da39452 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Skeleton/SkeletonTableExample.tsx @@ -0,0 +1,4 @@ +import React from 'react'; +import SkeletonTable from '@patternfly/react-core/dist/js/components/Skeleton/SkeletonTable'; + +export const SkeletonTableExample: React.FC = () => \ No newline at end of file diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Skeleton/SkeletonTableLoadingExample.tsx b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Skeleton/SkeletonTableLoadingExample.tsx new file mode 100644 index 00000000..c12d7b83 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Skeleton/SkeletonTableLoadingExample.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import SkeletonTable from '@patternfly/react-core/dist/js/components/Skeleton/SkeletonTable'; +import { Table, Tbody, Td, Th, Tr, Thead } from '@patternfly/react-table'; +import { Button, Stack, StackItem } from '@patternfly/react-core'; + +interface Repository { + name: string; + branches: string | null; + prs: string | null; + workspaces: string; + lastCommit: string; +} + +export const SkeletonTableExample: React.FC = () => { + const [ isLoaded, setIsLoaded ] = React.useState(false); + + const simulatedAsyncCall = new Promise((resolve) => { + setTimeout(() => { + resolve(true); + }, 5000); + }); + + const loadData = async () => { + const result = await simulatedAsyncCall; + setIsLoaded(result); + }; + + const repositories: Repository[] = [ + { name: 'one', branches: 'two', prs: 'three', workspaces: 'four', lastCommit: 'five' }, + { name: 'one - 2', branches: null, prs: null, workspaces: 'four - 2', lastCommit: 'five - 2' }, + { name: 'one - 3', branches: 'two - 3', prs: 'three - 3', workspaces: 'four - 3', lastCommit: 'five - 3' } + ]; + + const columnNames = { + name: 'Repositories', + branches: 'Branches', + prs: 'Pull requests', + workspaces: 'Workspaces', + lastCommit: 'Last commit' + }; + + let table: React.ReactNode; + + if (!isLoaded) { + table = ( + + ); + } else { + table = ( + + + + + + + + + + + + {repositories.map((repo) => ( + + + + + + + + ))} + +
{columnNames.name}{columnNames.branches}{columnNames.prs}{columnNames.workspaces}{columnNames.lastCommit}
{repo.name}{repo.branches}{repo.prs}{repo.workspaces}{repo.lastCommit}
+ ); + } + + return ( + <> + + {table} + + + + + + ); +}; diff --git a/packages/module/src/SkeletonTable/SkeletonTable.test.tsx b/packages/module/src/SkeletonTable/SkeletonTable.test.tsx new file mode 100644 index 00000000..72a426d5 --- /dev/null +++ b/packages/module/src/SkeletonTable/SkeletonTable.test.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import SkeletonTable from './SkeletonTable'; + +describe('SkeletonTable component', () => { + it('should render correctly', () => { + expect(render()).toMatchSnapshot(); + }); + + it('should render correctly with rows', () => { + expect(render()).toMatchSnapshot(); + }); +}); diff --git a/packages/module/src/SkeletonTable/SkeletonTable.tsx b/packages/module/src/SkeletonTable/SkeletonTable.tsx new file mode 100644 index 00000000..f45a4971 --- /dev/null +++ b/packages/module/src/SkeletonTable/SkeletonTable.tsx @@ -0,0 +1,61 @@ +import React, { ReactNode } from 'react'; +import { Caption, Table, TableProps, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; +import { Skeleton } from '@patternfly/react-core'; + +export type SkeletonTableProps = TableProps & { + /** Indicates the table variant */ + variant?: TableVariant; + /** The number of rows the skeleton table should contain */ + rows?: number; + /** Any captions that should be added to the table */ + caption?: ReactNode; +} & ( + | { + columns: ReactNode[]; + } + | { + numberOfColumns: number; + } + ); + + +function hasCustomColumns(props: Record): props is SkeletonTableProps & { + columns: ReactNode[]; +} { + return Array.isArray(props.columns); +} + +const SkeletonTable: React.FunctionComponent = (props: SkeletonTableProps) => { + const { variant, rows = 5, caption } = props; + const rowCells = hasCustomColumns(props) ? props.columns.length : props.numberOfColumns; + const rowArray = [ ...new Array(rowCells) ]; + const bodyRows = [ ...new Array(rows) ].map((_, index) => ( + + {rowArray.map((_, index) => ( + + + + ))} + + )); + + return ( + + {caption && } + + + {hasCustomColumns(props) + ? props.columns.map((c, index) => ) + : rowArray.map((_, index) => ( + + ))} + + + {bodyRows} +
{caption}
{c} + +
+ ); +}; + +export default SkeletonTable; diff --git a/packages/module/src/SkeletonTable/__snapshots__/SkeletonTable.test.tsx.snap b/packages/module/src/SkeletonTable/__snapshots__/SkeletonTable.test.tsx.snap new file mode 100644 index 00000000..dc85e3f0 --- /dev/null +++ b/packages/module/src/SkeletonTable/__snapshots__/SkeletonTable.test.tsx.snap @@ -0,0 +1,903 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SkeletonTable component should render correctly 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ first + + second +
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ , + "container":
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ first + + second +
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + +exports[`SkeletonTable component should render correctly with rows 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ first + + second +
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ , + "container":
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ first + + second +
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
+ +
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/packages/module/src/SkeletonTable/index.ts b/packages/module/src/SkeletonTable/index.ts new file mode 100644 index 00000000..7e0a19d0 --- /dev/null +++ b/packages/module/src/SkeletonTable/index.ts @@ -0,0 +1,2 @@ +export { default } from './SkeletonTable'; +export { default as SkeletonTable } from './SkeletonTable'; diff --git a/packages/module/src/index.ts b/packages/module/src/index.ts index 437bd712..84a1bf1d 100644 --- a/packages/module/src/index.ts +++ b/packages/module/src/index.ts @@ -27,6 +27,9 @@ export * from './HorizontalNav'; export { default as NotAuthorized } from './NotAuthorized'; export * from './NotAuthorized'; +export { default as SkeletonTable } from './SkeletonTable'; +export * from './SkeletonTable'; + export { default as TagCount } from './TagCount'; export * from './TagCount';