Skip to content

Commit

Permalink
feat: update ToastManager and PortalManager to optimize rendering…
Browse files Browse the repository at this point in the history
… and reduce unnecessary re-renders (#802)

* feat: memoize ToastManager context to avoid unnecessary re-renders

* feat: refine `usePortalManager` and `useToastManager` Hooks to avoid unnecessary re-renders

* docs: refine usePortalManager example

* docs: update portal component examples

* feat: update usePortalManager and useToastManager by eliminating the need for a return value in object assignment

* test: separate into Portal.test.js and PortalManager.test.js

* docs: update useToastManager example
  • Loading branch information
cheton authored Oct 15, 2023
1 parent f0f60a0 commit 4f2c8af
Show file tree
Hide file tree
Showing 14 changed files with 395 additions and 324 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
Button,
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
usePortalManager,
} from '@tonic-ui/react';
import React, { forwardRef, useCallback } from 'react';

const MyModal = forwardRef((
{
onClose,
...rest
},
ref,
) => (
<Modal
closeOnEsc
closeOnOutsideClick
isOpen
onClose={onClose}
size="sm"
{...rest}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
Modal Header
</ModalHeader>
<ModalBody>
Modal Body
</ModalBody>
<ModalFooter>
<Button onClick={onClose}>Close</Button>
</ModalFooter>
</ModalContent>
</Modal>
));

MyModal.displayName = 'MyModal';

const App = () => {
const portal = usePortalManager();
const openModal = useCallback(() => {
portal((close) => (
<MyModal onClose={close} />
));
}, [portal]);

return (
<Button onClick={openModal}>
Open Modal
</Button>
);
};

export default App;
Original file line number Diff line number Diff line change
Expand Up @@ -62,49 +62,4 @@ The `remove` method removes a portal by its id.

Here is an example of how to use `usePortalManager` to create and remove a modal:

```jsx noInline
render(() => {
const portal = usePortalManager();
const openModal = React.useCallback(() => {
portal((close) => (
<MyModal onClose={close} />
));
}, [portal]);

return (
<Button onClick={openModal}>
Open Modal
</Button>
);
});

const MyModal = React.forwardRef((
{
onClose,
...rest
},
ref,
) => (
<Modal
closeOnEsc
closeOnOutsideClick
isOpen
onClose={onClose}
size="sm"
{...rest}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>
Modal Header
</ModalHeader>
<ModalBody>
Modal Body
</ModalBody>
<ModalFooter>
<Button onClick={onClose}>Close</Button>
</ModalFooter>
</ModalContent>
</Modal>
));
```
{render('./usePortalManager')}
105 changes: 0 additions & 105 deletions packages/react-docs/pages/components/portal.page.mdx

This file was deleted.

31 changes: 31 additions & 0 deletions packages/react-docs/pages/components/portal/custom-container.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
Box,
Flex,
Portal,
useColorMode,
useColorStyle,
} from '@tonic-ui/react';
import React, { useRef } from 'react';

const App = () => {
const ref = useRef();
const [colorMode] = useColorMode();
const [colorStyle] = useColorStyle({ colorMode });

return (
<>
<Portal containerRef={ref}>
<Box bg={colorStyle.background.tertiary} px="3x" py="2x">
Portal - This is transported to the container
</Box>
</Portal>
<Flex flexDirection="column" rowGap="2x">
<Box ref={ref} bg={colorStyle.background.secondary} px="3x" py="2x">
I am the container
</Box>
</Flex>
</>
);
};

export default App;
39 changes: 39 additions & 0 deletions packages/react-docs/pages/components/portal/index.page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Portal

A Portal is a declarative component that allows you to render children into a DOM node that exists outside the DOM hierarchy of the parent component. This is useful for rendering elements, such as drawers, modals, or popovers, above other elements in the document.

## Import

```js
import {
Portal,
} from '@tonic-ui/react';
```

## Usage

To use the `Portal` component, you can wrap any element or component within it, and it will be rendered at the end of the document body.

{render('./portal')}

### Using a custom container

You can also specify a custom container for the portal to be rendered in by passing the `containerRef` prop and its value to the `ref` of the container element.

{render('./custom-container')}

### Nested portals

It is also possible to nest portals inside a parent portal. To do this, pass `appendToParentPortal={true}` to the nested portal. This will append the children of the nested portal to the container of the parent portal.

{render('./nested-portals')}

## Props

### Portal

| Name | Type | Default | Description |
| :--- | :--- | :------ | :---------- |
| appendToParentPortal | boolean | false | If `true`, the portal will check if it is within a parent portal and append itself to the parent's portal node. |
| children | ReactNode | | |
| containerRef | RefObject | | The `ref` to the component where the portal will be rendered. |
33 changes: 33 additions & 0 deletions packages/react-docs/pages/components/portal/nested-portals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
Box,
Portal,
useColorMode,
useColorStyle,
} from '@tonic-ui/react';
import React, { useRef } from 'react';

const App = () => {
const ref = useRef();
const [colorMode] = useColorMode();
const [colorStyle] = useColorStyle({ colorMode });

return (
<>
<Portal containerRef={ref}>
<Box bg={colorStyle.background.tertiary} px="3x" py="2x" mb="2x">
Parent portal - This is transported to the container
<Portal appendToParentPortal={true}>
<Box bg={colorStyle.background.tertiary} px="3x" py="2x" mb="2x">
Child portal - This is attached to its parent portal
</Box>
</Portal>
</Box>
</Portal>
<Box ref={ref} bg={colorStyle.background.secondary} px="3x" py="2x">
I am the container
</Box>
</>
);
};

export default App;
31 changes: 31 additions & 0 deletions packages/react-docs/pages/components/portal/portal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
Box,
Portal,
VisuallyHidden,
useColorMode,
useColorStyle,
} from '@tonic-ui/react';
import React from 'react';

const App = () => {
const [colorMode] = useColorMode();
const [colorStyle] = useColorStyle({ colorMode });

return (
<>
<Portal>
<VisuallyHidden>
{/* Open developer tool to inspect elements inside the body tag */}
<Box bg={colorStyle.background.tertiary} px="3x" py="2x">
Portal - This is transported to the end of the document body
</Box>
</VisuallyHidden>
</Portal>
<Box bg={colorStyle.background.secondary} px="3x" py="2x">
I am the container
</Box>
</>
);
};

export default App;
Loading

0 comments on commit 4f2c8af

Please sign in to comment.