@keycloakify/keycloak-ui-shared 26.0.6001
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/LICENSE +2 -0
- package/README.md +6 -0
- package/keycloak-theme/shared/keycloak-ui-shared/alerts/AlertPanel.tsx +43 -0
- package/keycloak-theme/shared/keycloak-ui-shared/alerts/Alerts.tsx +82 -0
- package/keycloak-theme/shared/keycloak-ui-shared/buttons/FormSubmitButton.tsx +47 -0
- package/keycloak-theme/shared/keycloak-ui-shared/context/ErrorPage.tsx +60 -0
- package/keycloak-theme/shared/keycloak-ui-shared/context/HelpContext.tsx +30 -0
- package/keycloak-theme/shared/keycloak-ui-shared/context/KeycloakContext.tsx +97 -0
- package/keycloak-theme/shared/keycloak-ui-shared/context/environment.ts +50 -0
- package/keycloak-theme/shared/keycloak-ui-shared/continue-cancel/ContinueCancelModal.tsx +75 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/FormErrorText.tsx +23 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/FormLabel.tsx +40 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/HelpItem.tsx +43 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/KeycloakSpinner.tsx +12 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/NumberControl.tsx +93 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/OrganizationTable.tsx +122 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/PasswordControl.tsx +71 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/PasswordInput.tsx +50 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/SwitchControl.tsx +67 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/TextAreaControl.tsx +60 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/TextControl.tsx +75 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/keycloak-text-area/KeycloakTextArea.tsx +23 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/select-control/SelectControl.tsx +75 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/select-control/SingleSelectControl.tsx +109 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/select-control/TypeaheadSelectControl.tsx +285 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/table/KeycloakDataTable.tsx +597 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/table/ListEmptyState.tsx +86 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/table/PaginatingTableToolbar.tsx +106 -0
- package/keycloak-theme/shared/keycloak-ui-shared/controls/table/TableToolbar.tsx +92 -0
- package/keycloak-theme/shared/keycloak-ui-shared/icons/IconMapper.tsx +63 -0
- package/keycloak-theme/shared/keycloak-ui-shared/index.ts +1 -0
- package/keycloak-theme/shared/keycloak-ui-shared/main.ts +96 -0
- package/keycloak-theme/shared/keycloak-ui-shared/masthead/DefaultAvatar.tsx +109 -0
- package/keycloak-theme/shared/keycloak-ui-shared/masthead/KeycloakDropdown.tsx +48 -0
- package/keycloak-theme/shared/keycloak-ui-shared/masthead/Masthead.tsx +161 -0
- package/keycloak-theme/shared/keycloak-ui-shared/scroll-form/FormPanel.tsx +29 -0
- package/keycloak-theme/shared/keycloak-ui-shared/scroll-form/FormTitle.tsx +28 -0
- package/keycloak-theme/shared/keycloak-ui-shared/scroll-form/ScrollForm.tsx +98 -0
- package/keycloak-theme/shared/keycloak-ui-shared/scroll-form/ScrollPanel.tsx +21 -0
- package/keycloak-theme/shared/keycloak-ui-shared/scroll-form/form-title.module.css +4 -0
- package/keycloak-theme/shared/keycloak-ui-shared/scroll-form/scroll-form.module.css +8 -0
- package/keycloak-theme/shared/keycloak-ui-shared/select/KeycloakSelect.tsx +49 -0
- package/keycloak-theme/shared/keycloak-ui-shared/select/SingleSelect.tsx +89 -0
- package/keycloak-theme/shared/keycloak-ui-shared/select/TypeaheadSelect.tsx +198 -0
- package/keycloak-theme/shared/keycloak-ui-shared/user-profile/LocaleSelector.tsx +51 -0
- package/keycloak-theme/shared/keycloak-ui-shared/user-profile/MultiInputComponent.tsx +146 -0
- package/keycloak-theme/shared/keycloak-ui-shared/user-profile/OptionsComponent.tsx +63 -0
- package/keycloak-theme/shared/keycloak-ui-shared/user-profile/SelectComponent.tsx +109 -0
- package/keycloak-theme/shared/keycloak-ui-shared/user-profile/TextAreaComponent.tsx +23 -0
- package/keycloak-theme/shared/keycloak-ui-shared/user-profile/TextComponent.tsx +32 -0
- package/keycloak-theme/shared/keycloak-ui-shared/user-profile/UserProfileFields.tsx +243 -0
- package/keycloak-theme/shared/keycloak-ui-shared/user-profile/UserProfileGroup.tsx +71 -0
- package/keycloak-theme/shared/keycloak-ui-shared/user-profile/utils.ts +170 -0
- package/keycloak-theme/shared/keycloak-ui-shared/utils/ErrorBoundary.tsx +77 -0
- package/keycloak-theme/shared/keycloak-ui-shared/utils/createNamedContext.ts +11 -0
- package/keycloak-theme/shared/keycloak-ui-shared/utils/darkMode.ts +19 -0
- package/keycloak-theme/shared/keycloak-ui-shared/utils/errors.ts +55 -0
- package/keycloak-theme/shared/keycloak-ui-shared/utils/generateId.ts +1 -0
- package/keycloak-theme/shared/keycloak-ui-shared/utils/getRuleValue.ts +17 -0
- package/keycloak-theme/shared/keycloak-ui-shared/utils/isDefined.ts +3 -0
- package/keycloak-theme/shared/keycloak-ui-shared/utils/useFetch.ts +44 -0
- package/keycloak-theme/shared/keycloak-ui-shared/utils/useRequiredContext.ts +24 -0
- package/keycloak-theme/shared/keycloak-ui-shared/utils/useSetTimeout.ts +40 -0
- package/keycloak-theme/shared/keycloak-ui-shared/utils/useStorageItem.ts +51 -0
- package/keycloak-theme/shared/keycloak-ui-shared/utils/useStoredState.ts +38 -0
- package/package.json +31 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Pagination,
|
|
3
|
+
PaginationToggleTemplateProps,
|
|
4
|
+
ToolbarItem,
|
|
5
|
+
} from "@patternfly/react-core";
|
|
6
|
+
import { PropsWithChildren, ReactNode } from "react";
|
|
7
|
+
import { useTranslation } from "react-i18next";
|
|
8
|
+
|
|
9
|
+
import { TableToolbar } from "./TableToolbar";
|
|
10
|
+
|
|
11
|
+
type KeycloakPaginationProps = {
|
|
12
|
+
id?: string;
|
|
13
|
+
count: number;
|
|
14
|
+
first: number;
|
|
15
|
+
max: number;
|
|
16
|
+
onNextClick: (page: number) => void;
|
|
17
|
+
onPreviousClick: (page: number) => void;
|
|
18
|
+
onPerPageSelect: (max: number, first: number) => void;
|
|
19
|
+
variant?: "top" | "bottom";
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type TableToolbarProps = KeycloakPaginationProps & {
|
|
23
|
+
searchTypeComponent?: ReactNode;
|
|
24
|
+
toolbarItem?: ReactNode;
|
|
25
|
+
subToolbar?: ReactNode;
|
|
26
|
+
inputGroupName?: string;
|
|
27
|
+
inputGroupPlaceholder?: string;
|
|
28
|
+
inputGroupOnEnter?: (value: string) => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const KeycloakPagination = ({
|
|
32
|
+
id,
|
|
33
|
+
variant = "top",
|
|
34
|
+
count,
|
|
35
|
+
first,
|
|
36
|
+
max,
|
|
37
|
+
onNextClick,
|
|
38
|
+
onPreviousClick,
|
|
39
|
+
onPerPageSelect,
|
|
40
|
+
}: KeycloakPaginationProps) => {
|
|
41
|
+
const { t } = useTranslation();
|
|
42
|
+
const page = Math.round(first / max);
|
|
43
|
+
return (
|
|
44
|
+
<Pagination
|
|
45
|
+
widgetId={id}
|
|
46
|
+
titles={{
|
|
47
|
+
paginationAriaLabel: `${t("pagination")} ${variant} `,
|
|
48
|
+
}}
|
|
49
|
+
isCompact
|
|
50
|
+
toggleTemplate={({
|
|
51
|
+
firstIndex,
|
|
52
|
+
lastIndex,
|
|
53
|
+
}: PaginationToggleTemplateProps) => (
|
|
54
|
+
<b>
|
|
55
|
+
{firstIndex} - {lastIndex}
|
|
56
|
+
</b>
|
|
57
|
+
)}
|
|
58
|
+
itemCount={count + page * max}
|
|
59
|
+
page={page + 1}
|
|
60
|
+
perPage={max}
|
|
61
|
+
onNextClick={(_, p) => onNextClick((p - 1) * max)}
|
|
62
|
+
onPreviousClick={(_, p) => onPreviousClick((p - 1) * max)}
|
|
63
|
+
onPerPageSelect={(_, m, f) => onPerPageSelect(f - 1, m)}
|
|
64
|
+
variant={variant}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const PaginatingTableToolbar = ({
|
|
70
|
+
count,
|
|
71
|
+
searchTypeComponent,
|
|
72
|
+
toolbarItem,
|
|
73
|
+
subToolbar,
|
|
74
|
+
children,
|
|
75
|
+
inputGroupName,
|
|
76
|
+
inputGroupPlaceholder,
|
|
77
|
+
inputGroupOnEnter,
|
|
78
|
+
...rest
|
|
79
|
+
}: PropsWithChildren<TableToolbarProps>) => {
|
|
80
|
+
return (
|
|
81
|
+
<TableToolbar
|
|
82
|
+
searchTypeComponent={searchTypeComponent}
|
|
83
|
+
toolbarItem={
|
|
84
|
+
<>
|
|
85
|
+
{toolbarItem}
|
|
86
|
+
<ToolbarItem variant="pagination">
|
|
87
|
+
<KeycloakPagination count={count} {...rest} />
|
|
88
|
+
</ToolbarItem>
|
|
89
|
+
</>
|
|
90
|
+
}
|
|
91
|
+
subToolbar={subToolbar}
|
|
92
|
+
toolbarItemFooter={
|
|
93
|
+
count !== 0 ? (
|
|
94
|
+
<ToolbarItem variant="pagination">
|
|
95
|
+
<KeycloakPagination count={count} variant="bottom" {...rest} />
|
|
96
|
+
</ToolbarItem>
|
|
97
|
+
) : null
|
|
98
|
+
}
|
|
99
|
+
inputGroupName={inputGroupName}
|
|
100
|
+
inputGroupPlaceholder={inputGroupPlaceholder}
|
|
101
|
+
inputGroupOnEnter={inputGroupOnEnter}
|
|
102
|
+
>
|
|
103
|
+
{children}
|
|
104
|
+
</TableToolbar>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Divider,
|
|
3
|
+
InputGroup,
|
|
4
|
+
SearchInput,
|
|
5
|
+
Toolbar,
|
|
6
|
+
ToolbarContent,
|
|
7
|
+
ToolbarItem,
|
|
8
|
+
} from "@patternfly/react-core";
|
|
9
|
+
import { KeyboardEvent, PropsWithChildren, ReactNode, useState } from "react";
|
|
10
|
+
import { useTranslation } from "react-i18next";
|
|
11
|
+
|
|
12
|
+
type TableToolbarProps = {
|
|
13
|
+
toolbarItem?: ReactNode;
|
|
14
|
+
subToolbar?: ReactNode;
|
|
15
|
+
toolbarItemFooter?: ReactNode;
|
|
16
|
+
searchTypeComponent?: ReactNode;
|
|
17
|
+
inputGroupName?: string;
|
|
18
|
+
inputGroupPlaceholder?: string;
|
|
19
|
+
inputGroupOnEnter?: (value: string) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const TableToolbar = ({
|
|
23
|
+
toolbarItem,
|
|
24
|
+
subToolbar,
|
|
25
|
+
toolbarItemFooter,
|
|
26
|
+
children,
|
|
27
|
+
searchTypeComponent,
|
|
28
|
+
inputGroupName,
|
|
29
|
+
inputGroupPlaceholder,
|
|
30
|
+
inputGroupOnEnter,
|
|
31
|
+
}: PropsWithChildren<TableToolbarProps>) => {
|
|
32
|
+
const { t } = useTranslation();
|
|
33
|
+
const [searchValue, setSearchValue] = useState<string>("");
|
|
34
|
+
|
|
35
|
+
const onSearch = () => {
|
|
36
|
+
if (searchValue !== "") {
|
|
37
|
+
setSearchValue(searchValue);
|
|
38
|
+
inputGroupOnEnter?.(searchValue);
|
|
39
|
+
} else {
|
|
40
|
+
setSearchValue("");
|
|
41
|
+
inputGroupOnEnter?.("");
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
46
|
+
if (e.key === "Enter") {
|
|
47
|
+
onSearch();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<>
|
|
53
|
+
<Toolbar>
|
|
54
|
+
<ToolbarContent>
|
|
55
|
+
{inputGroupName && (
|
|
56
|
+
<ToolbarItem>
|
|
57
|
+
<InputGroup data-testid={inputGroupName}>
|
|
58
|
+
{searchTypeComponent}
|
|
59
|
+
{inputGroupPlaceholder && (
|
|
60
|
+
<SearchInput
|
|
61
|
+
data-testid="table-search-input"
|
|
62
|
+
placeholder={inputGroupPlaceholder}
|
|
63
|
+
aria-label={t("search")}
|
|
64
|
+
value={searchValue}
|
|
65
|
+
onChange={(_, value) => {
|
|
66
|
+
setSearchValue(value);
|
|
67
|
+
}}
|
|
68
|
+
onSearch={onSearch}
|
|
69
|
+
onKeyDown={handleKeyDown}
|
|
70
|
+
onClear={() => {
|
|
71
|
+
setSearchValue("");
|
|
72
|
+
inputGroupOnEnter?.("");
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
</InputGroup>
|
|
77
|
+
</ToolbarItem>
|
|
78
|
+
)}
|
|
79
|
+
{toolbarItem}
|
|
80
|
+
</ToolbarContent>
|
|
81
|
+
</Toolbar>
|
|
82
|
+
{subToolbar && (
|
|
83
|
+
<Toolbar>
|
|
84
|
+
<ToolbarContent>{subToolbar}</ToolbarContent>
|
|
85
|
+
</Toolbar>
|
|
86
|
+
)}
|
|
87
|
+
<Divider />
|
|
88
|
+
{children}
|
|
89
|
+
<Toolbar>{toolbarItemFooter}</Toolbar>
|
|
90
|
+
</>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Icon } from "@patternfly/react-core";
|
|
2
|
+
import {
|
|
3
|
+
BitbucketIcon,
|
|
4
|
+
CubeIcon,
|
|
5
|
+
FacebookSquareIcon,
|
|
6
|
+
GithubIcon,
|
|
7
|
+
GitlabIcon,
|
|
8
|
+
GoogleIcon,
|
|
9
|
+
InstagramIcon,
|
|
10
|
+
LinkedinIcon,
|
|
11
|
+
MicrosoftIcon,
|
|
12
|
+
OpenshiftIcon,
|
|
13
|
+
PaypalIcon,
|
|
14
|
+
StackOverflowIcon,
|
|
15
|
+
TwitterIcon,
|
|
16
|
+
} from "@patternfly/react-icons";
|
|
17
|
+
|
|
18
|
+
type IconMapperProps = {
|
|
19
|
+
icon: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const IconMapper = ({ icon }: IconMapperProps) => {
|
|
23
|
+
const SpecificIcon = getIcon(icon);
|
|
24
|
+
return (
|
|
25
|
+
<Icon size="lg">
|
|
26
|
+
<SpecificIcon alt={icon} />
|
|
27
|
+
</Icon>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function getIcon(icon: string) {
|
|
32
|
+
switch (icon) {
|
|
33
|
+
case "github":
|
|
34
|
+
return GithubIcon;
|
|
35
|
+
case "facebook":
|
|
36
|
+
return FacebookSquareIcon;
|
|
37
|
+
case "gitlab":
|
|
38
|
+
return GitlabIcon;
|
|
39
|
+
case "google":
|
|
40
|
+
return GoogleIcon;
|
|
41
|
+
case "linkedin":
|
|
42
|
+
case "linkedin-openid-connect":
|
|
43
|
+
return LinkedinIcon;
|
|
44
|
+
|
|
45
|
+
case "openshift-v3":
|
|
46
|
+
case "openshift-v4":
|
|
47
|
+
return OpenshiftIcon;
|
|
48
|
+
case "stackoverflow":
|
|
49
|
+
return StackOverflowIcon;
|
|
50
|
+
case "twitter":
|
|
51
|
+
return TwitterIcon;
|
|
52
|
+
case "microsoft":
|
|
53
|
+
return MicrosoftIcon;
|
|
54
|
+
case "bitbucket":
|
|
55
|
+
return BitbucketIcon;
|
|
56
|
+
case "instagram":
|
|
57
|
+
return InstagramIcon;
|
|
58
|
+
case "paypal":
|
|
59
|
+
return PaypalIcon;
|
|
60
|
+
default:
|
|
61
|
+
return CubeIcon;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './main';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export {
|
|
2
|
+
AlertProvider,
|
|
3
|
+
useAlerts,
|
|
4
|
+
type AddAlertFunction,
|
|
5
|
+
type AddErrorFunction,
|
|
6
|
+
type AlertProps,
|
|
7
|
+
} from "./alerts/Alerts";
|
|
8
|
+
export { ErrorPage } from "./context/ErrorPage";
|
|
9
|
+
export { Help, useHelp } from "./context/HelpContext";
|
|
10
|
+
export {
|
|
11
|
+
KeycloakProvider,
|
|
12
|
+
useEnvironment,
|
|
13
|
+
type KeycloakContext,
|
|
14
|
+
} from "./context/KeycloakContext";
|
|
15
|
+
export {
|
|
16
|
+
getInjectedEnvironment,
|
|
17
|
+
type BaseEnvironment,
|
|
18
|
+
} from "./context/environment";
|
|
19
|
+
export { ContinueCancelModal } from "./continue-cancel/ContinueCancelModal";
|
|
20
|
+
export {
|
|
21
|
+
FormErrorText,
|
|
22
|
+
type FormErrorTextProps,
|
|
23
|
+
} from "./controls/FormErrorText";
|
|
24
|
+
export { HelpItem } from "./controls/HelpItem";
|
|
25
|
+
export { NumberControl } from "./controls/NumberControl";
|
|
26
|
+
export { PasswordControl } from "./controls/PasswordControl";
|
|
27
|
+
export { PasswordInput } from "./controls/PasswordInput";
|
|
28
|
+
export {
|
|
29
|
+
SelectControl,
|
|
30
|
+
SelectVariant,
|
|
31
|
+
} from "./controls/select-control/SelectControl";
|
|
32
|
+
export type {
|
|
33
|
+
SelectControlOption,
|
|
34
|
+
SelectControlProps,
|
|
35
|
+
} from "./controls/select-control/SelectControl";
|
|
36
|
+
export {
|
|
37
|
+
SwitchControl,
|
|
38
|
+
type SwitchControlProps,
|
|
39
|
+
} from "./controls/SwitchControl";
|
|
40
|
+
export { TextAreaControl } from "./controls/TextAreaControl";
|
|
41
|
+
export { TextControl } from "./controls/TextControl";
|
|
42
|
+
export {
|
|
43
|
+
KeycloakTextArea,
|
|
44
|
+
type KeycloakTextAreaProps,
|
|
45
|
+
} from "./controls/keycloak-text-area/KeycloakTextArea";
|
|
46
|
+
export { IconMapper } from "./icons/IconMapper";
|
|
47
|
+
export { FormPanel } from "./scroll-form/FormPanel";
|
|
48
|
+
export { ScrollForm, mainPageContentId } from "./scroll-form/ScrollForm";
|
|
49
|
+
export {
|
|
50
|
+
FormSubmitButton,
|
|
51
|
+
type FormSubmitButtonProps,
|
|
52
|
+
} from "./buttons/FormSubmitButton";
|
|
53
|
+
export { UserProfileFields } from "./user-profile/UserProfileFields";
|
|
54
|
+
export {
|
|
55
|
+
beerify,
|
|
56
|
+
debeerify,
|
|
57
|
+
isUserProfileError,
|
|
58
|
+
label,
|
|
59
|
+
setUserProfileServerError,
|
|
60
|
+
} from "./user-profile/utils";
|
|
61
|
+
export type { UserFormFields } from "./user-profile/utils";
|
|
62
|
+
export { createNamedContext } from "./utils/createNamedContext";
|
|
63
|
+
export {
|
|
64
|
+
getErrorDescription,
|
|
65
|
+
getErrorMessage,
|
|
66
|
+
getNetworkErrorMessage,
|
|
67
|
+
getNetworkErrorDescription,
|
|
68
|
+
} from "./utils/errors";
|
|
69
|
+
export { isDefined } from "./utils/isDefined";
|
|
70
|
+
export { useRequiredContext } from "./utils/useRequiredContext";
|
|
71
|
+
export { useStoredState } from "./utils/useStoredState";
|
|
72
|
+
export { useSetTimeout } from "./utils/useSetTimeout";
|
|
73
|
+
export { generateId } from "./utils/generateId";
|
|
74
|
+
export { default as KeycloakMasthead } from "./masthead/Masthead";
|
|
75
|
+
export { KeycloakSelect } from "./select/KeycloakSelect";
|
|
76
|
+
export type { Variant, KeycloakSelectProps } from "./select/KeycloakSelect";
|
|
77
|
+
export { KeycloakDataTable } from "./controls/table/KeycloakDataTable";
|
|
78
|
+
export type {
|
|
79
|
+
Action,
|
|
80
|
+
Field,
|
|
81
|
+
DetailField,
|
|
82
|
+
LoaderFunction,
|
|
83
|
+
} from "./controls/table/KeycloakDataTable";
|
|
84
|
+
export { PaginatingTableToolbar } from "./controls/table/PaginatingTableToolbar";
|
|
85
|
+
export { TableToolbar } from "./controls/table/TableToolbar";
|
|
86
|
+
export { ListEmptyState } from "./controls/table/ListEmptyState";
|
|
87
|
+
export { KeycloakSpinner } from "./controls/KeycloakSpinner";
|
|
88
|
+
export { useFetch } from "./utils/useFetch";
|
|
89
|
+
export {
|
|
90
|
+
useErrorBoundary,
|
|
91
|
+
ErrorBoundaryFallback,
|
|
92
|
+
ErrorBoundaryProvider,
|
|
93
|
+
} from "./utils/ErrorBoundary";
|
|
94
|
+
export type { FallbackProps } from "./utils/ErrorBoundary";
|
|
95
|
+
export { OrganizationTable } from "./controls/OrganizationTable";
|
|
96
|
+
export { initializeDarkMode } from "./utils/darkMode";
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import styles from "@patternfly/react-styles/css/components/Avatar/avatar";
|
|
2
|
+
import { css } from "@patternfly/react-styles";
|
|
3
|
+
|
|
4
|
+
type DefaultAvatarProps = {
|
|
5
|
+
className?: string;
|
|
6
|
+
border?: "light" | "dark";
|
|
7
|
+
size?: "sm" | "md" | "lg" | "xl";
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const DefaultAvatar = ({
|
|
11
|
+
className = "",
|
|
12
|
+
border,
|
|
13
|
+
size = "md",
|
|
14
|
+
}: DefaultAvatarProps) => (
|
|
15
|
+
<svg
|
|
16
|
+
className={css(
|
|
17
|
+
styles.avatar,
|
|
18
|
+
styles.modifiers[size],
|
|
19
|
+
border === "light" && styles.modifiers.light,
|
|
20
|
+
border === "dark" && styles.modifiers.dark,
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
23
|
+
enableBackground="new 0 0 36 36"
|
|
24
|
+
version="1.1"
|
|
25
|
+
viewBox="0 0 36 36"
|
|
26
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
27
|
+
>
|
|
28
|
+
<circle
|
|
29
|
+
style={{ fillRule: "evenodd", clipRule: "evenodd", fill: "#FFFFFF" }}
|
|
30
|
+
cx="18"
|
|
31
|
+
cy="18.5"
|
|
32
|
+
r="18"
|
|
33
|
+
/>
|
|
34
|
+
<defs>
|
|
35
|
+
<filter
|
|
36
|
+
id="b"
|
|
37
|
+
x="5.2"
|
|
38
|
+
y="7.2"
|
|
39
|
+
width="25.6"
|
|
40
|
+
height="53.6"
|
|
41
|
+
filterUnits="userSpaceOnUse"
|
|
42
|
+
>
|
|
43
|
+
<feColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" />
|
|
44
|
+
</filter>
|
|
45
|
+
</defs>
|
|
46
|
+
<mask
|
|
47
|
+
id="a"
|
|
48
|
+
x="5.2"
|
|
49
|
+
y="7.2"
|
|
50
|
+
width="25.6"
|
|
51
|
+
height="53.6"
|
|
52
|
+
maskUnits="userSpaceOnUse"
|
|
53
|
+
>
|
|
54
|
+
<g style={{ filter: 'url("#b")' }}>
|
|
55
|
+
<circle
|
|
56
|
+
style={{ fillRule: "evenodd", clipRule: "evenodd", fill: "#FFFFFF" }}
|
|
57
|
+
cx="18"
|
|
58
|
+
cy="18.5"
|
|
59
|
+
r="18"
|
|
60
|
+
/>
|
|
61
|
+
</g>
|
|
62
|
+
</mask>
|
|
63
|
+
<g style={{ filter: 'url("#a")' }}>
|
|
64
|
+
<g transform="translate(5.04 6.88)">
|
|
65
|
+
<path
|
|
66
|
+
style={{
|
|
67
|
+
fillRule: "evenodd",
|
|
68
|
+
clipRule: "evenodd",
|
|
69
|
+
fill: "#BBBBBB",
|
|
70
|
+
}}
|
|
71
|
+
d="m22.6 18.1c-1.1-1.4-2.3-2.2-3.5-2.6s-1.8-0.6-6.3-0.6-6.1 0.7-6.1 0.7 0 0 0 0c-1.2 0.4-2.4 1.2-3.4 2.6-2.3 2.8-3.2 12.3-3.2 14.8 0 3.2 0.4 12.3 0.6 15.4 0 0-0.4 5.5 4 5.5l-0.3-6.3-0.4-3.5 0.2-0.9c0.9 0.4 3.6 1.2 8.6 1.2 5.3 0 8-0.9 8.8-1.3l0.2 1-0.2 3.6-0.3 6.3c3 0.1 3.7-3 3.8-4.4s0.6-12.6 0.6-16.5c0.1-2.6-0.8-12.1-3.1-15z"
|
|
72
|
+
/>
|
|
73
|
+
<path
|
|
74
|
+
style={{
|
|
75
|
+
opacity: 0.1,
|
|
76
|
+
fillRule: "evenodd",
|
|
77
|
+
clipRule: "evenodd",
|
|
78
|
+
}}
|
|
79
|
+
d="m22.5 26c-0.1-2.1-1.5-2.8-4.8-2.8l2.2 9.6s1.8-1.7 3-1.8c0 0-0.4-4.6-0.4-5z"
|
|
80
|
+
/>
|
|
81
|
+
<path
|
|
82
|
+
style={{
|
|
83
|
+
fillRule: "evenodd",
|
|
84
|
+
clipRule: "evenodd",
|
|
85
|
+
fill: "#BBBBBB",
|
|
86
|
+
}}
|
|
87
|
+
d="m12.7 13.2c-3.5 0-6.4-2.9-6.4-6.4s2.9-6.4 6.4-6.4 6.4 2.9 6.4 6.4-2.8 6.4-6.4 6.4z"
|
|
88
|
+
/>
|
|
89
|
+
<path
|
|
90
|
+
style={{
|
|
91
|
+
opacity: 8.0e-2,
|
|
92
|
+
fillRule: "evenodd",
|
|
93
|
+
clipRule: "evenodd",
|
|
94
|
+
fill: "#231F20",
|
|
95
|
+
}}
|
|
96
|
+
d="m9.4 6.8c0-3 2.1-5.5 4.9-6.3-0.5-0.1-1-0.2-1.6-0.2-3.5 0-6.4 2.9-6.4 6.4s2.9 6.4 6.4 6.4c0.6 0 1.1-0.1 1.6-0.2-2.8-0.6-4.9-3.1-4.9-6.1z"
|
|
97
|
+
/>
|
|
98
|
+
<path
|
|
99
|
+
style={{
|
|
100
|
+
opacity: 0.1,
|
|
101
|
+
fillRule: "evenodd",
|
|
102
|
+
clipRule: "evenodd",
|
|
103
|
+
}}
|
|
104
|
+
d="m8.3 22.4c-2 0.4-2.9 1.4-3.1 3.5l-0.6 18.6s1.7 0.7 3.6 0.9l0.1-23z"
|
|
105
|
+
/>
|
|
106
|
+
</g>
|
|
107
|
+
</g>
|
|
108
|
+
</svg>
|
|
109
|
+
);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Dropdown,
|
|
3
|
+
DropdownList,
|
|
4
|
+
DropdownProps,
|
|
5
|
+
MenuToggle,
|
|
6
|
+
} from "@patternfly/react-core";
|
|
7
|
+
import { EllipsisVIcon } from "@patternfly/react-icons";
|
|
8
|
+
import { ReactNode, useState } from "react";
|
|
9
|
+
|
|
10
|
+
type KeycloakDropdownProps = Omit<DropdownProps, "toggle"> & {
|
|
11
|
+
"data-testid"?: string;
|
|
12
|
+
isKebab?: boolean;
|
|
13
|
+
title?: ReactNode;
|
|
14
|
+
dropDownItems: ReactNode[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const KeycloakDropdown = ({
|
|
18
|
+
isKebab = false,
|
|
19
|
+
title,
|
|
20
|
+
dropDownItems,
|
|
21
|
+
...rest
|
|
22
|
+
}: KeycloakDropdownProps) => {
|
|
23
|
+
const [open, setOpen] = useState(false);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Dropdown
|
|
27
|
+
{...rest}
|
|
28
|
+
popperProps={{
|
|
29
|
+
position: "right",
|
|
30
|
+
}}
|
|
31
|
+
onOpenChange={(isOpen) => setOpen(isOpen)}
|
|
32
|
+
toggle={(ref) => (
|
|
33
|
+
<MenuToggle
|
|
34
|
+
data-testid={`${rest["data-testid"]}-toggle`}
|
|
35
|
+
ref={ref}
|
|
36
|
+
onClick={() => setOpen(!open)}
|
|
37
|
+
isExpanded={open}
|
|
38
|
+
variant={isKebab ? "plain" : "default"}
|
|
39
|
+
>
|
|
40
|
+
{isKebab ? <EllipsisVIcon /> : title}
|
|
41
|
+
</MenuToggle>
|
|
42
|
+
)}
|
|
43
|
+
isOpen={open}
|
|
44
|
+
>
|
|
45
|
+
<DropdownList>{dropDownItems}</DropdownList>
|
|
46
|
+
</Dropdown>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Avatar,
|
|
3
|
+
AvatarProps,
|
|
4
|
+
DropdownItem,
|
|
5
|
+
Masthead,
|
|
6
|
+
MastheadBrand,
|
|
7
|
+
MastheadBrandProps,
|
|
8
|
+
MastheadContent,
|
|
9
|
+
MastheadMainProps,
|
|
10
|
+
MastheadToggle,
|
|
11
|
+
PageToggleButton,
|
|
12
|
+
Toolbar,
|
|
13
|
+
ToolbarContent,
|
|
14
|
+
ToolbarItem,
|
|
15
|
+
} from "@patternfly/react-core";
|
|
16
|
+
import { BarsIcon } from "@patternfly/react-icons";
|
|
17
|
+
import { TFunction } from "i18next";
|
|
18
|
+
import Keycloak, { type KeycloakTokenParsed } from "keycloak-js";
|
|
19
|
+
import { ReactNode } from "react";
|
|
20
|
+
import { useTranslation } from "react-i18next";
|
|
21
|
+
import { DefaultAvatar } from "./DefaultAvatar";
|
|
22
|
+
import { KeycloakDropdown } from "./KeycloakDropdown";
|
|
23
|
+
|
|
24
|
+
function loggedInUserName(
|
|
25
|
+
token: KeycloakTokenParsed | undefined,
|
|
26
|
+
t: TFunction,
|
|
27
|
+
) {
|
|
28
|
+
if (!token) {
|
|
29
|
+
return t("unknownUser");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const givenName = token.given_name;
|
|
33
|
+
const familyName = token.family_name;
|
|
34
|
+
const preferredUsername = token.preferred_username;
|
|
35
|
+
|
|
36
|
+
if (givenName && familyName) {
|
|
37
|
+
return t("fullName", { givenName, familyName });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return givenName || familyName || preferredUsername || t("unknownUser");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type BrandLogo = MastheadBrandProps;
|
|
44
|
+
|
|
45
|
+
type KeycloakMastheadProps = MastheadMainProps & {
|
|
46
|
+
keycloak: Keycloak;
|
|
47
|
+
brand: BrandLogo;
|
|
48
|
+
avatar?: AvatarProps;
|
|
49
|
+
features?: {
|
|
50
|
+
hasLogout?: boolean;
|
|
51
|
+
hasManageAccount?: boolean;
|
|
52
|
+
hasUsername?: boolean;
|
|
53
|
+
};
|
|
54
|
+
kebabDropdownItems?: ReactNode[];
|
|
55
|
+
dropdownItems?: ReactNode[];
|
|
56
|
+
toolbarItems?: ReactNode[];
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const KeycloakMasthead = ({
|
|
60
|
+
keycloak,
|
|
61
|
+
brand: { src, alt, className, ...brandProps },
|
|
62
|
+
avatar,
|
|
63
|
+
features: {
|
|
64
|
+
hasLogout = true,
|
|
65
|
+
hasManageAccount = true,
|
|
66
|
+
hasUsername = true,
|
|
67
|
+
} = {},
|
|
68
|
+
kebabDropdownItems,
|
|
69
|
+
dropdownItems = [],
|
|
70
|
+
toolbarItems,
|
|
71
|
+
...rest
|
|
72
|
+
}: KeycloakMastheadProps) => {
|
|
73
|
+
const { t } = useTranslation();
|
|
74
|
+
const extraItems = [];
|
|
75
|
+
if (hasManageAccount) {
|
|
76
|
+
extraItems.push(
|
|
77
|
+
<DropdownItem
|
|
78
|
+
key="manageAccount"
|
|
79
|
+
onClick={() => keycloak.accountManagement()}
|
|
80
|
+
>
|
|
81
|
+
{t("manageAccount")}
|
|
82
|
+
</DropdownItem>,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (hasLogout) {
|
|
86
|
+
extraItems.push(
|
|
87
|
+
<DropdownItem key="signOut" onClick={() => keycloak.logout()}>
|
|
88
|
+
{t("signOut")}
|
|
89
|
+
</DropdownItem>,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const picture = keycloak.idTokenParsed?.picture;
|
|
94
|
+
return (
|
|
95
|
+
<Masthead {...rest}>
|
|
96
|
+
<MastheadToggle>
|
|
97
|
+
<PageToggleButton variant="plain" aria-label={t("navigation")}>
|
|
98
|
+
<BarsIcon />
|
|
99
|
+
</PageToggleButton>
|
|
100
|
+
</MastheadToggle>
|
|
101
|
+
<MastheadBrand {...brandProps}>
|
|
102
|
+
<img src={src} alt={alt} className={className} />
|
|
103
|
+
</MastheadBrand>
|
|
104
|
+
<MastheadContent>
|
|
105
|
+
<Toolbar>
|
|
106
|
+
<ToolbarContent>
|
|
107
|
+
{toolbarItems?.map((item, index) => (
|
|
108
|
+
<ToolbarItem key={index} align={{ default: "alignRight" }}>
|
|
109
|
+
{item}
|
|
110
|
+
</ToolbarItem>
|
|
111
|
+
))}
|
|
112
|
+
<ToolbarItem
|
|
113
|
+
visibility={{
|
|
114
|
+
default: "hidden",
|
|
115
|
+
md: "visible",
|
|
116
|
+
}} /** this user dropdown is hidden on mobile sizes */
|
|
117
|
+
>
|
|
118
|
+
<KeycloakDropdown
|
|
119
|
+
data-testid="options"
|
|
120
|
+
dropDownItems={[...dropdownItems, extraItems]}
|
|
121
|
+
title={
|
|
122
|
+
hasUsername
|
|
123
|
+
? loggedInUserName(keycloak.idTokenParsed, t)
|
|
124
|
+
: undefined
|
|
125
|
+
}
|
|
126
|
+
/>
|
|
127
|
+
</ToolbarItem>
|
|
128
|
+
<ToolbarItem
|
|
129
|
+
align={{ default: "alignLeft" }}
|
|
130
|
+
visibility={{
|
|
131
|
+
md: "hidden",
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
<KeycloakDropdown
|
|
135
|
+
data-testid="options-kebab"
|
|
136
|
+
isKebab
|
|
137
|
+
dropDownItems={[
|
|
138
|
+
...(kebabDropdownItems || dropdownItems),
|
|
139
|
+
extraItems,
|
|
140
|
+
]}
|
|
141
|
+
/>
|
|
142
|
+
</ToolbarItem>
|
|
143
|
+
<ToolbarItem
|
|
144
|
+
variant="overflow-menu"
|
|
145
|
+
align={{ default: "alignRight" }}
|
|
146
|
+
className="pf-v5-u-m-0-on-lg"
|
|
147
|
+
>
|
|
148
|
+
{picture || avatar?.src ? (
|
|
149
|
+
<Avatar {...{ src: picture, alt: t("avatar"), ...avatar }} />
|
|
150
|
+
) : (
|
|
151
|
+
<DefaultAvatar {...avatar} />
|
|
152
|
+
)}
|
|
153
|
+
</ToolbarItem>
|
|
154
|
+
</ToolbarContent>
|
|
155
|
+
</Toolbar>
|
|
156
|
+
</MastheadContent>
|
|
157
|
+
</Masthead>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export default KeycloakMasthead;
|