diff --git a/src/components/Popover/Popover.stories.tsx b/src/components/Popover/Popover.stories.tsx
index c97bd3b4..1ded2fe0 100644
--- a/src/components/Popover/Popover.stories.tsx
+++ b/src/components/Popover/Popover.stories.tsx
@@ -3,21 +3,25 @@ import { Button } from '../Button'
import { Icon } from '../Icon'
import { placementOptions } from '../utils'
import { SbTheme } from '../../../.storybook/preview'
+import { styleguide } from 'components/assets/styles'
import { useTheme } from 'react-jss'
import { Meta, Story } from '@storybook/react/types-6-0'
import { Popover, PopoverProps } from './index'
import React, { FC } from 'react'
+const { spacing } = styleguide
+
export default {
argTypes: {
children: { control: { disable: true } },
+ classes: { control: { disable: true } },
content: {
control: { disable: true },
defaultValue: (
- <>
+
View account info
- >
+
)
},
placement: {
diff --git a/src/components/Popover/utils.ts b/src/components/Popover/utils.ts
index 55956d65..c4ce5653 100644
--- a/src/components/Popover/utils.ts
+++ b/src/components/Popover/utils.ts
@@ -2,8 +2,11 @@ import { styleguide } from 'components/assets/styles/styleguide'
import { ColorManipulationTypes, manipulateColor } from '../utils'
import { themedStyles, ThemeType } from 'components/assets/styles/themes'
-const { borderRadius, colors } = styleguide
-const { blacks, whites } = colors
+const {
+ borderRadius,
+ colors: { blacks, whites },
+ fontWeight
+} = styleguide
const { dark, light } = ThemeType
@@ -40,18 +43,19 @@ export const generatePopoverStyles = (themeType: ThemeType) => {
'& > .ant-popover-inner': {
'& > .ant-popover-inner-content': {
color: base.color,
- fontWeight: 300
+ fontWeight: fontWeight.light,
+ padding: 0
},
'& > .ant-popover-title': {
borderBottomColor: base.borderColor,
color: text.title,
- fontWeight: 300
+ fontWeight: fontWeight.light
},
backgroundColor: background,
borderRadius,
boxShadow: 'none',
color: base.color,
- fontWeight: 300
+ fontWeight: fontWeight.light
}
},
filter: `drop-shadow(0px 2px 8px ${accent})`
diff --git a/src/components/Skeleton/index.tsx b/src/components/Skeleton/index.tsx
index 7b5e1cc3..e0061e0d 100644
--- a/src/components/Skeleton/index.tsx
+++ b/src/components/Skeleton/index.tsx
@@ -70,7 +70,7 @@ interface DefaultSkeletonProps {
/**
* Skeleton width. If undefined, skeleton will span the width of parent container. **Note**: width is a required prop for a circle skeleton.
*/
- width?: number
+ width?: number | string
}
interface CircleSkeletonProps
diff --git a/src/components/Table/Table.stories.mdx b/src/components/Table/Table.stories.mdx
index 34b84062..6a981faa 100644
--- a/src/components/Table/Table.stories.mdx
+++ b/src/components/Table/Table.stories.mdx
@@ -8,7 +8,7 @@ The `Table` component creates a table from a provided data source. It allows for
The following examples start from a basic table and don't show all possible types of data that the table can render. If you'd like to view all possible column types and formats in one place, [click here.](?path=/docs/table--mixed#columntype--all-column-types-and-formats)
-
+
## Simple Usage
diff --git a/src/components/Table/Table.stories.tsx b/src/components/Table/Table.stories.tsx
index 384e5844..20df38a4 100644
--- a/src/components/Table/Table.stories.tsx
+++ b/src/components/Table/Table.stories.tsx
@@ -1,14 +1,20 @@
import { action } from '@storybook/addon-actions'
+import { createUseStyles } from 'react-jss'
import { Story } from '@storybook/react/types-6-0'
import tableData4 from './fixtures/4_sample_data'
import { DataId, Table, TableProps } from '.'
import React, { Key, useState } from 'react'
+import { styleguide, themes, ThemeType } from 'components/assets/styles'
import tableData0, { Person } from './fixtures/0_sample_data'
import tableData1, { File } from './fixtures/1_sample_data'
import tableData2, { Client } from './fixtures/2_sample_data'
import tableData3, { Client1 } from './fixtures/3_sample_data'
import tableData5, { Dot } from './fixtures/5_sample_data'
+const { spacing } = styleguide
+
+const { dark, light } = ThemeType
+
const commonArgTypes = {
activeRowKey: {
control: { disable: true }
@@ -47,6 +53,33 @@ const commonArgTypes = {
}
}
+const useStyles = createUseStyles({
+ decorator: {
+ background: themes[light].background.secondary,
+ height: `calc(100vh - ${spacing.m * 2}px)`,
+ padding: spacing.l,
+ width: '100%'
+ },
+ // eslint-disable-next-line sort-keys
+ '@global': {
+ [`.${dark}`]: {
+ '& $decorator': {
+ background: themes[dark].background.secondary
+ }
+ }
+ }
+})
+
+export const Decorator = (TableStory: Story) => {
+ const classes = useStyles()
+
+ return (
+
+ )
+}
+
const DecoratedTableStory = (props: TableProps) => {
const [activeRowKey, setActiveRowKey] = useState('')
@@ -239,3 +272,4 @@ const ColoredDotTemplate: Story> = args => (
)
export const ColoredDot = ColoredDotTemplate.bind({})
ColoredDot.args = tableData5
+ColoredDot.argTypes = commonArgTypes
diff --git a/src/components/Table/TableSkeleton.tsx b/src/components/Table/TableSkeleton.tsx
new file mode 100644
index 00000000..4ff3b5e6
--- /dev/null
+++ b/src/components/Table/TableSkeleton.tsx
@@ -0,0 +1,156 @@
+import { createUseStyles } from 'react-jss'
+import random from 'lodash/random'
+import { Skeleton } from '../Skeleton'
+import { tablePalette } from './styles'
+import times from 'lodash/times'
+import { ColumnFormats, ColumnType, ColumnTypes } from './types'
+import React, { FC } from 'react'
+import { styleguide, ThemeType } from 'components/assets/styles'
+
+const { spacing } = styleguide
+
+const { dark, light } = ThemeType
+
+const useStyles = createUseStyles({
+ skeleton: {
+ maxWidth: 300
+ },
+ table: {
+ borderCollapse: 'separate',
+ borderSpacing: 0,
+ tableLayout: 'fixed',
+ textAlign: 'left',
+ width: '100%'
+ },
+ td: {
+ '&:first-of-type': {
+ paddingLeft: spacing.l
+ },
+ '&:last-of-type': {
+ paddingRight: spacing.l
+ },
+ background: tablePalette[light].td.base.background,
+ borderBottom: `1px solid ${tablePalette[light].td.base.border}`,
+ height: 54,
+ padding: `0 ${spacing.m}px`
+ },
+ th: {
+ '&:first-of-type': {
+ paddingLeft: spacing.l
+ },
+ '&:last-of-type': {
+ paddingRight: spacing.l
+ },
+ background: tablePalette[light].th.base.background,
+ height: 55,
+ padding: `0 ${spacing.m}px`
+ },
+ // eslint-disable-next-line sort-keys
+ '@global': {
+ [`.${dark}`]: {
+ '& $table': {},
+ '& $td': {
+ background: tablePalette[dark].td.base.background,
+ borderBottom: `1px solid ${tablePalette[dark].td.base.border}`
+ },
+ '& $th': {
+ background: tablePalette[dark].th.base.background
+ }
+ }
+ }
+})
+
+// ------------------------------------
+
+const THeaderCellSkeleton = () => {
+ const classes = useStyles()
+
+ return (
+
+
+ |
+ )
+}
+
+// ------------------------------------
+
+const mappedSkeletonProps: Record = {
+ [ColumnFormats.coloredDot]: { circle: true, width: 15 },
+ [ColumnFormats.icon]: { width: 50 },
+ [ColumnFormats.toggle]: { width: 50 },
+ [ColumnTypes.number]: { width: 100 }
+}
+
+interface TDataCellSkeletonProps extends Pick {
+ index: number
+}
+
+const TDataCellSkeleton: FC = ({
+ columns,
+ index
+}: TDataCellSkeletonProps) => {
+ const classes = useStyles()
+
+ const format = columns[index].format
+ const type = columns[index].type
+
+ let props = {}
+
+ if (mappedSkeletonProps[type]) {
+ props = mappedSkeletonProps[type]
+ } else {
+ props =
+ format && mappedSkeletonProps[format]
+ ? mappedSkeletonProps[format]
+ : { width: `${random(25, 100)}%` }
+ }
+
+ return (
+
+
+ |
+ )
+}
+
+// ------------------------------------
+
+interface TableSkeletonProps {
+ columns: ColumnType[]
+ rowCount: number
+}
+
+export const TableSkeleton: FC = ({
+ columns,
+ rowCount
+}: TableSkeletonProps) => {
+ const classes = useStyles()
+
+ return (
+
+
+
+ {times(columns.length, (j: number) => (
+
+ ))}
+
+
+
+ {times(rowCount, (i: number) => (
+
+ {times(columns.length, (j: number) => (
+
+ ))}
+
+ ))}
+
+
+ )
+}
diff --git a/src/components/Table/__tests__/Table.test.tsx b/src/components/Table/__tests__/Table.test.tsx
index c4833d94..37b0bc2b 100644
--- a/src/components/Table/__tests__/Table.test.tsx
+++ b/src/components/Table/__tests__/Table.test.tsx
@@ -1,11 +1,12 @@
import { act } from 'react-dom/test-utils'
import moment from 'moment'
import React from 'react'
+import { TableSkeleton } from '../TableSkeleton'
import { Input as AntDInput, Table as AntDTable } from 'antd'
import mockData, { Data, dateFormat } from '__mocks__/table_mock_data'
import mockData0, { Person } from '../fixtures/0_sample_data'
import mockData1, { File } from '../fixtures/4_sample_data'
-import { mount, ReactWrapper } from 'enzyme'
+import { mount, ReactWrapper, shallow, ShallowWrapper } from 'enzyme'
import { Table, TableProps } from '..'
/* Helper functions */
@@ -129,6 +130,19 @@ describe('Table props', () => {
expect.arrayContaining(mockData0.columns)
)
})
+
+ it('throws an error if passed skeletonRowCount prop is less than 1', () => {
+ expect(() =>
+ shallow(
+
+ columns={mockData0.columns}
+ data={[]}
+ loading
+ skeletonRowCount={0}
+ />
+ )
+ ).toThrow()
+ })
})
describe('Table search and searchProps', () => {
@@ -163,9 +177,9 @@ describe('Table search and searchProps', () => {
it('it renders the search bar to the left by default', async () => {
const table = wrapper.find(Table)
- const searchBar = table.find('input')
+ const searchBarWrapper = table.find('[className*="searchBarWrapper"]')
- const style = window.getComputedStyle(searchBar.getDOMNode())
+ const style = window.getComputedStyle(searchBarWrapper.getDOMNode())
expect(style.alignSelf).toBe('flex-start')
})
@@ -179,9 +193,9 @@ describe('Table search and searchProps', () => {
)
const table = wrapper.find(Table)
- const searchBar = table.find('input')
+ const searchBarWrapper = table.find('[className*="searchBarWrapper"]')
- const style = window.getComputedStyle(searchBar.getDOMNode())
+ const style = window.getComputedStyle(searchBarWrapper.getDOMNode())
expect(style.alignSelf).toBe('flex-end')
})
@@ -233,12 +247,6 @@ describe('Table onRowClick, activeRowKey', () => {
describe('Table pagination', () => {
it('does not show pagination if there are less than 10 rows', () => {
- wrapper = mount(
- createTable({
- ...mockData0
- })
- )
-
expect(wrapper.find(AntDTable).props().pagination).toBe(false)
expect(wrapper.find('.ant-pagination').exists()).toBeFalsy()
@@ -258,3 +266,13 @@ describe('Table pagination', () => {
expect(wrapper.find('.ant-pagination').exists()).toBeTruthy()
})
})
+
+describe('Table loading', () => {
+ it('renders a TableSkeleton if loading prop is passed as true', () => {
+ const wrapper: ShallowWrapper = shallow(
+ columns={mockData0.columns} data={[]} loading />
+ )
+
+ expect(wrapper.find(TableSkeleton)).toHaveLength(1)
+ })
+})
diff --git a/src/components/Table/__tests__/TableSkeleton.test.tsx b/src/components/Table/__tests__/TableSkeleton.test.tsx
new file mode 100644
index 00000000..2da08c77
--- /dev/null
+++ b/src/components/Table/__tests__/TableSkeleton.test.tsx
@@ -0,0 +1,22 @@
+import mockData from '__mocks__/table_mock_data'
+import React from 'react'
+import { TableSkeleton } from '../TableSkeleton'
+import { mount, ReactWrapper } from 'enzyme'
+
+let wrapper: ReactWrapper
+
+beforeEach(() => {
+ wrapper = mount()
+})
+
+describe('TableSkeleton', () => {
+ it('renders', () => {
+ expect(wrapper.find(TableSkeleton)).toHaveLength(1)
+ })
+
+ it('renders correct number of skeleton Table rows', () => {
+ const tBody = wrapper.find(TableSkeleton).find('tbody')
+
+ expect(tBody.find('tr')).toHaveLength(5)
+ })
+})
diff --git a/src/components/Table/index.tsx b/src/components/Table/index.tsx
index eee4fca2..647564f6 100644
--- a/src/components/Table/index.tsx
+++ b/src/components/Table/index.tsx
@@ -7,6 +7,7 @@ import debounce from 'lodash/debounce'
import Fuse from 'fuse.js'
import { getDataTestAttributeProp } from '../utils'
import { Input } from '../Input'
+import { TableSkeleton } from './TableSkeleton'
import { useStyles } from './styles'
import { ColumnType, TableData } from './types'
import { mapData, mapFilterKeys, processColumns, processData } from './utils'
@@ -50,6 +51,10 @@ export interface TableProps extends CommonComponentProps {
* Array of data objects
*/
data: TableData[]
+ /**
+ * Whether or not to show skeleton loader
+ */
+ loading?: boolean
/**
* Optional callback that runs when a table row is clicked
*/
@@ -58,6 +63,10 @@ export interface TableProps extends CommonComponentProps {
* Optional prop to enable/disable table search
*/
search?: boolean
+ /**
+ * Number of skeleton table rows shown if loading is set to true
+ */
+ skeletonRowCount?: number
/**
* Optional props for search input
*/
@@ -74,8 +83,10 @@ export const Table = ({
columns,
data,
dataTag,
+ loading = false,
onRowClick,
search = true,
+ skeletonRowCount = 5,
searchProps = {} as SearchProps
}: TableProps) => {
const [searchTerm, setSearchTerm] = useState('')
@@ -156,25 +167,34 @@ export const Table = ({
})
}
+ if (skeletonRowCount < 1)
+ throw new Error('skeletonRowCount must be a positive integer')
+
return (
{search && (
-
+
+
+ )}
+ {loading ? (
+
+ ) : (
+
)}
-
)
}
diff --git a/src/components/Table/styles.ts b/src/components/Table/styles.ts
index a4c1c401..b38246c5 100644
--- a/src/components/Table/styles.ts
+++ b/src/components/Table/styles.ts
@@ -60,7 +60,7 @@ export const generatePaginationStyles = (themeType: ThemeType) => {
}
}
-const tablePalette = {
+export const tablePalette = {
[dark]: {
arrow: {
active: blacks['lighten-60'],
@@ -154,6 +154,18 @@ const generateTableStyles = (themeType: ThemeType) => {
},
cursor: 'default'
},
+ '&.ant-table-empty .ant-table-tbody > tr.ant-table-placeholder': {
+ '& .ant-empty-image': {
+ display: 'none'
+ },
+ '& .ant-table-cell': {
+ borderBottom: 'none'
+ },
+ '& > .ant-table-cell': {
+ background: td.base.background
+ },
+ color: td.base.background
+ },
background: td.base.background
}
}
@@ -238,7 +250,7 @@ export const useStyles = createUseStyles({
}
}
},
- searchBar: {
+ searchBarWrapper: {
alignSelf: props =>
props.searchProps.placement === 'right' ? 'flex-end' : 'flex-start',
marginBottom: spacing.m