Skip to content

Commit b8486f7

Browse files
Redo react 17 compatibility (#2433)
* Revert "React 17 compatibility (as patch to 1.28) (#2431)" This reverts commit 8a51b78. * fix: move UiShell, renderReactInWebComponent into separate export for React 17 compatibility * fix: related storybook updates
1 parent 00d13c9 commit b8486f7

25 files changed

+100
-91
lines changed

packages/odyssey-react-mui/package.json

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
"labs": [
2020
"./dist/src/labs/index.d.ts"
2121
],
22+
"ui-shell": [
23+
"./dist/src/ui-shell/index.d.ts"
24+
],
2225
"test-selectors": [
2326
"./dist/src/test-selectors/index.d.ts"
2427
]
@@ -33,6 +36,10 @@
3336
"types": "./dist/src/labs/index.d.ts",
3437
"default": "./dist/labs/index.js"
3538
},
39+
"./ui-shell": {
40+
"types": "./dist/src/ui-shell/index.d.ts",
41+
"default": "./dist/ui-shell/index.js"
42+
},
3643
"./test-selectors": {
3744
"types": "./dist/src/test-selectors/index.d.ts",
3845
"default": "./dist/test-selectors/index.js"

packages/odyssey-react-mui/src/labs/PageTemplate.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
import { DocumentationIcon } from "../icons.generated";
2121
import { Heading4, Subordinate } from "../Typography";
2222
import { Link } from "../Link";
23-
import { useHasUiShell } from "./UiShell";
23+
import { useHasUiShell } from "../ui-shell/UiShell/useHasUiShell";
2424

2525
export type PageTemplateProps = {
2626
/**

packages/odyssey-react-mui/src/labs/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,3 @@ export * from "./AppSwitcher";
4444
export * from "./SideNav/NavAccordion";
4545
export * from "./SideNav";
4646
export * from "./TopNav";
47-
export * from "./UiShell";

packages/odyssey-react-mui/src/labs/UiShell/UiShellContent.tsx packages/odyssey-react-mui/src/ui-shell/UiShell/UiShellContent.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import styled from "@emotion/styled";
1414
import { memo, type ReactElement, type ReactNode } from "react";
1515
import { ErrorBoundary, ErrorBoundaryProps } from "react-error-boundary";
1616

17-
import { AppSwitcher, type AppSwitcherProps } from "../AppSwitcher";
18-
import { SideNav, type SideNavProps } from "../SideNav";
19-
import { TopNav, type TopNavProps } from "../TopNav";
17+
import { AppSwitcher, type AppSwitcherProps } from "../../labs/AppSwitcher";
18+
import { SideNav, type SideNavProps } from "../../labs/SideNav";
19+
import { TopNav, type TopNavProps } from "../../labs/TopNav";
2020
import {
2121
useOdysseyDesignTokens,
2222
type DesignTokens,

packages/odyssey-react-mui/src/labs/UiShell/renderUiShell.test.tsx packages/odyssey-react-mui/src/ui-shell/UiShell/renderUiShell.test.tsx

+16-22
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
* See the License for the specific language governing permissions and limitations under the License.
1111
*/
1212

13-
import { act, waitFor } from "@testing-library/react";
13+
import { act } from "@testing-library/react";
1414

1515
import { renderUiShell } from "./renderUiShell";
1616
import {
1717
ReactInWebComponentElement,
1818
reactWebComponentElementName,
19-
} from "../../web-component/renderReactInWebComponent";
19+
} from "../renderReactInWebComponent";
2020

2121
describe("renderUiShell", () => {
2222
afterEach(() => {
@@ -126,11 +126,9 @@ describe("renderUiShell", () => {
126126
});
127127
});
128128

129-
await waitFor(() => {
130-
expect(
131-
rootElement.querySelector(reactWebComponentElementName)!.shadowRoot,
132-
).toHaveTextContent(appName);
133-
});
129+
expect(
130+
rootElement.querySelector(reactWebComponentElementName)!.shadowRoot,
131+
).toHaveTextContent(appName);
134132
});
135133

136134
test("renders `UiShell` with immediately updated props", async () => {
@@ -155,11 +153,9 @@ describe("renderUiShell", () => {
155153
});
156154
});
157155

158-
await waitFor(() => {
159-
expect(
160-
rootElement.querySelector(reactWebComponentElementName)!.shadowRoot,
161-
).toHaveTextContent(appName);
162-
});
156+
expect(
157+
rootElement.querySelector(reactWebComponentElementName)!.shadowRoot,
158+
).toHaveTextContent(appName);
163159
});
164160

