Skip to content

Commit baedc04

Browse files
committed
Add advanced example for Dialog menu portal target
1 parent 52c9bfe commit baedc04

File tree

5 files changed

+187
-0
lines changed

5 files changed

+187
-0
lines changed

demo/package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/src/app.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from "chakra-react-select";
2121
import ConnectedSelectMenuExample from "./components/advanced-examples/connected-select-menu-example";
2222
import CustomIndicatorIconsExample from "./components/advanced-examples/custom-indicator-icons-example";
23+
import MenuPortalTargetExample from "./components/advanced-examples/menu-portal-target-example";
2324
import OptionsWithIconsExample from "./components/advanced-examples/options-with-icons-example";
2425
import SelectPopoverExample from "./components/advanced-examples/select-popover-example";
2526
import { useColorModeValue } from "./components/ui/color-mode";
@@ -469,6 +470,10 @@ const App = () => {
469470
<Field label="Custom Indicator Icons">
470471
<CustomIndicatorIconsExample />
471472
</Field>
473+
474+
<Field label="Menu Portal Target">
475+
<MenuPortalTargetExample />
476+
</Field>
472477
</Stack>
473478
</Container>
474479
);
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { useState } from "react";
2+
import { Stack } from "@chakra-ui/react";
3+
import { Select, Props as SelectProps } from "chakra-react-select";
4+
import { colorOptions } from "../../data/options";
5+
import { Button } from "../ui/button";
6+
import {
7+
DialogBackdrop,
8+
DialogBody,
9+
DialogCloseTrigger,
10+
DialogContent,
11+
DialogHeader,
12+
DialogRoot,
13+
DialogTitle,
14+
DialogTrigger,
15+
} from "../ui/dialog";
16+
import { Field } from "../ui/field";
17+
import { Switch } from "../ui/switch";
18+
19+
const MenuPortalTargetExample = () => {
20+
const [isPortalEnabled, setIsPortalEnabled] = useState(false);
21+
22+
const portalProps: SelectProps = {
23+
menuPortalTarget: document.body,
24+
styles: {
25+
menuPortal: (base) => ({
26+
...base,
27+
// This could be --chakra-z-index-dropdown, to match the built-in Select component,
28+
// but we need to make sure it's above the Dialog component for this use case.
29+
zIndex: "var(--chakra-z-index-popover)",
30+
// By default, the Chakra Dialog component will set pointer-events to none on the body element.
31+
// We need to override this behavior to allow the menu to be interacted with.
32+
// This isn't necessary if you pass modal={false} to the Dialog component.
33+
pointerEvents: "auto",
34+
}),
35+
},
36+
};
37+
38+
return (
39+
<DialogRoot closeOnInteractOutside={false}>
40+
<DialogBackdrop />
41+
<DialogTrigger asChild>
42+
<Button colorPalette="blue">Open Modal</Button>
43+
</DialogTrigger>
44+
45+
<DialogContent>
46+
<DialogCloseTrigger />
47+
<DialogHeader>
48+
<DialogTitle>Menu Portal Target Example</DialogTitle>
49+
</DialogHeader>
50+
<DialogBody overflow="hidden">
51+
<Stack gap={4}>
52+
<Select
53+
focusRingColor="blue.600"
54+
options={colorOptions}
55+
defaultValue={colorOptions[0]}
56+
menuShouldScrollIntoView={false}
57+
{...(isPortalEnabled ? portalProps : {})}
58+
/>
59+
60+
<Field>
61+
<Switch
62+
colorPalette="blue"
63+
name="enable-portal"
64+
checked={isPortalEnabled}
65+
onCheckedChange={({ checked }) => {
66+
setIsPortalEnabled(checked);
67+
}}
68+
inputProps={{ onBlur: () => {} }}
69+
>
70+
Enable Portal
71+
</Switch>
72+
</Field>
73+
</Stack>
74+
</DialogBody>
75+
</DialogContent>
76+
</DialogRoot>
77+
);
78+
};
79+
80+
export default MenuPortalTargetExample;

