Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/big-candles-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@radix-ui/react-password-toggle-field": minor
"radix-ui": minor
---

Renamed misspelled `onVisiblityChange` prop to `onVisibilityChange`
10 changes: 10 additions & 0 deletions .changeset/chilly-squids-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@radix-ui/react-slot": minor
"radix-ui": minor
---

Added generic type arguments for `SlotProps` and `createSlot` to specify the type of element a slot should render, as well as its props.

```tsx
const Slot = createSlot<HTMLButtonElement, MyCustomButtonProps>('Slot');
```
6 changes: 6 additions & 0 deletions .changeset/kind-hornets-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@radix-ui/react-toggle-group": patch
"radix-ui": patch
---

Updated single-select and multi-select toggle groups to use the `radiogroup` and `toolbar` roles, respectively.
7 changes: 7 additions & 0 deletions .changeset/open-doodles-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@radix-ui/react-alert-dialog": patch
"@radix-ui/react-dialog": patch
"radix-ui": patch
---

Removed dev-only warnings for dialogs when title and/or description is not rendered.
9 changes: 9 additions & 0 deletions .changeset/six-worlds-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@radix-ui/react-menu': patch
'@radix-ui/react-dropdown-menu': patch
'@radix-ui/react-context-menu': patch
'@radix-ui/react-menubar': patch
'radix-ui': patch
---

Fixed a bug where menus and submenus remained open after a window loses focus.
6 changes: 6 additions & 0 deletions .changeset/slow-shirts-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@radix-ui/react-select": patch
"radix-ui": patch
---

Fixed a bug where typeahead search resulted in focusing an element that no longer exists
8 changes: 8 additions & 0 deletions .changeset/swift-bears-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@radix-ui/react-dialog": patch
"@radix-ui/react-dismissable-layer": patch
"@radix-ui/react-popover": patch
"radix-ui": patch
---

Fixed Dismissable Layer so outside interactions stopped by extension UI overlays do not dismiss dialogs or popovers
21 changes: 21 additions & 0 deletions apps/storybook/stories/context-menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import { ContextMenu } from 'radix-ui';
import { foodGroups } from '@repo/test-data/foods';
import styles from './context-menu.stories.module.css';
import { ExternalOverlayTrigger } from './external-overlay';

export default { title: 'Components/ContextMenu' };

Expand Down Expand Up @@ -473,6 +474,26 @@ export const Submenus = () => {
);
};

export const WithExtensionOverlay = () => {
const [open, setOpen] = React.useState(false);

return (
<div style={{ display: 'grid', gap: 20, padding: 50, justifyItems: 'start' }}>
<ExternalOverlayTrigger />
<ContextMenu.Root open={open} onOpenChange={setOpen}>
<ContextMenu.Trigger className={styles.trigger}>Right Click Here</ContextMenu.Trigger>
<ContextMenu.Portal>
<ContextMenu.Content className={styles.content}>
<ContextMenu.Item className={styles.item}>New Tab</ContextMenu.Item>
<ContextMenu.Item className={styles.item}>New Window</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
<div data-testid="context-menu-state">{open ? 'open' : 'closed'}</div>
</div>
);
};

export const WithLabels = () => (
<div style={{ textAlign: 'center', padding: 50 }}>
<ContextMenu.Root>
Expand Down
44 changes: 44 additions & 0 deletions apps/storybook/stories/dialog.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { Dialog } from 'radix-ui';
import styles from './dialog.stories.module.css';
import { ExternalOverlayTrigger } from './external-overlay';

export default { title: 'Components/Dialog' };

Expand Down Expand Up @@ -536,3 +537,46 @@ export const Cypress = () => {
</>
);
};