165161
test("renders `<slot>` in the event of an error", async () => {
@@ -188,16 +184,14 @@ describe("renderUiShell", () => {
188184
);
189185
});
190186

191-
await waitFor(() => {
192-
expect(onError).toHaveBeenCalledTimes(1);
193-
expect(consoleError).toHaveBeenCalledTimes(1);
194-
expect(
195-
rootElement
196-
.querySelector(reactWebComponentElementName)!
197-
.shadowRoot?.querySelector("slot"),
198-
).toBeInstanceOf(HTMLSlotElement);
199-
});
200-
201187
consoleErrorSpy.mockRestore();
188+
189+
expect(onError).toHaveBeenCalledTimes(1);
190+
expect(consoleError).toHaveBeenCalledTimes(1);
191+
expect(
192+
rootElement
193+
.querySelector(reactWebComponentElementName)!
194+
.shadowRoot?.querySelector("slot"),
195+
).toBeInstanceOf(HTMLSlotElement);
202196
});
203197
});

packages/odyssey-react-mui/src/labs/UiShell/renderUiShell.tsx packages/odyssey-react-mui/src/ui-shell/UiShell/renderUiShell.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@ import { ErrorBoundary } from "react-error-boundary";
1616
import { bufferLatest } from "./bufferLatest";
1717
import { createMessageBus } from "./createMessageBus";
1818
import { UiShell, UiShellProps } from "./UiShell";
19-
import { renderReactInWebComponent } from "../../web-component/renderReactInWebComponent";
19+
import { renderReactInWebComponent } from "../renderReactInWebComponent";
2020
import { type UiShellNavComponentProps } from "./UiShellContent";
21-
22-
export const uiShellDataAttribute = "data-unified-ui-shell";
21+
import { uiShellDataAttribute } from "./useHasUiShell";
2322

2423
export const optionalComponentSlotNames: Record<
2524
keyof Required<UiShellProps>["optionalComponents"],

packages/odyssey-react-mui/src/labs/UiShell/useHasUiShell.ts packages/odyssey-react-mui/src/ui-shell/UiShell/useHasUiShell.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import { useEffect, useState } from "react";
1414

15-
import { uiShellDataAttribute } from "./renderUiShell";
15+
export const uiShellDataAttribute = "data-unified-ui-shell";
1616

