Skip to content

Commit

Permalink
STCOM-1343 refactor away from findDOMNode (#2352)
Browse files Browse the repository at this point in the history
* provide refs where able

* remove wrapping elements from RowMeasurer and CellMeasurer

* metasection header

* fix useImperativeHandle in meta-accordion

* resolve MCL tests, point at srh branch with fix...

* have accordion supply its own container for header/hotkey catching.

* switch from branch of stripes-react-hotkeys

* update yarn.lock to use updated stripes-react-hotkeys

* consistently use renderprops for CellMeasurer

* Update CHANGELOG.md
  • Loading branch information
JohnC-80 authored Sep 30, 2024
1 parent 23a0c49 commit 5792577
Show file tree
Hide file tree
Showing 13 changed files with 135 additions and 79 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
* Apply `inert` attribute to header and siblings of `div#OverlayContainer` when modals are open. Refs STCOM-1334.
* Expand focus trapping of modal to the `div#OverlayContainer` so that overlay components can function within `<Modal>` using the `usePortal` prop. Refs STCOM-1334.
* Render string for `FilterGroups` clear button. Refs STCOM-1337.
* Refactored away from `findDOMNode` in codebase for React 19 preparation. Refs STCOM-1343.
* AdvancedSearch - added a wrapping div to ref for a HotKeys ref. Refs STCOM-1343.
* `<MultiColumnList>` components `<CellMeasurer>` and `<RowMeasurer>` updated to use refs vs `findDOMNode`. Refs STCOM-1343.
* `<AccordionHeaders>` are wrapped with a div for use as a HotKeys ref. Refs STCOM-1343.

## [12.1.0](https://github.com/folio-org/stripes-components/tree/v12.1.0) (2024-03-12)
[Full Changelog](https://github.com/folio-org/stripes-components/compare/v12.0.0...v12.1.0)
Expand Down
11 changes: 7 additions & 4 deletions lib/Accordion/Accordion.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const propTypes = {
disabled: PropTypes.bool,
displayWhenClosed: PropTypes.element, // eslint-disable-line react/no-unused-prop-types
displayWhenOpen: PropTypes.element, // eslint-disable-line react/no-unused-prop-types
header: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.func]),
header: PropTypes.elementType,
headerProps: PropTypes.object,
id: PropTypes.string,
label: PropTypes.oneOfType([ // eslint-disable-line react/no-unused-prop-types
Expand Down Expand Up @@ -120,7 +120,7 @@ const Accordion = (props) => {
const contentId = useRef(contentIdProp || uniqueId('accordion')).current;
const trackingId = useRef(id || uniqueId('acc')).current;
const labelId = useRef(headerProps?.labelId || `accordion-toggle-button-${trackingId}`).current;

const headerRef = useRef(null);

const getRef = useRef(() => toggle.current).current;
const [isOpen, updateOpen] = useState(open || !closedByDefault);
Expand All @@ -139,7 +139,7 @@ const Accordion = (props) => {
if (!focused) {
updateFocused(true);
updateZIndex((cur) => {
if(content.current.matches(':focus-within')) {
if (content.current.matches(':focus-within')) {
// we assign one greater than the highest z-index value.
const highest = getHighestStackOrder() + 1;
if (cur !== highest) {
Expand Down Expand Up @@ -215,9 +215,12 @@ const Accordion = (props) => {
id={`${trackingId}-hotkeys`}
keyMap={toggleKeyMap}
handlers={toggleKeyHandlers}
attach={headerRef.current}
noWrapper
>
{headerElement}
<div ref={headerRef} style={{ width: '100%', display: 'flex' }}>
{headerElement}
</div>
</HotKeys>
<div className={getWrapClass(isOpen)} style={{ zIndex }}>
<div
Expand Down
8 changes: 4 additions & 4 deletions lib/Accordion/headers/DefaultAccordionHeader.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, {forwardRef} from 'react';
import PropTypes from 'prop-types';
import Headline from '../../Headline';
import Icon from '../../Icon';
Expand All @@ -19,7 +19,7 @@ const propTypes = {
toggleRef: PropTypes.func,
};

const DefaultAccordionHeader = ({ headerProps = { headingLevel: 3 }, ...rest }) => {
const DefaultAccordionHeader = forwardRef(({ headerProps = { headingLevel: 3 }, ...rest }, ref) => {
const props = { headerProps, ...rest };
function handleHeaderClick(e) {
const { id, label } = props;
Expand Down Expand Up @@ -52,7 +52,7 @@ const DefaultAccordionHeader = ({ headerProps = { headingLevel: 3 }, ...rest })
}

return (
<div className={css.headerWrapper}>
<div className={css.headerWrapper} ref={ref}>
<div className={`${css.header} ${css.default}`}>
<Headline size="medium" margin="none" tag={headingLevel ? `h${headingLevel}` : 'div'} block>
<button
Expand Down Expand Up @@ -84,7 +84,7 @@ const DefaultAccordionHeader = ({ headerProps = { headingLevel: 3 }, ...rest })
{ headerRight }
</div>
);
};
});

DefaultAccordionHeader.propTypes = propTypes;

Expand Down
10 changes: 5 additions & 5 deletions lib/Accordion/headers/FilterAccordionHeader.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useRef, useImperativeHandle } from 'react';
import { useCallback, useRef, useImperativeHandle, forwardRef } from 'react';
import { useIntl } from 'react-intl';
import PropTypes from 'prop-types';
import classNames from 'classnames';
Expand All @@ -7,7 +7,7 @@ import Headline from '../../Headline';
import IconButton from '../../IconButton';
import css from '../Accordion.css';

const FilterAccordionHeader = ({
const FilterAccordionHeader = forwardRef(({
autoFocus,
contentId,
disabled,
Expand All @@ -24,7 +24,7 @@ const FilterAccordionHeader = ({
onToggle,
open,
toggleRef: toggleRefProp,
}) => {
}, ref) => {
const { formatMessage } = useIntl();
const toggleRef = useRef(null);
useImperativeHandle(toggleRefProp, () => toggleRef.current);
Expand Down Expand Up @@ -56,7 +56,7 @@ const FilterAccordionHeader = ({
);

return (
<div className={css.headerWrapper}>
<div className={css.headerWrapper} ref={ref}>
<Headline size="small" tag={`h${headingLevel}`} block={!clearButtonVisible}>
<button
type="button"
Expand Down Expand Up @@ -99,7 +99,7 @@ const FilterAccordionHeader = ({
{open ? displayWhenOpen : displayWhenClosed}
</div>
);
}
});

FilterAccordionHeader.propTypes = {
autoFocus: PropTypes.bool,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo } from 'react';
import { useMemo, useRef } from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';

Expand All @@ -23,6 +23,7 @@ const AdvancedSearchModal = ({
onSearch,
onCancel,
}) => {
const shortcutsRef = useRef(null);
const intl = useIntl();
const modalTitle = intl.formatMessage({ id: 'stripes-components.advancedSearch.title' });

Expand Down Expand Up @@ -75,8 +76,11 @@ const AdvancedSearchModal = ({
<HotKeys
keyMap={hotKeys}
handlers={handlers}
attach={shortcutsRef.current}
>
<div ref={shortcutsRef}>
{children}
</div>
</HotKeys>
</Modal>
);
Expand Down
11 changes: 9 additions & 2 deletions lib/Commander/CommandList.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component } from 'react';
import React, { Component, createRef } from 'react';
import PropTypes from 'prop-types';
import { HotKeys } from '../HotKeys';

Expand All @@ -16,6 +16,11 @@ class CommandList extends Component {
id: PropTypes.string,
};

constructor(props) {
super(props);
this.shortcutRef = createRef(null);
}

generateHotKeysProps() {
const {
commands
Expand Down Expand Up @@ -43,8 +48,10 @@ class CommandList extends Component {
} = this.props;

return (
<HotKeys id={id} {...this.generateHotKeysProps()} noWrapper>
<HotKeys id={id} {...this.generateHotKeysProps()} attach={this.shortcutRef.current} noWrapper>
<div ref={this.shortcutRef}>
{children}
</div>
</HotKeys>
);
}
Expand Down
19 changes: 10 additions & 9 deletions lib/MetaSection/MetaAccordionHeader.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Icon from '../Icon';
Expand All @@ -16,11 +16,12 @@ const propTypes = {
open: PropTypes.bool,
};

const MetaAccordionHeader = ({ headingLevel = 4, ...rest }) => {
const MetaAccordionHeader = forwardRef(({ headingLevel = 4, ...rest }, ref) => {
const props = { headingLevel, ...rest };
let toggleElem = null;
let labelElem = null;
let containerElem = null;
const toggleElem = useRef(null);
const labelElem = useRef(null);
const containerElem = useRef(null);
useImperativeHandle(ref, (elem) => { containerElem.current = elem });

function handleHeaderClick(e) {
const { id, label } = props;
Expand All @@ -41,7 +42,7 @@ const MetaAccordionHeader = ({ headingLevel = 4, ...rest }) => {
const { label, open, displayWhenOpen, displayWhenClosed, contentId } = props;

return (
<div className={css.headerWrapper} ref={(ref) => { containerElem = ref; }}>
<div className={css.headerWrapper} ref={containerElem}>
<Headline className="sr-only" size="small" margin="none" tag={`h${props.headingLevel}`}>
<FormattedMessage id="stripes-components.metaSection.screenReaderLabel" />
</Headline>
Expand All @@ -53,19 +54,19 @@ const MetaAccordionHeader = ({ headingLevel = 4, ...rest }) => {
onKeyPress={handleKeyPress}
aria-controls={contentId}
aria-expanded={open}
ref={(ref) => { toggleElem = ref; }}
ref={toggleElem}
>
<div className={css.metaHeader}>
<Icon size="small" icon={props.open ? 'caret-up' : 'caret-down'} />
<div ref={(ref) => { labelElem = ref; }}>
<div ref={labelElem}>
<div className={css.metaHeaderLabel}>{label}</div>
</div>
</div>
</button>
{open ? displayWhenOpen : displayWhenClosed}
</div>
);
};
});

MetaAccordionHeader.propTypes = propTypes;

Expand Down
25 changes: 17 additions & 8 deletions lib/MultiColumnList/CellMeasurer.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react';
import React, { createRef } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import noop from 'lodash/noop';

class CellMeasurer extends React.Component {
static propTypes = {
children: PropTypes.node,
children: PropTypes.func,
columnName: PropTypes.string,
onMeasure: PropTypes.func,
rowIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
Expand All @@ -17,6 +17,11 @@ class CellMeasurer extends React.Component {
onMeasure: noop
}

constructor(props) {
super(props);
this.element = createRef(null);
}

componentDidMount() {
this.measureNode();
}
Expand All @@ -25,22 +30,26 @@ class CellMeasurer extends React.Component {
this.measureNode();
}

element = React.createRef();

measureNode = () => {
const { widthCache, rowIndex, shouldMeasure, onMeasure, columnName } = this.props;
if (shouldMeasure) {
const elem = ReactDOM.findDOMNode(this); // eslint-disable-line react/no-find-dom-node
if (elem && elem.offsetParent !== null) {
const width = elem.offsetWidth;
// const elem = ReactDOM.findDOMNode(this); // eslint-disable-line react/no-find-dom-node
if (this.element.current && this.element.current.offsetParent !== null) {
const width = this.element.current.offsetWidth;
widthCache.set(rowIndex, width);
if (onMeasure) onMeasure(rowIndex, columnName, width);
}
}
};

render() {
return this.props.children;
const { children } = this.props;

if (typeof children === 'function') {
return children(this.element);
}

return null;
}
}

Expand Down
63 changes: 32 additions & 31 deletions lib/MultiColumnList/MCLRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ class MCLRenderer extends React.Component {
this.endOfList = React.createRef();
this.pageButton = React.createRef();
this.status = React.createRef();
this.shortcutsRef = React.createRef();

this.headerHeight = 0;
this.paginationHeight = 40;
Expand Down Expand Up @@ -1142,19 +1143,13 @@ class MCLRenderer extends React.Component {
measure={!staticBody && (columns.length <= Object.keys(columnWidths).length)}
onMeasure={this.checkForMaxHeight}
key={`row-measurer-${localRowIndex}-${this.keyId}`}
className={css.mclRowFormatterContainer}
onClick={onRowClick ? this.handleRowClick : undefined}
onFocus={(e) => this.handleRowFocus(e, localRowIndex + 2)}
onBlur={this.handleRowBlur}
positionedRowStyle={positionedRowStyle}
>
<div // eslint-disable-line
data-row-index={`row-${localRowIndex}`}
className={css.mclRowFormatterContainer}
aria-rowindex={localRowIndex + 2}
onClick={onRowClick ? this.handleRowClick : undefined}
onFocus={(e) => this.handleRowFocus(e, localRowIndex + 2)}
onBlur={this.handleRowBlur}
style={positionedRowStyle}
role="row"
>
{rowFormatter(injectedRowProps)}
</div>
{rowFormatter(injectedRowProps)}
</RowMeasurer>
);
}}
Expand Down Expand Up @@ -1476,14 +1471,17 @@ class MCLRenderer extends React.Component {
onMeasure={this.maybeUpdateColumnWidths}
key={`cell-${col}-row-${rowIndex}-${this.keyId}`}
>
<div
role="gridcell"
key={`${col}-${rowIndex}-${this.keyId}`}
className={classnames(css.mclCell, showOverflow, cellStyleClass, stickyClasses)}
style={cellStyle}
>
{value}
</div>
{(elementRef) => (
<div
role="gridcell"
key={`${col}-${rowIndex}-${this.keyId}`}
className={classnames(css.mclCell, showOverflow, cellStyleClass, stickyClasses)}
style={cellStyle}
ref={elementRef}
>
{value}
</div>
)}
</CellMeasurer>
));
});
Expand Down Expand Up @@ -1688,15 +1686,18 @@ class MCLRenderer extends React.Component {
columnName={header}
shouldMeasure={shouldMeasure}
>
<div
role="columnheader"
className={headerCellClass}
aria-sort={isSortHeader ? sortDirection : 'none'}
style={headerStyle}
id={columnId}
>
{headerInner}
</div>
{(elemRef) => (
<div
role="columnheader"
className={headerCellClass}
aria-sort={isSortHeader ? sortDirection : 'none'}
style={headerStyle}
id={columnId}
ref={elemRef}
>
{headerInner}
</div>
)}
</CellMeasurer>
);
});
Expand Down Expand Up @@ -1929,8 +1930,8 @@ class MCLRenderer extends React.Component {
: css.mclRowContainer;

return (
<HotKeys handlers={this.handlers} noWrapper>
<div className={css.mclContainer} style={this.getOuterElementStyle()}>
<HotKeys handlers={this.handlers} attach={this.shortcutsRef} noWrapper>
<div className={css.mclContainer} ref={this.shortcutsRef} style={this.getOuterElementStyle()}>
<SRStatus ref={this.status} />
<div
tabIndex="0" // eslint-disable-line jsx-a11y/no-noninteractive-tabindex
Expand Down
Loading

0 comments on commit 5792577

Please sign in to comment.