export const WithExtensionOverlay = () => {
const [open, setOpen] = React.useState(false);

return (
<>
<Dialog.Root open={open} onOpenChange={setOpen}>
<Dialog.Trigger className={styles.trigger}>open</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className={styles.overlay} />
<Dialog.Content className={styles.contentDefault}>
<Dialog.Title>title</Dialog.Title>
<Dialog.Description>
Simulates extension UI interacting with a dialog.
</Dialog.Description>
<ExternalOverlayTrigger />
<Dialog.Close className={styles.close}>close</Dialog.Close>
<InsideShadowSuggestion />
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
<div data-testid="dialog-state">{open ? 'open' : 'closed'}</div>
</>
);
};

function InsideShadowSuggestion() {
const hostRef = React.useRef<HTMLDivElement>(null);

React.useEffect(() => {
const host = hostRef.current;
if (!host || host.shadowRoot) return;

const shadowRoot = host.attachShadow({ mode: 'open' });
const button = document.createElement('button');
button.type = 'button';
button.textContent = 'inside shadow suggestion';
button.style.marginTop = '16px';
shadowRoot.append(button);
}, []);

return <div data-testid="inside-shadow-host" ref={hostRef} />;
}
21 changes: 21 additions & 0 deletions apps/storybook/stories/dropdown-menu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Dialog, DropdownMenu, Tooltip } from 'radix-ui';
import { Popper } from 'radix-ui/internal';
import { foodGroups } from '@repo/test-data/foods';
import styles from './dropdown-menu.stories.module.css';
import { ExternalOverlayTrigger } from './external-overlay';

const { SIDE_OPTIONS, ALIGN_OPTIONS } = Popper;

Expand Down Expand Up @@ -366,6 +367,26 @@ export const Submenus = () => {
);
};

export const WithExtensionOverlay = () => {
const [open, setOpen] = React.useState(false);

return (
<div style={{ display: 'grid', gap: 20, padding: 50, justifyItems: 'start' }}>
<ExternalOverlayTrigger />
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
<DropdownMenu.Trigger className={styles.trigger}>Open</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content className={styles.content} sideOffset={5}>
<DropdownMenu.Item className={styles.item}>New Tab</DropdownMenu.Item>
<DropdownMenu.Item className={styles.item}>New Window</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
<div data-testid="dropdown-menu-state">{open ? 'open' : 'closed'}</div>
</div>
);
};

export const InvertedWithSubmenus = () => (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh' }}>
<DropdownMenu.Root>
Expand Down
77 changes: 77 additions & 0 deletions apps/storybook/stories/external-overlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as React from 'react';

type ExternalOverlayTriggerProps = {
children?: React.ReactNode;
};

function ExternalOverlayTrigger({ children = 'Trigger overlay' }: ExternalOverlayTriggerProps) {
const cleanupRef = React.useRef<(() => void) | undefined>(undefined);

React.useEffect(() => {
return () => cleanupRef.current?.();
}, []);

return (
<button
type="button"
onClick={() => {
cleanupRef.current?.();
cleanupRef.current = createExternalOverlay();
}}
>
{children}
</button>
);
}

function createExternalOverlay() {
const container = document.createElement('div');
container.dataset.testid = 'external-overlay';
container.style.position = 'fixed';
container.style.top = '12px';
container.style.right = '12px';
container.style.zIndex = '2147483647';
container.style.pointerEvents = 'auto';
container.style.backgroundColor = 'hsl(0 0% 100%)';
container.style.border = '1px solid hsl(0 0% 80%)';
container.style.borderRadius = '4px';
container.style.padding = '8px';
container.style.boxShadow = '0 2px 8px hsl(0 0% 0% / 0.1)';

const button = document.createElement('button');
button.type = 'button';
button.dataset.testid = 'external-overlay-button';
button.textContent = 'external overlay';

const dismissButton = document.createElement('button');
dismissButton.type = 'button';
dismissButton.dataset.testid = 'external-overlay-dismiss-button';
dismissButton.textContent = 'dismiss';

const stopPropagation = (event: Event) => event.stopPropagation();
const handleDismiss = (event: Event) => {
stopPropagation(event);
cleanup();
};

button.addEventListener('mousedown', stopPropagation);
button.addEventListener('mouseup', stopPropagation);
button.addEventListener('click', stopPropagation);
dismissButton.addEventListener('click', handleDismiss);

container.append(button);
container.append(dismissButton);
document.body.append(container);

function cleanup() {
button.removeEventListener('mousedown', stopPropagation);
button.removeEventListener('mouseup', stopPropagation);
button.removeEventListener('click', stopPropagation);
dismissButton.removeEventListener('click', handleDismiss);
container.remove();
}

return cleanup;
}

export { ExternalOverlayTrigger };
23 changes: 23 additions & 0 deletions apps/storybook/stories/menubar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import { Menubar } from 'radix-ui';
import { foodGroups } from '@repo/test-data/foods';
import styles from './menubar.stories.module.css';
import { ExternalOverlayTrigger } from './external-overlay';

const subTriggerClass = [styles.item, styles.subTrigger].join(' ');

Expand Down Expand Up @@ -194,6 +195,28 @@ export const Styled = () => {
);
};

