Skip to content

Commit

Permalink
fix: shell content area overlaps side nav in mobile view (#2497)
Browse files Browse the repository at this point in the history
OKTA-864167 fix: fixes SSR warnings from useLayoutEffect

fix: exports missing UiShellContent component

fix: removes src from our package by adding .npmignore

fix: refactors setStylesToMatchElement, so its type-safe

fix: side nav overlapping content in mobile view

fix: side nav toggle being incorrectly overlapped by top nav

fix: top nav missing overflow-x: hidden causing whitespace at the bottom
  • Loading branch information
KevinGhadyani-Okta authored Feb 7, 2025
1 parent 8f9bb97 commit 63a09a1
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 38 deletions.
2 changes: 2 additions & 0 deletions packages/odyssey-react-mui/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!dist/**
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 @@ -1217,7 +1217,7 @@ export const getComponents = ({
[`.${inputBaseClasses.root}.${inputBaseClasses.disabled} &`]: {
color: odysseyTokens.TypographyColorDisabled,
WebkitTextFillColor: odysseyTokens.TypographyColorDisabled,
} as CSSProperties,
} satisfies CSSProperties,
},

deleteIcon: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const StyledToggleButton = styled(MuiButton, {
width: odysseyDesignTokens.Spacing6,
height: odysseyDesignTokens.Spacing6,
border: 0,
zIndex: 2,
zIndex: 200,

"&:focus-visible": {
boxShadow: `inset 0 0 0 2px ${odysseyDesignTokens.PalettePrimaryMain}`,
Expand Down
1 change: 1 addition & 0 deletions packages/odyssey-react-mui/src/ui-shell/TopNav/TopNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const StyledTopNavContainer = styled("div", {
justifyContent: "space-between",
maxHeight: TOP_NAV_HEIGHT,
minHeight: TOP_NAV_HEIGHT,
overflowX: "hidden",
paddingBlock: odysseyDesignTokens.Spacing2,
paddingInline: odysseyDesignTokens.Spacing8,
position: "relative",
Expand Down
99 changes: 67 additions & 32 deletions packages/odyssey-react-mui/src/ui-shell/UiShellContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@
*/

import styled from "@emotion/styled";
import { memo, useEffect, useMemo, useRef, type ReactElement } from "react";
import {
CSSProperties,
memo,
useEffect,
useMemo,
useRef,
type ReactElement,
} from "react";
import { ErrorBoundary, ErrorBoundaryProps } from "react-error-boundary";

import { AppSwitcher, type AppSwitcherProps } from "./AppSwitcher/index.js";
Expand Down Expand Up @@ -105,7 +112,7 @@ export type UiShellContentProps = {
*/
appBackgroundContrastMode?: ContrastMode;
/**
* The element within which the app will be rendered. This will be positioned appropriately while being kept out of the shadow DOM.
* When passed, the app is expected to render into this element, not the Shadow DOM. UI Shell will position this element appropriately as if it was rendered in the app content area of the Shadow DOM.
*/
appContainerElement: HTMLDivElement;
/**
Expand Down Expand Up @@ -136,20 +143,39 @@ export type UiShellContentProps = {
};
} & UiShellNavComponentProps;

const setStylesToMatchElement = (
elementToStyle: HTMLElement,
elementToReference: HTMLElement,
additionalStyles: Record<string, string | null>,
) => {
const boundingRect = elementToReference.getBoundingClientRect();
elementToStyle.style.setProperty("position", "absolute");
elementToStyle.style.setProperty("top", `${boundingRect.y}px`);
elementToStyle.style.setProperty("left", `${boundingRect.x}px`);
elementToStyle.style.setProperty("width", `${boundingRect.width}px`);
elementToStyle.style.setProperty("height", `${boundingRect.height}px`);
for (const key in additionalStyles) {
elementToStyle.style.setProperty(key, additionalStyles[key]);
}
export const convertCamelCaseToKebabCase = (string: string) =>
string.replace(/([A-Z])/g, "-$1").toLowerCase();

export const setStylesToMatchElement = ({
additionalStyles,
appContainerElement,
appContentReferenceElement,
}: {
additionalStyles: CSSProperties;
appContainerElement: HTMLElement;
appContentReferenceElement: HTMLElement;
}) => {
const boundingRect = appContentReferenceElement.getBoundingClientRect();

appContainerElement.style.setProperty("position", "absolute");
appContainerElement.style.setProperty("top", `${boundingRect.y}px`);
appContainerElement.style.setProperty("left", `${boundingRect.x}px`);
appContainerElement.style.setProperty("width", `${boundingRect.width}px`);
appContainerElement.style.setProperty("height", `${boundingRect.height}px`);

(
Object.entries(additionalStyles) as Array<
[
keyof typeof additionalStyles,
(typeof additionalStyles)[keyof typeof additionalStyles],
]
>
).forEach(([cssPropertyName, cssPropertyValue]) => {
appContainerElement.style.setProperty(
convertCamelCaseToKebabCase(cssPropertyName),
String(cssPropertyValue),
);
});
};

/**
Expand Down Expand Up @@ -177,29 +203,29 @@ const UiShellContent = ({
const topNavContainerRef = useRef<HTMLDivElement>(null);
const uiShellContext = useUiShellContext();

const paddingStyles = useMemo<Record<string, string | null>>(
const appContainerElementStyles = useMemo<CSSProperties>(
() => ({
...(hasStandardAppContentPadding
? {
"padding-block": odysseyDesignTokens.Spacing5 ?? null,
"padding-inline": odysseyDesignTokens.Spacing8 ?? null,
paddingBlock: odysseyDesignTokens.Spacing5 ?? null,
paddingInline: odysseyDesignTokens.Spacing8 ?? null,
}
: {}),
...(appContainerScrollingMode === "horizontal" ||
appContainerScrollingMode === "both"
? {
"overflow-x": "auto",
overflowX: "auto",
}
: {
"overflow-x": "hidden",
overflowX: "hidden",
}),
...(appContainerScrollingMode === "vertical" ||
appContainerScrollingMode === "both"
? {
"overflow-y": "auto",
overflowY: "auto",
}
: {
"overflow-y": "hidden",
overflowY: "hidden",
}),
}),
[
Expand All @@ -218,33 +244,42 @@ const UiShellContent = ({
cancelAnimationFrame(animationFrameId);
animationFrameId = requestAnimationFrame(() => {
if (appContainerRef.current) {
setStylesToMatchElement(
setStylesToMatchElement({
additionalStyles: appContainerElementStyles,
appContentReferenceElement: appContainerRef.current,
appContainerElement,
appContainerRef.current,
paddingStyles,
);
});
}
});
};

// This element might have changed from `.current`, so it's important to keep track of the old one when we attach event listeners.
const sideNavElement = sideNavContainerRef.current;

sideNavElement?.addEventListener("transitionend", updateStyles);

// Setup a mutation observer to sync later updates
const observer = new ResizeObserver(updateStyles);
observer.observe(appContainerRef.current);

if (sideNavContainerRef.current) {
observer.observe(sideNavContainerRef.current);
if (sideNavElement) {
observer.observe(sideNavElement);
}

if (topNavContainerRef.current) {
observer.observe(topNavContainerRef.current);
}

// Set the initial style
// Set the initial styles
updateStyles();
return () => observer.disconnect();

return () => {
observer.disconnect();
sideNavElement?.removeEventListener("transitionend", updateStyles);
};
}
return () => {};
}, [appContainerRef, appContainerElement, paddingStyles]);
}, [appContainerElement, appContainerElementStyles, appContainerRef]);

return (
<StyledShellContainer odysseyDesignTokens={odysseyDesignTokens}>
Expand Down
5 changes: 4 additions & 1 deletion packages/odyssey-react-mui/src/ui-shell/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ export * from "./useHasUiShell.js";
export * from "../web-component/renderReactInWebComponent.js"; // This is located here because some teams use React v17, and this uses React v18's `ReactDOM/client` import which isn't in older versions.

export { UiShell, type UiShellProps } from "./UiShell.js";
export { type UiShellNavComponentProps } from "./UiShellContent.js";
export {
UiShellContent,
type UiShellNavComponentProps,
} from "./UiShellContent.js";
6 changes: 3 additions & 3 deletions packages/odyssey-react-mui/src/useContrastMode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@

import {
createContext,
useCallback,
useContext,
useEffect,
useRef,
useLayoutEffect,
useState,
useCallback,
} from "react";
import * as Tokens from "@okta/odyssey-design-tokens";

Expand Down Expand Up @@ -109,7 +109,7 @@ export const useContrastMode = ({
}
}, [explicitContrastMode]);

useLayoutEffect(() => {
useEffect(() => {
const observer = new MutationObserver(updateBackgroundColor);
observer.observe(document.querySelector("html") as HTMLHtmlElement, {
attributes: true,
Expand Down

0 comments on commit 63a09a1

Please sign in to comment.