fix: dropdown menu portals (#2740)
This commit is contained in:
parent
1ba0ad234b
commit
3893b26d30
@ -139,7 +139,8 @@ const AccountHistoryManager = ({
|
|||||||
<div className="h-full w-full flex flex-col gap-8">
|
<div className="h-full w-full flex flex-col gap-8">
|
||||||
<div className="w-full flex flex-col-reverse lg:flex-row items-start lg:items-center justify-between gap-4 px-2">
|
<div className="w-full flex flex-col-reverse lg:flex-row items-start lg:items-center justify-between gap-4 px-2">
|
||||||
<div className="flex items-center gap-4 shrink-0">
|
<div className="flex items-center gap-4 shrink-0">
|
||||||
<DropdownMenu>
|
<DropdownMenu
|
||||||
|
trigger={
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
{accountType
|
{accountType
|
||||||
? `${
|
? `${
|
||||||
@ -149,6 +150,8 @@ const AccountHistoryManager = ({
|
|||||||
} Account`
|
} Account`
|
||||||
: t('Select account type')}
|
: t('Select account type')}
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
{[
|
{[
|
||||||
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
Schema.AccountType.ACCOUNT_TYPE_GENERAL,
|
||||||
@ -164,10 +167,13 @@ const AccountHistoryManager = ({
|
|||||||
))}
|
))}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<DropdownMenu>
|
<DropdownMenu
|
||||||
|
trigger={
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
{asset ? asset.symbol : t('Select asset')}
|
{asset ? asset.symbol : t('Select asset')}
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
{assets.map((a) => (
|
{assets.map((a) => (
|
||||||
<DropdownMenuItem key={a.id} onClick={() => setAsset(a)}>
|
<DropdownMenuItem key={a.id} onClick={() => setAsset(a)}>
|
||||||
|
@ -3,6 +3,7 @@ import { VegaWalletContext } from '@vegaprotocol/wallet';
|
|||||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||||
import { VegaWalletConnectButton } from './vega-wallet-connect-button';
|
import { VegaWalletConnectButton } from './vega-wallet-connect-button';
|
||||||
import { truncateByChars } from '@vegaprotocol/react-helpers';
|
import { truncateByChars } from '@vegaprotocol/react-helpers';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
const mockUpdateDialogOpen = jest.fn();
|
const mockUpdateDialogOpen = jest.fn();
|
||||||
jest.mock('@vegaprotocol/wallet', () => ({
|
jest.mock('@vegaprotocol/wallet', () => ({
|
||||||
@ -31,7 +32,7 @@ it('Not connected', () => {
|
|||||||
expect(mockUpdateDialogOpen).toHaveBeenCalled();
|
expect(mockUpdateDialogOpen).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Connected', () => {
|
it('Connected', async () => {
|
||||||
const pubKey = { publicKey: '123456__123456', name: 'test' };
|
const pubKey = { publicKey: '123456__123456', name: 'test' };
|
||||||
render(
|
render(
|
||||||
generateJsx({
|
generateJsx({
|
||||||
@ -42,6 +43,6 @@ it('Connected', () => {
|
|||||||
|
|
||||||
const button = screen.getByTestId('manage-vega-wallet');
|
const button = screen.getByTestId('manage-vega-wallet');
|
||||||
expect(button).toHaveTextContent(truncateByChars(pubKey.publicKey));
|
expect(button).toHaveTextContent(truncateByChars(pubKey.publicKey));
|
||||||
fireEvent.click(button);
|
userEvent.click(button);
|
||||||
expect(mockUpdateDialogOpen).not.toHaveBeenCalled();
|
expect(mockUpdateDialogOpen).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
@ -142,15 +142,21 @@ export const VegaWalletConnectButton = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="hidden lg:block">
|
<div className="hidden lg:block">
|
||||||
<DropdownMenu open={dropdownOpen}>
|
<DropdownMenu
|
||||||
|
open={dropdownOpen}
|
||||||
|
trigger={
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
data-testid="manage-vega-wallet"
|
data-testid="manage-vega-wallet"
|
||||||
onClick={() => setDropdownOpen((curr) => !curr)}
|
onClick={() => setDropdownOpen((curr) => !curr)}
|
||||||
>
|
>
|
||||||
{activeKey && <span className="uppercase">{activeKey.name}</span>}
|
{activeKey && (
|
||||||
|
<span className="uppercase">{activeKey.name}</span>
|
||||||
|
)}
|
||||||
{': '}
|
{': '}
|
||||||
{truncateByChars(pubKey)}
|
{truncateByChars(pubKey)}
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
onInteractOutside={() => setDropdownOpen(false)}
|
onInteractOutside={() => setDropdownOpen(false)}
|
||||||
>
|
>
|
||||||
|
@ -60,10 +60,13 @@ export const CandlesChartContainer = ({
|
|||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
<div className="px-4 py-2 flex flex-row flex-wrap gap-4">
|
<div className="px-4 py-2 flex flex-row flex-wrap gap-4">
|
||||||
<DropdownMenu>
|
<DropdownMenu
|
||||||
|
trigger={
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
{t(`Interval: ${intervalLabels[interval]}`)}
|
{t(`Interval: ${intervalLabels[interval]}`)}
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuRadioGroup
|
<DropdownMenuRadioGroup
|
||||||
value={interval}
|
value={interval}
|
||||||
@ -84,10 +87,13 @@ export const CandlesChartContainer = ({
|
|||||||
</DropdownMenuRadioGroup>
|
</DropdownMenuRadioGroup>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<DropdownMenu>
|
<DropdownMenu
|
||||||
|
trigger={
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<Icon name={chartTypeIcon.get(chartType) as IconName} />
|
<Icon name={chartTypeIcon.get(chartType) as IconName} />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuRadioGroup
|
<DropdownMenuRadioGroup
|
||||||
value={chartType}
|
value={chartType}
|
||||||
@ -104,8 +110,9 @@ export const CandlesChartContainer = ({
|
|||||||
</DropdownMenuRadioGroup>
|
</DropdownMenuRadioGroup>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<DropdownMenu>
|
<DropdownMenu
|
||||||
<DropdownMenuTrigger>{t('Overlays')}</DropdownMenuTrigger>
|
trigger={<DropdownMenuTrigger>{t('Overlays')}</DropdownMenuTrigger>}
|
||||||
|
>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
{Object.values(Overlay).map((overlay) => (
|
{Object.values(Overlay).map((overlay) => (
|
||||||
<DropdownMenuCheckboxItem
|
<DropdownMenuCheckboxItem
|
||||||
@ -128,8 +135,9 @@ export const CandlesChartContainer = ({
|
|||||||
))}
|
))}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<DropdownMenu>
|
<DropdownMenu
|
||||||
<DropdownMenuTrigger>{t('Studies')}</DropdownMenuTrigger>
|
trigger={<DropdownMenuTrigger>{t('Studies')}</DropdownMenuTrigger>}
|
||||||
|
>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
{Object.values(Study).map((study) => (
|
{Object.values(Study).map((study) => (
|
||||||
<DropdownMenuCheckboxItem
|
<DropdownMenuCheckboxItem
|
||||||
|
@ -96,13 +96,18 @@ export const NetworkSwitcher = () => {
|
|||||||
const menuRef = useRef<HTMLButtonElement | null>(null);
|
const menuRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu open={isOpen} onOpenChange={handleOpen}>
|
<DropdownMenu
|
||||||
|
open={isOpen}
|
||||||
|
onOpenChange={handleOpen}
|
||||||
|
trigger={
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
ref={menuRef}
|
ref={menuRef}
|
||||||
className="flex justify-between items-center"
|
className="flex justify-between items-center"
|
||||||
>
|
>
|
||||||
{envTriggerMapping[VEGA_ENV]}
|
{envTriggerMapping[VEGA_ENV]}
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
align="start"
|
align="start"
|
||||||
style={{ minWidth: `${menuRef.current?.offsetWidth || 290}px` }}
|
style={{ minWidth: `${menuRef.current?.offsetWidth || 290}px` }}
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from './dropdown-menu';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
describe('DropdownMenu', () => {
|
||||||
|
const text = 'Dropdown menu content';
|
||||||
|
|
||||||
|
// Upgrade from @radix-ui/react-dropdown-menu 0.1.6 to 2.0.2 renders
|
||||||
|
// dropdowns inline (rather than portals). Currently not using a portal
|
||||||
|
// will break the UI due to z-index issues
|
||||||
|
it('renders using a portal', async () => {
|
||||||
|
render(
|
||||||
|
<div className="test-wrapper">
|
||||||
|
<DropdownMenu
|
||||||
|
trigger={<DropdownMenuTrigger>Trigger</DropdownMenuTrigger>}
|
||||||
|
>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<p>{text}</p>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
userEvent.click(screen.getByText(/trigger/i));
|
||||||
|
const contentElement = await screen.findByText(text);
|
||||||
|
expect(contentElement).toBeInTheDocument();
|
||||||
|
// if content is within .test-wrapper then its not been rendered in a portal
|
||||||
|
expect(contentElement.closest('.test-wrapper')).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
@ -29,16 +29,21 @@ export const CheckboxItems = () => {
|
|||||||
console.log(checkboxItems);
|
console.log(checkboxItems);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu
|
||||||
|
trigger={
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<span>Select many things</span>
|
<span>Select many things</span>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
{checkboxItems.map(({ label, state: [checked, setChecked] }) => (
|
{checkboxItems.map(({ label, state: [checked, setChecked] }) => (
|
||||||
<DropdownMenuCheckboxItem
|
<DropdownMenuCheckboxItem
|
||||||
key={label}
|
key={label}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
onCheckedChange={setChecked}
|
onCheckedChange={(checked) =>
|
||||||
|
setChecked(typeof checked === 'boolean' ? checked : false)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
<DropdownMenuItemIndicator />
|
<DropdownMenuItemIndicator />
|
||||||
@ -55,10 +60,13 @@ export const RadioItems = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'center', padding: 50 }}>
|
<div style={{ textAlign: 'center', padding: 50 }}>
|
||||||
<DropdownMenu>
|
<DropdownMenu
|
||||||
|
trigger={
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<span>Open</span>
|
<span>Open</span>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem onSelect={() => console.log('minimize')}>
|
<DropdownMenuItem onSelect={() => console.log('minimize')}>
|
||||||
Minimize window
|
Minimize window
|
||||||
@ -92,10 +100,13 @@ export const IconMenu = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: 'center', padding: 50 }}>
|
<div style={{ textAlign: 'center', padding: 50 }}>
|
||||||
<DropdownMenu>
|
<DropdownMenu
|
||||||
|
trigger={
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<Icon name="cog" />
|
<Icon name="cog" />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
{iconMenuItems.map(({ label }) => (
|
{iconMenuItems.map(({ label }) => (
|
||||||
<DropdownMenuItem key={label}>{label}</DropdownMenuItem>
|
<DropdownMenuItem key={label}>{label}</DropdownMenuItem>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import { Icon } from '../icon';
|
import { Icon } from '../icon';
|
||||||
|
|
||||||
@ -12,11 +13,24 @@ const itemClass = classNames(
|
|||||||
'whitespace-nowrap'
|
'whitespace-nowrap'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
type DropdownMenuProps = DropdownMenuPrimitive.DropdownMenuProps & {
|
||||||
|
trigger: ReactNode;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Contains all the parts of a dropdown menu.
|
* Contains all the parts of a dropdown menu.
|
||||||
*/
|
*/
|
||||||
export const DropdownMenu = DropdownMenuPrimitive.Root;
|
export const DropdownMenu = ({
|
||||||
|
children,
|
||||||
|
trigger,
|
||||||
|
...props
|
||||||
|
}: DropdownMenuProps) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Root {...props}>
|
||||||
|
{trigger}
|
||||||
|
<DropdownMenuPrimitive.Portal>{children}</DropdownMenuPrimitive.Portal>
|
||||||
|
</DropdownMenuPrimitive.Root>
|
||||||
|
);
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* The button that toggles the dropdown menu.
|
* The button that toggles the dropdown menu.
|
||||||
* By default, the {@link DropdownMenuContent} will position itself against the trigger.
|
* By default, the {@link DropdownMenuContent} will position itself against the trigger.
|
||||||
|
Loading…
Reference in New Issue
Block a user