Skip to content

Commit b4c07db

Browse files
committed
Stop using forwardRef
1 parent 7ab5ee0 commit b4c07db

28 files changed

+1998
-2089
lines changed

src/Accordion.tsx

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import React, { forwardRef, memo, ReactNode, useId, useState } from "react";
3+
import React, { memo, ReactNode, useId, useState, ForwardedRef } from "react";
44
import { assert } from "tsafe";
55
import type { Equals } from "tsafe";
66
import { fr } from "./fr";
@@ -16,6 +16,7 @@ export namespace AccordionProps {
1616
titleAs?: `h${2 | 3 | 4 | 5 | 6}`;
1717
label: ReactNode;
1818
classes?: Partial<Record<"root" | "accordion" | "title" | "collapse", string>>;
19+
ref?: ForwardedRef<HTMLDivElement>;
1920
children: NonNullable<ReactNode>;
2021
};
2122

@@ -39,54 +40,53 @@ export namespace AccordionProps {
3940
}
4041

4142
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-accordion> */
42-
export const Accordion = memo(
43-
forwardRef<HTMLDivElement, AccordionProps>((props, ref) => {
44-
const {
45-
className,
46-
titleAs: HtmlTitleTag = "h3",
47-
label,
48-
classes = {},
49-
children,
50-
expanded: expandedProp,
51-
defaultExpanded = false,
52-
onExpandedChange,
53-
...rest
54-
} = props;
43+
export const Accordion = memo((props: AccordionProps) => {
44+
const {
45+
className,
46+
titleAs: HtmlTitleTag = "h3",
47+
label,
48+
classes = {},
49+
ref,
50+
children,
51+
expanded: expandedProp,
52+
defaultExpanded = false,
53+
onExpandedChange,
54+
...rest
55+
} = props;
5556

56-
assert<Equals<keyof typeof rest, never>>();
57+
assert<Equals<keyof typeof rest, never>>();
5758

58-
const accordionId = `accordion-${useId()}`;
59+
const accordionId = `accordion-${useId()}`;
5960

60-
const [expandedState, setExpandedState] = useState(defaultExpanded);
61+
const [expandedState, setExpandedState] = useState(defaultExpanded);
6162

62-
const value = expandedProp ? expandedProp : expandedState;
63+
const value = expandedProp ? expandedProp : expandedState;
6364

64-
const onExtendButtonClick = useConstCallback(
65-
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
66-
setExpandedState(!value);
67-
onExpandedChange?.(!value, event);
68-
}
69-
);
65+
const onExtendButtonClick = useConstCallback(
66+
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
67+
setExpandedState(!value);
68+
onExpandedChange?.(!value, event);
69+
}
70+
);
7071

71-
return (
72-
<section className={cx(fr.cx("fr-accordion"), className)} ref={ref} {...rest}>
73-
<HtmlTitleTag className={cx(fr.cx("fr-accordion__title"), classes.title)}>
74-
<button
75-
className={fr.cx("fr-accordion__btn")}
76-
aria-expanded={value}
77-
aria-controls={accordionId}
78-
onClick={onExtendButtonClick}
79-
>
80-
{label}
81-
</button>
82-
</HtmlTitleTag>
83-
<div className={cx(fr.cx("fr-collapse"), classes.collapse)} id={accordionId}>
84-
{children}
85-
</div>
86-
</section>
87-
);
88-
})
89-
);
72+
return (
73+
<section className={cx(fr.cx("fr-accordion"), className)} ref={ref}>
74+
<HtmlTitleTag className={cx(fr.cx("fr-accordion__title"), classes.title)}>
75+
<button
76+
className={fr.cx("fr-accordion__btn")}
77+
aria-expanded={value}
78+
aria-controls={accordionId}
79+
onClick={onExtendButtonClick}
80+
>
81+
{label}
82+
</button>
83+
</HtmlTitleTag>
84+
<div className={cx(fr.cx("fr-collapse"), classes.collapse)} id={accordionId}>
85+
{children}
86+
</div>
87+
</section>
88+
);
89+
});
9090

9191
Accordion.displayName = symToStr({ Accordion });
9292

src/Alert.tsx

Lines changed: 93 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

3-
import React, { memo, forwardRef, useState, useEffect, useRef } from "react";
4-
import type { ReactNode } from "react";
3+
import React, { memo, useState, useEffect, useRef } from "react";
4+
import type { ReactNode, ForwardedRef } from "react";
55
import type { FrClassName } from "./fr/generatedFromCss/classNames";
66
import { symToStr } from "tsafe/symToStr";
77
import { fr } from "./fr";
@@ -16,6 +16,7 @@ export type AlertProps = {
1616
severity: AlertProps.Severity;
1717
/** Default h3 */
1818
as?: `h${2 | 3 | 4 | 5 | 6}`;
19+
ref?: ForwardedRef<HTMLDivElement>;
1920
classes?: Partial<Record<"root" | "title" | "description" | "close", string>>;
2021
} & (AlertProps.DefaultSize | AlertProps.Small) &
2122
(AlertProps.NonClosable | AlertProps.Closable);
@@ -67,105 +68,103 @@ export namespace AlertProps {
6768
}
6869

