Skip to content
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

fix: fixed typing for shadow root element. updated tests to reflect changes #2048

Merged
merged 7 commits into from
Dec 5, 2023
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} />;
},
};
Loading
Loading