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

[DRAFT] Add BackgroundContext for gray background component theme changes #2362

Closed
wants to merge 9 commits into from
33 changes: 33 additions & 0 deletions packages/odyssey-react-mui/src/BackgroundContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*!
* Copyright (c) 2023-present, Okta, Inc. and/or its affiliates. All rights reserved.
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
*
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
* See the License for the specific language governing permissions and limitations under the License.
*/

import { createContext, useContext, ReactNode } from "react";

export type BackgroundType = "highContrast" | "lowContrast";

const BackgroundContext = createContext<BackgroundType>("highContrast");

export const useBackground = () => useContext(BackgroundContext);

export const BackgroundProvider = ({
value,
children,
}: {
value: BackgroundType;
children: ReactNode;
}) => (
<BackgroundContext.Provider value={value}>
{children}
</BackgroundContext.Provider>
);

export { BackgroundContext };
46 changes: 41 additions & 5 deletions packages/odyssey-react-mui/src/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
* See the License for the specific language governing permissions and limitations under the License.
*/

import { Button as MuiButton } from "@mui/material";
import type { ButtonProps as MuiButtonProps } from "@mui/material";
import {
HTMLAttributes,
memo,
Expand All @@ -21,7 +19,13 @@ import {
useMemo,
useRef,
} from "react";

import {
Button as MuiButton,
ButtonProps as MuiButtonProps,
} from "@mui/material";
import { buttonClasses } from "@mui/material/Button";
import styled from "@emotion/styled";
import { useBackground } from "./BackgroundContext";
import { useButton } from "./ButtonContext";
import type { HtmlProps } from "./HtmlProps";
import { FocusHandle } from "./inputUtils";
Expand All @@ -31,6 +35,10 @@ import {
useMuiProps,
} from "./MuiPropsContext";
import { Tooltip } from "./Tooltip";
import {
DesignTokens,
useOdysseyDesignTokens,
} from "./OdysseyDesignTokensContext";

export const buttonSizeValues = ["small", "medium", "large"] as const;
export const buttonTypeValues = ["button", "submit", "reset"] as const;
Expand Down Expand Up @@ -142,6 +150,24 @@ export type ButtonProps = {
| "translate"
>;

// Styled version of the Button for gray background context style overrides
const nonForwardedProps = ["isLowContrast", "odysseyDesignTokens"];

const StyledButton = styled(MuiButton, {
shouldForwardProp: (prop: string) => !nonForwardedProps.includes(prop),
})<{
isLowContrast?: boolean;
odysseyDesignTokens: DesignTokens;
}>(({ isLowContrast, odysseyDesignTokens }) => ({
...(isLowContrast && {
[`&.${buttonClasses.root}.${buttonClasses.disabled}.MuiButton-secondary`]: {
backgroundColor: odysseyDesignTokens.HueNeutral200,
color: odysseyDesignTokens.TypographyColorDisabled,
borderColor: "transparent",
},
}),
}));

const Button = ({
ariaControls,
ariaDescribedBy,
Expand All @@ -167,10 +193,16 @@ const Button = ({
variant: variantProp,
}: ButtonProps) => {
const muiProps = useMuiProps();
const background = useBackground();
const odysseyDesignTokens = useOdysseyDesignTokens();
// Determine if the button is in a gray background and is a secondary variant
const isLowContrast =
background === "lowContrast" && variantProp === "secondary";

// We're deprecating the "tertiary" variant, so map it to
// "secondary" in lieu of making a breaking change
const variant = variantProp === "tertiary" ? "secondary" : variantProp;

const localButtonRef = useRef<HTMLButtonElement | HTMLAnchorElement>(null);
const buttonContext = useButton();
const isFullWidth = useMemo(
Expand All @@ -192,7 +224,7 @@ const Button = ({
const renderButton = useCallback(
(muiProps: MuiPropsContextType) => {
return (
<MuiButton
<StyledButton
{...muiProps}
aria-controls={ariaControls}
aria-describedby={ariaDescribedBy}
Expand Down Expand Up @@ -224,9 +256,11 @@ const Button = ({
translate={translate}
type={type}
variant={variant}
isLowContrast={isLowContrast}
odysseyDesignTokens={odysseyDesignTokens}
>
{label}
</MuiButton>
</StyledButton>
);
},
[
Expand All @@ -240,8 +274,10 @@ const Button = ({
href,
id,
isDisabled,
isLowContrast,
isFullWidth,
label,
odysseyDesignTokens,
onClick,
size,
startIcon,
Expand Down
47 changes: 46 additions & 1 deletion packages/odyssey-react-mui/src/Status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@

import { memo } from "react";
import { Chip } from "@mui/material";
import { chipClasses } from "@mui/material/Chip";
import styled from "@emotion/styled";

import { useMuiProps } from "./MuiPropsContext";
import type { HtmlProps } from "./HtmlProps";
import { useBackground } from "./BackgroundContext";
import {
DesignTokens,
useOdysseyDesignTokens,
} from "./OdysseyDesignTokensContext";

export const statusSeverityValues = [
"default",
Expand All @@ -40,6 +47,38 @@ export type StatusProps = {
variant?: (typeof statusVariantValues)[number];
} & Pick<HtmlProps, "testId" | "translate">;

const nonForwardedProps = ["isLowContrast", "odysseyDesignTokens"];

const StyledChip = styled(Chip, {
shouldForwardProp: (prop) => !nonForwardedProps.includes(prop as string),
})<{
isLowContrast: boolean;
odysseyDesignTokens: DesignTokens;
}>(({ isLowContrast, odysseyDesignTokens }) => ({
...(isLowContrast && {
[`&.${chipClasses.root}`]: {
backgroundColor: odysseyDesignTokens.HueNeutral200,
color: odysseyDesignTokens.TypographyColorBody,
},
[`&.${chipClasses.colorError}`]: {
backgroundColor: odysseyDesignTokens.PaletteDangerLight,
color: odysseyDesignTokens.PaletteDangerDark,
},
[`&.${chipClasses.colorInfo}`]: {
backgroundColor: odysseyDesignTokens.PalettePrimaryLight,
color: odysseyDesignTokens.PalettePrimaryDark,
},
[`&.${chipClasses.colorSuccess}`]: {
backgroundColor: odysseyDesignTokens.PaletteSuccessLight,
color: odysseyDesignTokens.PaletteSuccessDark,
},
[`&.${chipClasses.colorWarning}`]: {
backgroundColor: odysseyDesignTokens.PaletteWarningLight,
color: odysseyDesignTokens.PaletteWarningDark,
},
}),
}));

const Status = ({
label,
severity,
Expand All @@ -48,15 +87,21 @@ const Status = ({
variant = "pill",
}: StatusProps) => {
const muiProps = useMuiProps();
const background = useBackground();
const odysseyDesignTokens = useOdysseyDesignTokens();

const isLowContrast = background === "lowContrast";

return (
<Chip
<StyledChip
{...muiProps}
color={severity}
data-se={testId}
label={label}
translate={translate}
variant={variant}
isLowContrast={isLowContrast}
odysseyDesignTokens={odysseyDesignTokens}
/>
);
};
Expand Down
67 changes: 52 additions & 15 deletions packages/odyssey-react-mui/src/Tag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
useOdysseyDesignTokens,
} from "./OdysseyDesignTokensContext";
import { CloseCircleFilledIcon } from "./icons.generated";
import { useBackground } from "./BackgroundContext";

export const tagColorVariants = [
"default",
Expand Down Expand Up @@ -57,58 +58,83 @@ export type TagProps = {
const getChipColors = (
colorVariant: TagColorVariant,
odysseyDesignTokens: DesignTokens,
isLowContrast: boolean,
) => {
const colors = {
default: {
background: odysseyDesignTokens.HueNeutral100,
background: isLowContrast
? odysseyDesignTokens.HueNeutral200
: odysseyDesignTokens.HueNeutral100,
hover: odysseyDesignTokens.HueNeutral200,
active: odysseyDesignTokens.HueNeutral300,
text: odysseyDesignTokens.HueNeutral700,
text: isLowContrast
? odysseyDesignTokens.HueNeutral700
: odysseyDesignTokens.HueNeutral600,
border: odysseyDesignTokens.HueNeutral200,
deleteIcon: odysseyDesignTokens.HueNeutral500,
deleteIconHover: odysseyDesignTokens.HueNeutral600,
},
info: {
background: odysseyDesignTokens.HueBlue100,
background: isLowContrast
? odysseyDesignTokens.HueBlue200
: odysseyDesignTokens.HueBlue100,
hover: odysseyDesignTokens.HueBlue200,
active: odysseyDesignTokens.HueBlue300,
text: odysseyDesignTokens.HueBlue700,
text: isLowContrast
? odysseyDesignTokens.HueBlue700
: odysseyDesignTokens.HueBlue600,
border: odysseyDesignTokens.HueBlue200,
deleteIcon: odysseyDesignTokens.HueBlue500,
deleteIconHover: odysseyDesignTokens.HueBlue600,
},
accentOne: {
background: odysseyDesignTokens.HueAccentOne100,
background: isLowContrast
? odysseyDesignTokens.HueAccentOne200
: odysseyDesignTokens.HueAccentOne100,
hover: odysseyDesignTokens.HueAccentOne200,
active: odysseyDesignTokens.HueAccentOne300,
text: odysseyDesignTokens.HueAccentOne700,
text: isLowContrast
? odysseyDesignTokens.HueAccentOne700
: odysseyDesignTokens.HueAccentOne600,
border: odysseyDesignTokens.HueAccentOne200,
deleteIcon: odysseyDesignTokens.HueAccentOne500,
deleteIconHover: odysseyDesignTokens.HueAccentOne600,
},
accentTwo: {
background: odysseyDesignTokens.HueAccentTwo100,
background: isLowContrast
? odysseyDesignTokens.HueAccentTwo200
: odysseyDesignTokens.HueAccentTwo100,
hover: odysseyDesignTokens.HueAccentTwo200,
active: odysseyDesignTokens.HueAccentTwo300,
text: odysseyDesignTokens.HueAccentTwo700,
text: isLowContrast
? odysseyDesignTokens.HueAccentTwo700
: odysseyDesignTokens.HueAccentTwo600,
border: odysseyDesignTokens.HueAccentTwo200,
deleteIcon: odysseyDesignTokens.HueAccentTwo500,
deleteIconHover: odysseyDesignTokens.HueAccentTwo600,
},
accentThree: {
background: odysseyDesignTokens.HueAccentThree100,
background: isLowContrast
? odysseyDesignTokens.HueAccentThree200
: odysseyDesignTokens.HueAccentThree100,
hover: odysseyDesignTokens.HueAccentThree200,
active: odysseyDesignTokens.HueAccentThree300,
text: odysseyDesignTokens.HueAccentThree700,
text: isLowContrast
? odysseyDesignTokens.HueAccentThree700
: odysseyDesignTokens.HueAccentThree600,
border: odysseyDesignTokens.HueAccentThree200,
deleteIcon: odysseyDesignTokens.HueAccentThree500,
deleteIconHover: odysseyDesignTokens.HueAccentThree600,
},
accentFour: {
background: odysseyDesignTokens.HueAccentFour100,
background: isLowContrast
? odysseyDesignTokens.HueAccentFour200
: odysseyDesignTokens.HueAccentFour100,
hover: odysseyDesignTokens.HueAccentFour200,
active: odysseyDesignTokens.HueAccentFour300,
text: odysseyDesignTokens.HueAccentFour700,
text: isLowContrast
? odysseyDesignTokens.HueAccentFour700
: odysseyDesignTokens.HueAccentFour600,
border: odysseyDesignTokens.HueAccentFour200,
deleteIcon: odysseyDesignTokens.HueAccentFour500,
deleteIconHover: odysseyDesignTokens.HueAccentFour600,
Expand All @@ -120,13 +146,20 @@ const getChipColors = (

const StyledTag = styled(MuiChip, {
shouldForwardProp: (prop) =>
!["colorVariant", "odysseyDesignTokens"].includes(prop as string),
!["colorVariant", "isLowContrast", "odysseyDesignTokens"].includes(
prop as string,
),
})<{
colorVariant: TagColorVariant;
isLowContrast: boolean;
odysseyDesignTokens: DesignTokens;
as?: React.ElementType; // Allow the 'as' prop to be forwarded
}>(({ colorVariant, odysseyDesignTokens }) => {
const colors = getChipColors(colorVariant, odysseyDesignTokens);
}>(({ colorVariant, isLowContrast, odysseyDesignTokens }) => {
const colors = getChipColors(
colorVariant,
odysseyDesignTokens,
isLowContrast,
);

return {
backgroundColor: colors.background,
Expand Down Expand Up @@ -169,6 +202,8 @@ const Tag = ({
}: TagProps) => {
const odysseyDesignTokens = useOdysseyDesignTokens();
const { chipElementType } = useContext(TagListContext);
const background = useBackground();
const isLowContrast = background === "lowContrast";

const renderTag = useCallback(
(muiProps: MuiPropsContextType) => (
Expand All @@ -180,6 +215,7 @@ const Tag = ({
data-se={testId}
colorVariant={colorVariant}
odysseyDesignTokens={odysseyDesignTokens}
isLowContrast={isLowContrast}
disabled={isDisabled}
icon={icon}
label={label}
Expand All @@ -200,6 +236,7 @@ const Tag = ({
translate,
colorVariant,
odysseyDesignTokens,
isLowContrast,
],
);

Expand Down
1 change: 1 addition & 0 deletions packages/odyssey-react-mui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export { useOdysseyDesignTokens } from "./OdysseyDesignTokensContext";
export * from "./Accordion";
export * from "./Autocomplete";
export { badgeContentMaxValues } from "./Badge";
export * from "./BackgroundContext";
export * from "./Banner";
export * from "./Box";
export * from "./Breadcrumbs";
Expand Down
2 changes: 2 additions & 0 deletions packages/odyssey-react-mui/src/theme/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ export const components = ({
justifyContent: "center",
alignItems: "center",
borderRadius: 0,
borderWidth: 0,
borderBottomWidth: "1px",

...(ownerState.onClose !== undefined && {
paddingInline: odysseyTokens.Spacing6,
Expand Down
Loading