Skip to content

Commit

Permalink
feat(tile-select): use React.forwardRef and deprecate inputRef prop
Browse files Browse the repository at this point in the history
allow a `ref` to be passed directly to the component, removing the need for the `inputRef` prop
which is now deprecated

re #5564
  • Loading branch information
robinzigmond committed Jan 4, 2023
1 parent 9673259 commit 7ff69a2
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 151 deletions.
320 changes: 169 additions & 151 deletions src/components/tile-select/tile-select.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import createGuid from "../../__internal__/utils/helpers/guid";
import Button from "../button";
import Box from "../box";
import Accordion from "./__internal__/accordion";
import Logger from "../../__internal__/utils/logger";

import {
StyledTileSelectContainer,
Expand All @@ -31,166 +32,183 @@ const marginPropTypes = filterStyledSystemMarginProps(

const checkPropTypeIsNode = (prop) => typeof prop !== "string" && { as: "div" };

const TileSelect = ({
onChange,
onBlur,
onFocus,
value,
name,
checked,
className,
disabled,
title,
subtitle,
description,
titleAdornment,
type,
id,
customActionButton,
actionButtonAdornment,
footer,
prefixAdornment,
additionalInformation,
accordionContent,
accordionControl,
accordionExpanded,
...rest
}) => {
const l = useLocale();
const [hasFocus, setHasFocus] = useState(false);
const handleDeselect = () =>
onChange({
target: {
...(name && { name }),
...(id && { id }),
value: null,
checked: false,
},
});

const renderActionButton = () => (
<StyledDeselectWrapper hasActionAdornment={!!actionButtonAdornment}>
{customActionButton && customActionButton(handleDeselect)}
{!customActionButton && checked && (
<Button
buttonType="tertiary"
size="small"
disabled={disabled}
onClick={handleDeselect}
>
{l.tileSelect.deselect()}
</Button>
)}
{actionButtonAdornment}
</StyledDeselectWrapper>
);

useEffect(() => {
if (disabled && hasFocus) {
setHasFocus(false);
let deprecateInputRefWarnTriggered = false;

const TileSelect = React.forwardRef(
(
{
onChange,
onBlur,
onFocus,
value,
name,
checked,
className,
disabled,
title,
subtitle,
description,
titleAdornment,
type,
id,
customActionButton,
actionButtonAdornment,
footer,
prefixAdornment,
additionalInformation,
accordionContent,
accordionControl,
accordionExpanded,
inputRef,
...rest
},
ref
) => {
const l = useLocale();
const [hasFocus, setHasFocus] = useState(false);
const handleDeselect = () =>
onChange({
target: {
...(name && { name }),
...(id && { id }),
value: null,
checked: false,
},
});

if (!deprecateInputRefWarnTriggered && inputRef) {
deprecateInputRefWarnTriggered = true;
Logger.deprecate(
"The `inputRef` prop in `TileSelect` component is deprecated and will soon be removed. Please use `ref` instead."
);
}
}, [disabled, hasFocus]);

const guid = useRef(createGuid());
const contentId = `AccordionContent_${guid.current}`;
const controlId = `AccordionControl_${guid.current}`;

return (
<StyledTileSelectContainer
checked={checked}
className={className}
disabled={disabled}
{...tagComponent("tile-select", rest)}
{...filterStyledSystemMarginProps(rest)}
>
<StyledFocusWrapper hasFocus={hasFocus} checked={checked}>
<StyledTileSelectInput
onChange={onChange}
onBlur={(ev) => {
setHasFocus(false);
/* istanbul ignore else */
if (onBlur) onBlur(ev);
}}
onFocus={(ev) => {
setHasFocus(true);
/* istanbul ignore else */
if (onFocus) onFocus(ev);
}}
checked={checked}
name={name}
type={type}
value={value}
disabled={disabled}
aria-checked={checked}
id={id}
{...rest}
/>
<StyledTileSelect disabled={disabled} checked={checked}>
<Box
display="flex"
justifyContent="space-between"
flexDirection="row-reverse"

const renderActionButton = () => (
<StyledDeselectWrapper hasActionAdornment={!!actionButtonAdornment}>
{customActionButton && customActionButton(handleDeselect)}
{!customActionButton && checked && (
<Button
buttonType="tertiary"
size="small"
disabled={disabled}
onClick={handleDeselect}
>
{(customActionButton || checked) && renderActionButton()}
<Box flexGrow="1">
<StyledTitleContainer>
{title && (
<StyledTitle {...checkPropTypeIsNode(title)}>
{title}
</StyledTitle>
)}
{l.tileSelect.deselect()}
</Button>
)}
{actionButtonAdornment}
</StyledDeselectWrapper>
);

{subtitle && (
<StyledSubtitle {...checkPropTypeIsNode(subtitle)}>
{subtitle}
</StyledSubtitle>
)}
useEffect(() => {
if (disabled && hasFocus) {
setHasFocus(false);
}
}, [disabled, hasFocus]);

const guid = useRef(createGuid());
const contentId = `AccordionContent_${guid.current}`;
const controlId = `AccordionControl_${guid.current}`;

return (
<StyledTileSelectContainer
checked={checked}
className={className}
disabled={disabled}
{...tagComponent("tile-select", rest)}
{...filterStyledSystemMarginProps(rest)}
>
<StyledFocusWrapper hasFocus={hasFocus} checked={checked}>
<StyledTileSelectInput
onChange={onChange}
onBlur={(ev) => {
setHasFocus(false);
/* istanbul ignore else */
if (onBlur) onBlur(ev);
}}
onFocus={(ev) => {
setHasFocus(true);
/* istanbul ignore else */
if (onFocus) onFocus(ev);
}}
checked={checked}
name={name}
type={type}
value={value}
disabled={disabled}
aria-checked={checked}
id={id}
inputRef={inputRef}
ref={ref}
{...rest}
/>
<StyledTileSelect disabled={disabled} checked={checked}>
<Box
display="flex"
justifyContent="space-between"
flexDirection="row-reverse"
>
{(customActionButton || checked) && renderActionButton()}
<Box flexGrow="1">
<StyledTitleContainer>
{title && (
<StyledTitle {...checkPropTypeIsNode(title)}>
{title}
</StyledTitle>
)}

{titleAdornment && (
<StyledAdornment
hasAdditionalInformation={!!additionalInformation}
{subtitle && (
<StyledSubtitle {...checkPropTypeIsNode(subtitle)}>
{subtitle}
</StyledSubtitle>
)}

{titleAdornment && (
<StyledAdornment
hasAdditionalInformation={!!additionalInformation}
>
{titleAdornment}
</StyledAdornment>
)}
</StyledTitleContainer>
{additionalInformation && <div>{additionalInformation}</div>}
<StyledDescription {...checkPropTypeIsNode(description)}>
{description}
</StyledDescription>
{footer && <StyledFooterWrapper>{footer}</StyledFooterWrapper>}
{accordionContent && accordionControl && (
<StyledAccordionFooterWrapper
accordionExpanded={accordionExpanded}
>
{titleAdornment}
</StyledAdornment>
{accordionControl(controlId, contentId)}
</StyledAccordionFooterWrapper>
)}
</StyledTitleContainer>
{additionalInformation && <div>{additionalInformation}</div>}
<StyledDescription {...checkPropTypeIsNode(description)}>
{description}
</StyledDescription>
{footer && <StyledFooterWrapper>{footer}</StyledFooterWrapper>}
{accordionContent && accordionControl && (
<StyledAccordionFooterWrapper
accordionExpanded={accordionExpanded}
</Box>
{prefixAdornment && (
<Box
data-element="prefix-adornment"
mr={3}
opacity={disabled ? "0.3" : undefined}
>
{accordionControl(controlId, contentId)}
</StyledAccordionFooterWrapper>
{prefixAdornment}
</Box>
)}
</Box>
{prefixAdornment && (
<Box
data-element="prefix-adornment"
mr={3}
opacity={disabled ? "0.3" : undefined}
>
{prefixAdornment}
</Box>
)}
</Box>
</StyledTileSelect>
{accordionContent && (
<Accordion
contentId={contentId}
controlId={controlId}
expanded={accordionExpanded}
>
{accordionContent}
</Accordion>
)}
</StyledFocusWrapper>
</StyledTileSelectContainer>
);
};
</StyledTileSelect>
{accordionContent && (
<Accordion
contentId={contentId}
controlId={controlId}
expanded={accordionExpanded}
>
{accordionContent}
</Accordion>
)}
</StyledFocusWrapper>
</StyledTileSelectContainer>
);
}
);

TileSelect.defaultProps = {
checked: false,
Expand Down
41 changes: 41 additions & 0 deletions src/components/tile-select/tile-select.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
assertStyleMatch,
testStyledSystemMargin,
} from "../../__spec_helper__/test-utils";
import Logger from "../../__internal__/utils/logger";

const radioValues = ["val1", "val2", "val3"];

Expand Down Expand Up @@ -429,6 +430,46 @@ describe("TileSelect", () => {
);
});
});

describe("refs", () => {
it("should display deprecation warning when the inputRef prop is used", () => {
const loggerSpy = jest.spyOn(Logger, "deprecate");
const ref = () => {};

render({ inputRef: ref });

expect(loggerSpy).toHaveBeenCalledWith(
"The `inputRef` prop in `TileSelect` component is deprecated and will soon be removed. Please use `ref` instead."
);

wrapper.setProps({ prop1: true });
expect(loggerSpy).toHaveBeenCalledTimes(1);
loggerSpy.mockRestore();
});

it("accepts ref as a ref object", () => {
const ref = { current: null };
render({ ref });

expect(ref.current).toBe(wrapper.find("input").getDOMNode());
});

it("accepts ref as a ref callback", () => {
const ref = jest.fn();
render({ ref });

expect(ref).toHaveBeenCalledWith(wrapper.find("input").getDOMNode());
});

it("sets ref to empty after unmount", () => {
const ref = { current: null };
render({ ref });

wrapper.unmount();

expect(ref.current).toBe(null);
});
});
});

describe("TileSelectGroup", () => {
Expand Down

0 comments on commit 7ff69a2

Please sign in to comment.