1717
export const useHasUiShell = () => {
1818
const [hasUiShell, setHasUiShell] = useState(false);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*!
2+
* Copyright (c) 2022-present, Okta, Inc. and/or its affiliates. All rights reserved.
3+
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
4+
*
5+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
6+
* Unless required by applicable law or agreed to in writing, software
7+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
8+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
*
10+
* See the License for the specific language governing permissions and limitations under the License.
11+
*/
12+
13+
export * from "./UiShell";
14+
export * from "./renderReactInWebComponent";

packages/odyssey-react-mui/src/web-component/renderReactInWebComponent.test.tsx packages/odyssey-react-mui/src/ui-shell/renderReactInWebComponent.test.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
import { waitFor } from "@testing-library/dom";
1414

15+
import { createReactRootElements } from "../web-component";
1516
import {
16-
createReactRootElements,
1717
ReactInWebComponentElement,
1818
reactWebComponentElementName,
1919
renderReactInWebComponent,

packages/odyssey-react-mui/src/web-component/renderReactInWebComponent.ts packages/odyssey-react-mui/src/ui-shell/renderReactInWebComponent.ts

+9-45
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,11 @@
1111
*/
1212

1313
import { type ReactNode } from "react";
14-
import type { Root } from "react-dom/client";
15-
16-
/**
17-
* Creates elements for a Shadow DOM that Odyssey will render into.
18-
* The Emotion root is for `<style>` tags and the app root is for an app to render into.
19-
* These are bare elements that
20-
*/
21-
export const createReactRootElements = () => {
22-
const appRootElement = document.createElement("div");
23-
const stylesRootElement = document.createElement("div");
24-
25-
// This `div` may cause layout issues unless it inherits the parent's height.
26-
appRootElement.style.setProperty("height", "inherit");
27-
28-
appRootElement.setAttribute("id", "app-root");
29-
stylesRootElement.setAttribute("id", "style-root");
30-
stylesRootElement.setAttribute("nonce", window.cspNonce);
31-
32-
return {
33-
/**
34-
* The element your React root component renders into.
35-
* React has to render or portal somewhere, and this element can be used for that root element.
36-
*
37-
* In the case of a web component, there is no defined root element, so you have to define it yourself.
38-
*/
39-
appRootElement,
40-
/**
41-
* In React apps, your styles typically go in `document.head`, but you may want to render them somewhere else.
42-
*
43-
* Specifically when rendering in a web component, there is no `<head>`, so you have to create a spot for styles to render.
44-
*/
45-
stylesRootElement,
46-
};
47-
};
48-
49-
export type ReactRootElements = ReturnType<typeof createReactRootElements>;
14+
import { createRoot, type Root } from "react-dom/client";
15+
import {
16+
createReactRootElements,
17+
type ReactRootElements,
18+
} from "../web-component";
5019

5120
export const reactWebComponentElementName = "odyssey-react-web-component";
5221

@@ -56,8 +25,8 @@ export type GetReactComponentInWebComponent = (
5625

5726
export class ReactInWebComponentElement extends HTMLElement {
5827
getReactComponent: GetReactComponentInWebComponent;
28+
reactRoot: Root;
5929
reactRootElements: ReactRootElements;
60-
reactRootPromise: Promise<Root>;
6130

6231
constructor(getReactComponent: GetReactComponentInWebComponent) {
6332
super();
@@ -81,20 +50,15 @@ export class ReactInWebComponentElement extends HTMLElement {
8150
shadowRoot.appendChild(this.reactRootElements.stylesRootElement);
8251
shadowRoot.appendChild(this.reactRootElements.appRootElement);
8352

84-
// If we want to support React v17 in the future, we can use a try-catch on the import to grab the old `ReactDOM.render` function if `react-dom/client` errors. --Kevin Ghadyani
85-
this.reactRootPromise = import("react-dom/client").then(({ createRoot }) =>
86-
createRoot(this.reactRootElements.appRootElement),
87-
);
53+
this.reactRoot = createRoot(this.reactRootElements.appRootElement);
8854
}
8955

9056
connectedCallback() {
91-
this.reactRootPromise.then((reactRoot) =>
92-
reactRoot.render(this.getReactComponent(this.reactRootElements)),
93-
);
57+
this.reactRoot.render(this.getReactComponent(this.reactRootElements));
9458
}
9559

9660
disconnectedCallback() {
97-
this.reactRootPromise.then((reactRoot) => reactRoot.unmount());
61+
this.reactRoot.unmount();
9862
}
9963
}
10064

packages/odyssey-react-mui/src/web-component/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,4 @@
1010
* See the License for the specific language governing permissions and limitations under the License.
1111
*/
1212

13-
export * from "./renderReactInWebComponent";
1413
export * from "./shadow-dom";

packages/odyssey-react-mui/src/web-component/shadow-dom.ts

+36-3
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,43 @@
1010
* See the License for the specific language governing permissions and limitations under the License.
1111
*/
1212

13-
import { createReactRootElements } from "./renderReactInWebComponent";
13+
/**
14+
* Creates elements for a Shadow DOM that Odyssey will render into.
15+
* The Emotion root is for `<style>` tags and the app root is for an app to render into.
16+
* These are bare elements that
17+
*/
18+
export const createReactRootElements = () => {
19+
const appRootElement = document.createElement("div");
20+
const stylesRootElement = document.createElement("div");
21+
22+
// This `div` may cause layout issues unless it inherits the parent's height.
23+
appRootElement.style.setProperty("height", "inherit");
24+
25+
appRootElement.setAttribute("id", "app-root");
26+
stylesRootElement.setAttribute("id", "style-root");
27+
stylesRootElement.setAttribute("nonce", window.cspNonce);
28+
29+
return {
30+
/**
31+
* The element your React root component renders into.
32+
* React has to render or portal somewhere, and this element can be used for that root element.
33+
*
34+
* In the case of a web component, there is no defined root element, so you have to define it yourself.
35+
*/
36+
appRootElement,
37+
/**
38+
* In React apps, your styles typically go in `document.head`, but you may want to render them somewhere else.
39+
*
40+
* Specifically when rendering in a web component, there is no `<head>`, so you have to create a spot for styles to render.
41+
*/
42+
stylesRootElement,
43+
};
44+
};
45+
46+
export type ReactRootElements = ReturnType<typeof createReactRootElements>;
1447

1548
/**
16-
* @deprecated Use `renderReactInWebComponent` instead. This function was necessary when using bare Shadow DOM, but with UI Shell rendering in a Web Component, you won't be able to render your Shadow DOM in its Shadow DOM without using a Web Component.
49+
* @deprecated Use `renderReactInWebComponent` from `@okta/odyssey-react-mui/ui-shell` instead. This function was necessary when using bare Shadow DOM, but with UI Shell rendering in a Web Component, you won't be able to render your Shadow DOM in its Shadow DOM without using a Web Component.
1750
*/
1851
export const createShadowDomElements = (containerElement: HTMLElement) => {
1952
const shadowRoot = containerElement.attachShadow({ mode: "open" });
@@ -32,7 +65,7 @@ export const createShadowDomElements = (containerElement: HTMLElement) => {
3265

3366
/**
3467
* @deprecated Use `createShadowDomElements` instead which returns an object instead of an array. It's otherwise the same.
35-
* @deprecated Ideally, use `renderReactInWebComponent` instead. This function was necessary when using bare Shadow DOM, but with UI Shell rendering in a Web Component, you won't be able to render your Shadow DOM in its Shadow DOM without using a Web Component. */
68+
* @deprecated Ideally, use `renderReactInWebComponent` from `@okta/odyssey-react-mui/ui-shell` instead. This function was necessary when using bare Shadow DOM, but with UI Shell rendering in a Web Component, you won't be able to render your Shadow DOM in its Shadow DOM without using a Web Component. */
3669
export const createShadowRootElement = (
3770
containerElement: HTMLElement,
3871
): [HTMLStyleElement, HTMLDivElement] => {

packages/odyssey-storybook/.storybook/preview.ts

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ const preview: Preview = {
7878
"Contributing",
7979
"MUI Components",
8080
"Labs Components",
81+
"UI Shell Components",
8182
"Customization",
8283
],
8384
locales: "",

packages/odyssey-storybook/src/components/odyssey-labs/UiShell/UiShell.stories.tsx packages/odyssey-storybook/src/components/odyssey-ui-shell/UiShell/UiShell.stories.tsx

+8-9
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,6 @@
1212

1313
import { Meta, StoryObj } from "@storybook/react";
1414
import { MuiThemeDecorator } from "../../../../.storybook/components";
15-
import {
16-
PageTemplate,
17-
UiShell,
18-
uiShellDataAttribute,
19-
UserProfile,
20-
type UiShellNavComponentProps,
21-
type UiShellProps,
22-
} from "@okta/odyssey-react-mui/labs";
2315
import {
2416
Banner,
2517
Button,
@@ -28,14 +20,21 @@ import {
2820
SearchField,
2921
Surface,
3022
} from "@okta/odyssey-react-mui";
23+
import { PageTemplate, UserProfile } from "@okta/odyssey-react-mui/labs";
24+
import {
25+
UiShell,
26+
uiShellDataAttribute,
27+
type UiShellNavComponentProps,
28+
type UiShellProps,
29+
} from "@okta/odyssey-react-mui/ui-shell";
3130
import {
3231
AddCircleIcon,
3332
HomeIcon,
3433
UserIcon,
3534
} from "@okta/odyssey-react-mui/icons";
3635

3736
const storybookMeta: Meta<UiShellProps> = {
38-
title: "Labs Components/UI Shell",
37+
title: "UI Shell Components/UI Shell",
3938
component: UiShell,
4039
argTypes: {
4140
appComponent: {

packages/odyssey-storybook/src/installation/ShadowDom.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ To sandbox your application's styles, you need to use a Web Component which also
1313
To create a Shadow DOM, simply use the `renderReactInWebComponent` in your app's top-level file (typically `index.ts`):
1414

1515
```tsx
16-
import { renderReactInWebComponent } from "@okta/odyssey-react-mui/labs";
16+
import { renderReactInWebComponent } from "@okta/odyssey-react-mui/ui-shell";
1717

1818
const appRootElement = document.createElement("div");
1919
const containerRootElement = document.getElementById("container-root");

0 commit comments

Comments
 (0)