@okta/odyssey-react-mui 1.8.2 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/Checkbox.js +6 -1
  3. package/dist/Checkbox.js.map +1 -1
  4. package/dist/OdysseyCacheProvider.js +13 -17
  5. package/dist/OdysseyCacheProvider.js.map +1 -1
  6. package/dist/OdysseyProvider.js +4 -0
  7. package/dist/OdysseyProvider.js.map +1 -1
  8. package/dist/OdysseyThemeProvider.js +28 -1
  9. package/dist/OdysseyThemeProvider.js.map +1 -1
  10. package/dist/Radio.js +7 -2
  11. package/dist/Radio.js.map +1 -1
  12. package/dist/createShadowRootElement.js +7 -2
  13. package/dist/createShadowRootElement.js.map +1 -1
  14. package/dist/src/Checkbox.d.ts +6 -2
  15. package/dist/src/Checkbox.d.ts.map +1 -1
  16. package/dist/src/OdysseyCacheProvider.d.ts +8 -3
  17. package/dist/src/OdysseyCacheProvider.d.ts.map +1 -1
  18. package/dist/src/OdysseyProvider.d.ts +1 -1
  19. package/dist/src/OdysseyProvider.d.ts.map +1 -1
  20. package/dist/src/OdysseyThemeProvider.d.ts +4 -2
  21. package/dist/src/OdysseyThemeProvider.d.ts.map +1 -1
  22. package/dist/src/Radio.d.ts +6 -2
  23. package/dist/src/Radio.d.ts.map +1 -1
  24. package/dist/src/createShadowRootElement.d.ts +1 -1
  25. package/dist/src/createShadowRootElement.d.ts.map +1 -1
  26. package/dist/src/theme/components.d.ts +1 -1
  27. package/dist/src/theme/createOdysseyMuiTheme.d.ts +1 -1
  28. package/dist/theme/components.js.map +1 -1
  29. package/dist/theme/createOdysseyMuiTheme.js.map +1 -1
  30. package/dist/tsconfig.production.tsbuildinfo +1 -1
  31. package/package.json +3 -3
  32. package/src/Checkbox.tsx +14 -0
  33. package/src/OdysseyCacheProvider.tsx +19 -29
  34. package/src/OdysseyProvider.tsx +4 -0
  35. package/src/OdysseyThemeProvider.tsx +33 -1
  36. package/src/Radio.tsx +14 -0
  37. package/src/createShadowRootElement.ts +13 -3
  38. package/src/theme/components.tsx +1 -1
  39. package/src/theme/createOdysseyMuiTheme.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okta/odyssey-react-mui",
3
- "version": "1.8.2",
3
+ "version": "1.9.1",
4
4
  "description": "React MUI components for Odyssey, Okta's design system",
5
5
  "author": "Okta, Inc.",
6
6
  "license": "Apache-2.0",
@@ -51,7 +51,7 @@
51
51
  "@mui/system": "^5.14.9",
52
52
  "@mui/utils": "^5.11.2",
53
53
  "@mui/x-date-pickers": "^5.0.15",
54
- "@okta/odyssey-design-tokens": "1.8.2",
54
+ "@okta/odyssey-design-tokens": "1.9.1",
55
55
  "date-fns": "^2.30.0",
56
56
  "i18next": "^23.5.1",
57
57
  "material-react-table": "^2.0.2",
@@ -63,5 +63,5 @@
63
63
  "react": ">=17 <19",
64
64
  "react-dom": ">=17 <19"
65
65
  },
66
- "gitHead": "0b0034e9fa97a5dd852f01249aa5093484040ec9"
66
+ "gitHead": "09181cf65fb26379fca04275a4adbed25478cdd1"
67
67
  }
package/src/Checkbox.tsx CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  Checkbox as MuiCheckbox,
17
17
  CheckboxProps as MuiCheckboxProps,
18
18
  FormControlLabel,
19
+ FormControlLabelProps as MuiFormControlLabelProps,
19
20
  FormHelperText,
20
21
  } from "@mui/material";
21
22
 
