Skip to content

Commit

Permalink
Merge pull request #56 from dlabaj/skeletonTable
Browse files Browse the repository at this point in the history
feat(skeletonTable): Added skeleton table component.
  • Loading branch information
fhlavac authored Oct 24, 2023
2 parents d4984bd + 831f8e2 commit 6e0ab4b
Show file tree
Hide file tree
Showing 8 changed files with 1,125 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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"

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import React from 'react';
import SkeletonTable from '@patternfly/react-core/dist/js/components/Skeleton/SkeletonTable';

export const SkeletonTableExample: React.FC = () => <SkeletonTable rowSize={10} columns={[ 'first', 'second' ]} />
Original file line number Diff line number Diff line change
@@ -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<boolean>((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 = (
<SkeletonTable
rows={3}
columns={[
columnNames.name,
columnNames.branches,
columnNames.prs,
columnNames.workspaces,
columnNames.lastCommit
]}
/>
);
} else {
table = (
<Table>
<Thead>
<Tr>
<Th>{columnNames.name}</Th>
<Th>{columnNames.branches}</Th>
<Th>{columnNames.prs}</Th>
<Th>{columnNames.workspaces}</Th>
<Th>{columnNames.lastCommit}</Th>
</Tr>
</Thead>
<Tbody>
{repositories.map((repo) => (
<Tr key={repo.name}>
<Td dataLabel={columnNames.name}>{repo.name}</Td>
<Td dataLabel={columnNames.branches}>{repo.branches}</Td>
<Td dataLabel={columnNames.prs}>{repo.prs}</Td>
<Td dataLabel={columnNames.workspaces}>{repo.workspaces}</Td>
<Td dataLabel={columnNames.lastCommit}>{repo.lastCommit}</Td>
</Tr>
))}
</Tbody>
</Table>
);
}

return (
<>
<Stack hasGutter>
<StackItem>{table}</StackItem>
<StackItem>
<Button
onClick={() => {
setIsLoaded(false);
loadData();
}}
>
Reload table
</Button>
</StackItem>
</Stack>
</>
);
};
13 changes: 13 additions & 0 deletions packages/module/src/SkeletonTable/SkeletonTable.test.tsx
Original file line number Diff line number Diff line change
@@ -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(<SkeletonTable columns={[ 'first', 'second' ]}/>)).toMatchSnapshot();
});

it('should render correctly with rows', () => {
expect(render(<SkeletonTable columns={[ 'first', 'second' ]} rows={5} />)).toMatchSnapshot();
});
});
61 changes: 61 additions & 0 deletions packages/module/src/SkeletonTable/SkeletonTable.tsx
Original file line number Diff line number Diff line change
@@ -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<string, any>): props is SkeletonTableProps & {

Check warning on line 22 in packages/module/src/SkeletonTable/SkeletonTable.tsx

View workflow job for this annotation

GitHub Actions / call-build-lint-test-workflow / lint

Unexpected any. Specify a different type
columns: ReactNode[];
} {
return Array.isArray(props.columns);
}

const SkeletonTable: React.FunctionComponent<SkeletonTableProps> = (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) => (
<Tr key={index}>
{rowArray.map((_, index) => (
<Td key={index}>
<Skeleton />
</Td>
))}
</Tr>
));

return (
<Table aria-label="Loading" variant={variant}>
{caption && <Caption>{caption}</Caption>}
<Thead>
<Tr>
{hasCustomColumns(props)
? props.columns.map((c, index) => <Th key={index}>{c}</Th>)
: rowArray.map((_, index) => (
<Th key={index}>
<Skeleton />
</Th>
))}
</Tr>
</Thead>
<Tbody>{bodyRows}</Tbody>
</Table>
);
};

export default SkeletonTable;
Loading

0 comments on commit 6e0ab4b

Please sign in to comment.