@zendeskgarden/react-grid
Version:
Components relating to layout grids in the Garden Design System
198 lines (195 loc) • 7.1 kB
JavaScript
/**
* 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, useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import { useId } from '@zendeskgarden/container-utilities';
import usePaneProviderContext, { PaneProviderContext } from '../../utils/usePaneProviderContext.js';
const getPixelsPerFr = (totalFrs, totalDimension) => {
return totalDimension / totalFrs;
};
const convertToPixels = (values, pixelsPerFr) => {
return Object.entries(values).reduce((prev, _ref) => {
let [key, value] = _ref;
prev[key] = value * pixelsPerFr;
return prev;
}, {});
};
const PaneProvider = _ref2 => {
let {
id,
totalPanesWidth,
totalPanesHeight,
defaultRowValues,
defaultColumnValues,
rowValues,
columnValues,
onChange,
children
} = _ref2;
const isControlled = rowValues !== undefined && rowValues !== null && columnValues !== undefined && columnValues !== null;
const [rowState, setRowState] = useState(defaultRowValues || {});
const [columnState, setColumnState] = useState(defaultColumnValues || {});
const rowsTrack = isControlled ? rowValues : rowState;
const columnsTrack = isControlled ? columnValues : columnState;
const setRowsTrack = useCallback(values => {
if (isControlled && onChange) {
return onChange(values(rowsTrack), columnsTrack);
}
return setRowState(values);
}, [isControlled, onChange, setRowState, columnsTrack, rowsTrack]);
const setColumnsTrack = useCallback(values => {
if (isControlled && onChange) {
return onChange(rowsTrack, values(columnsTrack));
}
return setColumnState(values);
}, [isControlled, onChange, setColumnState, rowsTrack, columnsTrack]);
const totalFractions = useMemo(() => ({
rows: Object.values(rowsTrack).reduce((prev, value) => value + prev, 0),
columns: Object.values(columnsTrack).reduce((prev, value) => value + prev, 0)
}), [rowsTrack, columnsTrack]);
const pixelsPerFr = useMemo(() => ({
rows: getPixelsPerFr(totalFractions.rows, totalPanesHeight),
columns: getPixelsPerFr(totalFractions.columns, totalPanesWidth)
}), [totalFractions, totalPanesHeight, totalPanesWidth]);
const layoutStateInPixels = useMemo(() => ({
rows: convertToPixels(rowsTrack, pixelsPerFr.rows),
columns: convertToPixels(columnsTrack, pixelsPerFr.columns)
}), [rowsTrack, columnsTrack, pixelsPerFr]);
const layoutIndices = useMemo(() => {
const rowArray = Object.keys(rowsTrack);
const columnArray = Object.keys(columnsTrack);
const rows = rowArray.reduce((prev, key, index) => {
prev[key] = index;
return prev;
}, {});
const columns = columnArray.reduce((prev, key, index) => {
prev[key] = index;
return prev;
}, {});
return {
rows,
columns,
rowArray,
columnArray
};
}, [rowsTrack, columnsTrack]);
const setRowValue = useCallback((isTop, splitterId, value) => {
const {
rows,
rowArray
} = layoutIndices;
const stealFromTraversal = isTop ? -1 : 1;
const addToTraversal = 0;
setRowsTrack(state => {
const oldValue = rowsTrack[splitterId];
const stealFromIndex = rows[splitterId] + stealFromTraversal;
const addToIndex = rows[splitterId] + addToTraversal;
const stealFromKey = rowArray[stealFromIndex];
const addToKey = rowArray[addToIndex];
const difference = oldValue - value;
const nextState = {
...state
};
nextState[addToKey] = rowsTrack[addToKey] - difference;
nextState[stealFromKey] = rowsTrack[stealFromKey] + difference;
return nextState;
});
}, [layoutIndices, rowsTrack, setRowsTrack]);
const setColumnValue = useCallback((isStart, splitterId, value) => {
const {
columns,
columnArray
} = layoutIndices;
const stealFromTraversal = isStart ? -1 : 1;
const addToTraversal = 0;
setColumnsTrack(state => {
const stealFromIndex = columns[splitterId] + stealFromTraversal;
const addToIndex = columns[splitterId] + addToTraversal;
const oldValue = columnsTrack[splitterId];
const stealFromKey = columnArray[stealFromIndex];
const addToKey = columnArray[addToIndex];
const difference = oldValue - value;
const nextState = {
...state
};
nextState[addToKey] = columnsTrack[addToKey] - difference;
nextState[stealFromKey] = columnsTrack[stealFromKey] + difference;
return nextState;
});
}, [layoutIndices, columnsTrack, setColumnsTrack]);
const getColumnValue = useCallback((splitterKey, isPixels) => {
if (isPixels) {
return layoutStateInPixels.columns[splitterKey];
}
return columnsTrack[splitterKey];
}, [columnsTrack, layoutStateInPixels]);
const getRowValue = useCallback((splitterKey, isPixels) => {
if (isPixels) {
return layoutStateInPixels.rows[splitterKey];
}
return rowsTrack[splitterKey];
}, [rowsTrack, layoutStateInPixels]);
const getGridTemplateColumns = useCallback(isPixels => {
const {
columnArray
} = layoutIndices;
if (isPixels) {
return columnArray.map(col => `${layoutStateInPixels.columns[col]}px`).join(' ');
}
return columnArray.map(col => `${columnsTrack[col]}fr`).join(' ');
}, [layoutIndices, columnsTrack, layoutStateInPixels]);
const getGridTemplateRows = useCallback(isPixels => {
const {
rowArray
} = layoutIndices;
if (isPixels) {
return rowArray.map(row => `${layoutStateInPixels.rows[row]}px`).join(' ');
}
return rowArray.map(row => `${rowsTrack[row]}fr`).join(' ');
}, [layoutIndices, rowsTrack, layoutStateInPixels]);
const providerId = useId(id);
const parentPaneProviderContext = usePaneProviderContext();
const paneProviderContext = useMemo(() => providerId ? {
providerId,
contextData: {
...parentPaneProviderContext.contextData,
[providerId]: {
columnState,
rowState,
setRowValue,
setColumnValue,
getRowValue,
getColumnValue,
totalPanesHeight,
totalPanesWidth,
pixelsPerFr
}
}
} : {}, [providerId, parentPaneProviderContext, rowState, columnState, setRowValue, setColumnValue, getRowValue, getColumnValue, totalPanesHeight, totalPanesWidth, pixelsPerFr]);
return React.createElement(PaneProviderContext.Provider, {
value: paneProviderContext
}, children?.({
id: providerId,
getRowValue,
getColumnValue,
getGridTemplateColumns,
getGridTemplateRows
}));
};
PaneProvider.displayName = 'PaneProvider';
PaneProvider.propTypes = {
id: PropTypes.string,
totalPanesWidth: PropTypes.number.isRequired,
totalPanesHeight: PropTypes.number.isRequired,
defaultRowValues: PropTypes.object,
defaultColumnValues: PropTypes.object,
rowValues: PropTypes.object,
columnValues: PropTypes.object,
onChange: PropTypes.func,
children: PropTypes.func
};
export { PaneProvider };