-
Notifications
You must be signed in to change notification settings - Fork 2.9k
react-portal Spec #17824
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
Merged
Merged
react-portal Spec #17824
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
c5b6a04
react-portal Spec
ling1726 5e9d4d9
Update Spec.md
ling1726 05e7ad1
Update Spec.md
ling1726 56390c4
Update Spec.md
ling1726 b3eb6dc
Update Spec.md
ling1726 0c63d9c
Update Spec.md
ling1726 5273afa
Update Spec.md
ling1726 ecc68b6
Update Spec.md
ling1726 fee8fd6
Update Spec.md
ling1726 aefd4b8
Update packages/react-portal/Spec.md
ling1726 992b80e
Update packages/react-portal/Spec.md
ling1726 4161fae
Update packages/react-portal/Spec.md
ling1726 21231ec
Update packages/react-portal/Spec.md
ling1726 00ef40b
Update packages/react-portal/Spec.md
ling1726 833234a
Update packages/react-portal/Spec.md
ling1726 2d876e8
Update packages/react-portal/Spec.md
ling1726 c6f0348
Update packages/react-portal/Spec.md
ling1726 7fd6615
Update packages/react-portal/Spec.md
ling1726 a448d88
Update packages/react-portal/Spec.md
ling1726 929521d
Update packages/react-portal/Spec.md
ling1726 a3bf685
Update packages/react-portal/Spec.md
ling1726 15d604e
Update packages/react-portal/Spec.md
ling1726 136900c
Update packages/react-portal/Spec.md
ling1726 f7016ab
Update packages/react-portal/Spec.md
ling1726 f342c0f
Update Spec.md
ling1726 4375fcb
Update Spec.md
ling1726 a7d4ddd
Update Spec.md
ling1726 efab426
Update Spec.md
ling1726 ca925d3
Update Spec.md
ling1726 75c9e81
Update Spec.md
ling1726 b5a7db2
Update Spec.md
ling1726 6fbbb36
Update packages/react-portal/Spec.md
ling1726 cb796a5
react-portal Spec
ling1726 ce77c33
Change files
ling1726 005fc9e
merge upstream
ling1726 9e13876
Merge remote-tracking branch 'origin/master' into spec/react-portal
ling1726 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
change/@fluentui-react-portal-50936d70-5bfe-4a99-ab79-bd021c92b43a.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "type": "none", | ||
| "comment": "Add spec to component", | ||
| "packageName": "@fluentui/react-portal", | ||
| "email": "lingfan.gao@microsoft.com", | ||
| "dependentChangeType": "none" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| # Portal Spec | ||
|
|
||
| ## Background | ||
|
|
||
| Components that require positioning out of the normal DOM order such as Menu and Tooltip are generally rendered through React portals. This is very useful when the components need to break out of the bounds of a parent component so that the content is not overflowed or covered by another element with zIndex. Portals also support event bubbling in the React tree. | ||
|
|
||
| Since our styling system uses css variables that are written onto DOM, we need to ensure that all portals are rendered onto a part of the DOM where the css variables are available. | ||
|
|
||
| Portals also need need to include the same dir attribute as the parent react tree, so that RTL/LTR is displayed correctly in the portal and the parent content. | ||
|
|
||
| ## Prior Art | ||
|
|
||
| Open UI research was not done for this component. `Portal` does not actually represent a UI control, but a utility component specific to React that makes the rendering of out of order DOM elements easy from within the React tree. | ||
|
|
||
| ### v0/v8 Comparison | ||
|
|
||
| This section compares the noteworthy differences/similarities between the `Layer` and `Portal` components of v8 and v0 respectively that share functionality with this proposed converged component. | ||
|
|
||
| #### Portal Trigger | ||
|
|
||
| The v0 Portal component includes a `trigger` prop that allows consumers to open a React portal. The adoption is not widely used across v0's main consumer, Teams. | ||
|
|
||
| The v8 `Layer` component fills a similar role to `PortalInner` which is the component that actually renders a React portal. | ||
|
|
||
| #### Event propagation | ||
|
|
||
| v8 `Layer` users must explicitly use the `enableEventBubbling` behaviour, which is default in React portals. This is mainly because `Layer` is older than `React.Portal`. Since synthetic event propagation was only introduced with React portals, it had to be enabled to preserve backwards compat. | ||
|
|
||
| Meanwhile v0 does not allow any kind of way to disable native React event bubbling through portals, to achieve a similar effect, the user must manage this themselves. | ||
|
|
||
| #### DOM insertion | ||
|
|
||
| Both `Layer` and `Portal` allow insertion of portals to the same part of the DOM element. | ||
|
|
||
| v8 Layer does this through a `LayerHost` which can be rendered at any part of the React tree. The result is a HTMLDiv element with a specific CSS class and unique Id. `Layer` will attempt to find a `LayerHost` to mount either by CSS class or user provided `hostId`. The default fallback is `document.body`. Each `Layer` will render its own `Fabric` provider. The `insertFirst` property supported by `Layer` was introduced in #8065 for modeless Dialogs which was achieved through `position: static` and DOM insertion order. The same effect can be achieved through `pointer-events: none`. | ||
|
|
||
| The v0 `PortalBoxContext` stores a single HTMLDiv that is usually a child of `document.body` where all `Portal` components are rendered by default. The v0 `Provider` is rendered for the default portal element, where style and RTL overrides could be applied for all portals within. | ||
|
|
||
| #### Lifecycle methods | ||
|
|
||
| v0 `Portal` only supports the following lifecycle methods: | ||
|
|
||
| - onMount | ||
| - onUnmount | ||
|
|
||
| `Layer` supports the following: | ||
|
|
||
| - onLayerMounted | ||
| - onLayerWillUnmount | ||
|
|
||
| The lifecycle events are very similar, with the only difference being that `onLayerWillUnmount` is called with `hostId` changes. v0 does not consider the mountNode changing as an unmount of the component itself. | ||
|
|
||
| #### Focus Management | ||
|
|
||
| v0 `Portal` can be configured to focus trap its contents while v8 `Layer` does not offer this and users would need to use `FocusTrapZone` for this purpose. | ||
|
|
||
| ## Sample Code | ||
|
|
||
| `Portal` by default mounts the content to `document.body`. In the event a consumer needs to target a specific mount node for Portal content this should be configurable via prop. Both variants should still be able to access theme and fluent context if available. | ||
|
|
||
| ``` | ||
| const customElement = document.createElement('div'); | ||
|
|
||
| <App> // using FluentProvider of ThemeProvider but not PortalProvider | ||
| <Portal /> // attached to document.body | ||
| <Portal mountNode={customElement} /> // mounted on custom element | ||
| </App> | ||
| ``` | ||
|
|
||
| `Portal` should be used as a component at any part of the React tree: | ||
|
|
||
| ```tsx | ||
| <ContextProvider> | ||
| <Portal> | ||
| <ContextConsumer /> // should be able to access context | ||
| </Portal> | ||
| </ContextProvider> | ||
| ``` | ||
|
|
||
| `Portal` should be able to access theme values as css variables: | ||
|
|
||
| ```tsx | ||
| const useStyles = makeStyles({ | ||
| portalContent: theme => {...} | ||
| }) | ||
|
|
||
|
|
||
| const styles = useStyles(); | ||
|
|
||
| <ThemeProvider> | ||
| <Portal> | ||
| <div className={styles.portalContent}> | ||
| Can use all theme CSS variables from the parent ThemeProvider | ||
| </div> | ||
| </Portal> | ||
| </ThemeProvider> | ||
| ``` | ||
|
|
||
| ## Variants | ||
|
|
||
| - Mounting the portal on a custom node | ||
|
|
||
| ## API | ||
|
|
||
| ### Portal | ||
|
|
||
| | Name | Description | Required | Type | Default value | | ||
| | --------- | -------------------------------------- | -------- | ----------- | -------------------------------- | | ||
| | mountNode | Where the portal is mounted to the DOM | No | HTMLElement | ProviderContext or document.body | | ||
|
|
||
| ## Structure | ||
|
|
||
| ``` | ||
| <FluentProvider | ||
| <Portal id="portal-1" /> | ||
| <Portal id="portal-2" /> | ||
| </FluentProvider | ||
| ``` | ||
|
|
||
| DOM output: | ||
|
|
||
| ```tsx | ||
| <body> | ||
| <div>Maintree</div> | ||
|
|
||
| <div id="portal-1" class="theme-provider-0"}>{children}</div> | ||
| <div id="portal-2" class="theme-provider-0"}>{children}</div> | ||
| </body> | ||
| ``` | ||
|
|
||
| ## Migration | ||
|
|
||
| _Describe what will need to be done to upgrade from the existing implementations:_ | ||
|
|
||
| ### v8 migration | ||
|
|
||
| - There will be no way to disable event bubbling, it will be up to consumers to call `stopPropagation` themselves or create extra utilities that do so | ||
| - No more concept of `LayerHost` and id/class selectors, raw HTML elements/refs can be stored in context on the consumer app and used in `mountNode` for `Portals` if required | ||
| - No more mount lifecycle methods, users can remedy this easily with `useEffect` or `useLayoutEffect` hooks | ||
| - `insertFirst` will no longer be supported, and can be handled by a custom `mountNode` if necessary, sticky Dialog can be implmented with `pointer-events: none` | ||
|
|
||
| ### v0 migration | ||
|
|
||
| - No more openable portals - should use future converged `Popup` | ||
| - No more focus trapping in `Portals` do that manually (Tabster) | ||
| - No more mount lifecycle methods, users can remedy this easily with `useEffect` or `useLayoutEffect` hooks | ||
|
|
||
| ## Behaviors | ||
|
|
||
| ### Server Side Rendering (SSR) | ||
|
|
||
| The ReactDOM `createPortal` requires a valid DOM node to render. This is problematic when `document` does not actually exist during the server render. Instead during the server render `null` will be used. This is not a big problem for most components that use portals such as popups or dialogs since they must be opened from some kind of trigger element (i.e. button) | ||
|
|
||
| However, there are some cases where a `Portal` content will always need to be rendered on the page. Tooltips should always be rendered so that `aria` attributes will refer to actual elements. This is problematic since the Tooltip (or higher order component) needs to be aware of the server render where `null`is rendered and render the actual content on the first client render. | ||
|
|
||
| The `Portal` component should handle this SSR case, and should be aware of the server and client renders when calling `ReactDOM.createPortal`. | ||
|
|
||
| ## Accessibility | ||
|
|
||
| This component is considered a utility to render its children to an out of order DOM element. Since the component itself does not render DOM it is up to the consumer to handle the A11y requirements of their portal content. | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.