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

feat(DocumentContext): Add support for React Portals in new windows & document picture-in-picture #3357

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
add stories and SSR test page
  • Loading branch information
arturbien committed Feb 12, 2025
commit 6f9eb7540a98a9f3907ea7c9970192ee14bd2278
7 changes: 7 additions & 0 deletions apps/ssr-testing/app/document-context/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as React from 'react';

import * as DocumentContext from '@radix-ui/react-document-context';

export default function Page() {
return <DocumentContext.DocumentProvider>Document Context</DocumentContext.DocumentProvider>;
}
1 change: 1 addition & 0 deletions apps/ssr-testing/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
<Link href="/toolbar">Toolbar</Link>
<Link href="/tooltip">Tooltip</Link>
<Link href="/visually-hidden">VisuallyHidden</Link>
<Link href="/document-context">DocumentContext</Link>
</div>

<div>{children}</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
.trigger {
border: 1px solid black;
border-radius: 6px;
background-color: transparent;
padding: 5px 10px;
font-family: apple-system, BlinkMacSystemFont, helvetica, arial, sans-serif;
font-size: 13px;

&:focus {
outline: none;
box-shadow: 0 0 0 2px rgb(0 0 0 / 0.5);
}
}

.content {
display: inline-block;
box-sizing: border-box;
min-width: 130px;
background-color: var(--color-white);
border: 1px solid var(--color-gray100);
border-radius: 6px;
padding: 5px;
box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.1);
font-family: apple-system, BlinkMacSystemFont, helvetica, arial, sans-serif;
font-size: 13px;
&:focus-within {
border-color: var(--color-black);
}
}

.item {
display: flex;
align-items: center;
justify-content: space-between;
line-height: 1;
cursor: default;
user-select: none;
white-space: nowrap;
height: 25px;
padding: 0 10px;
color: var(--color-black);
border-radius: 3px;

outline: none;

&[data-highlighted] {
background-color: var(--color-black);
color: var(--color-white);
}

&[data-disabled] {
color: var(--color-gray100);
}
}

.dialog {
position: fixed;
background: white;
border: 1px solid black;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding: 30px;
}
149 changes: 149 additions & 0 deletions packages/react/document-context/src/DocumentContext.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import * as React from 'react';

import * as Tooltip from '@radix-ui/react-tooltip';
import * as Dialog from '@radix-ui/react-dialog';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
import { createPortal } from 'react-dom';
import * as DocumentContext from '@radix-ui/react-document-context';

import styles from './DocumentContext.stories.module.css';
export default { title: 'Utilities/DocumentContext' };

export const Default = () => {
const [portalElement, setPortalElement] = React.useState<HTMLElement | null>(null);
const [count, setCount] = React.useState(0);

const openContentInPopup = async () => {
const popup = window.open(
'',
'Popup Test',
'height=600,width=600,left=300,top=300,resizable=yes,scrollbars=yes,toolbar=no,menubar=no,location=no,directories=no,status=no'
);
if (!popup) return;

// Copy all parent window styles and fonts
// https://developer.chrome.com/docs/web-platform/document-picture-in-picture/#copy-style-sheets-to-the-picture-in-picture-window
[...document.styleSheets].forEach((styleSheet) => {
try {
const cssRules = [...styleSheet.cssRules].map((rule) => rule.cssText).join('');
const style = document.createElement('style');

style.textContent = cssRules;
popup.document.head.appendChild(style);
} catch (e) {
console.error(e);
const link = document.createElement('link');
if (styleSheet.href === null) {
return;
}

link.rel = 'stylesheet';
link.type = styleSheet.type;
link.media = styleSheet.media.toString();
link.href = styleSheet.href;
popup.document.head.appendChild(link);
}
});

setPortalElement(popup.document.body);

// Detect when window is closed by user
popup.addEventListener('pagehide', () => {
setPortalElement(null);
});
};

const content = (
<div style={{ display: 'flex', flexDirection: 'column', gap: 40 }}>
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: 32,
padding: 32,
background: 'yellow',
}}
>
<h1>This section will be portalled to another document/window</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
<DropdownMenu.Root>
<DropdownMenu.Trigger className={styles.trigger}>
Dropdown with dialog test
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className={styles.content} sideOffset={5}>
<Dialog.Root>
<Dialog.Trigger className={styles.item} asChild>
<DropdownMenu.Item onSelect={(event) => event.preventDefault()}>
Open dialog
</DropdownMenu.Item>
</Dialog.Trigger>

<Dialog.Portal>
<Dialog.Content className={styles.dialog}>
<Dialog.Title>Nested dropdown</Dialog.Title>
<DropdownMenu.Root>
<DropdownMenu.Trigger
className={styles.trigger}
style={{ width: '100%', marginBottom: 20 }}
>
Open
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className={styles.content} sideOffset={5}>
<DropdownMenu.Item
className={styles.item}
onSelect={() => console.log('undo')}
>
Undo
</DropdownMenu.Item>
<DropdownMenu.Item
className={styles.item}
onSelect={() => console.log('redo')}
>
Redo
</DropdownMenu.Item>
<DropdownMenu.Arrow />
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
<Dialog.Close>Close</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
<DropdownMenu.Item className={styles.item}>Test</DropdownMenu.Item>
<DropdownMenu.Arrow />
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>

<Tooltip.Provider>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<button>Tooltip test</button>
</Tooltip.Trigger>
<Tooltip.Content>Tooltip content</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
</div>
</div>
);

return (
<div>
<button onClick={openContentInPopup} type="button">
Open in Popup
</button>
<mark>{count}</mark>

{portalElement
? createPortal(
<DocumentContext.DocumentProvider document={portalElement.ownerDocument}>
{content}
</DocumentContext.DocumentProvider>,
portalElement
)
: content}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.trigger {
border: 1px solid $black;
border: 1px solid black;
border-radius: 6px;
background-color: transparent;
padding: 5px 10px;
Expand Down