@okta/odyssey-react-mui 0.19.0 → 0.21.0

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.
@@ -0,0 +1,99 @@
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 {
14
+ Button,
15
+ ButtonProps,
16
+ ChevronDownIcon,
17
+ Divider,
18
+ ListSubheader,
19
+ Menu,
20
+ MenuItem,
21
+ useUniqueId,
22
+ } from "./";
23
+ import { memo, MouseEvent, ReactElement, useMemo, useState } from "react";
24
+
25
+ export interface MenuButtonProps {
26
+ /**
27
+ * The <MenuItem> components within the Menu.
28
+ */
29
+ children: Array<
30
+ ReactElement<typeof MenuItem | typeof Divider | typeof ListSubheader>
31
+ >;
32
+ /**
33
+ * The end Icon on the trigggering Button
34
+ */
35
+ buttonEndIcon?: ReactElement;
36
+ /**
37
+ * The label on the triggering Button
38
+ */
39
+ buttonLabel?: string;
40
+ /**
41
+ * The variant of the triggering Button
42
+ */
43
+ buttonVariant?: ButtonProps["variant"];
44
+ }
45
+
46
+ const MenuButton = ({
47
+ buttonLabel = "",
48
+ children,
49
+ buttonEndIcon = <ChevronDownIcon />,
50
+ buttonVariant = "secondary",
51
+ }: MenuButtonProps) => {
52
+ const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
53
+
54
+ const open = Boolean(anchorEl);
55
+
56
+ const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
57
+ setAnchorEl(event.currentTarget);
58
+ };
59
+
60
+ const handleClose = () => {
61
+ setAnchorEl(null);
62
+ };
63
+
64
+ const uniqueId = useUniqueId();
65
+
66
+ const menuListProps = useMemo(
67
+ () => ({ "aria-labelledby": `${uniqueId}-button` }),
68
+ [uniqueId]
69
+ );
70
+
71
+ return (
72
+ <div>
73
+ <Button
74
+ endIcon={buttonEndIcon}
75
+ id={`${uniqueId}-button`}
76
+ aria-controls={open ? `${uniqueId}-menu` : undefined}
77
+ aria-haspopup="true"
78
+ aria-expanded={open ? "true" : undefined}
79
+ onClick={handleClick}
80
+ variant={buttonVariant}
81
+ >
82
+ {buttonLabel}
83
+ </Button>
84
+ <Menu
85
+ id={`${uniqueId}-menu`}
86
+ anchorEl={anchorEl}
87
+ open={open}
88
+ onClose={handleClose}
89
+ MenuListProps={menuListProps}
90
+ >
91
+ {children}
92
+ </Menu>
93
+ </div>
94
+ );
95
+ };
96
+
97
+ const MemoizedMenuButton = memo(MenuButton);
98
+
99
+ export { MemoizedMenuButton as MenuButton };
@@ -0,0 +1,50 @@
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 { memo, forwardRef } from "react";
14
+ import { MenuItem as MuiMenuItem } from "@mui/material";
15
+ import { menuItemClasses } from "@mui/material/MenuItem";
16
+ import type { MenuItemProps as MuiMenuItemProps } from "@mui/material";
17
+
18
+ export interface MenuItemProps
19
+ extends Omit<
20
+ MuiMenuItemProps,
21
+ | "component"
22
+ | "dense"
23
+ | "disableGutters"
24
+ | "divider"
25
+ | "focusVisibleClassName"
26
+ > {
27
+ /**
28
+ * Toggles whether or not the MenuItem represents a destructive action.
29
+ */
30
+ isDestructive?: boolean;
31
+ }
32
+
33
+ const MenuItem = forwardRef<HTMLLIElement, MenuItemProps>(
34
+ ({ isDestructive, ...props }, ref) => (
35
+ <MuiMenuItem
36
+ {...props}
37
+ ref={ref}
38
+ className={
39
+ isDestructive ? `${menuItemClasses.root}-destructive` : undefined
40
+ }
41
+ >
42
+ {props.children}
43
+ </MuiMenuItem>
44
+ )
45
+ );
46
+
47
+ const MemoizedMenuItem = memo(MenuItem);
48
+ MemoizedMenuItem.displayName = "MenuItem";
49
+
50
+ export { MemoizedMenuItem as MenuItem };
package/src/index.ts CHANGED
@@ -24,6 +24,7 @@ export {
24
24
  DialogContent,
25
25
  DialogContentText,
26
26
  DialogTitle,
27
+ Divider,
27
28
  FormControl,
28
29
  FormControlLabel,
29
30
  FormGroup,
@@ -38,7 +39,9 @@ export {
38
39
  ListItemIcon,
39
40
  ListItemText,
40
41
  ListSubheader,
41
- MenuItem,
42
+ Menu,
43
+ MenuList,
44
+ Paper,
42
45
  ScopedCssBaseline,
43
46
  Select,
44
47
  Snackbar,
@@ -72,6 +75,7 @@ export type {
72
75
  DialogContentProps,
73
76
  DialogContentTextProps,
74
77
  DialogTitleProps,
78
+ DividerProps,
75
79
  FormControlLabelProps,
76
80
  FormControlProps,
77
81
  FormGroupProps,
@@ -86,7 +90,9 @@ export type {
86
90
  ListItemIconProps,
87
91
  ListItemTextProps,
88
92
  ListSubheaderProps,
89
- MenuItemProps,
93
+ MenuProps,
94
+ MenuListProps,
95
+ PaperProps,
90
96
  ScopedCssBaselineProps,
91
97
  SelectChangeEvent,
92
98
  SelectProps,
@@ -121,6 +127,8 @@ export * from "./Icon";
121
127
  export * from "./iconDictionary";
122
128
  export * from "./Infobox";
123
129
  export * from "./Link";
130
+ export * from "./MenuButton";
131
+ export * from "./MenuItem";
124
132
  export * from "./OdysseyCacheProvider";
125
133
  export * from "./OdysseyThemeProvider";
126
134
  export * from "./PasswordInput";
@@ -10,13 +10,18 @@
10
10
  * See the License for the specific language governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import type { ThemeOptions } from "@mui/material";
13
+ import { ThemeOptions } from "@mui/material";
14
14
  import type {} from "@mui/lab/themeAugmentation";
15
15
  //import radioClasses from "@mui/material";
16
+ import { buttonClasses } from "@mui/material/Button";
16
17
  import { chipClasses } from "@mui/material/Chip";
17
18
  import { dialogActionsClasses } from "@mui/material/DialogActions";
19
+ import { dividerClasses } from "@mui/material/Divider";
18
20
  import { inputAdornmentClasses } from "@mui/material/InputAdornment";
19
21
  import { inputBaseClasses } from "@mui/material/InputBase";
22
+ import { listItemIconClasses } from "@mui/material/ListItemIcon";
23
+ import { listItemTextClasses } from "@mui/material/ListItemText";
24
+ import { menuItemClasses } from "@mui/material/MenuItem";
20
25
  import { svgIconClasses } from "@mui/material/SvgIcon";
21
26
  import { tableBodyClasses } from "@mui/material/TableBody";
22
27
  import { tableCellClasses } from "@mui/material/TableCell";
@@ -292,7 +297,7 @@ export const components: ThemeOptions["components"] = {
292
297
  style: {
293
298
  minWidth: "auto",
294
299
 
295
- ".MuiButton-startIcon": {
300
+ [`.${buttonClasses.endIcon}, .${buttonClasses.startIcon}`]: {
296
301
  margin: "0",
297
302
  },
298
303
  },
@@ -335,18 +340,31 @@ export const components: ThemeOptions["components"] = {
335
340
  pointerEvents: "inherit", // in order to have cursor: not-allowed, must change pointer-events from "none"
336
341
  },
337
342
 
338
- ".MuiButton-startIcon > *:nth-of-type(1)": {
339
- fontSize: `${theme.typography.ui.lineHeight}em`,
343
+ [`.${buttonClasses.startIcon}, .${buttonClasses.endIcon}`]: {
344
+ "& > *:nth-of-type(1)": {
345
+ fontSize: `${theme.typography.ui.lineHeight}em`,
346
+ },
340
347
  },
341
348
  }),
342
- startIcon: ({ theme }) => ({
349
+
350
+ endIcon: ({ theme, ownerState }) => ({
351
+ display: "inline-flex",
352
+ margin: 0,
353
+ marginInlineStart: theme.spacing(2),
354
+
355
+ ...(ownerState.children === undefined && {
356
+ marginInlineStart: 0,
357
+ }),
358
+ }),
359
+
360
+ startIcon: ({ theme, ownerState }) => ({
343
361
  display: "inline-flex",
344
362
  margin: 0,
345
363
  marginInlineEnd: theme.spacing(2),
346
364
 
347
- "&:only-child": {
348
- margin: 0,
349
- },
365
+ ...(ownerState.children === undefined && {
366
+ marginInlineEnd: 0,
367
+ }),
350
368
  }),
351
369
  },
352
370
  },
@@ -735,7 +753,7 @@ export const components: ThemeOptions["components"] = {
735
753
  },
736
754
  },
737
755
 
738
- "ul, ol": {
756
+ "ul:not([class]), ol:not([class])": {
739
757
  maxWidth: theme.mixins.maxWidth,
740
758
  marginBlockStart: 0,
741
759
  marginBlockEnd: theme.spacing(4),
@@ -755,7 +773,7 @@ export const components: ThemeOptions["components"] = {
755
773
  },
756
774
  },
757
775
 
758
- li: {
776
+ "li:not([class])": {
759
777
  marginBlockEnd: theme.spacing(2),
760
778
  paddingInlineStart: theme.spacing(1),
761
779
 
@@ -1263,6 +1281,7 @@ export const components: ThemeOptions["components"] = {
1263
1281
  paddingBlock: theme.spacing(2),
1264
1282
  paddingInline: theme.spacing(4),
1265
1283
  fontSize: theme.typography.caption.fontSize,
1284
+ fontWeight: theme.typography.fontWeightBold,
1266
1285
  lineHeight: theme.typography.caption.lineHeight,
1267
1286
  color: theme.palette.text.secondary,
1268
1287
  textTransform: "uppercase",
@@ -1271,21 +1290,73 @@ export const components: ThemeOptions["components"] = {
1271
1290
  },
1272
1291
  MuiMenuItem: {
1273
1292
  styleOverrides: {
1274
- root: ({ theme }) => ({
1275
- justifyContent: "space-between",
1293
+ root: ({ theme, ownerState }) => ({
1276
1294
  gap: theme.spacing(2),
1295
+ minHeight: "unset",
1296
+ paddingBlock: theme.spacing(3),
1277
1297
 
1278
- "&.Mui-selected": {
1298
+ "&:hover": {
1299
+ textDecoration: "none",
1300
+ backgroundColor: theme.palette.grey[100],
1301
+
1302
+ // Reset on touch devices, it doesn't add specificity
1303
+ "@media (hover: none)": {
1304
+ backgroundColor: "transparent",
1305
+ },
1306
+ },
1307
+
1308
+ [`&.${menuItemClasses.root}-destructive`]: {
1309
+ color: theme.palette.error.main,
1310
+ },
1311
+
1312
+ [`&.${menuItemClasses.selected}`]: {
1279
1313
  backgroundColor: "transparent",
1280
1314
  color: theme.palette.primary.main,
1281
1315
 
1282
1316
  "&:hover": {
1283
1317
  backgroundColor: theme.palette.primary.lighter,
1318
+
1319
+ "@media (hover: none)": {
1320
+ backgroundColor: `rgba(${theme.palette.primary.main} / ${theme.palette.action.selectedOpacity})`,
1321
+ },
1284
1322
  },
1285
1323
  },
1324
+
1325
+ ...(!ownerState.disableGutters && {
1326
+ paddingInline: theme.spacing(4),
1327
+ }),
1328
+
1329
+ ...(ownerState.divider && {
1330
+ borderBlockEnd: `1px solid ${theme.palette.divider}`,
1331
+ }),
1332
+
1333
+ [`&.${menuItemClasses.disabled}`]: {
1334
+ opacity: 1,
1335
+ color: theme.palette.text.disabled,
1336
+ },
1337
+
1338
+ [`& + .${dividerClasses.root}`]: {
1339
+ marginBlock: theme.spacing(1),
1340
+ },
1341
+
1342
+ [`& .${listItemTextClasses.root}`]: {
1343
+ marginBlock: 0,
1344
+ },
1345
+
1346
+ [`& .${listItemIconClasses.root}`]: {
1347
+ minWidth: "unset",
1348
+ },
1286
1349
  }),
1287
1350
  },
1288
1351
  },
1352
+ MuiListItemIcon: {
1353
+ styleOverrides: {
1354
+ root: {
1355
+ minWidth: "unset",
1356
+ color: "inherit",
1357
+ },
1358
+ },
1359
+ },
1289
1360
  MuiNativeSelect: {
1290
1361
  defaultProps: {
1291
1362
  variant: "standard",
@@ -1306,6 +1377,9 @@ export const components: ThemeOptions["components"] = {
1306
1377
  styleOverrides: {
1307
1378
  paper: ({ theme }) => ({
1308
1379
  marginBlockStart: theme.spacing(1),
1380
+ borderWidth: theme.mixins.borderWidth,
1381
+ borderStyle: theme.mixins.borderStyle,
1382
+ borderColor: theme.palette.grey[200],
1309
1383
  }),
1310
1384
  },
1311
1385
  },