@keycloakify/keycloak-account-ui 25.0.4-rc.6 → 26.0.0-rc.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.
Files changed (193) hide show
  1. package/KcAccountUiLoader.d.ts +2 -1
  2. package/KcAccountUiLoader.js +5 -2
  3. package/KcAccountUiLoader.js.map +1 -1
  4. package/README.md +26 -28
  5. package/account-security/AccountRow.js +5 -4
  6. package/account-security/AccountRow.js.map +1 -1
  7. package/account-security/DeviceActivity.js +5 -4
  8. package/account-security/DeviceActivity.js.map +1 -1
  9. package/account-security/LinkedAccounts.js +24 -9
  10. package/account-security/LinkedAccounts.js.map +1 -1
  11. package/account-security/LinkedAccountsToolbar.d.ts +12 -0
  12. package/account-security/LinkedAccountsToolbar.js +24 -0
  13. package/account-security/LinkedAccountsToolbar.js.map +1 -0
  14. package/account-security/SigningIn.js +1 -1
  15. package/account-security/SigningIn.js.map +1 -1
  16. package/api/methods.d.ts +7 -1
  17. package/api/methods.js +9 -2
  18. package/api/methods.js.map +1 -1
  19. package/api/parse-links.js +3 -3
  20. package/api/parse-links.js.map +1 -1
  21. package/api/parse-response.d.ts +2 -0
  22. package/api/parse-response.js +11 -15
  23. package/api/parse-response.js.map +1 -1
  24. package/api/request.js +1 -1
  25. package/api/request.js.map +1 -1
  26. package/api.js +1 -7
  27. package/api.js.map +1 -1
  28. package/applications/Applications.js +5 -4
  29. package/applications/Applications.js.map +1 -1
  30. package/environment.d.ts +1 -0
  31. package/environment.js.map +1 -1
  32. package/messages/messages_ca.properties +105 -12
  33. package/messages/messages_de.properties +23 -1
  34. package/messages/messages_en.properties +16 -1
  35. package/messages/messages_es.properties +1 -1
  36. package/messages/messages_fr.properties +64 -19
  37. package/messages/messages_it.properties +3 -0
  38. package/messages/messages_ka.properties +15 -0
  39. package/messages/messages_nl.properties +2 -0
  40. package/organizations/Organizations.d.ts +2 -0
  41. package/organizations/Organizations.js +19 -0
  42. package/organizations/Organizations.js.map +1 -0
  43. package/package.json +89 -29
  44. package/personal-info/PersonalInfo.js +11 -9
  45. package/personal-info/PersonalInfo.js.map +1 -1
  46. package/public/content.d.ts +4 -0
  47. package/public/content.js +5 -0
  48. package/public/content.js.map +1 -1
  49. package/resources/EditTheResource.js +4 -3
  50. package/resources/EditTheResource.js.map +1 -1
  51. package/resources/PermissionRequest.js +4 -3
  52. package/resources/PermissionRequest.js.map +1 -1
  53. package/resources/ResourcesTab.js +4 -3
  54. package/resources/ResourcesTab.js.map +1 -1
  55. package/resources/ShareTheResource.js +4 -3
  56. package/resources/ShareTheResource.js.map +1 -1
  57. package/root/Header.js +1 -1
  58. package/root/Header.js.map +1 -1
  59. package/root/PageNav.js +1 -1
  60. package/root/PageNav.js.map +1 -1
  61. package/routes.d.ts +1 -0
  62. package/routes.js +6 -0
  63. package/routes.js.map +1 -1
  64. package/src/KcAccountUiLoader.tsx +7 -2
  65. package/src/account-security/AccountRow.tsx +6 -8
  66. package/src/account-security/DeviceActivity.tsx +10 -9
  67. package/src/account-security/LinkedAccounts.tsx +107 -30
  68. package/src/account-security/LinkedAccountsToolbar.tsx +88 -0
  69. package/src/account-security/SigningIn.tsx +1 -1
  70. package/src/api/methods.ts +22 -2
  71. package/src/api/parse-links.ts +3 -3
  72. package/src/api/parse-response.ts +22 -23
  73. package/src/api/request.ts +1 -1
  74. package/src/api.ts +1 -7
  75. package/src/applications/Applications.tsx +19 -11
  76. package/src/environment.ts +1 -0
  77. package/src/organizations/Organizations.tsx +48 -0
  78. package/src/personal-info/PersonalInfo.tsx +10 -8
  79. package/src/public/content.ts +5 -0
  80. package/src/resources/EditTheResource.tsx +8 -7
  81. package/src/resources/PermissionRequest.tsx +5 -3
  82. package/src/resources/ResourcesTab.tsx +8 -7
  83. package/src/resources/ShareTheResource.tsx +9 -8
  84. package/src/root/Header.tsx +0 -1
  85. package/src/root/PageNav.tsx +1 -1
  86. package/src/routes.tsx +7 -0
  87. package/src/ui-shared/alerts/AlertPanel.tsx +43 -0
  88. package/src/ui-shared/alerts/Alerts.tsx +48 -52
  89. package/src/ui-shared/context/environment.ts +1 -1
  90. package/src/ui-shared/controls/KeycloakSpinner.tsx +12 -0
  91. package/src/ui-shared/controls/OrganizationTable.tsx +122 -0
  92. package/src/ui-shared/controls/select-control/SingleSelectControl.tsx +3 -1
  93. package/src/ui-shared/controls/select-control/TypeaheadSelectControl.tsx +5 -3
  94. package/src/ui-shared/controls/table/KeycloakDataTable.tsx +597 -0
  95. package/src/ui-shared/controls/table/ListEmptyState.tsx +86 -0
  96. package/src/ui-shared/controls/table/PaginatingTableToolbar.tsx +106 -0
  97. package/src/ui-shared/controls/table/TableToolbar.tsx +92 -0
  98. package/src/ui-shared/main.ts +35 -1
  99. package/src/ui-shared/masthead/Masthead.tsx +64 -48
  100. package/src/ui-shared/select/SingleSelect.tsx +2 -0
  101. package/src/ui-shared/select/TypeaheadSelect.tsx +2 -0
  102. package/src/ui-shared/user-profile/LocaleSelector.tsx +1 -1
  103. package/src/ui-shared/user-profile/UserProfileFields.tsx +18 -21
  104. package/src/ui-shared/user-profile/UserProfileGroup.tsx +3 -2
  105. package/src/ui-shared/user-profile/utils.ts +12 -6
  106. package/src/ui-shared/utils/ErrorBoundary.tsx +77 -0
  107. package/src/ui-shared/utils/darkMode.ts +19 -0
  108. package/src/ui-shared/utils/errors.ts +55 -0
  109. package/src/ui-shared/utils/generateId.ts +1 -0
  110. package/src/ui-shared/utils/useFetch.ts +44 -0
  111. package/src/ui-shared/utils/useSetTimeout.ts +40 -0
  112. package/src/utils/useAccountAlerts.ts +28 -0
  113. package/src/utils/usePromise.ts +8 -3
  114. package/src/zKcContextLike.ts +2 -1
  115. package/ui-shared/alerts/AlertPanel.d.ts +6 -0
  116. package/ui-shared/alerts/AlertPanel.js +6 -0
  117. package/ui-shared/alerts/AlertPanel.js.map +1 -0
  118. package/ui-shared/alerts/Alerts.d.ts +2 -3
  119. package/ui-shared/alerts/Alerts.js +32 -22
  120. package/ui-shared/alerts/Alerts.js.map +1 -1
  121. package/ui-shared/context/environment.js +1 -1
  122. package/ui-shared/context/environment.js.map +1 -1
  123. package/ui-shared/controls/KeycloakSpinner.d.ts +1 -0
  124. package/ui-shared/controls/KeycloakSpinner.js +8 -0
  125. package/ui-shared/controls/KeycloakSpinner.js.map +1 -0
  126. package/ui-shared/controls/OrganizationTable.d.ts +16 -0
  127. package/ui-shared/controls/OrganizationTable.js +45 -0
  128. package/ui-shared/controls/OrganizationTable.js.map +1 -0
  129. package/ui-shared/controls/select-control/SingleSelectControl.js +3 -1
  130. package/ui-shared/controls/select-control/SingleSelectControl.js.map +1 -1
  131. package/ui-shared/controls/select-control/TypeaheadSelectControl.js +5 -3
  132. package/ui-shared/controls/select-control/TypeaheadSelectControl.js.map +1 -1
  133. package/ui-shared/controls/table/KeycloakDataTable.d.ts +64 -0
  134. package/ui-shared/controls/table/KeycloakDataTable.js +279 -0
  135. package/ui-shared/controls/table/KeycloakDataTable.js.map +1 -0
  136. package/ui-shared/controls/table/ListEmptyState.d.ts +20 -0
  137. package/ui-shared/controls/table/ListEmptyState.js +11 -0
  138. package/ui-shared/controls/table/ListEmptyState.js.map +1 -0
  139. package/ui-shared/controls/table/PaginatingTableToolbar.d.ts +21 -0
  140. package/ui-shared/controls/table/PaginatingTableToolbar.js +27 -0
  141. package/ui-shared/controls/table/PaginatingTableToolbar.js.map +1 -0
  142. package/ui-shared/controls/table/TableToolbar.d.ts +12 -0
  143. package/ui-shared/controls/table/TableToolbar.js +30 -0
  144. package/ui-shared/controls/table/TableToolbar.js.map +1 -0
  145. package/ui-shared/main.d.ts +15 -1
  146. package/ui-shared/main.js +13 -1
  147. package/ui-shared/main.js.map +1 -1
  148. package/ui-shared/masthead/Masthead.d.ts +4 -7
  149. package/ui-shared/masthead/Masthead.js +14 -14
  150. package/ui-shared/masthead/Masthead.js.map +1 -1
  151. package/ui-shared/select/SingleSelect.d.ts +1 -1
  152. package/ui-shared/select/SingleSelect.js +2 -2
  153. package/ui-shared/select/SingleSelect.js.map +1 -1
  154. package/ui-shared/select/TypeaheadSelect.d.ts +1 -1
  155. package/ui-shared/select/TypeaheadSelect.js +2 -2
  156. package/ui-shared/select/TypeaheadSelect.js.map +1 -1
  157. package/ui-shared/user-profile/LocaleSelector.js +1 -1
  158. package/ui-shared/user-profile/LocaleSelector.js.map +1 -1
  159. package/ui-shared/user-profile/UserProfileFields.d.ts +2 -4
  160. package/ui-shared/user-profile/UserProfileFields.js +0 -18
  161. package/ui-shared/user-profile/UserProfileFields.js.map +1 -1
  162. package/ui-shared/user-profile/UserProfileGroup.js.map +1 -1
  163. package/ui-shared/user-profile/utils.js +2 -2
  164. package/ui-shared/user-profile/utils.js.map +1 -1
  165. package/ui-shared/utils/ErrorBoundary.d.ts +26 -0
  166. package/ui-shared/utils/ErrorBoundary.js +29 -0
  167. package/ui-shared/utils/ErrorBoundary.js.map +1 -0
  168. package/ui-shared/utils/darkMode.d.ts +1 -0
  169. package/ui-shared/utils/darkMode.js +16 -0
  170. package/ui-shared/utils/darkMode.js.map +1 -0
  171. package/ui-shared/utils/errors.d.ts +4 -0
  172. package/ui-shared/utils/errors.js +42 -0
  173. package/ui-shared/utils/errors.js.map +1 -0
  174. package/ui-shared/utils/generateId.d.ts +1 -0
  175. package/ui-shared/utils/generateId.js +2 -0
  176. package/ui-shared/utils/generateId.js.map +1 -0
  177. package/ui-shared/utils/useFetch.d.ts +17 -0
  178. package/ui-shared/utils/useFetch.js +38 -0
  179. package/ui-shared/utils/useFetch.js.map +1 -0
  180. package/ui-shared/utils/useSetTimeout.d.ts +1 -0
  181. package/ui-shared/utils/useSetTimeout.js +32 -0
  182. package/ui-shared/utils/useSetTimeout.js.map +1 -0
  183. package/utils/useAccountAlerts.d.ts +4 -0
  184. package/utils/useAccountAlerts.js +19 -0
  185. package/utils/useAccountAlerts.js.map +1 -0
  186. package/utils/usePromise.js +7 -3
  187. package/utils/usePromise.js.map +1 -1
  188. package/zKcContextLike.js +2 -1
  189. package/zKcContextLike.js.map +1 -1
  190. package/src/utils/isRecord.ts +0 -2
  191. package/utils/isRecord.d.ts +0 -1
  192. package/utils/isRecord.js +0 -2
  193. package/utils/isRecord.js.map +0 -1
