Skip to content

Commit 12dc5ba

Browse files
Ensure ref and inert usage is React 18 compatible (#464)
1 parent 67c2517 commit 12dc5ba

10 files changed

Lines changed: 87 additions & 58 deletions

File tree

.changeset/fresh-mirrors-jam.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'playroom': patch
3+
---
4+
5+
Ensure `ref` and `inert` usage is React 18 compatible

src/components/EditorActions/EditorActions.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import {
44
Ellipsis,
55
type LucideIcon,
66
} from 'lucide-react';
7-
import { useContext, useRef, type ButtonHTMLAttributes } from 'react';
7+
import {
8+
forwardRef,
9+
useContext,
10+
useRef,
11+
type ButtonHTMLAttributes,
12+
} from 'react';
813

914
import { useEditor } from '../../contexts/EditorContext';
1015
import { StoreContext } from '../../contexts/StoreContext';
@@ -38,7 +43,10 @@ interface EditorActionButtonProps
3843
hideTooltip?: boolean;
3944
}
4045

41-
const EditorActionButton = (props: EditorActionButtonProps) => {
46+
const EditorActionButton = forwardRef<
47+
HTMLButtonElement,
48+
EditorActionButtonProps
49+
>((props, ref) => {
4250
const {
4351
onClick,
4452
name,
@@ -77,6 +85,7 @@ const EditorActionButton = (props: EditorActionButtonProps) => {
7785
trigger={
7886
<button
7987
{...restProps}
88+
ref={ref}
8089
onClick={handleClick}
8190
aria-disabled={disabled}
8291
className={styles.button}
@@ -88,7 +97,7 @@ const EditorActionButton = (props: EditorActionButtonProps) => {
8897
}
8998
/>
9099
);
91-
};
100+
});
92101

93102
const menuSideOffset = 2;
94103
const overflowCommands = editorCommandList.filter(

src/components/Header/Header.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
type ComponentProps,
2929
type ReactNode,
3030
useContext,
31+
useEffect,
3132
useRef,
3233
useState,
3334
} from 'react';
@@ -384,13 +385,25 @@ export const Header = () => {
384385
const [{ code, selectedThemes }] = useContext(StoreContext);
385386
const { copying, onCopyClick } = useCopy();
386387
const [shareOpen, setShareOpen] = useState(false);
388+
const sharedActionsRef = useRef<HTMLDivElement>(null);
387389

388390
const hasCode = code.trim().length > 0;
389391

390392
const previewUrl = usePreviewUrl(
391393
themesEnabled ? selectedThemes[0] : undefined
392394
);
393395

396+
// Remove in favour of direct DOM attribute when we drop React 18 support
397+
useEffect(() => {
398+
if (sharedActionsRef.current) {
399+
if (!hasCode) {
400+
sharedActionsRef.current.setAttribute('inert', '');
401+
} else {
402+
sharedActionsRef.current.removeAttribute('inert');
403+
}
404+
}
405+
}, [hasCode]);
406+
394407
return (
395408
<Box className={styles.root}>
396409
<div className={styles.menuContainer}>
@@ -400,11 +413,11 @@ export const Header = () => {
400413
<SharedTooltipContext>
401414
<div className={styles.actionsContainer}>
402415
<div
416+
ref={sharedActionsRef}
403417
className={clsx({
404418
[styles.shareActions]: true,
405419
[styles.shareActionsReady]: hasCode,
406420
})}
407-
inert={!hasCode ? true : undefined}
408421
>
409422
<div className={styles.segmentedGroup}>
410423
<button

src/components/Menu/Menu.tsx

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -39,49 +39,48 @@ type MenuItemProps = Omit<
3939
icon?: LucideIcon;
4040
disabledReason?: string;
4141
};
42-
export const MenuItem = ({
43-
children,
44-
shortcut,
45-
icon: Icon,
46-
disabled,
47-
disabledReason,
48-
...restProps
49-
}: MenuItemProps) => {
50-
const isSubMenuTrigger = useContext(SubMenuTriggerContext);
42+
export const MenuItem = forwardRef<HTMLButtonElement, MenuItemProps>(
43+
(
44+
{ children, shortcut, icon: Icon, disabled, disabledReason, ...restProps },
45+
ref
46+
) => {
47+
const isSubMenuTrigger = useContext(SubMenuTriggerContext);
5148

52-
const item = (
53-
<BaseUIMenu.Item
54-
className={styles.item}
55-
disabled={disabled}
56-
closeOnClick={isSubMenuTrigger ? false : undefined}
57-
{...restProps}
58-
>
59-
<span className={styles.itemLeft}>
60-
{Icon ? <Icon size={menuIconSize} /> : null}
61-
<Text tone={disabled ? 'secondary' : undefined} truncate>
62-
{children}
63-
</Text>
64-
</span>
65-
{shortcut && (
66-
<Text tone="secondary">
67-
<KeyboardShortcut shortcut={shortcut} />
68-
</Text>
69-
)}
70-
{isSubMenuTrigger && !disabled ? <ChevronRight size={12} /> : null}
71-
</BaseUIMenu.Item>
72-
);
49+
const item = (
50+
<BaseUIMenu.Item
51+
ref={ref}
52+
className={styles.item}
53+
disabled={disabled}
54+
closeOnClick={isSubMenuTrigger ? false : undefined}
55+
{...restProps}
56+
>
57+
<span className={styles.itemLeft}>
58+
{Icon ? <Icon size={menuIconSize} /> : null}
59+
<Text tone={disabled ? 'secondary' : undefined} truncate>
60+
{children}
61+
</Text>
62+
</span>
63+
{shortcut && (
64+
<Text tone="secondary">
65+
<KeyboardShortcut shortcut={shortcut} />
66+
</Text>
67+
)}
68+
{isSubMenuTrigger && !disabled ? <ChevronRight size={12} /> : null}
69+
</BaseUIMenu.Item>
70+
);
7371

74-
return disabled && disabledReason ? (
75-
<Tooltip
76-
label={disabledReason}
77-
announceAsDescription
78-
side="right"
79-
trigger={item}
80-
/>
81-
) : (
82-
item
83-
);
84-
};
72+
return disabled && disabledReason ? (
73+
<Tooltip
74+
label={disabledReason}
75+
announceAsDescription
76+
side="right"
77+
trigger={item}
78+
/>
79+
) : (
80+
item
81+
);
82+
}
83+
);
8584

8685
export const linkArrowSize = 12;
8786

src/components/Playroom/Playroom.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,12 @@ export default () => {
7171
hideActionSource.current === 'editor'
7272
) {
7373
if (editorHidden) {
74+
// Remove in favour of direct DOM attribute when we drop React 18 support
75+
editorRef.current?.setAttribute('inert', '');
7476
showCodeButtonRef.current?.focus();
7577
} else {
78+
// Remove in favour of direct DOM attribute when we drop React 18 support
79+
editorRef.current?.removeAttribute('inert');
7680
hideCodeButtonRef.current?.focus();
7781
}
7882

@@ -124,13 +128,12 @@ export default () => {
124128
<Box
125129
position="relative"
126130
className={styles.editor}
127-
inert={!editorVisible}
128131
opacity={!editorVisible ? 0 : undefined}
129132
pointerEvents={!editorVisible ? 'none' : undefined}
130133
ref={editorRef}
131134
>
132135
<ResizeHandle
133-
ref={editorRef}
136+
targetRef={editorRef}
134137
position={resizeHandlePosition[editorOrientation]}
135138
onResize={(newValue) => {
136139
dispatch({

src/components/Playroom/ResizeHandle.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ const resolvePosition = (
2525

2626
export const ResizeHandle = ({
2727
position,
28-
ref,
28+
targetRef,
2929
onResize,
3030
onResizeStart,
3131
onResizeEnd,
3232
}: {
3333
position: 'top' | 'right' | 'left';
3434
flip?: boolean;
35-
ref: RefObject<HTMLElement | null>;
35+
targetRef: RefObject<HTMLElement | null>;
3636
onResize: (newValue: number) => void;
3737
onResizeStart?: (startValue: number) => void;
3838
onResizeEnd?: (endValue: number) => void;
@@ -48,7 +48,7 @@ export const ResizeHandle = ({
4848
event: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>
4949
) => {
5050
const startPosition = resolvePosition(event.nativeEvent, pagePos);
51-
const startSize = ref.current?.[elementSize] ?? 0;
51+
const startSize = targetRef.current?.[elementSize] ?? 0;
5252

5353
setResizing(true);
5454
onResizeStart?.(startSize);
@@ -61,7 +61,7 @@ export const ResizeHandle = ({
6161
};
6262

6363
const stopHandler = () => {
64-
const endSize = ref.current?.[elementSize] ?? 0;
64+
const endSize = targetRef.current?.[elementSize] ?? 0;
6565
setResizing(false);
6666
onResizeEnd?.(endSize);
6767
document.body.classList.remove(styles.resizeCursor[direction]);

src/entries/frame.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,5 @@ renderElement(
1919
/>
2020
</>
2121
)}
22-
</UrlParams>,
23-
document.body
22+
</UrlParams>
2423
);

src/entries/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,5 @@ renderElement(
1919
<EditorProvider>
2020
<Playroom />
2121
</EditorProvider>
22-
</StoreProvider>,
23-
document.body
22+
</StoreProvider>
2423
);

src/entries/preview.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,5 @@ renderElement(
2020
{({ code, themeName, title }) => (
2121
<Preview title={title} code={code} themeName={themeName} />
2222
)}
23-
</UrlParams>,
24-
document.body
23+
</UrlParams>
2524
);

src/render.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import type { ReactNode } from 'react';
22
import { createRoot } from 'react-dom/client';
33

4-
export const renderElement = async (node: ReactNode, outlet: HTMLElement) => {
4+
const outlet = document.createElement('div');
5+
document.body.appendChild(outlet);
6+
7+
export const renderElement = async (node: ReactNode) => {
58
const root = createRoot(outlet);
69
root.render(node);
710
};

0 commit comments

Comments
 (0)