11import { SortAscIcon , SortDescIcon } from '@primer/octicons-react'
2+ import cx from 'classnames'
23import React from 'react'
3- import styled from 'styled-components'
4+ import styled , { keyframes } from 'styled-components'
45import Box from '../Box'
56import Text from '../Text'
67import { get } from '../constants'
78import sx , { SxProp } from '../sx'
9+ import VisuallyHidden from '../_VisuallyHidden'
10+ import { Column , CellAlignment } from './column'
11+ import { UniqueRow } from './row'
812import { SortDirection } from './sorting'
13+ import { useTableLayout } from './useTable'
914import { useOverflow } from '../hooks/useOverflow'
10- import { CellAlignment } from './column'
1115
1216// ----------------------------------------------------------------------------
1317// Table
1418// ----------------------------------------------------------------------------
1519
20+ const shimmer = keyframes `
21+ from { mask-position: 200%; }
22+ to { mask-position: 0%; }
23+ `
1624const StyledTable = styled . table < React . ComponentPropsWithoutRef < 'table' >> `
1725 /* Default table styles */
1826 --table-border-radius: 0.375rem;
@@ -104,11 +112,13 @@ const StyledTable = styled.table<React.ComponentPropsWithoutRef<'table'>>`
104112 * Offset padding to make sure type aligns regardless of cell padding
105113 * selection
106114 */
107- .TableRow > *:first-child {
115+ .TableRow > *:first-child:not(.TableCellSkeleton),
116+ .TableRow > *:first-child .TableCellSkeletonItem {
108117 padding-inline-start: 1rem;
109118 }
110119
111- .TableRow > *:last-child {
120+ .TableRow > *:last-child:not(.TableCellSkeleton),
121+ .TableRow > *:last-child .TableCellSkeletonItem {
112122 padding-inline-end: 1rem;
113123 }
114124
@@ -143,7 +153,7 @@ const StyledTable = styled.table<React.ComponentPropsWithoutRef<'table'>>`
143153 }
144154
145155 /* TableRow */
146- .TableRow:hover .TableCell {
156+ .TableRow:hover .TableCell:not(.TableCellSkeleton) {
147157 /* TODO: update this token when the new primitive tokens are released */
148158 background-color: ${ get ( 'colors.actionListItem.default.hoverBg' ) } ;
149159 }
@@ -154,6 +164,66 @@ const StyledTable = styled.table<React.ComponentPropsWithoutRef<'table'>>`
154164 font-weight: 600;
155165 }
156166
167+ /* TableCellSkeleton */
168+ .TableCellSkeleton {
169+ padding: 0;
170+ }
171+
172+ .TableCellSkeletonItems {
173+ display: flex;
174+ flex-direction: column;
175+ }
176+
177+ .TableCellSkeletonItem {
178+ padding: var(--table-cell-padding);
179+
180+ &:nth-of-type(5n + 1) {
181+ --skeleton-item-width: 85%;
182+ }
183+
184+ &:nth-of-type(5n + 2) {
185+ --skeleton-item-width: 67.5%;
186+ }
187+
188+ &:nth-of-type(5n + 3) {
189+ --skeleton-item-width: 80%;
190+ }
191+
192+ &:nth-of-type(5n + 4) {
193+ --skeleton-item-width: 60%;
194+ }
195+
196+ &:nth-of-type(5n + 5) {
197+ --skeleton-item-width: 75%;
198+ }
199+ }
200+
201+ .TableCellSkeletonItem:not(:last-of-type) {
202+ border-bottom: 1px solid ${ get ( 'colors.border.default' ) } ;
203+ }
204+
205+ .TableCellSkeletonItem::before {
206+ display: block;
207+ content: '';
208+ height: 1rem;
209+ width: var(--skeleton-item-width, 67%);
210+ background-color: ${ get ( 'colors.canvas.subtle' ) } ;
211+ border-radius: 3px;
212+
213+ @media (prefers-reduced-motion: no-preference) {
214+ mask-image: linear-gradient(75deg, #000 30%, rgba(0, 0, 0, 0.65) 80%);
215+ mask-size: 200%;
216+ animation: ${ shimmer } ;
217+ animation-duration: 1s;
218+ animation-iteration-count: infinite;
219+ }
220+
221+ @media (forced-colors: active) {
222+ outline: 1px solid transparent;
223+ outline-offset: -1px;
224+ }
225+ }
226+
157227 /* Grid layout */
158228 .TableHead,
159229 .TableBody,
@@ -195,7 +265,7 @@ export type TableProps = React.ComponentPropsWithoutRef<'table'> & {
195265}
196266
197267const Table = React . forwardRef < HTMLTableElement , TableProps > ( function Table (
198- { 'aria-labelledby' : labelledby , cellPadding = 'normal' , gridTemplateColumns, ...rest } ,
268+ { 'aria-labelledby' : labelledby , cellPadding = 'normal' , className , gridTemplateColumns, ...rest } ,
199269 ref ,
200270) {
201271 return (
@@ -204,7 +274,7 @@ const Table = React.forwardRef<HTMLTableElement, TableProps>(function Table(
204274 { ...rest }
205275 aria-labelledby = { labelledby }
206276 data-cell-padding = { cellPadding }
207- className = " Table"
277+ className = { cx ( ' Table' , className ) }
208278 role = "table"
209279 ref = { ref }
210280 style = { { '--grid-template-columns' : gridTemplateColumns } as React . CSSProperties }
@@ -332,12 +402,12 @@ export type TableCellProps = Omit<React.ComponentPropsWithoutRef<'td'>, 'align'>
332402 scope ?: 'row'
333403}
334404
335- function TableCell ( { align, children, scope, ...rest } : TableCellProps ) {
405+ function TableCell ( { align, className , children, scope, ...rest } : TableCellProps ) {
336406 const BaseComponent = scope ? 'th' : 'td'
337407 const role = scope ? 'rowheader' : 'cell'
338408
339409 return (
340- < BaseComponent { ...rest } className = " TableCell" scope = { scope } role = { role } data-cell-align = { align } >
410+ < BaseComponent { ...rest } className = { cx ( ' TableCell' , className ) } scope = { scope } role = { role } data-cell-align = { align } >
341411 { children }
342412 </ BaseComponent >
343413 )
@@ -504,6 +574,66 @@ function TableActions({children}: TableActionsProps) {
504574 return < div className = "TableActions" > { children } </ div >
505575}
506576
577+ // ----------------------------------------------------------------------------
578+ // TableSkeleton
579+ // ----------------------------------------------------------------------------
580+ export type TableSkeletonProps < Data extends UniqueRow > = React . ComponentPropsWithoutRef < 'table' > & {
581+ /**
582+ * Specify the amount of space that should be available around the contents of
583+ * a cell
584+ */
585+ cellPadding ?: 'condensed' | 'normal' | 'spacious'
586+
587+ /**
588+ * Provide an array of columns for the table. Columns will render as the headers
589+ * of the table.
590+ */
591+ columns : Array < Column < Data > >
592+
593+ /**
594+ * Optionally specify the number of rows which should be included in the
595+ * skeleton state of the component
596+ */
597+ rows ?: number
598+ }
599+
600+ function TableSkeleton < Data extends UniqueRow > ( { cellPadding, columns, rows = 10 , ...rest } : TableSkeletonProps < Data > ) {
601+ const { gridTemplateColumns} = useTableLayout ( columns )
602+ return (
603+ < Table { ...rest } cellPadding = { cellPadding } gridTemplateColumns = { gridTemplateColumns } >
604+ < TableHead >
605+ < TableRow >
606+ { Array . isArray ( columns )
607+ ? columns . map ( ( column , i ) => {
608+ return (
609+ < TableHeader key = { i } >
610+ { typeof column . header === 'string' ? column . header : column . header ( ) }
611+ </ TableHeader >
612+ )
613+ } )
614+ : null }
615+ </ TableRow >
616+ </ TableHead >
617+ < TableBody >
618+ < TableRow >
619+ { Array . from ( { length : columns . length } ) . map ( ( _ , i ) => {
620+ return (
621+ < TableCell key = { i } className = "TableCellSkeleton" >
622+ < VisuallyHidden > Loading</ VisuallyHidden >
623+ < div className = "TableCellSkeletonItems" >
624+ { Array . from ( { length : rows } ) . map ( ( _ , i ) => {
625+ return < div key = { i } className = "TableCellSkeletonItem" />
626+ } ) }
627+ </ div >
628+ </ TableCell >
629+ )
630+ } ) }
631+ </ TableRow >
632+ </ TableBody >
633+ </ Table >
634+ )
635+ }
636+
507637// ----------------------------------------------------------------------------
508638// Utilities
509639// ----------------------------------------------------------------------------
@@ -567,4 +697,5 @@ export {
567697 TableSortHeader ,
568698 TableCell ,
569699 TableCellPlaceholder ,
700+ TableSkeleton ,
570701}
0 commit comments