@okta/odyssey-react-mui 1.2.0 → 1.4.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.
- package/CHANGELOG.md +20 -0
- package/README.md +1 -1
- package/dist/Autocomplete.js +1 -0
- package/dist/Autocomplete.js.map +1 -1
- package/dist/Breadcrumbs.js +146 -0
- package/dist/Breadcrumbs.js.map +1 -0
- package/dist/Link.js +3 -1
- package/dist/Link.js.map +1 -1
- package/dist/MenuButton.js.map +1 -1
- package/dist/Tabs.js +8 -6
- package/dist/Tabs.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/labs/GroupPicker.js +190 -0
- package/dist/labs/GroupPicker.js.map +1 -0
- package/dist/labs/index.js +1 -0
- package/dist/labs/index.js.map +1 -1
- package/dist/properties/ts/odyssey-react-mui.js +2 -0
- package/dist/properties/ts/odyssey-react-mui.js.map +1 -1
- package/dist/src/Autocomplete.d.ts.map +1 -1
- package/dist/src/Breadcrumbs.d.ts +31 -0
- package/dist/src/Breadcrumbs.d.ts.map +1 -0
- package/dist/src/Link.d.ts +6 -1
- package/dist/src/Link.d.ts.map +1 -1
- package/dist/src/MenuButton.d.ts +1 -1
- package/dist/src/MenuButton.d.ts.map +1 -1
- package/dist/src/Tabs.d.ts +7 -2
- package/dist/src/Tabs.d.ts.map +1 -1
- package/dist/src/i18n.d.ts +2 -0
- package/dist/src/i18n.d.ts.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/labs/GroupPicker.d.ts +25 -0
- package/dist/src/labs/GroupPicker.d.ts.map +1 -0
- package/dist/src/labs/index.d.ts +1 -0
- package/dist/src/labs/index.d.ts.map +1 -1
- package/dist/src/properties/ts/odyssey-react-mui.d.ts +2 -0
- package/dist/src/properties/ts/odyssey-react-mui.d.ts.map +1 -1
- package/dist/src/theme/components.d.ts.map +1 -1
- package/dist/theme/components.js +32 -4
- package/dist/theme/components.js.map +1 -1
- package/dist/tsconfig.production.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/scripts/properties-to-ts.js +1 -1
- package/src/Autocomplete.tsx +3 -0
- package/src/Breadcrumbs.tsx +199 -0
- package/src/Link.tsx +7 -1
- package/src/MenuButton.tsx +2 -3
- package/src/Tabs.tsx +24 -12
- package/src/index.ts +1 -0
- package/src/labs/GroupPicker.tsx +241 -0
- package/src/labs/README.md +1 -1
- package/src/labs/index.ts +1 -0
- package/src/properties/odyssey-react-mui.properties +2 -0
- package/src/properties/ts/odyssey-react-mui.ts +1 -1
- package/src/theme/components.tsx +35 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@okta/odyssey-react-mui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
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.
|
|
54
|
+
"@okta/odyssey-design-tokens": "1.4.0",
|
|
55
55
|
"date-fns": "^2.30.0",
|
|
56
56
|
"i18next": "^23.5.1",
|
|
57
57
|
"material-react-table": "^1.14.0",
|
|
@@ -63,5 +63,5 @@
|
|
|
63
63
|
"react": ">=17 <19",
|
|
64
64
|
"react-dom": ">=17 <19"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "612e08d4ea3bd4c8acb732df7036bce16f4eda0b"
|
|
67
67
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
// Part of this has been copied over from @okta/ui-build-tools' own internal node script
|
|
14
|
-
// https://github.com/okta/ui-build-tools/blob/
|
|
14
|
+
// https://github.com/okta/ui-build-tools/blob/main/packages/clis/i18n/properties-to-json.js
|
|
15
15
|
|
|
16
16
|
const { resolve, join, basename, extname } = require("node:path");
|
|
17
17
|
const {
|
package/src/Autocomplete.tsx
CHANGED
|
@@ -223,6 +223,9 @@ const Autocomplete = <
|
|
|
223
223
|
);
|
|
224
224
|
};
|
|
225
225
|
|
|
226
|
+
// Need the `typeof Autocomplete` because generics don't get passed through
|
|
226
227
|
const MemoizedAutocomplete = memo(Autocomplete) as typeof Autocomplete;
|
|
228
|
+
// @ts-expect-error displayName is expected to not be on `typeof Autocomplete`
|
|
229
|
+
MemoizedAutocomplete.displayName = "Autocomplete";
|
|
227
230
|
|
|
228
231
|
export { MemoizedAutocomplete as Autocomplete };
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2023-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
|
+
ButtonBase,
|
|
15
|
+
Menu,
|
|
16
|
+
MenuItem,
|
|
17
|
+
Breadcrumbs as MuiBreadcrumbs,
|
|
18
|
+
} from "@mui/material";
|
|
19
|
+
import {
|
|
20
|
+
MouseEventHandler,
|
|
21
|
+
ReactElement,
|
|
22
|
+
createContext,
|
|
23
|
+
memo,
|
|
24
|
+
useCallback,
|
|
25
|
+
useContext,
|
|
26
|
+
useMemo,
|
|
27
|
+
useState,
|
|
28
|
+
} from "react";
|
|
29
|
+
import { GroupIcon, HomeIcon, UserIcon } from "./icons.generated";
|
|
30
|
+
import { Typography } from "./Typography";
|
|
31
|
+
import { useTranslation } from "react-i18next";
|
|
32
|
+
|
|
33
|
+
export type BreadcrumbType = "listItem" | "menuItem" | "currentPage";
|
|
34
|
+
|
|
35
|
+
export type BreadcrumbProps = {
|
|
36
|
+
children?: string;
|
|
37
|
+
href: string;
|
|
38
|
+
iconName?: "user" | "group";
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type BreadcrumbsProps = {
|
|
42
|
+
children: ReactElement<typeof Breadcrumb>[];
|
|
43
|
+
homeHref?: string;
|
|
44
|
+
maxVisibleItems?: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type BreadcrumbContextType = {
|
|
48
|
+
breadcrumbType: BreadcrumbType;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const BreadcrumbContext = createContext<BreadcrumbContextType>({
|
|
52
|
+
breadcrumbType: "listItem",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const Breadcrumb = ({ children, href, iconName }: BreadcrumbProps) => {
|
|
56
|
+
const { breadcrumbType } = useContext(BreadcrumbContext);
|
|
57
|
+
|
|
58
|
+
const breadcrumbContent = (
|
|
59
|
+
<>
|
|
60
|
+
{iconName === "group" ? (
|
|
61
|
+
<GroupIcon />
|
|
62
|
+
) : iconName === "user" ? (
|
|
63
|
+
<UserIcon />
|
|
64
|
+
) : null}
|
|
65
|
+
{children}
|
|
66
|
+
</>
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (breadcrumbType === "menuItem") {
|
|
70
|
+
return <MenuItem href={href}>{breadcrumbContent}</MenuItem>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (breadcrumbType === "currentPage") {
|
|
74
|
+
return <Typography>{breadcrumbContent}</Typography>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// breadcrumbType === "listItem" is the default
|
|
78
|
+
// Provided here without a conditional to get TS to be quiet
|
|
79
|
+
// about potential undefined returns
|
|
80
|
+
return <ButtonBase href={href}>{breadcrumbContent}</ButtonBase>;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const breadcrumbProviderValue: Record<
|
|
84
|
+
BreadcrumbType,
|
|
85
|
+
{ breadcrumbType: BreadcrumbType }
|
|
86
|
+
> = {
|
|
87
|
+
currentPage: {
|
|
88
|
+
breadcrumbType: "currentPage",
|
|
89
|
+
},
|
|
90
|
+
listItem: {
|
|
91
|
+
breadcrumbType: "listItem",
|
|
92
|
+
},
|
|
93
|
+
menuItem: {
|
|
94
|
+
breadcrumbType: "menuItem",
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const BreadcrumbList = ({
|
|
99
|
+
children,
|
|
100
|
+
homeHref,
|
|
101
|
+
maxVisibleItems: maxVisibleItemsOverride,
|
|
102
|
+
}: BreadcrumbsProps) => {
|
|
103
|
+
const maxVisibleItems = maxVisibleItemsOverride ?? children.length;
|
|
104
|
+
|
|
105
|
+
const { t } = useTranslation();
|
|
106
|
+
|
|
107
|
+
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
|
108
|
+
|
|
109
|
+
const breadcrumbSections = useMemo(() => {
|
|
110
|
+
if (children.length <= maxVisibleItems) {
|
|
111
|
+
return {
|
|
112
|
+
beforeMenu: [],
|
|
113
|
+
insideMenu: [],
|
|
114
|
+
remainingBreadcrumbs: children,
|
|
115
|
+
};
|
|
116
|
+
} else {
|
|
117
|
+
const menuStart = Math.floor(maxVisibleItems / 2);
|
|
118
|
+
const menuLength = children.length - maxVisibleItems;
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
beforeMenu: children.slice(0, menuStart),
|
|
122
|
+
insideMenu: children.slice(menuStart, menuStart + menuLength),
|
|
123
|
+
remainingBreadcrumbs: children.slice(menuStart + menuLength),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}, [children, maxVisibleItems]);
|
|
127
|
+
|
|
128
|
+
const onMenuButtonClick = useCallback<MouseEventHandler<HTMLButtonElement>>(
|
|
129
|
+
(event) => setAnchorEl(event.currentTarget),
|
|
130
|
+
[]
|
|
131
|
+
);
|
|
132
|
+
const onCloseMenu = useCallback(() => {
|
|
133
|
+
setAnchorEl(null);
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<MuiBreadcrumbs
|
|
138
|
+
maxItems={children.length + 1}
|
|
139
|
+
aria-label={t("breadcrumbs.label.text")}
|
|
140
|
+
>
|
|
141
|
+
{homeHref && (
|
|
142
|
+
<ButtonBase href={homeHref} aria-label={t("breadcrumbs.home.text")}>
|
|
143
|
+
<HomeIcon />
|
|
144
|
+
</ButtonBase>
|
|
145
|
+
)}
|
|
146
|
+
|
|
147
|
+
{breadcrumbSections.beforeMenu.map((breadcrumb) => (
|
|
148
|
+
<BreadcrumbContext.Provider value={breadcrumbProviderValue.listItem}>
|
|
149
|
+
{breadcrumb}
|
|
150
|
+
</BreadcrumbContext.Provider>
|
|
151
|
+
))}
|
|
152
|
+
|
|
153
|
+
{breadcrumbSections.insideMenu && (
|
|
154
|
+
<>
|
|
155
|
+
<ButtonBase onClick={onMenuButtonClick}>…</ButtonBase>
|
|
156
|
+
<Menu
|
|
157
|
+
open={Boolean(anchorEl)}
|
|
158
|
+
onClose={onCloseMenu}
|
|
159
|
+
anchorEl={anchorEl}
|
|
160
|
+
anchorOrigin={{ horizontal: "left", vertical: "bottom" }}
|
|
161
|
+
MenuListProps={{
|
|
162
|
+
sx: {
|
|
163
|
+
minWidth: 180,
|
|
164
|
+
},
|
|
165
|
+
}}
|
|
166
|
+
>
|
|
167
|
+
<BreadcrumbContext.Provider
|
|
168
|
+
value={breadcrumbProviderValue.menuItem}
|
|
169
|
+
>
|
|
170
|
+
{breadcrumbSections.insideMenu}
|
|
171
|
+
</BreadcrumbContext.Provider>
|
|
172
|
+
</Menu>
|
|
173
|
+
</>
|
|
174
|
+
)}
|
|
175
|
+
|
|
176
|
+
{breadcrumbSections.remainingBreadcrumbs.map((breadcrumb, i) => {
|
|
177
|
+
if (i === breadcrumbSections.remainingBreadcrumbs.length - 1) {
|
|
178
|
+
return (
|
|
179
|
+
<BreadcrumbContext.Provider
|
|
180
|
+
value={breadcrumbProviderValue.currentPage}
|
|
181
|
+
>
|
|
182
|
+
{breadcrumb}
|
|
183
|
+
</BreadcrumbContext.Provider>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
return (
|
|
187
|
+
<BreadcrumbContext.Provider value={breadcrumbProviderValue.listItem}>
|
|
188
|
+
{breadcrumb}
|
|
189
|
+
</BreadcrumbContext.Provider>
|
|
190
|
+
);
|
|
191
|
+
})}
|
|
192
|
+
</MuiBreadcrumbs>
|
|
193
|
+
);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const MemoizedBreadcrumbList = memo(BreadcrumbList);
|
|
197
|
+
MemoizedBreadcrumbList.displayName = "BreadcrumbList";
|
|
198
|
+
|
|
199
|
+
export { MemoizedBreadcrumbList as BreadcrumbList };
|
package/src/Link.tsx
CHANGED
|
@@ -14,7 +14,7 @@ import { memo, ReactElement } from "react";
|
|
|
14
14
|
import { ExternalLinkIcon } from "./icons.generated";
|
|
15
15
|
import type { SeleniumProps } from "./SeleniumProps";
|
|
16
16
|
|
|
17
|
-
import { Link as MuiLink } from "@mui/material";
|
|
17
|
+
import { Link as MuiLink, LinkProps as MuiLinkProps } from "@mui/material";
|
|
18
18
|
|
|
19
19
|
export const linkVariantValues = ["default", "monochrome"] as const;
|
|
20
20
|
|
|
@@ -31,6 +31,10 @@ export type LinkProps = {
|
|
|
31
31
|
* An optional Icon component at the start of the Link
|
|
32
32
|
*/
|
|
33
33
|
icon?: ReactElement;
|
|
34
|
+
/**
|
|
35
|
+
* The click event handler for the Link
|
|
36
|
+
*/
|
|
37
|
+
onClick?: MuiLinkProps["onClick"];
|
|
34
38
|
/**
|
|
35
39
|
* The HTML `rel` attribute for the Link
|
|
36
40
|
*/
|
|
@@ -58,6 +62,7 @@ const Link = ({
|
|
|
58
62
|
target,
|
|
59
63
|
testId,
|
|
60
64
|
variant,
|
|
65
|
+
onClick,
|
|
61
66
|
}: LinkProps) => (
|
|
62
67
|
<MuiLink
|
|
63
68
|
data-se={testId}
|
|
@@ -65,6 +70,7 @@ const Link = ({
|
|
|
65
70
|
rel={rel}
|
|
66
71
|
target={target}
|
|
67
72
|
variant={variant}
|
|
73
|
+
onClick={onClick}
|
|
68
74
|
>
|
|
69
75
|
{icon && <span className="Link-icon">{icon}</span>}
|
|
70
76
|
|
package/src/MenuButton.tsx
CHANGED
|
@@ -50,9 +50,8 @@ export type MenuButtonProps = {
|
|
|
50
50
|
* The <MenuItem> components within the Menu.
|
|
51
51
|
*/
|
|
52
52
|
children: Array<
|
|
53
|
-
ReactElement<
|
|
54
|
-
|
|
55
|
-
>
|
|
53
|
+
| ReactElement<typeof MenuItem | typeof Divider | typeof ListSubheader>
|
|
54
|
+
| NullElement
|
|
56
55
|
>;
|
|
57
56
|
/**
|
|
58
57
|
* The end Icon on the trigggering Button
|
package/src/Tabs.tsx
CHANGED
|
@@ -10,7 +10,14 @@
|
|
|
10
10
|
* See the License for the specific language governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import
|
|
13
|
+
import {
|
|
14
|
+
TabContext as MuiTabContext,
|
|
15
|
+
TabList as MuiTabList,
|
|
16
|
+
TabListProps as MuiTabListProps,
|
|
17
|
+
TabPanel as MuiTabPanel,
|
|
18
|
+
} from "@mui/lab";
|
|
19
|
+
import { Tab as MuiTab } from "@mui/material";
|
|
20
|
+
import {
|
|
14
21
|
ReactElement,
|
|
15
22
|
ReactNode,
|
|
16
23
|
memo,
|
|
@@ -18,12 +25,6 @@ import React, {
|
|
|
18
25
|
useEffect,
|
|
19
26
|
useState,
|
|
20
27
|
} from "react";
|
|
21
|
-
import { Tab as MuiTab } from "@mui/material";
|
|
22
|
-
import {
|
|
23
|
-
TabList as MuiTabList,
|
|
24
|
-
TabPanel as MuiTabPanel,
|
|
25
|
-
TabContext as MuiTabContext,
|
|
26
|
-
} from "@mui/lab";
|
|
27
28
|
import { SeleniumProps } from "./SeleniumProps";
|
|
28
29
|
|
|
29
30
|
export type TabItemProps = {
|
|
@@ -67,16 +68,27 @@ export type TabsProps = {
|
|
|
67
68
|
* Identifier for the selected tab.
|
|
68
69
|
*/
|
|
69
70
|
value?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Callback fired when the active tab is changed.
|
|
73
|
+
*/
|
|
74
|
+
onChange?: MuiTabListProps["onChange"];
|
|
70
75
|
};
|
|
71
76
|
|
|
72
|
-
const Tabs = ({
|
|
77
|
+
const Tabs = ({
|
|
78
|
+
ariaLabel,
|
|
79
|
+
initialValue,
|
|
80
|
+
tabs,
|
|
81
|
+
value,
|
|
82
|
+
onChange: onChangeProp,
|
|
83
|
+
}: TabsProps) => {
|
|
73
84
|
const [tabState, setTabState] = useState(initialValue ?? value ?? "0");
|
|
74
85
|
|
|
75
|
-
const onChange = useCallback(
|
|
76
|
-
(
|
|
77
|
-
setTabState(
|
|
86
|
+
const onChange = useCallback<NonNullable<MuiTabListProps["onChange"]>>(
|
|
87
|
+
(event, value: string) => {
|
|
88
|
+
setTabState(value);
|
|
89
|
+
onChangeProp?.(event, value);
|
|
78
90
|
},
|
|
79
|
-
[]
|
|
91
|
+
[onChangeProp]
|
|
80
92
|
);
|
|
81
93
|
|
|
82
94
|
useEffect(() => {
|
package/src/index.ts
CHANGED
|
@@ -59,6 +59,7 @@ export { useOdysseyDesignTokens } from "./OdysseyDesignTokensContext";
|
|
|
59
59
|
export * from "./Autocomplete";
|
|
60
60
|
export * from "./Banner";
|
|
61
61
|
export * from "./Box";
|
|
62
|
+
export * from "./Breadcrumbs";
|
|
62
63
|
export * from "./Button";
|
|
63
64
|
export * from "./Callout";
|
|
64
65
|
export * from "./Checkbox";
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2023-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 type { AutocompleteGetTagProps } from "@mui/material/useAutocomplete";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
Autocomplete as MuiAutocomplete,
|
|
17
|
+
Avatar as MuiAvatar,
|
|
18
|
+
Box,
|
|
19
|
+
InputBase,
|
|
20
|
+
} from "@mui/material";
|
|
21
|
+
import { avatarClasses } from "@mui/material/Avatar";
|
|
22
|
+
import { memo, useCallback } from "react";
|
|
23
|
+
|
|
24
|
+
import { AutocompleteProps } from "../Autocomplete";
|
|
25
|
+
import { Field } from "../Field";
|
|
26
|
+
import { Subordinate } from "../Typography";
|
|
27
|
+
import { Tag } from "../Tag";
|
|
28
|
+
import { useOdysseyDesignTokens } from "../OdysseyDesignTokensContext";
|
|
29
|
+
import { UserIcon, GridIcon, GroupIcon } from "../icons.generated";
|
|
30
|
+
|
|
31
|
+
export type GroupPickerOptionType = {
|
|
32
|
+
appsCount?: number;
|
|
33
|
+
description: string;
|
|
34
|
+
groupPushMappingsCount?: number;
|
|
35
|
+
id: string;
|
|
36
|
+
logo?: string;
|
|
37
|
+
name: string;
|
|
38
|
+
usersCount?: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type GroupPickerProps<
|
|
42
|
+
GroupPickerOptionType,
|
|
43
|
+
HasMultipleChoices extends boolean | undefined,
|
|
44
|
+
IsCustomValueAllowed extends boolean | undefined
|
|
45
|
+
> = AutocompleteProps<
|
|
46
|
+
GroupPickerOptionType,
|
|
47
|
+
HasMultipleChoices,
|
|
48
|
+
IsCustomValueAllowed
|
|
49
|
+
>;
|
|
50
|
+
|
|
51
|
+
const avatarImageSizeSmall = 16;
|
|
52
|
+
const avatarImageSizeMedium = 24;
|
|
53
|
+
|
|
54
|
+
const GroupPicker = <
|
|
55
|
+
OptionType extends GroupPickerOptionType,
|
|
56
|
+
HasMultipleChoices extends boolean | undefined,
|
|
57
|
+
IsCustomValueAllowed extends boolean | undefined
|
|
58
|
+
>({
|
|
59
|
+
hasMultipleChoices,
|
|
60
|
+
isCustomValueAllowed,
|
|
61
|
+
isDisabled,
|
|
62
|
+
isLoading,
|
|
63
|
+
isOptional = false,
|
|
64
|
+
isReadOnly,
|
|
65
|
+
hint,
|
|
66
|
+
label,
|
|
67
|
+
onChange,
|
|
68
|
+
onInputChange,
|
|
69
|
+
options,
|
|
70
|
+
value,
|
|
71
|
+
testId,
|
|
72
|
+
}: GroupPickerProps<OptionType, HasMultipleChoices, IsCustomValueAllowed>) => {
|
|
73
|
+
const odysseyDesignTokens = useOdysseyDesignTokens();
|
|
74
|
+
|
|
75
|
+
const isOptionEqualToValue = useCallback((sourceValue, targetValue) => {
|
|
76
|
+
return sourceValue.id === targetValue.id;
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
const getOptionLabel = useCallback((option) => {
|
|
80
|
+
return option.name;
|
|
81
|
+
}, []);
|
|
82
|
+
|
|
83
|
+
const renderOption = useCallback(
|
|
84
|
+
(props, option) => {
|
|
85
|
+
return (
|
|
86
|
+
<li {...props} key={option.id}>
|
|
87
|
+
<Box
|
|
88
|
+
sx={{
|
|
89
|
+
alignItems: "top",
|
|
90
|
+
display: "flex",
|
|
91
|
+
flexDirection: "row",
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
<Box sx={{ paddingRight: odysseyDesignTokens.Spacing2 }}>
|
|
95
|
+
<MuiAvatar
|
|
96
|
+
alt={option.name}
|
|
97
|
+
src={option.logo}
|
|
98
|
+
sx={{
|
|
99
|
+
[`.${avatarClasses.fallback}`]: {
|
|
100
|
+
visibility: "hidden",
|
|
101
|
+
},
|
|
102
|
+
background: "transparent",
|
|
103
|
+
height: avatarImageSizeMedium,
|
|
104
|
+
width: avatarImageSizeMedium,
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
107
|
+
</Box>
|
|
108
|
+
<Box>
|
|
109
|
+
{option.name}
|
|
110
|
+
<Subordinate>{option.description}</Subordinate>
|
|
111
|
+
<Box
|
|
112
|
+
sx={{
|
|
113
|
+
display: "flex",
|
|
114
|
+
flexDirection: "row",
|
|
115
|
+
paddingTop: odysseyDesignTokens.Spacing1,
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
{typeof option.usersCount === "number" && (
|
|
119
|
+
<Box
|
|
120
|
+
sx={{
|
|
121
|
+
display: "flex",
|
|
122
|
+
flexDirection: "row",
|
|
123
|
+
paddingRight: odysseyDesignTokens.Spacing4,
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
<UserIcon />
|
|
127
|
+
{option.usersCount}
|
|
128
|
+
</Box>
|
|
129
|
+
)}
|
|
130
|
+
{typeof option.appsCount === "number" && (
|
|
131
|
+
<Box
|
|
132
|
+
sx={{
|
|
133
|
+
display: "flex",
|
|
134
|
+
flexDirection: "row",
|
|
135
|
+
paddingRight: odysseyDesignTokens.Spacing4,
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
<GridIcon />
|
|
139
|
+
{option.appsCount}
|
|
140
|
+
</Box>
|
|
141
|
+
)}
|
|
142
|
+
{typeof option.groupPushMappingsCount === "number" && (
|
|
143
|
+
<Box sx={{ display: "flex", flexDirection: "row" }}>
|
|
144
|
+
<GroupIcon />
|
|
145
|
+
{option.groupPushMappingsCount}
|
|
146
|
+
</Box>
|
|
147
|
+
)}
|
|
148
|
+
</Box>
|
|
149
|
+
</Box>
|
|
150
|
+
</Box>
|
|
151
|
+
</li>
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
[odysseyDesignTokens]
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const renderTags = useCallback(
|
|
158
|
+
(values: OptionType[], getTagProps: AutocompleteGetTagProps) =>
|
|
159
|
+
values.map((option, index) => {
|
|
160
|
+
const { key, onDelete } = getTagProps({ index });
|
|
161
|
+
return (
|
|
162
|
+
<Box
|
|
163
|
+
key={key}
|
|
164
|
+
sx={{
|
|
165
|
+
margin: odysseyDesignTokens.Spacing1,
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
<Tag
|
|
169
|
+
icon={
|
|
170
|
+
<MuiAvatar
|
|
171
|
+
alt={option.name}
|
|
172
|
+
src={option.logo}
|
|
173
|
+
sx={{
|
|
174
|
+
[`.${avatarClasses.fallback}`]: {
|
|
175
|
+
visibility: "hidden",
|
|
176
|
+
},
|
|
177
|
+
background: "transparent",
|
|
178
|
+
height: avatarImageSizeSmall,
|
|
179
|
+
width: avatarImageSizeSmall,
|
|
180
|
+
}}
|
|
181
|
+
/>
|
|
182
|
+
}
|
|
183
|
+
label={option.name}
|
|
184
|
+
onRemove={onDelete}
|
|
185
|
+
/>
|
|
186
|
+
</Box>
|
|
187
|
+
);
|
|
188
|
+
}),
|
|
189
|
+
[odysseyDesignTokens]
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const renderInput = useCallback(
|
|
193
|
+
({ InputLabelProps, InputProps, ...params }) => (
|
|
194
|
+
<Field
|
|
195
|
+
fieldType="single"
|
|
196
|
+
hasVisibleLabel
|
|
197
|
+
id={InputLabelProps.htmlFor}
|
|
198
|
+
hint={hint}
|
|
199
|
+
label={label}
|
|
200
|
+
isOptional={isOptional}
|
|
201
|
+
renderFieldComponent={({ ariaDescribedBy, id }) => (
|
|
202
|
+
<InputBase
|
|
203
|
+
{...params}
|
|
204
|
+
{...InputProps}
|
|
205
|
+
aria-describedby={ariaDescribedBy}
|
|
206
|
+
id={id}
|
|
207
|
+
required={!isOptional}
|
|
208
|
+
/>
|
|
209
|
+
)}
|
|
210
|
+
/>
|
|
211
|
+
),
|
|
212
|
+
[hint, isOptional, label]
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<MuiAutocomplete
|
|
217
|
+
// AutoComplete is wrapped in a div within MUI which does not get the disabled attr. So this aria-disabled gets set in the div
|
|
218
|
+
aria-disabled={isDisabled}
|
|
219
|
+
data-se={testId}
|
|
220
|
+
disabled={isDisabled}
|
|
221
|
+
filterSelectedOptions={true}
|
|
222
|
+
freeSolo={isCustomValueAllowed}
|
|
223
|
+
getOptionLabel={getOptionLabel}
|
|
224
|
+
isOptionEqualToValue={isOptionEqualToValue}
|
|
225
|
+
loading={isLoading}
|
|
226
|
+
multiple={hasMultipleChoices}
|
|
227
|
+
onChange={onChange}
|
|
228
|
+
onInputChange={onInputChange}
|
|
229
|
+
options={options}
|
|
230
|
+
readOnly={isReadOnly}
|
|
231
|
+
renderInput={renderInput}
|
|
232
|
+
renderOption={renderOption}
|
|
233
|
+
renderTags={renderTags}
|
|
234
|
+
value={value}
|
|
235
|
+
/>
|
|
236
|
+
);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const MemoizedGroupPicker = memo(GroupPicker) as typeof GroupPicker;
|
|
240
|
+
|
|
241
|
+
export { MemoizedGroupPicker as GroupPicker };
|
package/src/labs/README.md
CHANGED
|
@@ -37,7 +37,7 @@ bundled, and polyfilled for advanced use cases and browser support targets.
|
|
|
37
37
|
|
|
38
38
|
## License
|
|
39
39
|
|
|
40
|
-
[Apache Version 2.0](https://github.com/okta/odyssey/blob/
|
|
40
|
+
[Apache Version 2.0](https://github.com/okta/odyssey/blob/main/LICENSE)
|
|
41
41
|
|
|
42
42
|
## Support Disclaimer
|
|
43
43
|
|
package/src/labs/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const translation = {"fielderror.screenreader.text":"Error","fieldlabel.optional.text":"Optional","fieldlabel.required.text":"Required","passwordfield.icon.label.show":"Show password","passwordfield.icon.label.hide":"Hide password","severity.error":"error","severity.info":"info","severity.success":"success","severity.warning":"warning","table.error":"Error loading data.","table.fetchedrows.text":"Fetched {{totalRows}} row","table.fetchedrows.text_plural":"Fetched {{totalRows}} total rows","table.rows.text":"{{totalRows}} row","table.rows.text_plural":"{{totalRows}} rows","toast.close.text":"close"};
|
|
1
|
+
export const translation = {"breadcrumbs.home.text":"Home","breadcrumbs.label.text":"Breadcrumbs","fielderror.screenreader.text":"Error","fieldlabel.optional.text":"Optional","fieldlabel.required.text":"Required","passwordfield.icon.label.show":"Show password","passwordfield.icon.label.hide":"Hide password","severity.error":"error","severity.info":"info","severity.success":"success","severity.warning":"warning","table.error":"Error loading data.","table.fetchedrows.text":"Fetched {{totalRows}} row","table.fetchedrows.text_plural":"Fetched {{totalRows}} total rows","table.rows.text":"{{totalRows}} row","table.rows.text_plural":"{{totalRows}} rows","toast.close.text":"close"};
|
package/src/theme/components.tsx
CHANGED
|
@@ -333,6 +333,40 @@ export const components = ({
|
|
|
333
333
|
}),
|
|
334
334
|
},
|
|
335
335
|
},
|
|
336
|
+
MuiBreadcrumbs: {
|
|
337
|
+
styleOverrides: {
|
|
338
|
+
li: {
|
|
339
|
+
fontSize: odysseyTokens.TypographySizeBody,
|
|
340
|
+
lineHeight: odysseyTokens.TypographyLineHeightUi,
|
|
341
|
+
|
|
342
|
+
"& > a, & > button": {
|
|
343
|
+
borderRadius: odysseyTokens.BorderRadiusTight,
|
|
344
|
+
color: odysseyTokens.TypographyColorSubordinate,
|
|
345
|
+
display: "flex",
|
|
346
|
+
gap: odysseyTokens.Spacing1,
|
|
347
|
+
padding: odysseyTokens.Spacing1,
|
|
348
|
+
transitionProperty: "color, background-color",
|
|
349
|
+
transitionDuration: "100ms",
|
|
350
|
+
transitionTimingFunction: "linear",
|
|
351
|
+
|
|
352
|
+
"&:hover": {
|
|
353
|
+
backgroundColor: odysseyTokens.HueNeutral200,
|
|
354
|
+
color: odysseyTokens.TypographyColorBody,
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
"&:focus-visible": {
|
|
358
|
+
boxShadow: `0 0 0 2px ${odysseyTokens.HueNeutralWhite}, 0 0 0 4px ${odysseyTokens.PalettePrimaryMain}`,
|
|
359
|
+
outline: "2px solid transparent",
|
|
360
|
+
outlineOffset: "1px",
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
separator: {
|
|
365
|
+
color: odysseyTokens.BorderColorDisplay,
|
|
366
|
+
marginInline: odysseyTokens.Spacing1,
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
},
|
|
336
370
|
MuiButton: {
|
|
337
371
|
defaultProps: {
|
|
338
372
|
variant: "primary",
|
|
@@ -688,10 +722,7 @@ export const components = ({
|
|
|
688
722
|
height: ".64em",
|
|
689
723
|
marginInlineEnd: odysseyTokens.Spacing2,
|
|
690
724
|
borderRadius: "100%",
|
|
691
|
-
backgroundColor:
|
|
692
|
-
borderColor: odysseyTokens.TypographyColorBody,
|
|
693
|
-
borderWidth: odysseyTokens.BorderWidthHeavy,
|
|
694
|
-
borderStyle: odysseyTokens.BorderStyleMain,
|
|
725
|
+
backgroundColor: odysseyTokens.HueNeutral600,
|
|
695
726
|
},
|
|
696
727
|
|
|
697
728
|
[`&.${chipClasses.colorError}`]: {
|