@okta/odyssey-react-mui 1.13.9 → 1.13.11

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okta/odyssey-react-mui",
3
- "version": "1.13.9",
3
+ "version": "1.13.11",
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.13.9",
54
+ "@okta/odyssey-design-tokens": "1.13.11",
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": "c2ab862795fbdc1c11b7b3bb4118a7ed24600843"
66
+ "gitHead": "056c36afdaaae83d531f50b9dd04eca4b8b5fb4c"
67
67
  }
package/src/Button.tsx CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  ReactElement,
20
20
  useCallback,
21
21
  useImperativeHandle,
22
+ useMemo,
22
23
  useRef,
23
24
  } from "react";
24
25
 
@@ -26,6 +27,7 @@ import { MuiPropsContext, useMuiProps } from "./MuiPropsContext";
26
27
  import { Tooltip } from "./Tooltip";
27
28
  import type { HtmlProps } from "./HtmlProps";
28
29
  import { FocusHandle } from "./inputUtils";
30
+ import { useButton } from "./ButtonContext";
29
31
 
30
32
  export const buttonSizeValues = ["small", "medium", "large"] as const;
31
33
  export const buttonTypeValues = ["button", "submit", "reset"] as const;
@@ -142,7 +144,7 @@ const Button = ({
142
144
  endIcon,
143
145
  id,
144
146
  isDisabled,
145
- isFullWidth,
147
+ isFullWidth: isFullWidthProp,
146
148
  label = "",
147
149
  onClick,
148
150
  size = "medium",
@@ -160,6 +162,12 @@ const Button = ({
160
162
  // "secondary" in lieu of making a breaking change
161
163
  const variant = variantProp === "tertiary" ? "secondary" : variantProp;
162
164
  const localButtonRef = useRef<HTMLButtonElement>(null);
165
+ const buttonContext = useButton();
166
+ const isFullWidth = useMemo(
167
+ () =>
168
+ buttonContext.isFullWidth ? buttonContext.isFullWidth : isFullWidthProp,
169
+ [buttonContext, isFullWidthProp]
170
+ );
163
171
 
164
172
  useImperativeHandle(
165
173
  buttonRef,
@@ -0,0 +1,23 @@
1
+ /*!
2
+ * Copyright (c) 2024-present, Okta, Inc. and/or its affiliates. All rights reserved.
3
+ * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
4
+ *
5
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
6
+ * Unless required by applicable law or agreed to in writing, software
7
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
8
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+ *
10
+ * See the License for the specific language governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { createContext, useContext } from "react";
14
+
15
+ export type ButtonContextValue = {
16
+ isFullWidth: boolean;
17
+ };
18
+
19
+ export const ButtonContext = createContext<ButtonContextValue>({
20
+ isFullWidth: false,
21
+ });
22
+
23
+ export const useButton = () => useContext(ButtonContext);
package/src/Form.tsx CHANGED
@@ -10,14 +10,15 @@
10
10
  * See the License for the specific language governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { memo, ReactElement } from "react";
14
-
13
+ import { FormEventHandler, memo, ReactElement } from "react";
15
14
  import { Box } from "@mui/material";
15
+
16
16
  import { Button } from "./Button";
17
17
  import { Callout } from "./Callout";
18
+ import { FieldComponentProps } from "./FieldComponentProps";
19
+ import type { HtmlProps } from "./HtmlProps";
18
20
  import { Heading4, Support } from "./Typography";
19
21
  import { useUniqueId } from "./useUniqueId";
20
- import type { HtmlProps } from "./HtmlProps";
21
22
 
22
23
  export const formEncodingTypeValues = [
23
24
  "application/x-www-form-urlencoded",
@@ -76,6 +77,10 @@ export type FormProps = {
76
77
  * it can be overridden by a formnovalidate attribute on a <button>, <input type="submit">, or <input type="image"> element belonging to the form.
77
78
  */
78
79
  noValidate?: boolean;
80
+ /**
81
+ * Callback that passes the submit event to the consumer
82
+ */
83
+ onSubmit?: FormEventHandler<HTMLFormElement>;
79
84
  /**
80
85
  * Indicates where to display the response after submitting the form. It is a name/keyword for a browsing context (for example, tab, window, or iframe).
81
86
  * This value can be overridden by a formtarget attribute on a <button>, <input type="submit">, or <input type="image"> element.
@@ -85,7 +90,8 @@ export type FormProps = {
85
90
  * The title of the Form
86
91
  */
87
92
  title?: string;
88
- } & HtmlProps;
93
+ } & Pick<FieldComponentProps, "isFullWidth"> &
94
+ HtmlProps;
89
95
 
90
96
  const Form = ({
91
97
  alert,
@@ -95,9 +101,11 @@ const Form = ({
95
101
  encodingType,
96
102
  formActions,
97
103
  id: idOverride,
104
+ isFullWidth,
98
105
  method,
99
106
  name,
100
107
  noValidate = false,
108
+ onSubmit,
101
109
  target,
102
110
  testId,
103
111
  title,
@@ -115,9 +123,10 @@ const Form = ({
115
123
  method={method}
116
124
  name={name}
117
125
  noValidate={noValidate}
126
+ onSubmit={onSubmit}
118
127
  target={target}
119
128
  sx={{
120
- maxWidth: (theme) => theme.mixins.maxWidth,
129
+ maxWidth: (theme) => (isFullWidth ? "100%" : theme.mixins.maxWidth),
121
130
  margin: (theme) => theme.spacing(0),
122
131
  padding: (theme) => theme.spacing(0),
123
132
  }}
@@ -142,7 +151,7 @@ const Form = ({
142
151
  component="div"
143
152
  sx={{
144
153
  display: "flex",
145
- justifyContent: "flex-start",
154
+ justifyContent: "flex-end",
146
155
  gap: (theme) => theme.spacing(1),
147
156
  marginBlockStart: (theme) => theme.spacing(7),
148
157
  }}
@@ -10,7 +10,7 @@
10
10
  * See the License for the specific language governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { createContext, MouseEventHandler } from "react";
13
+ import { MouseEventHandler, createContext } from "react";
14
14
 
15
15
  export type MenuContextType = {
16
16
  closeMenu: () => void;
package/src/Tile.tsx ADDED
@@ -0,0 +1,147 @@
1
+ /*!
2
+ * Copyright (c) 2022-present, Okta, Inc. and/or its affiliates. All rights reserved.
3
+ * The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
4
+ *
5
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
6
+ * Unless required by applicable law or agreed to in writing, software
7
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
8
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9
+ *
10
+ * See the License for the specific language governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { ReactElement, memo, useMemo } from "react";
14
+
15
+ import { NullElement } from "./NullElement";
16
+ import {
17
+ Card as MuiCard,
18
+ CardActions as MuiCardActions,
19
+ CardActionArea as MuiCardActionArea,
20
+ } from "@mui/material";
21
+ import { Button } from "./Button";
22
+ import { ButtonContext } from "./ButtonContext";
23
+ import { Heading5, Paragraph, Support } from "./Typography";
24
+ import { MoreIcon } from "./icons.generated";
25
+ import { HtmlProps } from "./HtmlProps";
26
+ import styled from "@emotion/styled";
27
+ import {
28
+ DesignTokens,
29
+ useOdysseyDesignTokens,
30
+ } from "./OdysseyDesignTokensContext";
31
+ import { MenuButton } from "./MenuButton";
32
+
33
+ export type TileProps = {
34
+ description?: string;
35
+ image?: ReactElement | NullElement; // Icon or image
36
+ menuItems?: ReactElement;
37
+ overline?: string;
38
+ title?: string;
39
+ } & ( // You can't have actions and onClick at the same time
40
+ | {
41
+ onClick: () => void;
42
+ button?: never;
43
+ menuItems?: never;
44
+ }
45
+ | {
46
+ onClick?: never;
47
+ button?: ReactElement<typeof Button> | NullElement;
48
+ menuItems?: ReactElement;
49
+ }
50
+ ) &
51
+ HtmlProps;
52
+
53
+ const ImageContainer = styled.div<{
54
+ odysseyDesignTokens: DesignTokens;
55
+ hasMenuItems: boolean;
56
+ }>`
57
+ display: flex;
58
+ align-items: flex-start;
59
+ max-height: 64px;
60
+ margin-block-end: ${(props) => props.odysseyDesignTokens.Spacing5};
61
+ padding-right: ${(props) =>
62
+ props.hasMenuItems ? props.odysseyDesignTokens.Spacing5 : 0};
63
+ `;
64
+
65
+ const MenuButtonContainer = styled.div<{ odysseyDesignTokens: DesignTokens }>`
66
+ position: absolute;
67
+ right: ${(props) => props.odysseyDesignTokens.Spacing3};
68
+ top: ${(props) => props.odysseyDesignTokens.Spacing3};
69
+ `;
70
+
71
+ const Tile = ({
72
+ button,
73
+ description,
74
+ image,
75
+ menuItems,
76
+ onClick,
77
+ overline,
78
+ title,
79
+ }: TileProps) => {
80
+ const odysseyDesignTokens = useOdysseyDesignTokens();
81
+
82
+ const cardContent = useMemo(() => {
83
+ return (
84
+ <>
85
+ {image && (
86
+ <ImageContainer
87
+ odysseyDesignTokens={odysseyDesignTokens}
88
+ hasMenuItems={Boolean(menuItems)}
89
+ >
90
+ {image}
91
+ </ImageContainer>
92
+ )}
93
+
94
+ {overline && <Support component="div">{overline}</Support>}
95
+ {title && <Heading5 component="div">{title}</Heading5>}
96
+ {description && (
97
+ <Paragraph color="textSecondary">{description}</Paragraph>
98
+ )}
99
+
100
+ {button && (
101
+ <MuiCardActions>
102
+ <ButtonContext.Provider value={{ isFullWidth: true }}>
103
+ {button}
104
+ </ButtonContext.Provider>
105
+ </MuiCardActions>
106
+ )}
107
+ </>
108
+ );
109
+ }, [
110
+ button,
111
+ description,
112
+ image,
113
+ menuItems,
114
+ overline,
115
+ title,
116
+ odysseyDesignTokens,
117
+ ]);
118
+
119
+ return (
120
+ <MuiCard className={onClick ? "isClickable" : ""}>
121
+ {onClick && (
122
+ <MuiCardActionArea onClick={onClick}>{cardContent}</MuiCardActionArea>
123
+ )}
124
+
125
+ {!onClick && cardContent}
126
+
127
+ {menuItems && (
128
+ <MenuButtonContainer odysseyDesignTokens={odysseyDesignTokens}>
129
+ <MenuButton
130
+ endIcon={<MoreIcon />}
131
+ ariaLabel="Tile menu"
132
+ buttonVariant="floating"
133
+ menuAlignment="right"
134
+ size="small"
135
+ >
136
+ {menuItems}
137
+ </MenuButton>
138
+ </MenuButtonContainer>
139
+ )}
140
+ </MuiCard>
141
+ );
142
+ };
143
+
144
+ const MemoizedTile = memo(Tile);
145
+ MemoizedTile.displayName = "Tile";
146
+
147
+ export { MemoizedTile as Tile };
package/src/index.ts CHANGED
@@ -62,6 +62,7 @@ export * from "./Banner";
62
62
  export * from "./Box";
63
63
  export * from "./Breadcrumbs";
64
64
  export * from "./Button";
65
+ export * from "./Tile";
65
66
  export * from "./Callout";
66
67
  export * from "./Checkbox";
67
68
  export * from "./CheckboxGroup";
@@ -701,6 +701,70 @@ export const components = ({
701
701
  disableRipple: true,
702
702
  },
703
703
  },
704
+ MuiCard: {
705
+ styleOverrides: {
706
+ root: () => ({
707
+ backgroundColor: odysseyTokens.HueNeutralWhite,
708
+ borderRadius: odysseyTokens.BorderRadiusOuter,
709
+ boxShadow: odysseyTokens.DepthMedium,
710
+ padding: odysseyTokens.Spacing5,
711
+ position: "relative",
712
+ transition: `all ${odysseyTokens.TransitionDurationMain} ${odysseyTokens.TransitionTimingMain}`,
713
+
714
+ "& img": {
715
+ height: "64px",
716
+ },
717
+
718
+ "&.isClickable:hover": {
719
+ backgroundColor: odysseyTokens.HueNeutral50,
720
+ boxShadow: odysseyTokens.DepthHigh,
721
+ },
722
+
723
+ [`& .${typographyClasses.h5}`]: {
724
+ lineHeight: odysseyTokens.TypographyLineHeightHeading5,
725
+ marginBottom: odysseyTokens.Spacing3,
726
+ },
727
+
728
+ [`& .${typographyClasses.subtitle2}`]: {
729
+ marginBottom: odysseyTokens.Spacing1,
730
+ textTransform: "uppercase",
731
+ fontWeight: odysseyTokens.TypographyWeightBodyBold,
732
+ fontSize: odysseyTokens.TypographySizeOverline,
733
+ lineHeight: odysseyTokens.TypographyLineHeightOverline,
734
+ color: odysseyTokens.TypographyColorSubordinate,
735
+ letterSpacing: 1.3,
736
+ },
737
+
738
+ [`& .${typographyClasses.body1}`]: {
739
+ fontSize: odysseyTokens.TypographySizeSubordinate,
740
+ lineHeight: odysseyTokens.TypographyLineHeightBody,
741
+ },
742
+ }),
743
+ },
744
+ },
745
+ MuiCardActionArea: {
746
+ styleOverrides: {
747
+ root: () => ({
748
+ margin: `-${odysseyTokens.Spacing5}`,
749
+ padding: odysseyTokens.Spacing5,
750
+ width: `calc(100% + (${odysseyTokens.Spacing5} * 2))`,
751
+
752
+ "&:hover": {
753
+ "& .MuiCardActionArea-focusHighlight": {
754
+ display: "none",
755
+ },
756
+ },
757
+ }),
758
+ },
759
+ },
760
+ MuiCardActions: {
761
+ styleOverrides: {
762
+ root: () => ({
763
+ marginBlockStart: odysseyTokens.Spacing5,
764
+ padding: 0,
765
+ }),
766
+ },
767
+ },
704
768
  MuiCheckbox: {
705
769
  defaultProps: {
706
770
  size: "small",