Skip to content

Commit

Permalink
fix: fixed typing for shadow root element. updated tests to reflect c…
Browse files Browse the repository at this point in the history
…hanges (#2048)

* fix: fixed providers caching issue and how shadow dom works with providers
  • Loading branch information
chrispulsinelli-okta committed Dec 5, 2023
1 parent dbcfcc1 commit 3ae92a1
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 97 deletions.
48 changes: 19 additions & 29 deletions packages/odyssey-react-mui/src/OdysseyCacheProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,51 +17,41 @@ declare global {
}

import createCache, { StylisPlugin } from "@emotion/cache";
import { CacheProvider } from "@emotion/react";
import { memo, ReactNode, useMemo } from "react";

import { memo, useMemo, ReactNode } from "react";
import { useUniqueAlphabeticalId } from "./useUniqueAlphabeticalId";
import { CacheProvider } from "@emotion/react";

export type OdysseyCacheProviderProps = {
children: ReactNode;
nonce?: string;
/**
* Emotion caches styles into the style element.
* When enabling this prop, Emotion caches the styles at this element, rather than in <head>.
*/
emotionRoot?: HTMLStyleElement;
/**
* Emotion renders into this HTML element.
* When enabling this prop, Emotion renders at the top of this component rather than the bottom like it does in the HTML `<head>`.
*/
nonce?: string;
shadowDomElement?: HTMLDivElement;
shadowDomElement?: HTMLDivElement | HTMLElement;
stylisPlugins?: StylisPlugin[];
};

const OdysseyCacheProvider = ({
children,
nonce,
shadowDomElement,
stylisPlugins,
emotionRoot,
}: OdysseyCacheProviderProps) => {
const uniqueAlphabeticalId = useUniqueAlphabeticalId();

const emotionRootElement = useMemo(() => {
const emotionRootElement = document.createElement("div");

emotionRootElement.setAttribute("data-emotion-root", "data-emotion-root");

shadowDomElement?.prepend?.(emotionRootElement);

return emotionRootElement;
}, [shadowDomElement]);

const emotionCache = useMemo(
() =>
createCache({
container: emotionRootElement,
key: uniqueAlphabeticalId,
nonce: nonce || window.cspNonce,
prepend: Boolean(emotionRootElement),
stylisPlugins,
}),
[emotionRootElement, nonce, stylisPlugins, uniqueAlphabeticalId]
);
const emotionCache = useMemo(() => {
return createCache({
...(emotionRoot && { container: emotionRoot }),
key: uniqueAlphabeticalId,
nonce: window.cspNonce,
prepend: true,
speedy: false, // <-- Needs to be set to false when shadow-dom is used!! https://github.com/emotion-js/emotion/issues/2053#issuecomment-713429122
});
}, [emotionRoot, uniqueAlphabeticalId]);

return <CacheProvider value={emotionCache}>{children}</CacheProvider>;
};
Expand Down
4 changes: 4 additions & 0 deletions packages/odyssey-react-mui/src/OdysseyProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export type OdysseyProviderProps = OdysseyCacheProviderProps &
const OdysseyProvider = ({
children,
designTokensOverride,
emotionRoot,
shadowDomElement,
languageCode,
nonce,
Expand All @@ -44,13 +45,16 @@ const OdysseyProvider = ({
}: OdysseyProviderProps) => (
<OdysseyCacheProvider
nonce={nonce}
emotionRoot={emotionRoot}
shadowDomElement={shadowDomElement}
stylisPlugins={stylisPlugins}
>
<OdysseyThemeProvider
designTokensOverride={designTokensOverride}
emotionRoot={emotionRoot}
shadowDomElement={shadowDomElement}
themeOverride={themeOverride}
withCache={false}
>
<ScopedCssBaseline>
<OdysseyTranslationProvider
Expand Down
34 changes: 33 additions & 1 deletion packages/odyssey-react-mui/src/OdysseyThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,26 @@ import { deepmerge } from "@mui/utils";
import { createOdysseyMuiTheme, DesignTokensOverride } from "./theme";
import * as Tokens from "@okta/odyssey-design-tokens";
import { OdysseyDesignTokensContext } from "./OdysseyDesignTokensContext";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
import { useUniqueAlphabeticalId } from "./useUniqueAlphabeticalId";

export type OdysseyThemeProviderProps = {
children: ReactNode;
designTokensOverride?: DesignTokensOverride;
shadowDomElement?: HTMLDivElement;
emotionRoot?: HTMLStyleElement;
shadowDomElement?: HTMLDivElement | HTMLElement | undefined;
themeOverride?: ThemeOptions;
withCache?: boolean;
};

const OdysseyThemeProvider = ({
children,
designTokensOverride,
emotionRoot,
shadowDomElement,
themeOverride,
withCache = true,
}: OdysseyThemeProviderProps) => {
const odysseyTokens = useMemo(
() => ({ ...Tokens, ...designTokensOverride }),
Expand All @@ -53,6 +60,31 @@ const OdysseyThemeProvider = ({
[odysseyTheme, themeOverride]
);

const uniqueAlphabeticalId = useUniqueAlphabeticalId();

const cache = useMemo(
() =>
createCache({
...(emotionRoot && { container: emotionRoot }),
key: uniqueAlphabeticalId,
prepend: true,
nonce: window.cspNonce,
speedy: false, // <-- Needs to be set to false when shadow-dom is used!! https://github.com/emotion-js/emotion/issues/2053#issuecomment-713429122
}),
[emotionRoot, uniqueAlphabeticalId]
);

if (withCache) {
return (
<CacheProvider value={cache}>
<MuiThemeProvider theme={customOdysseyTheme ?? odysseyTheme}>
<OdysseyDesignTokensContext.Provider value={odysseyTokens}>
{children}
</OdysseyDesignTokensContext.Provider>
</MuiThemeProvider>
</CacheProvider>
);
}
return (
<MuiThemeProvider theme={customOdysseyTheme ?? odysseyTheme}>
<OdysseyDesignTokensContext.Provider value={odysseyTokens}>
Expand Down
16 changes: 13 additions & 3 deletions packages/odyssey-react-mui/src/createShadowRootElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,22 @@
* See the License for the specific language governing permissions and limitations under the License.
*/

export const createShadowRootElement = (containerElement: HTMLElement) => {
export const createShadowRootElement = (
containerElement: HTMLElement
): [HTMLStyleElement, HTMLDivElement] => {
const shadowRoot = containerElement.attachShadow({ mode: "open" });

// the element that styles will be cached into
const emotionRoot = document.createElement("style");
emotionRoot.setAttribute("id", "style-root");
emotionRoot.setAttribute("nonce", window.cspNonce);

// the element that emotion renders html into
const shadowRootElement = document.createElement("div");
shadowRootElement.setAttribute("id", "shadow-root");

shadowRoot.append(shadowRootElement);
shadowRoot.appendChild(emotionRoot);
shadowRoot.appendChild(shadowRootElement);

return shadowRoot;
return [emotionRoot, shadowRootElement];
};
2 changes: 1 addition & 1 deletion packages/odyssey-react-mui/src/theme/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const components = ({
shadowDomElement,
}: {
odysseyTokens: DesignTokens;
shadowDomElement?: HTMLDivElement;
shadowDomElement?: HTMLElement;
}): ThemeOptions["components"] => {
return {
MuiAccordion: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const createOdysseyMuiTheme = ({
shadowDomElement,
}: {
odysseyTokens: DesignTokens;
shadowDomElement?: HTMLDivElement;
shadowDomElement?: HTMLElement;
}) =>
createTheme({
components: components({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
createOdysseyMuiTheme,
OdysseyThemeProvider,
OdysseyTranslationProvider,
OdysseyProvider,
} from "@okta/odyssey-react-mui";
import { CssBaseline, ScopedCssBaseline } from "@mui/material";
import { ThemeProvider as StorybookThemeProvider } from "@storybook/theming";
Expand All @@ -15,9 +14,14 @@ const styles = {

const odysseyTheme = createOdysseyMuiTheme({ odysseyTokens });

export const MuiThemeDecorator: Decorator = (Story, context) => (
<OdysseyThemeProvider>
<OdysseyTranslationProvider languageCode={context.globals.locale}>
export const MuiThemeDecorator: Decorator = (Story, context) => {
const {
canvasElement,
globals: { locale },
} = context;
const shadowRootElement = canvasElement.parentElement ?? undefined;
return (
<OdysseyProvider languageCode={locale} shadowDomElement={shadowRootElement}>
{/* @ts-expect-error type mismatch on "typography" */}
<StorybookThemeProvider theme={odysseyTheme}>
<CssBaseline />
Expand All @@ -27,6 +31,6 @@ export const MuiThemeDecorator: Decorator = (Story, context) => (
</ScopedCssBaseline>
</div>
</StorybookThemeProvider>
</OdysseyTranslationProvider>
</OdysseyThemeProvider>
);
</OdysseyProvider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import { useMemo, useState } from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { OdysseyThemeProvider } from "@okta/odyssey-react-mui";
import { OdysseyProvider } from "@okta/odyssey-react-mui";
import {
AdapterDateFns,
DatePicker,
Expand Down Expand Up @@ -70,6 +70,22 @@ const storybookMeta: Meta<DatePickerProps<unknown, unknown>> = {
export default storybookMeta;

export const DatePickerStandard: StoryObj<DatePickerProps<string, string>> = {
decorators: [
(Story, context) => {
const { canvasElement } = context;
const parentElement = canvasElement.parentElement ?? undefined;
return (
<OdysseyProvider
themeOverride={datePickerTheme}
shadowDomElement={parentElement}
>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Story />
</LocalizationProvider>
</OdysseyProvider>
);
},
],
render: function C(props) {
const [value, setValue] = useState("09/05/1977");
const datePickerProps = useMemo<DatePickerProps<string, string>>(
Expand All @@ -81,12 +97,6 @@ export const DatePickerStandard: StoryObj<DatePickerProps<string, string>> = {
[props, value]
);

return (
<OdysseyThemeProvider themeOverride={datePickerTheme}>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker {...datePickerProps} />
</LocalizationProvider>
</OdysseyThemeProvider>
);
return <DatePicker {...datePickerProps} />;
},
};

0 comments on commit 3ae92a1

Please sign in to comment.