@@ -68,6 +69,10 @@ export type CheckboxProps = {
68
69
  * The value attribute of the Checkbox
69
70
  */
70
71
  value?: string;
72
+ /**
73
+ * Callback fired when the blur event happens. Provides event value.
74
+ */
75
+ onBlur?: MuiFormControlLabelProps["onBlur"];
71
76
  } & Pick<FieldComponentProps, "id" | "isDisabled" | "name"> &
72
77
  CheckedFieldProps<MuiCheckboxProps> &
73
78
  SeleniumProps;
@@ -85,6 +90,7 @@ const Checkbox = ({
85
90
  hint,
86
91
  name: nameOverride,
87
92
  onChange: onChangeProp,
93
+ onBlur: onBlurProp,
88
94
  testId,
89
95
  validity = "inherit",
90
96
  value,
@@ -127,6 +133,13 @@ const Checkbox = ({
127
133
  [onChangeProp]
128
134
  );
129
135
 
136
+ const onBlur = useCallback<NonNullable<MuiFormControlLabelProps["onBlur"]>>(
137
+ (event) => {
138
+ onBlurProp?.(event);
139
+ },
140
+ [onBlurProp]
141
+ );
142
+
130
143
  return (
131
144
  <FormControlLabel
132
145
  sx={{ alignItems: "flex-start" }}
@@ -157,6 +170,7 @@ const Checkbox = ({
157
170
  name={nameOverride ?? idOverride}
158
171
  value={value}
159
172
  required={isRequired}
173
+ onBlur={onBlur}
160
174
  />
161
175
  );
162
176
  };
@@ -17,51 +17,41 @@ declare global {
17
17
  }
18
18
 
19
19
  import createCache, { StylisPlugin } from "@emotion/cache";
20
- import { CacheProvider } from "@emotion/react";
21
- import { memo, ReactNode, useMemo } from "react";
22
-
20
+ import { memo, useMemo, ReactNode } from "react";
23
21
  import { useUniqueAlphabeticalId } from "./useUniqueAlphabeticalId";
22
+ import { CacheProvider } from "@emotion/react";
24
23
 
25
24
  export type OdysseyCacheProviderProps = {
26
25
  children: ReactNode;
26
+ nonce?: string;
27
+ /**
28
+ * Emotion caches styles into the style element.
29
+ * When enabling this prop, Emotion caches the styles at this element, rather than in <head>.
30
+ */
31
+ emotionRoot?: HTMLStyleElement;
27
32
  /**
28
33
  * Emotion renders into this HTML element.
29
34
  * When enabling this prop, Emotion renders at the top of this component rather than the bottom like it does in the HTML `<head>`.
30
35
  */
31
- nonce?: string;
32
- shadowDomElement?: HTMLDivElement;
36
+ shadowDomElement?: HTMLDivElement | HTMLElement;
33
37
  stylisPlugins?: StylisPlugin[];
34
38
  };
35
39
 
36
40
  const OdysseyCacheProvider = ({
37
41
  children,
38
- nonce,
39
- shadowDomElement,
40
- stylisPlugins,
42
+ emotionRoot,
41
43
  }: OdysseyCacheProviderProps) => {
42
44
  const uniqueAlphabeticalId = useUniqueAlphabeticalId();
43
45
 
44
- const emotionRootElement = useMemo(() => {
45
- const emotionRootElement = document.createElement("div");
46
-
47
- emotionRootElement.setAttribute("data-emotion-root", "data-emotion-root");
48
-
49
- shadowDomElement?.prepend?.(emotionRootElement);
50
-
51
- return emotionRootElement;
52
- }, [shadowDomElement]);
53
-
54
- const emotionCache = useMemo(
55
- () =>
56
- createCache({
57
- container: emotionRootElement,
58
- key: uniqueAlphabeticalId,
59
- nonce: nonce || window.cspNonce,
60
- prepend: Boolean(emotionRootElement),
61
- stylisPlugins,
62
- }),
63
- [emotionRootElement, nonce, stylisPlugins, uniqueAlphabeticalId]
64
- );
46
+ const emotionCache = useMemo(() => {
47
+ return createCache({
48
+ ...(emotionRoot && { container: emotionRoot }),
49
+ key: uniqueAlphabeticalId,
50
+ nonce: window.cspNonce,
51
+ prepend: true,
52
+ speedy: false, // <-- Needs to be set to false when shadow-dom is used!! https://github.com/emotion-js/emotion/issues/2053#issuecomment-713429122
53
+ });
54
+ }, [emotionRoot, uniqueAlphabeticalId]);
65
55
 
66
56
  return <CacheProvider value={emotionCache}>{children}</CacheProvider>;
67
57
  };
@@ -35,6 +35,7 @@ export type OdysseyProviderProps = OdysseyCacheProviderProps &
35
35
  const OdysseyProvider = ({
36
36
  children,
37
37
  designTokensOverride,
38
+ emotionRoot,
38
39
  shadowDomElement,
39
40
  languageCode,
40
41
  nonce,
@@ -44,13 +45,16 @@ const OdysseyProvider = ({
44
45
  }: OdysseyProviderProps) => (
45
46
  <OdysseyCacheProvider
46
47
  nonce={nonce}
48
+ emotionRoot={emotionRoot}
47
49
  shadowDomElement={shadowDomElement}
48
50
  stylisPlugins={stylisPlugins}
49
51
  >
50
52
  <OdysseyThemeProvider
51
53
  designTokensOverride={designTokensOverride}
54
+ emotionRoot={emotionRoot}
52
55
  shadowDomElement={shadowDomElement}
53
56
  themeOverride={themeOverride}
57
+ withCache={false}
54
58
  >
55
59
  <ScopedCssBaseline>
56
60
  <OdysseyTranslationProvider
@@ -21,19 +21,26 @@ import { deepmerge } from "@mui/utils";
21
21
  import { createOdysseyMuiTheme, DesignTokensOverride } from "./theme";
22
22
  import * as Tokens from "@okta/odyssey-design-tokens";
23
23
  import { OdysseyDesignTokensContext } from "./OdysseyDesignTokensContext";
24
+ import { CacheProvider } from "@emotion/react";
25
+ import createCache from "@emotion/cache";
26
+ import { useUniqueAlphabeticalId } from "./useUniqueAlphabeticalId";
24
27
 
25
28
  export type OdysseyThemeProviderProps = {
26
29
  children: ReactNode;
27
30
  designTokensOverride?: DesignTokensOverride;
28
- shadowDomElement?: HTMLDivElement;
31
+ emotionRoot?: HTMLStyleElement;
32
+ shadowDomElement?: HTMLDivElement | HTMLElement | undefined;
29
33
  themeOverride?: ThemeOptions;
34
+ withCache?: boolean;
30
35
  };
31
36
 
32
37
  const OdysseyThemeProvider = ({
33
38
  children,
34
39
  designTokensOverride,
40
+ emotionRoot,
35
41
  shadowDomElement,
36
42
  themeOverride,
43
+ withCache = true,
37
44
  }: OdysseyThemeProviderProps) => {
38
45
  const odysseyTokens = useMemo(
39
46
  () => ({ ...Tokens, ...designTokensOverride }),
@@ -53,6 +60,31 @@ const OdysseyThemeProvider = ({
53
60
  [odysseyTheme, themeOverride]
54
61
  );
55
62
 
63
+ const uniqueAlphabeticalId = useUniqueAlphabeticalId();
64
+
65
+ const cache = useMemo(
66
+ () =>
67
+ createCache({
68
+ ...(emotionRoot && { container: emotionRoot }),
69
+ key: uniqueAlphabeticalId,
70
+ prepend: true,
71
+ nonce: window.cspNonce,
72
+ speedy: false, // <-- Needs to be set to false when shadow-dom is used!! https://github.com/emotion-js/emotion/issues/2053#issuecomment-713429122
73
+ }),
74
+ [emotionRoot, uniqueAlphabeticalId]
75
+ );
76
+
77
+ if (withCache) {
78
+ return (
79
+ <CacheProvider value={cache}>
80
+ <MuiThemeProvider theme={customOdysseyTheme ?? odysseyTheme}>
81
+ <OdysseyDesignTokensContext.Provider value={odysseyTokens}>
82
+ {children}
83
+ </OdysseyDesignTokensContext.Provider>
84
+ </MuiThemeProvider>
85
+ </CacheProvider>
86
+ );
87
+ }
56
88
  return (
57
89
  <MuiThemeProvider theme={customOdysseyTheme ?? odysseyTheme}>
58
90
  <OdysseyDesignTokensContext.Provider value={odysseyTokens}>
package/src/Radio.tsx CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  import {
14
14
  FormControlLabel,
15
+ FormControlLabelProps as MuiFormControlLabelProps,
15
16
  Radio as MuiRadio,
16
17
  RadioProps as MuiRadioProps,
17
18
  } from "@mui/material";
@@ -41,6 +42,10 @@ export type RadioProps = {
41
42
  * Callback fired when the state is changed. Provides event and checked value.
42
43
  */
43
44
  onChange?: MuiRadioProps["onChange"];
45
+ /**
46
+ * Callback fired when the blur event happens. Provides event value.
47
+ */
48
+ onBlur?: MuiFormControlLabelProps["onBlur"];
44
49
  } & Pick<FieldComponentProps, "isDisabled" | "name"> &
45
50
  SeleniumProps;
46
51
 
@@ -53,6 +58,7 @@ const Radio = ({
53
58
  testId,
54
59
  value,
55
60
  onChange: onChangeProp,
61
+ onBlur: onBlurProp,
56
62
  }: RadioProps) => {
57
63
  const onChange = useCallback<NonNullable<MuiRadioProps["onChange"]>>(
58
64
  (event, checked) => {
@@ -61,6 +67,13 @@ const Radio = ({
61
67
  [onChangeProp]
62
68
  );
63
69
 
70
+ const onBlur = useCallback<NonNullable<MuiFormControlLabelProps["onBlur"]>>(
71
+ (event) => {
72
+ onBlurProp?.(event);
73
+ },
74
+ [onBlurProp]
75
+ );
76
+
64
77
  return (
65
78
  <FormControlLabel
66
79
  checked={isChecked}
@@ -71,6 +84,7 @@ const Radio = ({
71
84
  label={label}
72
85
  name={name}
73
86
  value={value}
87
+ onBlur={onBlur}
74
88
  />
75
89
  );
76
90
  };
@@ -10,12 +10,22 @@
10
10
  * See the License for the specific language governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- export const createShadowRootElement = (containerElement: HTMLElement) => {
13
+ export const createShadowRootElement = (
14
+ containerElement: HTMLElement
15
+ ): [HTMLStyleElement, HTMLDivElement] => {
14
16
  const shadowRoot = containerElement.attachShadow({ mode: "open" });
15
17
 
18
+ // the element that styles will be cached into
19
+ const emotionRoot = document.createElement("style");
20
+ emotionRoot.setAttribute("id", "style-root");
21
+ emotionRoot.setAttribute("nonce", window.cspNonce);
22
+
23
+ // the element that emotion renders html into
16
24
  const shadowRootElement = document.createElement("div");
25
+ shadowRootElement.setAttribute("id", "shadow-root");
17
26
 
18
- shadowRoot.append(shadowRootElement);
27
+ shadowRoot.appendChild(emotionRoot);
28
+ shadowRoot.appendChild(shadowRootElement);
19
29
 
20
- return shadowRoot;
30
+ return [emotionRoot, shadowRootElement];
21
31
  };
@@ -53,7 +53,7 @@ export const components = ({
53
53
  shadowDomElement,
54
54
  }: {
55
55
  odysseyTokens: DesignTokens;
56
- shadowDomElement?: HTMLDivElement;
56
+ shadowDomElement?: HTMLElement;
57
57
  }): ThemeOptions["components"] => {
58
58
  return {
59
59
  MuiAccordion: {
@@ -32,7 +32,7 @@ export const createOdysseyMuiTheme = ({
32
32
  shadowDomElement,
33
33
  }: {
34
34
  odysseyTokens: DesignTokens;
35
- shadowDomElement?: HTMLDivElement;
35
+ shadowDomElement?: HTMLElement;
36
36
  }) =>
37
37
  createTheme({
38
38
  components: components({