@@ -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 "@keycloakify/keycloak-account-ui/ui-shared/controls/table/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
+ };
@@ -1,4 +1,10 @@
1
- export { AlertProvider, useAlerts } from "@keycloakify/keycloak-account-ui/ui-shared/alerts/Alerts";
1
+ export {
2
+ AlertProvider,
3
+ useAlerts,
4
+ type AddAlertFunction,
5
+ type AddErrorFunction,
6
+ type AlertProps,
7
+ } from "@keycloakify/keycloak-account-ui/ui-shared/alerts/Alerts";
2
8
  export { ErrorPage } from "@keycloakify/keycloak-account-ui/ui-shared/context/ErrorPage";
3
9
  export { Help, useHelp } from "@keycloakify/keycloak-account-ui/ui-shared/context/HelpContext";
4
10
  export {
@@ -54,9 +60,37 @@ export {
54
60
  } from "@keycloakify/keycloak-account-ui/ui-shared/user-profile/utils";
55
61
  export type { UserFormFields } from "@keycloakify/keycloak-account-ui/ui-shared/user-profile/utils";
56
62
  export { createNamedContext } from "@keycloakify/keycloak-account-ui/ui-shared/utils/createNamedContext";
63
+ export {
64
+ getErrorDescription,
65
+ getErrorMessage,
66
+ getNetworkErrorMessage,
67
+ getNetworkErrorDescription,
68
+ } from "@keycloakify/keycloak-account-ui/ui-shared/utils/errors";
57
69
  export { isDefined } from "@keycloakify/keycloak-account-ui/ui-shared/utils/isDefined";
58
70
  export { useRequiredContext } from "@keycloakify/keycloak-account-ui/ui-shared/utils/useRequiredContext";
59
71
  export { useStoredState } from "@keycloakify/keycloak-account-ui/ui-shared/utils/useStoredState";
72
+ export { useSetTimeout } from "@keycloakify/keycloak-account-ui/ui-shared/utils/useSetTimeout";
73
+ export { generateId } from "@keycloakify/keycloak-account-ui/ui-shared/utils/generateId";
60
74
  export { default as KeycloakMasthead } from "@keycloakify/keycloak-account-ui/ui-shared/masthead/Masthead";
61
75
  export { KeycloakSelect } from "@keycloakify/keycloak-account-ui/ui-shared/select/KeycloakSelect";
62
76
  export type { Variant, KeycloakSelectProps } from "@keycloakify/keycloak-account-ui/ui-shared/select/KeycloakSelect";
77
+ export { KeycloakDataTable } from "@keycloakify/keycloak-account-ui/ui-shared/controls/table/KeycloakDataTable";
78
+ export type {
79
+ Action,
80
+ Field,
81
+ DetailField,
82
+ LoaderFunction,
83
+ } from "@keycloakify/keycloak-account-ui/ui-shared/controls/table/KeycloakDataTable";
84
+ export { PaginatingTableToolbar } from "@keycloakify/keycloak-account-ui/ui-shared/controls/table/PaginatingTableToolbar";
85
+ export { TableToolbar } from "@keycloakify/keycloak-account-ui/ui-shared/controls/table/TableToolbar";
86
+ export { ListEmptyState } from "@keycloakify/keycloak-account-ui/ui-shared/controls/table/ListEmptyState";
87
+ export { KeycloakSpinner } from "@keycloakify/keycloak-account-ui/ui-shared/controls/KeycloakSpinner";
88
+ export { useFetch } from "@keycloakify/keycloak-account-ui/ui-shared/utils/useFetch";
89
+ export {
90
+ useErrorBoundary,
91
+ ErrorBoundaryFallback,
92
+ ErrorBoundaryProvider,
93
+ } from "@keycloakify/keycloak-account-ui/ui-shared/utils/ErrorBoundary";
94
+ export type { FallbackProps } from "@keycloakify/keycloak-account-ui/ui-shared/utils/ErrorBoundary";
95
+ export { OrganizationTable } from "@keycloakify/keycloak-account-ui/ui-shared/controls/OrganizationTable";
96
+ export { initializeDarkMode } from "@keycloakify/keycloak-account-ui/ui-shared/utils/darkMode";
@@ -1,17 +1,19 @@
1
1
  import {
2
2
  Avatar,
3
3
  AvatarProps,
4
- Brand,
5
- BrandProps,
6
4
  DropdownItem,
5
+ Masthead,
6
+ MastheadBrand,
7
+ MastheadBrandProps,
8
+ MastheadContent,
9
+ MastheadMainProps,
10
+ MastheadToggle,
11
+ PageToggleButton,
12
+ Toolbar,
13
+ ToolbarContent,
14
+ ToolbarItem,
7
15
  } from "@patternfly/react-core";
8
- import {
9
- PageHeader,
10
- PageHeaderProps,
11
- PageHeaderTools,
12
- PageHeaderToolsGroup,
13
- PageHeaderToolsItem,
14
- } from "@patternfly/react-core/deprecated";
16
+ import { BarsIcon } from "@patternfly/react-icons";
15
17
  import { TFunction } from "i18next";
16
18
  import Keycloak, { type KeycloakTokenParsed } from "keycloak-js";
17
19
  import { ReactNode } from "react";
@@ -38,11 +40,9 @@ function loggedInUserName(
38
40
  return givenName || familyName || preferredUsername || t("unknownUser");
39
41
  }
40
42
 
41
- type BrandLogo = BrandProps & {
42
- href: string;
43
- };
43
+ type BrandLogo = MastheadBrandProps;
44
44
 
45
- type KeycloakMastheadProps = PageHeaderProps & {
45
+ type KeycloakMastheadProps = MastheadMainProps & {
46
46
  keycloak: Keycloak;
47
47
  brand: BrandLogo;
48
48
  avatar?: AvatarProps;
@@ -58,7 +58,7 @@ type KeycloakMastheadProps = PageHeaderProps & {
58
58
 
59
59
  const KeycloakMasthead = ({
60
60
  keycloak,
61
- brand: { href: brandHref, ...brandProps },
61
+ brand: { src, alt, className, ...brandProps },
62
62
  avatar,
63
63
  features: {
64
64
  hasLogout = true,
@@ -92,33 +92,28 @@ const KeycloakMasthead = ({
92
92
 
93
93
  const picture = keycloak.idTokenParsed?.picture;
94
94
  return (
95
- <PageHeader
96
- {...rest}
97
- logo={<Brand {...brandProps} />}
98
- logoProps={{ href: brandHref }}
99
- headerTools={
100
- <PageHeaderTools>
101
- <PageHeaderToolsGroup>
102
- <PageHeaderToolsItem
103
- visibility={{
104
- md: "hidden",
105
- }}
106
- >
107
- <KeycloakDropdown
108
- data-testid="options-kebab"
109
- isKebab
110
- dropDownItems={[
111
- ...(kebabDropdownItems || dropdownItems),
112
- extraItems,
113
- ]}
114
- />
115
- </PageHeaderToolsItem>
116
- <PageHeaderToolsItem>{toolbarItems}</PageHeaderToolsItem>
117
- <PageHeaderToolsItem
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
118
113
  visibility={{
119
114
  default: "hidden",
120
115
  md: "visible",
121
- }}
116
+ }} /** this user dropdown is hidden on mobile sizes */
122
117
  >
123
118
  <KeycloakDropdown
124
119
  data-testid="options"
@@ -129,16 +124,37 @@ const KeycloakMasthead = ({
129
124
  : undefined
130
125
  }
131
126
  />
132
- </PageHeaderToolsItem>
133
- </PageHeaderToolsGroup>
134
- {picture || avatar?.src ? (
135
- <Avatar {...{ src: picture, alt: t("avatar"), ...avatar }} />
136
- ) : (
137
- <DefaultAvatar {...avatar} />
138
- )}
139
- </PageHeaderTools>
140
- }
141
- />
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>
142
158
  );
143
159
  };
144
160
 
@@ -21,6 +21,7 @@ export const SingleSelect = ({
21
21
  maxHeight,
22
22
  toggleIcon,
23
23
  className,
24
+ isDisabled,
24
25
  children,
25
26
  ...props
26
27
  }: SingleSelectProps) => {
@@ -69,6 +70,7 @@ export const SingleSelect = ({
69
70
  isExpanded={isOpen}
70
71
  aria-label={props["aria-label"]}
71
72
  icon={toggleIcon}
73
+ isDisabled={isDisabled}
72
74
  isFullWidth
73
75
  >
74
76
  {childArray.find((c) => c.props.value === selections)?.props
@@ -37,6 +37,7 @@ export const TypeaheadSelect = ({
37
37
  chipGroupComponent,
38
38
  chipGroupProps,
39
39
  footer,
40
+ isDisabled,
40
41
  children,
41
42
  ...rest
42
43
  }: KeycloakSelectProps) => {
@@ -124,6 +125,7 @@ export const TypeaheadSelect = ({
124
125
  variant="typeahead"
125
126
  onClick={() => onToggle?.(true)}
126
127
  icon={toggleIcon}
128
+ isDisabled={isDisabled}
127
129
  isExpanded={rest.isOpen}
128
130
  isFullWidth
129
131
  status={validated === "error" ? MenuToggleStatus.danger : undefined}
@@ -6,7 +6,7 @@ import { UserProfileFieldProps } from "@keycloakify/keycloak-account-ui/ui-share
6
6
  const localeToDisplayName = (locale: string) => {
7
7
  try {
8
8
  return new Intl.DisplayNames([locale], { type: "language" }).of(locale);
9
- } catch (error) {
9
+ } catch {
10
10
  return locale;
11
11
  }
12
12
  };
@@ -5,7 +5,7 @@ import {
5
5
  } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
6
6
  import { Text } from "@patternfly/react-core";
7
7
  import { TFunction } from "i18next";
8
- import { ReactNode, useMemo } from "react";
8
+ import { ReactNode, useMemo, type JSX } from "react";
9
9
  import { FieldPath, UseFormReturn } from "react-hook-form";
10
10
 
11
11
  import { ScrollForm } from "@keycloakify/keycloak-account-ui/ui-shared/main";
@@ -25,26 +25,23 @@ export type Options = {
25
25
  options?: string[];
26
26
  };
27
27
 
28
- const INPUT_TYPES = [
29
- "text",
30
- "textarea",
31
- "select",
32
- "select-radiobuttons",
33
- "multiselect",
34
- "multiselect-checkboxes",
35
- "html5-email",
36
- "html5-tel",
37
- "html5-url",
38
- "html5-number",
39
- "html5-range",
40
- "html5-datetime-local",
41
- "html5-date",
42
- "html5-month",
43
- "html5-time",
44
- "multi-input",
45
- ] as const;
46
-
47
- export type InputType = (typeof INPUT_TYPES)[number];
28
+ export type InputType =
29
+ | "text"
30
+ | "textarea"
31
+ | "select"
32
+ | "select-radiobuttons"
33
+ | "multiselect"
34
+ | "multiselect-checkboxes"
35
+ | "html5-email"
36
+ | "html5-tel"
37
+ | "html5-url"
38
+ | "html5-number"
39
+ | "html5-range"
40
+ | "html5-datetime-local"
41
+ | "html5-date"
42
+ | "html5-month"
43
+ | "html5-time"
44
+ | "multi-input";
48
45
 
49
46
  export type UserProfileFieldProps = {
50
47
  t: TFunction;
@@ -3,7 +3,7 @@ import { FormGroup, InputGroup } from "@patternfly/react-core";
3
3
  import { TFunction } from "i18next";
4
4
  import { get } from "lodash-es";
5
5
  import { PropsWithChildren, ReactNode } from "react";
6
- import { UseFormReturn } from "react-hook-form";
6
+ import { UseFormReturn, type FieldError } from "react-hook-form";
7
7
 
8
8
  import { FormErrorText } from "@keycloakify/keycloak-account-ui/ui-shared/controls/FormErrorText";
9
9
  import { HelpItem } from "@keycloakify/keycloak-account-ui/ui-shared/controls/HelpItem";
@@ -38,7 +38,8 @@ export const UserProfileGroup = ({
38
38
  } = form;
39
39
 
40
40
  const component = renderer?.(attribute);
41
- const error = get(errors, fieldName(attribute.name));
41
+ const error = get(errors, fieldName(attribute.name)) as FieldError;
42
+
42
43
  return (
43
44
  <FormGroup
44
45
  key={attribute.name}
@@ -50,7 +50,10 @@ export const isRootAttribute = (attr?: string) =>
50
50
  attr && ROOT_ATTRIBUTES.includes(attr);
51
51
 
52
52
  export const fieldName = (name?: string) =>
53
- `${isRootAttribute(name) ? "" : "attributes."}${name?.replaceAll(".", "🍺")}` as FieldPath<UserFormFields>;
53
+ `${isRootAttribute(name) ? "" : "attributes."}${name?.replaceAll(
54
+ ".",
55
+ "🍺",
56
+ )}` as FieldPath<UserFormFields>;
54
57
 
55
58
  export const beerify = <T extends string>(name: T) =>
56
59
  name.replaceAll(".", "🍺");
@@ -70,13 +73,16 @@ export function setUserProfileServerError<T>(
70
73
  ).forEach((e) => {
71
74
  const params = Object.assign(
72
75
  {},
73
- e.params?.map((p) => t(isBundleKey(p.toString()) ? unWrap(p) : p)),
76
+ e.params?.map((p) => (isBundleKey(p.toString()) ? t(unWrap(p)) : p)),
74
77
  );
75
78
  setError(fieldName(e.field) as keyof T, {
76
- message: t(e.errorMessage, {
77
- ...params,
78
- defaultValue: e.errorMessage || e.field,
79
- }),
79
+ message: t(
80
+ isBundleKey(e.errorMessage) ? unWrap(e.errorMessage) : e.errorMessage,
81
+ {
82
+ ...params,
83
+ defaultValue: e.errorMessage || e.field,
84
+ },
85
+ ),
80
86
  type: "server",
81
87
  });
82
88
  });
@@ -0,0 +1,77 @@
1
+ import {
2
+ Component,
3
+ type ComponentType,
4
+ type FunctionComponent,
5
+ type GetDerivedStateFromError,
6
+ type ReactNode,
7
+ } from "react";
8
+ import { createNamedContext } from "@keycloakify/keycloak-account-ui/ui-shared/utils/createNamedContext";
9
+ import { useRequiredContext } from "@keycloakify/keycloak-account-ui/ui-shared/utils/useRequiredContext";
10
+
11
+ export interface ErrorBoundaryContextValue {
12
+ error?: Error;
13
+ showBoundary: (error: Error) => void;
14
+ }
15
+
16
+ const ErrorBoundaryContext = createNamedContext<
17
+ ErrorBoundaryContextValue | undefined
18
+ >("ErrorBoundaryContext", undefined);
19
+
20
+ export const useErrorBoundary = () => useRequiredContext(ErrorBoundaryContext);
21
+
22
+ export interface ErrorBoundaryProviderProps {
23
+ children: ReactNode;
24
+ }
25
+
26
+ export interface ErrorBoundaryProviderState {
27
+ error?: Error;
28
+ }
29
+
30
+ export class ErrorBoundaryProvider extends Component<
31
+ ErrorBoundaryProviderProps,
32
+ ErrorBoundaryProviderState
33
+ > {
34
+ state: ErrorBoundaryProviderState = {};
35
+
36
+ static getDerivedStateFromError: GetDerivedStateFromError<
37
+ ErrorBoundaryProviderProps,
38
+ ErrorBoundaryProviderState
39
+ > = (error) => {
40
+ return { error };
41
+ };
42
+
43
+ showBoundary = (error: Error) => {
44
+ this.setState({ error });
45
+ };
46
+
47
+ render() {
48
+ return (
49
+ <ErrorBoundaryContext.Provider
50
+ value={{ error: this.state.error, showBoundary: this.showBoundary }}
51
+ >
52
+ {this.props.children}
53
+ </ErrorBoundaryContext.Provider>
54
+ );
55
+ }
56
+ }
57
+
58
+ export interface FallbackProps {
59
+ error: Error;
60
+ }
61
+
62
+ export interface ErrorBoundaryFallbackProps {
63
+ fallback: ComponentType<FallbackProps>;
64
+ children: ReactNode;
65
+ }
66
+
67
+ export const ErrorBoundaryFallback: FunctionComponent<
68
+ ErrorBoundaryFallbackProps
69
+ > = ({ children, fallback: FallbackComponent }) => {
70
+ const { error } = useErrorBoundary();
71
+
72
+ if (error) {
73
+ return <FallbackComponent error={error} />;
74
+ }
75
+
76
+ return children;
77
+ };
@@ -0,0 +1,19 @@
1
+ const DARK_MODE_CLASS = "pf-v5-theme-dark";
2
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
3
+
4
+ function updateDarkMode(isEnabled: boolean) {
5
+ const { classList } = document.documentElement;
6
+
7
+ if (isEnabled) {
8
+ classList.add(DARK_MODE_CLASS);
9
+ } else {
10
+ classList.remove(DARK_MODE_CLASS);
11
+ }
12
+ }
13
+
14
+ export function initializeDarkMode() {
15
+ updateDarkMode(mediaQuery.matches);
16
+ mediaQuery.addEventListener("change", (event) =>
17
+ updateDarkMode(event.matches),
18
+ );
19
+ }