export const WithExtensionOverlay = () => {
const [value, setValue] = React.useState('');

return (
<div style={{ display: 'grid', gap: 20, padding: 50, justifyItems: 'start' }}>
<ExternalOverlayTrigger />
<Menubar.Root className={styles.root} value={value} onValueChange={setValue}>
<Menubar.Menu value="edit">
<Menubar.Trigger className={styles.trigger}>Edit</Menubar.Trigger>
<Menubar.Portal>
<Menubar.Content className={styles.content} sideOffset={2}>
<Menubar.Item className={styles.item}>Undo</Menubar.Item>
<Menubar.Item className={styles.item}>Redo</Menubar.Item>
</Menubar.Content>
</Menubar.Portal>
</Menubar.Menu>
</Menubar.Root>
<div data-testid="menubar-state">{value === '' ? 'closed' : 'open'}</div>
</div>
);
};

export const Cypress = () => {
const [loop, setLoop] = React.useState(false);
const [rtl, setRtl] = React.useState(false);
Expand Down
4 changes: 2 additions & 2 deletions apps/storybook/stories/password-toggle-field.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const Controlled = {
<PasswordToggleField.Root
{...args}
visible={visible}
onVisiblityChange={(visible) => updateArgs({ visible })}
onVisibilityChange={(visible) => updateArgs({ visible })}
>
<div className={styles.field}>
<PasswordToggleField.Input className={styles.input} />
Expand Down Expand Up @@ -96,7 +96,7 @@ export const InsideForm = {
>
<PasswordToggleField.Root
visible={visible}
onVisiblityChange={(visible) => updateArgs({ visible })}
onVisibilityChange={(visible) => updateArgs({ visible })}
{...args}
>
<div className={styles.field}>
Expand Down
23 changes: 23 additions & 0 deletions apps/storybook/stories/popover.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import { Popover } from 'radix-ui';
import { Popper } from 'radix-ui/internal';
import styles from './popover.stories.module.css';
import { ExternalOverlayTrigger } from './external-overlay';

const { SIDE_OPTIONS, ALIGN_OPTIONS } = Popper;

Expand Down Expand Up @@ -128,6 +129,28 @@ export const Controlled = () => {
);
};

export const WithExtensionOverlay = () => {
const [open, setOpen] = React.useState(false);

return (
<div
style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '50vh' }}
>
<Popover.Root open={open} onOpenChange={setOpen}>
<Popover.Trigger className={styles.trigger}>open</Popover.Trigger>
<Popover.Portal>
<Popover.Content className={styles.content}>
<ExternalOverlayTrigger />
<Popover.Close className={styles.close}>close</Popover.Close>
<Popover.Arrow className={styles.arrow} width={20} height={10} />
</Popover.Content>
</Popover.Portal>
</Popover.Root>
<div data-testid="popover-state">{open ? 'open' : 'closed'}</div>
</div>
);
};

export const Animated = () => {
return (
<div
Expand Down
Loading
Loading