Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

STCOM-1343 refactor away from findDOMNode #2352

Merged
merged 11 commits into from
Sep 30, 2024
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
Loading