6970
/** @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-alert> */
70-
export const Alert = memo(
71-
forwardRef<HTMLDivElement, AlertProps>((props, ref) => {
72-
const {
73-
className,
74-
severity,
75-
as: HtmlTitleTag = "h3",
76-
classes = {},
77-
small: isSmall,
78-
title,
79-
description,
80-
closable: isClosable = false,
81-
isClosed: props_isClosed,
82-
onClose,
83-
...rest
84-
} = props;
85-
86-
assert<Equals<keyof typeof rest, never>>();
87-
88-
const [isClosed, setIsClosed] = useState(props_isClosed ?? false);
89-
90-
const [buttonElement, setButtonElement] = useState<HTMLButtonElement | null>(null);
91-
92-
const refShouldButtonGetFocus = useRef(false);
93-
const refShouldSetRole = useRef(false);
94-
95-
useEffect(() => {
96-
if (props_isClosed === undefined) {
97-
return;
98-
}
99-
setIsClosed(isClosed => {
100-
if (isClosed && !props_isClosed) {
101-
refShouldButtonGetFocus.current = true;
102-
refShouldSetRole.current = true;
103-
}
104-
105-
return props_isClosed;
106-
});
107-
}, [props_isClosed]);
108-
109-
useEffect(() => {
110-
if (!refShouldButtonGetFocus.current) {
111-
return;
112-
}
113-
114-
if (buttonElement === null) {
115-
//NOTE: This should not be reachable
116-
return;
71+
export const Alert = memo((props: AlertProps) => {
72+
const {
73+
className,
74+
severity,
75+
as: HtmlTitleTag = "h3",
76+
classes = {},
77+
ref,
78+
small: isSmall,
79+
title,
80+
description,
81+
closable: isClosable = false,
82+
isClosed: props_isClosed,
83+
onClose,
84+
...rest
85+
} = props;
86+
87+
assert<Equals<keyof typeof rest, never>>();
88+
89+
const [isClosed, setIsClosed] = useState(props_isClosed ?? false);
90+
91+
const [buttonElement, setButtonElement] = useState<HTMLButtonElement | null>(null);
92+
93+
const refShouldButtonGetFocus = useRef(false);
94+
const refShouldSetRole = useRef(false);
95+
96+
useEffect(() => {
97+
if (props_isClosed === undefined) {
98+
return;
99+
}
100+
setIsClosed(isClosed => {
101+
if (isClosed && !props_isClosed) {
102+
refShouldButtonGetFocus.current = true;
103+
refShouldSetRole.current = true;
117104
}
118105

119-
refShouldButtonGetFocus.current = false;
120-
buttonElement.focus();
121-
}, [buttonElement]);
122-
123-
const onCloseButtonClick = useConstCallback(() => {
124-
if (props_isClosed === undefined) {
125-
//Uncontrolled
126-
setIsClosed(true);
127-
onClose?.();
128-
} else {
129-
//Controlled
130-
onClose();
131-
}
106+
return props_isClosed;
132107
});
108+
}, [props_isClosed]);
109+
110+
useEffect(() => {
111+
if (!refShouldButtonGetFocus.current) {
112+
return;
113+
}
133114

134-
const { t } = useTranslation();
115+
if (buttonElement === null) {
116+
//NOTE: This should not be reachable
117+
return;
118+
}
135119

136-
if (isClosed) {
137-
return null;
120+
refShouldButtonGetFocus.current = false;
121+
buttonElement.focus();
122+
}, [buttonElement]);
123+
124+
const onCloseButtonClick = useConstCallback(() => {
125+
if (props_isClosed === undefined) {
126+
//Uncontrolled
127+
setIsClosed(true);
128+
onClose?.();
129+
} else {
130+
//Controlled
131+
onClose();
138132
}
133+
});
134+
135+
const { t } = useTranslation();
139136

140-
return (
141-
<div
142-
className={cx(
143-
fr.cx("fr-alert", `fr-alert--${severity}`, { "fr-alert--sm": isSmall }),
144-
classes.root,
145-
className
146-
)}
147-
{...(refShouldSetRole.current && { "role": "alert" })}
148-
ref={ref}
149-
{...rest}
150-
>
151-
<HtmlTitleTag className={cx(fr.cx("fr-alert__title"), classes.title)}>
152-
{title}
153-
</HtmlTitleTag>
154-
<p className={classes.description}>{description}</p>
155-
{/* TODO: Use our button once we have one */}
156-
{isClosable && (
157-
<button
158-
ref={setButtonElement}
159-
className={cx(fr.cx("fr-link--close", "fr-link"), classes.close)}
160-
onClick={onCloseButtonClick}
161-
>
162-
{t("hide message")}
163-
</button>
164-
)}
165-
</div>
166-
);
167-
})
168-
);
137+
if (isClosed) {
138+
return null;
139+
}
140+
141+
return (
142+
<div
143+
className={cx(
144+
fr.cx("fr-alert", `fr-alert--${severity}`, { "fr-alert--sm": isSmall }),
145+
classes.root,
146+
className
147+
)}
148+
{...(refShouldSetRole.current && { "role": "alert" })}
149+
ref={ref}
150+
>
151+
<HtmlTitleTag className={cx(fr.cx("fr-alert__title"), classes.title)}>
152+
{title}
153+
</HtmlTitleTag>
154+
<p className={classes.description}>{description}</p>
155+
{/* TODO: Use our button once we have one */}
156+
{isClosable && (
157+
<button
158+
ref={setButtonElement}
159+
className={cx(fr.cx("fr-link--close", "fr-link"), classes.close)}
160+
onClick={onCloseButtonClick}
161+
>
162+
{t("hide message")}
163+
</button>
164+
)}
165+
</div>
166+
);
167+
});
169168

170169
Alert.displayName = symToStr({ Alert });
171170

0 commit comments

Comments
 (0)