demo/src/components/ui/dialog.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import * as React from "react";
2+
import { Dialog as ChakraDialog, Portal } from "@chakra-ui/react";
3+
import { CloseButton } from "./close-button";
4+
5+
interface DialogContentProps extends ChakraDialog.ContentProps {
6+
portalled?: boolean;
7+
portalRef?: React.RefObject<HTMLElement>;
8+
backdrop?: boolean;
9+
}
10+
11+
export const DialogContent = React.forwardRef<
12+
HTMLDivElement,
13+
DialogContentProps
14+
>(function DialogContent(props, ref) {
15+
const {
16+
children,
17+
portalled = true,
18+
portalRef,
19+
backdrop = true,
20+
...rest
21+
} = props;
22+
23+
return (
24+
<Portal disabled={!portalled} container={portalRef}>
25+
{backdrop && <ChakraDialog.Backdrop />}
26+
<ChakraDialog.Positioner>
27+
<ChakraDialog.Content ref={ref} {...rest} asChild={false}>
28+
{children}
29+
</ChakraDialog.Content>
30+
</ChakraDialog.Positioner>
31+
</Portal>
32+
);
33+
});
34+
35+
export const DialogCloseTrigger = React.forwardRef<
36+
HTMLButtonElement,
37+
ChakraDialog.CloseTriggerProps
38+
>(function DialogCloseTrigger(props, ref) {
39+
return (
40+
<ChakraDialog.CloseTrigger
41+
position="absolute"
42+
top="2"
43+
insetEnd="2"
44+
{...props}
45+
asChild
46+
>
47+
<CloseButton size="sm" ref={ref}>
48+
{props.children}
49+
</CloseButton>
50+
</ChakraDialog.CloseTrigger>
51+
);
52+
});
53+
54+
export const DialogRoot = ChakraDialog.Root;
55+
export const DialogFooter = ChakraDialog.Footer;
56+
export const DialogHeader = ChakraDialog.Header;
57+
export const DialogBody = ChakraDialog.Body;
58+
export const DialogBackdrop = ChakraDialog.Backdrop;
59+
export const DialogTitle = ChakraDialog.Title;
60+
export const DialogDescription = ChakraDialog.Description;
61+
export const DialogTrigger = ChakraDialog.Trigger;
62+
export const DialogActionTrigger = ChakraDialog.ActionTrigger;

demo/src/components/ui/switch.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import * as React from "react";
2+
import { Switch as ChakraSwitch } from "@chakra-ui/react";
3+
4+
export interface SwitchProps extends ChakraSwitch.RootProps {
5+
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
6+
rootRef?: React.Ref<HTMLLabelElement>;
7+
trackLabel?: { on: React.ReactNode; off: React.ReactNode };
8+
thumbLabel?: { on: React.ReactNode; off: React.ReactNode };
9+
}
10+
11+
export const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
12+
function Switch(props, ref) {
13+
const { inputProps, children, rootRef, trackLabel, thumbLabel, ...rest } =
14+
props;
15+
16+
return (
17+
<ChakraSwitch.Root ref={rootRef} {...rest}>
18+
<ChakraSwitch.HiddenInput ref={ref} {...inputProps} />
19+
<ChakraSwitch.Control>
20+
<ChakraSwitch.Thumb>
21+
{thumbLabel && (
22+
<ChakraSwitch.ThumbIndicator fallback={thumbLabel?.off}>
23+
{thumbLabel?.on}
24+
</ChakraSwitch.ThumbIndicator>
25+
)}
26+
</ChakraSwitch.Thumb>
27+
{trackLabel && (
28+
<ChakraSwitch.Indicator fallback={trackLabel.off}>
29+
{trackLabel.on}
30+
</ChakraSwitch.Indicator>
31+
)}
32+
</ChakraSwitch.Control>
33+
{children != null && (
34+
<ChakraSwitch.Label>{children}</ChakraSwitch.Label>
35+
)}
36+
</ChakraSwitch.Root>
37+
);
38+
}
39+
);

0 commit comments

Comments
 (0)