import React from 'react'; import PropTypes from 'prop-types'; import { css, keyframes } from '@emotion/core'; import styled from '@emotion/styled'; import { space, layout, typography, color, flexbox, overflow, compose, SpaceProps, LayoutProps, TypographyProps, ColorProps, FlexboxProps, } from 'styled-system'; import { themeGet } from '@styled-system/theme-get'; type LoadingStates = 'loading' | 'completed'; export const TableContext = React.createContext({ data: [] }); const loadingAnimation = keyframes` 0% { width: 0; } 60% { width: 85%; } 100% { width: 90%; } `; const loadingComplete = keyframes` 0% { opacity: 1; } 100% { opacity: 0; } `; const loadingAnimationBG = keyframes` 0% { background-position: 0 50%; } 100% { background-position: 100% 50%; } `; const LoadingContainer = styled('div')` visibility: visible; display: flex; position: absolute; top: 0; bottom: 0; left: 0; right: 0; `; const LoadingProgress = styled('div')` height: 3px; position: absolute; top: 0px; left: 0; z-index: 1; ${props => css` background-image: ${themeGet('tableStyles.progress.background')(props)}; background-size: ${themeGet('tableStyles.progress.backgroundSize')(props)}; `}; `; const LoadingProgressBackground = styled('div')` height: 1px; background-color: ${themeGet('tableStyles.progress.loadingBackground')}; position: absolute; top: 1px; left: 0; width: 100%; `; const LoadingRow = styled('tr')` td { position: relative; height: 0; padding: 0; padding-bottom: 2px; } `; const setHoverSelectedTr = (props: { theme?: any }) => css` background-color: ${themeGet('tableStyles.tr.hover.background', 'blue')(props)}; `; export interface TrProps extends SpaceProps, Omit { isHoverable?: boolean; selected?: boolean; } export const Tr = styled('tr')` tbody &:hover { ${props => props.isHoverable && setHoverSelectedTr(props)} } ${props => props.selected && setHoverSelectedTr(props)}; ${compose( space, color, )}; `; Tr.defaultProps = { isHoverable: true, }; type CommonProps = LayoutProps & SpaceProps & TypographyProps & Omit & FlexboxProps; export const Td = styled('td')` padding-left: ${themeGet('space.3')}; padding-right: ${themeGet('space.3')}; padding-top: ${themeGet('space.2')}; padding-bottom: ${themeGet('space.2')}; text-align: left; font-size: ${themeGet('fontSizes.text-s')}; ${compose( layout, space, typography, color, flexbox, )}; `; export const Th = styled('th')` box-sizing: border-box; height: ${themeGet('space.8')}; font-weight: ${themeGet('fontWeights.bold')}; padding-left: ${themeGet('space.3')}; padding-right: ${themeGet('space.3')}; padding-top: ${themeGet('space.2')}; padding-bottom: ${themeGet('space.2')}; text-align: left; font-size: ${themeGet('fontSizes.text-s')}; color: ${themeGet('colors.textHeading')}; ${compose( layout, space, typography, color, flexbox, )}; `; const setLoadingStyles = (props: { loadingStatus?: LoadingStates }) => { switch (props.loadingStatus) { case 'loading': return css` ${LoadingProgress} { width: 90%; animation: ${loadingAnimation} 10s ease-out, ${loadingAnimationBG} 3s linear infinite; } tbody { opacity: 0.6; pointer-events: none; } `; case 'completed': return css` ${LoadingProgress} { width: 100%; opacity: 0; animation: ${loadingComplete} 0.3s linear, ${loadingAnimationBG} 3s linear infinite; } tbody { opacity: 1; } `; default: return ''; } }; interface TableProps { loadingStatus?: LoadingStates; } export const Table = styled('table')` border-collapse: collapse; width: 100%; ${overflow} ${color} th:not(${Th}) { padding: ${themeGet('space.2')} ${themeGet('space.3')}; font-weight: ${themeGet('fontWeights.bold')}; font-size: ${themeGet('fontSizes.text-s')}; height: ${themeGet('space.8')}; color: ${themeGet('colors.textHeading')}; box-sizing: border-box; text-align: left; } tbody { transition: opacity 0.15s ease-in; width: 100%; } tbody tr { border-bottom: 1px solid ${themeGet('tableStyles.tr.borderColor')}; &:last-of-type { border-bottom: 0; } } tbody td { box-sizing: border-box; height: ${themeGet('space.8')}; } tbody td:not(${Td}), tfoot td:not(${Td}) { padding: ${themeGet('space.2')} ${themeGet('space.3')}; font-size: ${themeGet('fontSizes.text-s')}; } tfoot td { height: ${themeGet('space.8')}; } tfoot tr { border-top: 1px solid ${themeGet('tableStyles.tr.borderColor')}; } ${setLoadingStyles} `; export const Thead: React.FC = ({ children }) => ( {children} ); Thead.propTypes = { children: PropTypes.node, }; export const NoDataTd = styled(Td)` color: ${themeGet('colors.textDimmed')}; text-align: center; `; export const Tbody = styled('tbody')``; export const Tfoot = styled('tfoot')``; NoDataTd.defaultProps = { ...Td.defaultProps, };