Skip to content

Commit 6e0ab4b

Browse files
authored
Merge pull request #56 from dlabaj/skeletonTable
feat(skeletonTable): Added skeleton table component.
2 parents d4984bd + 831f8e2 commit 6e0ab4b

8 files changed

Lines changed: 1125 additions & 0 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
# Sidenav top-level section
3+
# should be the same for all markdown files
4+
section: extensions
5+
subsection: Component groups
6+
# Sidenav secondary level section
7+
# should be the same for all markdown files
8+
id: Skeleton table
9+
# Tab (react | react-demos | html | html-demos | design-guidelines | accessibility)
10+
source: react
11+
# If you use typescript, the name of the interface to display props for
12+
# These are found through the sourceProps function provided in patternfly-docs.source.js
13+
propComponents: ['SkeletonTable']
14+
---
15+
import SkeletonTable from '@patternfly/react-component-groups/dist/dynamic/SkeletonTable';
16+
17+
The **skeleton table** component is used to display placeholder "skeletons" within a table as its contents load.
18+
19+
## Examples
20+
21+
### Basic skeleton table
22+
23+
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.
24+
25+
```js file="./SkeletonTableExample.tsx"
26+
27+
```
28+
29+
### Full loading simulation
30+
31+
The following example demonstrates the typical behavior of a skeleton table transitioning to a normal table as the data becomes available.
32+
33+
To simulate this loading process, select the `Reload table` button and wait for the data to populate.
34+
35+
36+
```js file="./SkeletonTableLoadingExample.tsx"
37+
38+
```
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import React from 'react';
2+
import SkeletonTable from '@patternfly/react-core/dist/js/components/Skeleton/SkeletonTable';
3+
4+
export const SkeletonTableExample: React.FC = () => <SkeletonTable rowSize={10} columns={[ 'first', 'second' ]} />
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React from 'react';
2+
import SkeletonTable from '@patternfly/react-core/dist/js/components/Skeleton/SkeletonTable';
3+
import { Table, Tbody, Td, Th, Tr, Thead } from '@patternfly/react-table';
4+
import { Button, Stack, StackItem } from '@patternfly/react-core';
5+
6+
interface Repository {
7+
name: string;
8+
branches: string | null;
9+
prs: string | null;
10+
workspaces: string;
11+
lastCommit: string;
12+
}
13+
14+
export const SkeletonTableExample: React.FC = () => {
15+
const [ isLoaded, setIsLoaded ] = React.useState(false);
16+
17+
const simulatedAsyncCall = new Promise<boolean>((resolve) => {
18+
setTimeout(() => {
19+
resolve(true);
20+
}, 5000);
21+
});
22+
23+
const loadData = async () => {
24+
const result = await simulatedAsyncCall;
25+
setIsLoaded(result);
26+
};
27+
28+
const repositories: Repository[] = [
29+
{ name: 'one', branches: 'two', prs: 'three', workspaces: 'four', lastCommit: 'five' },
30+
{ name: 'one - 2', branches: null, prs: null, workspaces: 'four - 2', lastCommit: 'five - 2' },
31+
{ name: 'one - 3', branches: 'two - 3', prs: 'three - 3', workspaces: 'four - 3', lastCommit: 'five - 3' }
32+
];
33+
34+
const columnNames = {
35+
name: 'Repositories',
36+
branches: 'Branches',
37+
prs: 'Pull requests',
38+
workspaces: 'Workspaces',
39+
lastCommit: 'Last commit'
40+
};
41+
42+
let table: React.ReactNode;
43+
44+
if (!isLoaded) {
45+
table = (
46+
<SkeletonTable
47+
rows={3}
48+
columns={[
49+
columnNames.name,
50+
columnNames.branches,
51+
columnNames.prs,
52+
columnNames.workspaces,
53+
columnNames.lastCommit
54+
]}
55+
/>
56+
);
57+
} else {
58+
table = (
59+
<Table>
60+
<Thead>
61+
<Tr>
62+
<Th>{columnNames.name}</Th>
63+
<Th>{columnNames.branches}</Th>
64+
<Th>{columnNames.prs}</Th>
65+
<Th>{columnNames.workspaces}</Th>
66+
<Th>{columnNames.lastCommit}</Th>
67+
</Tr>
68+
</Thead>
69+
<Tbody>
70+
{repositories.map((repo) => (
71+
<Tr key={repo.name}>
72+
<Td dataLabel={columnNames.name}>{repo.name}</Td>
73+
<Td dataLabel={columnNames.branches}>{repo.branches}</Td>
74+
<Td dataLabel={columnNames.prs}>{repo.prs}</Td>
75+
<Td dataLabel={columnNames.workspaces}>{repo.workspaces}</Td>
76+
<Td dataLabel={columnNames.lastCommit}>{repo.lastCommit}</Td>
77+
</Tr>
78+
))}
79+
</Tbody>
80+
</Table>
81+
);
82+
}
83+
84+
return (
85+
<>
86+
<Stack hasGutter>
87+
<StackItem>{table}</StackItem>
88+
<StackItem>
89+
<Button
90+
onClick={() => {
91+
setIsLoaded(false);
92+
loadData();
93+
}}
94+
>
95+
Reload table
96+
</Button>
97+
</StackItem>
98+
</Stack>
99+
</>
100+
);
101+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import React from 'react';
2+
import { render } from '@testing-library/react';
3+
import SkeletonTable from './SkeletonTable';
4+
5+
describe('SkeletonTable component', () => {
6+
it('should render correctly', () => {
7+
expect(render(<SkeletonTable columns={[ 'first', 'second' ]}/>)).toMatchSnapshot();
8+
});
9+
10+
it('should render correctly with rows', () => {
11+
expect(render(<SkeletonTable columns={[ 'first', 'second' ]} rows={5} />)).toMatchSnapshot();
12+
});
13+
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React, { ReactNode } from 'react';
2+
import { Caption, Table, TableProps, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
3+
import { Skeleton } from '@patternfly/react-core';
4+
5+
export type SkeletonTableProps = TableProps & {
6+
/** Indicates the table variant */
7+
variant?: TableVariant;
8+
/** The number of rows the skeleton table should contain */
9+
rows?: number;
10+
/** Any captions that should be added to the table */
11+
caption?: ReactNode;
12+
} & (
13+
| {
14+
columns: ReactNode[];
15+
}
16+
| {
17+
numberOfColumns: number;
18+
}
19+
);
20+
21+
22+
function hasCustomColumns(props: Record<string, any>): props is SkeletonTableProps & {
23+
columns: ReactNode[];
24+
} {
25+
return Array.isArray(props.columns);
26+
}
27+
28+
const SkeletonTable: React.FunctionComponent<SkeletonTableProps> = (props: SkeletonTableProps) => {
29+
const { variant, rows = 5, caption } = props;
30+
const rowCells = hasCustomColumns(props) ? props.columns.length : props.numberOfColumns;
31+
const rowArray = [ ...new Array(rowCells) ];
32+
const bodyRows = [ ...new Array(rows) ].map((_, index) => (
33+
<Tr key={index}>
34+
{rowArray.map((_, index) => (
35+
<Td key={index}>
36+
<Skeleton />
37+
</Td>
38+
))}
39+
</Tr>
40+
));
41+
42+
return (
43+
<Table aria-label="Loading" variant={variant}>
44+
{caption && <Caption>{caption}</Caption>}
45+
<Thead>
46+
<Tr>
47+
{hasCustomColumns(props)
48+
? props.columns.map((c, index) => <Th key={index}>{c}</Th>)
49+
: rowArray.map((_, index) => (
50+
<Th key={index}>
51+
<Skeleton />
52+
</Th>
53+
))}
54+
</Tr>
55+
</Thead>
56+
<Tbody>{bodyRows}</Tbody>
57+
</Table>
58+
);
59+
};
60+
61+
export default SkeletonTable;

0 commit comments

Comments
 (0)