11import classnames from 'classnames' ;
2+ import CustomPropTypes from '../utils/CustomPropTypes/CustomPropTypes' ;
3+ import GridManager from '../utils/gridManager/gridManager' ;
4+ import keycode from 'keycode' ;
25import PropTypes from 'prop-types' ;
3- import React from 'react' ;
6+ import shortid from 'shortid' ;
7+ import React , { useCallback , useRef , useState } from 'react' ;
48import 'fundamental-styles/dist/table.css' ;
59
610/** A **Table** is a set of tabular data. Line items can support `data`, `images` and `actions`. */
7- const Table = React . forwardRef ( ( { headers, tableData, className, tableBodyClassName,
11+ const Table = React . forwardRef ( ( { headers, tableData, className, compact , condensed , keyboardNavigation , localizedText , tableBodyClassName,
812 tableBodyProps, tableBodyRowProps, tableCellClassName, tableCheckboxClassName, tableHeaderClassName, tableHeaderProps,
913 tableHeaderRowClassName, tableHeaderRowProps, tableRowClassName, richTable, ...props } , ref ) => {
1014
1115 const tableClasses = classnames (
1216 'fd-table' ,
17+ {
18+ 'fd-table--compact' : compact ,
19+ 'fd-table--condensed' : condensed
20+ } ,
1321 className
1422 ) ;
1523
@@ -20,6 +28,9 @@ const Table = React.forwardRef(({ headers, tableData, className, tableBodyClassN
2028
2129 const tableHeaderRowClasses = classnames (
2230 'fd-table__row' ,
31+ {
32+ 'fd-table__row--focusable' : keyboardNavigation === 'row'
33+ } ,
2334 tableHeaderRowClassName
2435 ) ;
2536
@@ -30,11 +41,17 @@ const Table = React.forwardRef(({ headers, tableData, className, tableBodyClassN
3041
3142 const tableRowClasses = classnames (
3243 'fd-table__row' ,
44+ {
45+ 'fd-table__cell--focusable' : keyboardNavigation === 'row'
46+ } ,
3347 tableRowClassName
3448 ) ;
3549
3650 const tableCellClasses = classnames (
3751 'fd-table__cell' ,
52+ {
53+ 'fd-table__cell--focusable' : keyboardNavigation === 'cell'
54+ } ,
3855 tableCellClassName
3956 ) ;
4057
@@ -44,17 +61,67 @@ const Table = React.forwardRef(({ headers, tableData, className, tableBodyClassN
4461 tableCheckboxClassName
4562 ) ;
4663
64+ const useHookWithRefCallback = ( ) => {
65+ const newRef = useRef ( null ) ;
66+ const setRef = useCallback ( node => {
67+ if ( node && keyboardNavigation !== 'none' ) {
68+ gridManager . current . attachTo ( { gridNode : node , onFocusCell, onToggleEditMode } ) ;
69+ }
70+ newRef . current = node ;
71+ } , [ ] ) ;
72+
73+ return setRef ;
74+ } ;
75+
76+ const captionId = shortid . generate ( ) ;
77+ const gridManager = useRef ( new GridManager ( ) ) ;
78+ const tableRef = ref || useHookWithRefCallback ( ) ;
79+ const [ instructionsText , setInstructionsText ] = useState ( '' ) ;
80+
81+ const onToggleEditMode = ( enable ) => {
82+ setInstructionsText ( enable ? localizedText . editModeDisable : '' ) ;
83+ } ;
84+
85+ const onFocusCell = ( cell , event ) => {
86+ const { row, col } = cell ;
87+ const key = event . which || event . keyCode ;
88+ const navigatedHorizontally = ( key === keycode . codes . left || key === keycode . codes . right ) && col > 0 ;
89+ const navigatedVertically = ( key === keycode . codes . up || key === keycode . codes . down ) && row > 0 ;
90+ if ( gridManager . current ?. editMode ) {
91+ setInstructionsText ( localizedText . editModeDisable ) ;
92+ } else {
93+ let newInstructionsText = '' ;
94+ if ( navigatedVertically ) {
95+ newInstructionsText += `${ localizedText . row } ${ row } ` ;
96+ } else if ( navigatedHorizontally ) {
97+ newInstructionsText += `${ localizedText . column } ${ col } ${ headers [ col ] } ` ;
98+ } else {
99+ newInstructionsText += `${ localizedText . arrowKeys } ` ;
100+ }
101+ if ( gridManager . current ?. isEditableCell ( cell ) ) {
102+ newInstructionsText += localizedText . editModeEnable ;
103+ }
104+ setInstructionsText ( newInstructionsText ) ;
105+ }
106+ } ;
107+
47108 let checkboxHeader ;
48- let displayHeaders = headers ;
109+ let displayHeaders = [ ... headers ] ;
49110
50111 if ( richTable ) {
51112 checkboxHeader = < th className = { tableCheckboxClasses } > { headers [ 0 ] } </ th > ;
52- displayHeaders = headers . splice ( 1 , headers . length ) ;
113+ displayHeaders = displayHeaders . splice ( 1 , headers . length ) ;
53114 }
54115
55116 return (
56- < table { ...props } className = { tableClasses }
57- ref = { ref } >
117+ < table { ...props } aria-describedby = { captionId }
118+ className = { tableClasses }
119+ ref = { tableRef }
120+ role = { keyboardNavigation ? 'grid' : 'table' } >
121+ < caption aria-live = 'polite' className = 'fd-table__caption'
122+ id = { captionId } >
123+ { instructionsText }
124+ </ caption >
58125 < thead className = { tableHeaderClasses } { ...tableHeaderProps } >
59126 < tr className = { tableHeaderRowClasses } { ...tableHeaderRowProps } >
60127 { richTable && checkboxHeader }
@@ -66,7 +133,8 @@ const Table = React.forwardRef(({ headers, tableData, className, tableBodyClassN
66133 < tbody className = { tableBodyClasses } { ...tableBodyProps } >
67134 { tableData . map ( ( row , index ) => {
68135 let rowProps , checkboxCell ;
69- let displayRows = row . rowData ;
136+ let displayCells = [ ...row . rowData ] ;
137+
70138 if ( tableBodyRowProps ) {
71139 rowProps = ( typeof tableBodyRowProps === 'function'
72140 ? tableBodyRowProps ( row , index )
@@ -80,7 +148,7 @@ const Table = React.forwardRef(({ headers, tableData, className, tableBodyClassN
80148 { row . rowData [ 0 ] }
81149 </ td >
82150 ) ;
83- displayRows = row . rowData . splice ( 1 , row . rowData . length ) ;
151+ displayCells = displayCells . splice ( 1 , row . rowData . length ) ;
84152 }
85153
86154 return (
@@ -90,8 +158,11 @@ const Table = React.forwardRef(({ headers, tableData, className, tableBodyClassN
90158 aria-selected = { row ?. rowData [ 0 ] ?. props ?. checked }
91159 key = { index } >
92160 { richTable && checkboxCell }
93- { displayRows . map ( ( rowData , cellIndex ) => {
94- return < td className = { tableCellClasses } key = { cellIndex } > { rowData } </ td > ;
161+ { displayCells . map ( ( cellData , cellIndex ) => {
162+ if ( cellData . type ?. propTypes ?. compact ) {
163+ cellData = React . cloneElement ( cellData , { compact : compact || condensed } ) ;
164+ }
165+ return < td className = { tableCellClasses } key = { cellIndex } > { cellData } </ td > ;
95166 } ) }
96167 </ tr >
97168 ) ;
@@ -106,14 +177,33 @@ Table.displayName = 'Table';
106177Table . propTypes = {
107178 /** Array of localized text strings for the column headers */
108179 headers : PropTypes . array . isRequired ,
109- /** Array of objects that contain one property: `rowData` (an array of strings containing data for each column in the row) */
180+ /** Array of objects that contain one property: `rowData` (an array of nodes containing data for each column in the row) */
110181 tableData : PropTypes . arrayOf (
111182 PropTypes . shape ( {
112183 rowData : PropTypes . array
113184 } ) . isRequired
114185 ) . isRequired ,
115186 /** CSS class(es) to add to the element */
116187 className : PropTypes . string ,
188+ /** Set to **true** to enable compact mode */
189+ compact : PropTypes . bool ,
190+ /** Set to **true** to enable condensed mode */
191+ condensed : PropTypes . bool ,
192+ /** Determines the type of keyboard navigation for the table. Set to `'cell'` for cell-level navigation or `'row'` for row-level navigation */
193+ keyboardNavigation : PropTypes . oneOf ( [ 'none' , 'cell' , 'row' ] ) ,
194+ /** Localized text to be updated based on location/language */
195+ localizedText : CustomPropTypes . i18n ( {
196+ /** Localized string informing screen reader users the table can be navigated by arrow keys */
197+ arrowKeys : PropTypes . string ,
198+ /** Localized string informing screen reader users the current cell can be edited */
199+ editModeEnable : PropTypes . string ,
200+ /** Localized string informing screen reader users how to return to cell navigation */
201+ editModeDisable : PropTypes . string ,
202+ /** Localized string for 'row' */
203+ row : PropTypes . string ,
204+ /** Localized string for 'column' */
205+ column : PropTypes . string
206+ } ) ,
117207 /** Set to **true** if Table contains checkboxes */
118208 richTable : PropTypes . bool ,
119209 /** Additional classes to be added to the `<tbody>` element */
@@ -142,4 +232,15 @@ Table.propTypes = {
142232 tableRowClassName : PropTypes . string
143233} ;
144234
235+ Table . defaultProps = {
236+ keyboardNavigation : 'none' ,
237+ localizedText : {
238+ arrowKeys : 'Use arrow keys to navigate between cells' ,
239+ editModeEnable : 'Press Enter to edit this cell' ,
240+ editModeDisable : 'Press Escape to return to cell navigation' ,
241+ row : 'row' ,
242+ column : 'column'
243+ }
244+ } ;
245+
145246export default Table ;
0 commit comments