UNPKG

@zendeskgarden/container-grid

Version:

Containers relating to Grid in the Garden Design System

235 lines (229 loc) 7.73 kB
/** * Copyright Zendesk, Inc. * * Use of this source code is governed under the Apache License, Version 2.0 * found at http://www.apache.org/licenses/LICENSE-2.0. */ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { KEYS, useId, composeEventHandlers } from '@zendeskgarden/container-utilities'; import PropTypes from 'prop-types'; const getCellDown = (matrix, rowIndex, colIndex, wrap) => { let retVal = []; const rowCount = matrix.length; const colCount = matrix[0].length; const lastRowLength = matrix[rowCount - 1].length; const isLastCellFocused = rowIndex === rowCount - (colCount > lastRowLength ? 2 : 1) && colIndex === colCount - 1; if (!isLastCellFocused) { if (rowIndex === rowCount - (colIndex >= lastRowLength ? 2 : 1) ) { if (wrap) { retVal = [0, colIndex + 1]; } } else { retVal = [rowIndex + 1, colIndex]; } } return retVal; }; const getCellLeft = (matrix, rowIndex, colIndex, wrap) => { let retVal = []; const colCount = matrix[0].length; const isFirstCellFocused = rowIndex === 0 && colIndex === 0; if (!isFirstCellFocused) { if (colIndex === 0 ) { if (wrap) { retVal = [rowIndex - 1, colCount - 1]; } } else { retVal = [rowIndex, colIndex - 1]; } } return retVal; }; const getCellRight = (matrix, rowIndex, colIndex, wrap) => { let retVal = []; const rowCount = matrix.length; const colCount = matrix[0].length; const lastRowIndex = rowCount - 1; const lastColIndex = matrix[lastRowIndex].length - 1; const isLastCellFocused = rowIndex === lastRowIndex && colIndex === lastColIndex; if (!isLastCellFocused) { if (colIndex === colCount - 1 ) { if (wrap) { retVal = [rowIndex + 1, 0]; } } else { retVal = [rowIndex, colIndex + 1]; } } return retVal; }; const getCellUp = (matrix, rowIndex, colIndex, wrap) => { let retVal = []; const rowCount = matrix.length; const isFirstCellFocused = rowIndex === 0 && colIndex === 0; if (!isFirstCellFocused) { if (rowIndex === 0 ) { if (wrap) { const lastRowLength = matrix[rowCount - 1].length; const col = colIndex - 1; const row = rowCount - (col >= lastRowLength ? 2 : 1); retVal = [row, col]; } } else { retVal = [rowIndex - 1, colIndex]; } } return retVal; }; const getId = (idPrefix, rowIndex, colIndex) => `${idPrefix}--R${rowIndex + 1}C${colIndex + 1}`; const GRID_KEYS = [KEYS.LEFT, KEYS.RIGHT, KEYS.UP, KEYS.DOWN, KEYS.HOME, KEYS.END]; function useGrid(_ref) { let { rtl, wrap, matrix, idPrefix, onChange = () => undefined, environment, rowIndex: controlledRowIndex, colIndex: controlledColIndex, defaultRowIndex, defaultColIndex } = _ref; const doc = environment || document; const prefix = useId(idPrefix); const [uncontrolledRowIndex, setUncontrolledRowIndex] = useState(defaultRowIndex !== null && defaultRowIndex !== undefined ? defaultRowIndex : 0); const [uncontrolledColIndex, setUncontrolledColIndex] = useState(defaultColIndex !== null && defaultColIndex !== undefined ? defaultColIndex : 0); const isControlled = controlledRowIndex !== null && controlledColIndex !== null && controlledRowIndex !== undefined && controlledColIndex !== undefined; const rowIndex = isControlled ? controlledRowIndex : uncontrolledRowIndex; const colIndex = isControlled ? controlledColIndex : uncontrolledColIndex; useEffect(() => { const rowCount = matrix.length; const colCount = matrix[0].length; const isRowIndexInvalid = rowIndex >= rowCount; const isColIndexInvalid = colIndex >= colCount; if (isRowIndexInvalid || isColIndexInvalid) { let _rowIndex = rowIndex; let _colIndex = colIndex; if (isRowIndexInvalid) { _rowIndex = rowCount > 0 ? rowCount - 1 : 0; } if (isColIndexInvalid) { _colIndex = colCount > 0 ? colCount - 1 : 0; } if (!isControlled) { setUncontrolledRowIndex(_rowIndex); setUncontrolledColIndex(_colIndex); } onChange(_rowIndex, _colIndex); } }, [matrix, rowIndex, colIndex, isControlled, setUncontrolledColIndex, onChange]); const getGridProps = useCallback(_ref2 => { let { role = 'grid', ...other } = _ref2; return { 'data-garden-container-id': 'containers.grid', 'data-garden-container-version': '3.0.19', role: role === null ? undefined : role, ...other }; }, []); const getGridCellProps = useCallback(function (_temp) { let { role = 'gridcell', rowIndex: _rowIndex, colIndex: _colIndex, onFocus, onKeyDown, ...other } = _temp === void 0 ? { rowIndex: 0, colIndex: 0 } : _temp; const handleFocus = () => { if (!isControlled) { setUncontrolledRowIndex(_rowIndex); setUncontrolledColIndex(_colIndex); } onChange(_rowIndex, _colIndex); }; const handleKeyDown = event => { if (GRID_KEYS.includes(event.key)) { event.preventDefault(); let row = rowIndex; let col = colIndex; switch (event.key) { case KEYS.RIGHT: [row, col] = rtl ? getCellLeft(matrix, rowIndex, colIndex, wrap) : getCellRight(matrix, rowIndex, colIndex, wrap); break; case KEYS.LEFT: [row, col] = rtl ? getCellRight(matrix, rowIndex, colIndex, wrap) : getCellLeft(matrix, rowIndex, colIndex, wrap); break; case KEYS.DOWN: [row, col] = getCellDown(matrix, rowIndex, colIndex, wrap); break; case KEYS.UP: [row, col] = getCellUp(matrix, rowIndex, colIndex, wrap); break; case KEYS.HOME: row = event.ctrlKey ? 0 : rowIndex; col = 0; break; case KEYS.END: { const rowCount = matrix.length; const lastRowIndex = rowCount - 1; const lastColIndex = matrix[lastRowIndex].length - 1; row = event.ctrlKey ? lastRowIndex : rowIndex; col = event.ctrlKey ? lastColIndex : matrix[rowIndex].length - 1; break; } } if (row !== rowIndex || col !== colIndex) { const id = getId(prefix, row, col); const element = doc.getElementById(id); element?.focus(); } } }; return { 'data-garden-container-id': 'containers.grid.cell', 'data-garden-container-version': '3.0.19', id: getId(prefix, _rowIndex, _colIndex), role: role === null ? undefined : role, tabIndex: rowIndex === _rowIndex && colIndex === _colIndex ? 0 : -1, onFocus: composeEventHandlers(onFocus, handleFocus), onKeyDown: composeEventHandlers(onKeyDown, handleKeyDown), ...other }; }, [matrix, rowIndex, colIndex, doc, prefix, isControlled, onChange, rtl, wrap]); return useMemo(() => ({ getGridProps, getGridCellProps }), [getGridProps, getGridCellProps]); } const GridContainer = _ref => { let { children, render = children, ...options } = _ref; return React.createElement(React.Fragment, null, render(useGrid(options))); }; GridContainer.propTypes = { children: PropTypes.func, render: PropTypes.func, rtl: PropTypes.bool, wrap: PropTypes.bool, matrix: PropTypes.any, idPrefix: PropTypes.string, rowIndex: PropTypes.number, colIndex: PropTypes.number, defaultRowIndex: PropTypes.number, defaultColIndex: PropTypes.number, onChange: PropTypes.func, environment: PropTypes.any }; export { GridContainer, useGrid };