Moved masthead to ui-shared (#28871)
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
ddacfbdefd
commit
6a020d93f1
19 changed files with 48 additions and 326 deletions
|
@ -10,9 +10,7 @@ This directory contains the UIs and related libraries of the Keycloak project wr
|
|||
│ └── keycloak-server # Keycloak server for local development of UIs
|
||||
├── libs
|
||||
│ ├── keycloak-admin-client # Keycloak Admin Client library for Keycloak REST API
|
||||
│ ├── keycloak-js # Keycloak JS library for securing HTML5/JavaScript applications
|
||||
│ └── keycloak-masthead # Keycloak Masthead library for an easy way to bring applications into the Keycloak ecosystem, allow users to access
|
||||
│ # and manage security for those applications and manage authorization of resources
|
||||
│ └── keycloak-js # Keycloak JS library for securing HTML5/JavaScript applications
|
||||
├── ...
|
||||
|
||||
## Data processing
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
"i18next": "^23.11.2",
|
||||
"i18next-http-backend": "^2.5.0",
|
||||
"keycloak-js": "workspace:*",
|
||||
"keycloak-masthead": "workspace:*",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
@ -50,7 +49,6 @@
|
|||
"command": "vite --host",
|
||||
"dependencies": [
|
||||
"../../libs/ui-shared:build",
|
||||
"../../libs/keycloak-masthead:build",
|
||||
"../../libs/keycloak-js:build",
|
||||
"../../libs/keycloak-admin-client:build"
|
||||
]
|
||||
|
@ -59,7 +57,6 @@
|
|||
"command": "vite preview",
|
||||
"dependencies": [
|
||||
"../../libs/ui-shared:build",
|
||||
"../../libs/keycloak-masthead:build",
|
||||
"../../libs/keycloak-js:build",
|
||||
"../../libs/keycloak-admin-client:build"
|
||||
]
|
||||
|
@ -68,7 +65,6 @@
|
|||
"command": "vite build",
|
||||
"dependencies": [
|
||||
"../../libs/ui-shared:build",
|
||||
"../../libs/keycloak-masthead:build",
|
||||
"../../libs/keycloak-js:build",
|
||||
"../../libs/keycloak-admin-client:build"
|
||||
]
|
||||
|
@ -77,7 +73,6 @@
|
|||
"command": "eslint .",
|
||||
"dependencies": [
|
||||
"../../libs/ui-shared:build",
|
||||
"../../libs/keycloak-masthead:build",
|
||||
"../../libs/keycloak-js:build",
|
||||
"../../libs/keycloak-admin-client:build"
|
||||
]
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
import { Button } from "@patternfly/react-core";
|
||||
import { ExternalLinkSquareAltIcon } from "@patternfly/react-icons";
|
||||
import {
|
||||
KeycloakMasthead,
|
||||
KeycloakProvider,
|
||||
Translations,
|
||||
TranslationsProvider,
|
||||
} from "keycloak-masthead";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useHref } from "react-router-dom";
|
||||
import { label } from "ui-shared";
|
||||
import { KeycloakMasthead, label } from "ui-shared";
|
||||
|
||||
import { environment } from "../environment";
|
||||
import { joinPath } from "../utils/joinPath";
|
||||
|
@ -47,32 +40,19 @@ export const Header = () => {
|
|||
|
||||
// User can indicate that he wants an internal URL by starting it with "/"
|
||||
const indexHref = logoUrl.startsWith("/") ? internalLogoHref : logoUrl;
|
||||
const translations = useMemo<Translations>(
|
||||
() => ({
|
||||
avatar: t("avatar"),
|
||||
fullName: t("fullName"),
|
||||
manageAccount: t("manageAccount"),
|
||||
signOut: t("signOut"),
|
||||
unknownUser: t("unknownUser"),
|
||||
}),
|
||||
[t],
|
||||
);
|
||||
|
||||
return (
|
||||
<TranslationsProvider translations={translations}>
|
||||
<KeycloakProvider keycloak={keycloak}>
|
||||
<KeycloakMasthead
|
||||
features={{ hasManageAccount: false }}
|
||||
showNavToggle
|
||||
brand={{
|
||||
href: indexHref,
|
||||
src: joinPath(environment.resourceUrl, brandImage),
|
||||
alt: t("logo"),
|
||||
className: style.brand,
|
||||
}}
|
||||
toolbarItems={[<ReferrerLink key="link" />]}
|
||||
/>
|
||||
</KeycloakProvider>
|
||||
</TranslationsProvider>
|
||||
<KeycloakMasthead
|
||||
keycloak={keycloak}
|
||||
features={{ hasManageAccount: false }}
|
||||
showNavToggle
|
||||
brand={{
|
||||
href: indexHref,
|
||||
src: joinPath(environment.resourceUrl, brandImage),
|
||||
alt: t("logo"),
|
||||
className: style.brand,
|
||||
}}
|
||||
toolbarItems={[<ReferrerLink key="link" />]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
{
|
||||
"name": "keycloak-masthead",
|
||||
"type": "module",
|
||||
"main": "./dist/keycloak-masthead.js",
|
||||
"types": "./dist/keycloak-masthead.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/keycloak-masthead.js",
|
||||
"types": "./dist/keycloak-masthead.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "wireit",
|
||||
"lint": "wireit"
|
||||
},
|
||||
"wireit": {
|
||||
"build": {
|
||||
"command": "vite build",
|
||||
"dependencies": [
|
||||
"../../libs/keycloak-js:build"
|
||||
],
|
||||
"files": [
|
||||
"src/**",
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
"vite.config.ts"
|
||||
],
|
||||
"output": [
|
||||
"dist/**"
|
||||
]
|
||||
},
|
||||
"lint": {
|
||||
"command": "eslint .",
|
||||
"dependencies": [
|
||||
"../../libs/keycloak-js:build"
|
||||
]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@patternfly/react-core": "^5.2.3",
|
||||
"@patternfly/react-styles": "^5.2.1",
|
||||
"keycloak-js": "workspace:*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.79",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"@vitejs/plugin-react-swc": "^3.6.0",
|
||||
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||
"vite": "^5.2.9",
|
||||
"vite-plugin-checker": "^0.6.4",
|
||||
"vite-plugin-dts": "^3.8.3"
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
import Keycloak, { type KeycloakTokenParsed } from "keycloak-js";
|
||||
import {
|
||||
createContext,
|
||||
PropsWithChildren,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
type KeycloakProps = {
|
||||
keycloak: Keycloak;
|
||||
token?: KeycloakTokenParsed;
|
||||
updateToken: () => void;
|
||||
};
|
||||
|
||||
const KeycloakContext = createContext<KeycloakProps | undefined>(undefined);
|
||||
|
||||
export type KeycloakProviderProps = {
|
||||
keycloak: Keycloak;
|
||||
};
|
||||
|
||||
export const useKeycloak = () => useContext(KeycloakContext);
|
||||
|
||||
export const KeycloakProvider = ({
|
||||
keycloak,
|
||||
children,
|
||||
}: PropsWithChildren<KeycloakProviderProps>) => {
|
||||
const [token, setToken] = useState(keycloak.tokenParsed);
|
||||
const context = useMemo(
|
||||
() => ({
|
||||
keycloak,
|
||||
token,
|
||||
updateToken: async () => {
|
||||
await keycloak.updateToken(-1);
|
||||
setToken(keycloak.tokenParsed);
|
||||
},
|
||||
}),
|
||||
[keycloak, token],
|
||||
);
|
||||
return (
|
||||
<KeycloakContext.Provider value={context}>
|
||||
{children}
|
||||
</KeycloakContext.Provider>
|
||||
);
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
export { default as KeycloakMasthead } from "./Masthead";
|
||||
|
||||
// Keycloak
|
||||
export { KeycloakProvider, useKeycloak } from "./KeycloakContext";
|
||||
export type { KeycloakProviderProps } from "./KeycloakContext";
|
||||
|
||||
// Translation
|
||||
export { defaultTranslations } from "./translation/translations";
|
||||
export type { Translations } from "./translation/translations";
|
||||
export { TranslationsProvider } from "./translation/TranslationsContext";
|
||||
export type { TranslationsProviderProps } from "./translation/TranslationsContext";
|
4
js/libs/keycloak-masthead/src/svg.d.ts
vendored
4
js/libs/keycloak-masthead/src/svg.d.ts
vendored
|
@ -1,4 +0,0 @@
|
|||
declare module "*.svg" {
|
||||
const content: React.FC<React.SVGProps<SVGElement>>;
|
||||
export default content;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
import { createContext, PropsWithChildren, useContext } from "react";
|
||||
|
||||
import type { Translations } from "./translations";
|
||||
import { defaultTranslations } from "./translations";
|
||||
|
||||
const TranslationsContext = createContext<Translations>(defaultTranslations);
|
||||
|
||||
export const useTranslations = () => useContext(TranslationsContext);
|
||||
|
||||
export type TranslationsProviderProps = {
|
||||
translations: Translations;
|
||||
};
|
||||
|
||||
export const TranslationsProvider = ({
|
||||
translations,
|
||||
children,
|
||||
}: PropsWithChildren<TranslationsProviderProps>) => {
|
||||
return (
|
||||
<TranslationsContext.Provider value={translations}>
|
||||
{children}
|
||||
</TranslationsContext.Provider>
|
||||
);
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
export const defaultTranslations = {
|
||||
avatar: "Avatar",
|
||||
fullName: "{{givenName}} {{familyName}}",
|
||||
manageAccount: "Manage account",
|
||||
signOut: "Sign out",
|
||||
unknownUser: "Anonymous",
|
||||
};
|
||||
|
||||
export type Translations = typeof defaultTranslations;
|
|
@ -1,30 +0,0 @@
|
|||
import { useCallback, useMemo } from "react";
|
||||
|
||||
import { Translations } from "./translations";
|
||||
import { useTranslations } from "./TranslationsContext";
|
||||
|
||||
export type TranslateFunction = (
|
||||
key: keyof Translations,
|
||||
args?: Record<string, string>,
|
||||
) => Translations[typeof key];
|
||||
|
||||
export const useTranslation = () => {
|
||||
const translations = useTranslations();
|
||||
const translate = useCallback<TranslateFunction>(
|
||||
(key, args) => {
|
||||
const translation = translations[key];
|
||||
|
||||
if (!args) {
|
||||
return translation;
|
||||
}
|
||||
|
||||
return Object.entries(args).reduce(
|
||||
(formatted, [key, value]) => formatted.replaceAll(`{{${key}}}`, value),
|
||||
translation,
|
||||
);
|
||||
},
|
||||
[translations],
|
||||
);
|
||||
|
||||
return useMemo(() => ({ t: translate }), [translate]);
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
import { type KeycloakTokenParsed } from "keycloak-js";
|
||||
|
||||
import { TranslateFunction } from "./translation/useTranslation";
|
||||
|
||||
export function loggedInUserName(
|
||||
token: KeycloakTokenParsed | undefined,
|
||||
t: TranslateFunction,
|
||||
) {
|
||||
if (!token) {
|
||||
return t("unknownUser");
|
||||
}
|
||||
|
||||
const givenName = token.given_name;
|
||||
const familyName = token.family_name;
|
||||
const preferredUsername = token.preferred_username;
|
||||
|
||||
if (givenName && familyName) {
|
||||
return t("fullName", { givenName, familyName });
|
||||
}
|
||||
|
||||
return givenName || familyName || preferredUsername || t("unknownUser");
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"include": ["src"],
|
||||
"exclude": ["**/*.test.ts", "**/*.test.tsx"]
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import react from "@vitejs/plugin-react-swc";
|
||||
import path from "node:path";
|
||||
import peerDepsExternal from "rollup-plugin-peer-deps-external";
|
||||
import { defineConfig } from "vite";
|
||||
import { checker } from "vite-plugin-checker";
|
||||
import dts from "vite-plugin-dts";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
build: {
|
||||
target: "esnext",
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, "src/main.ts"),
|
||||
formats: ["es"],
|
||||
},
|
||||
rollupOptions: {
|
||||
plugins: [
|
||||
peerDepsExternal({
|
||||
includeDependencies: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
checker({ typescript: true }),
|
||||
dts({ insertTypesEntry: true }),
|
||||
],
|
||||
});
|
|
@ -43,7 +43,9 @@
|
|||
"@keycloak/keycloak-admin-client": "workspace:*",
|
||||
"@patternfly/react-core": "^5.2.3",
|
||||
"@patternfly/react-icons": "^5.2.1",
|
||||
"@patternfly/react-styles": "^5.2.1",
|
||||
"i18next": "^23.11.2",
|
||||
"keycloak-js": "workspace:*",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
|
@ -37,3 +37,4 @@ export { createNamedContext } from "./utils/createNamedContext";
|
|||
export { isDefined } from "./utils/isDefined";
|
||||
export { useRequiredContext } from "./utils/useRequiredContext";
|
||||
export { useStoredState } from "./utils/useStoredState";
|
||||
export { default as KeycloakMasthead } from "./masthead/Masthead";
|
||||
|
|
|
@ -7,19 +7,38 @@ import {
|
|||
PageHeaderToolsGroup,
|
||||
PageHeaderToolsItem,
|
||||
} from "@patternfly/react-core/deprecated";
|
||||
import { TFunction } from "i18next";
|
||||
import Keycloak, { type KeycloakTokenParsed } from "keycloak-js";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import { KeycloakDropdown } from "./KeycloakDropdown";
|
||||
import { useTranslation } from "./translation/useTranslation";
|
||||
import { loggedInUserName } from "./util";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DefaultAvatar } from "./DefaultAvatar";
|
||||
import { useKeycloak } from "./KeycloakContext";
|
||||
import { KeycloakDropdown } from "./KeycloakDropdown";
|
||||
|
||||
function loggedInUserName(
|
||||
token: KeycloakTokenParsed | undefined,
|
||||
t: TFunction,
|
||||
) {
|
||||
if (!token) {
|
||||
return t("unknownUser");
|
||||
}
|
||||
|
||||
const givenName = token.given_name;
|
||||
const familyName = token.family_name;
|
||||
const preferredUsername = token.preferred_username;
|
||||
|
||||
if (givenName && familyName) {
|
||||
return t("fullName", { givenName, familyName });
|
||||
}
|
||||
|
||||
return givenName || familyName || preferredUsername || t("unknownUser");
|
||||
}
|
||||
|
||||
type BrandLogo = BrandProps & {
|
||||
href: string;
|
||||
};
|
||||
|
||||
type KeycloakMastheadProps = PageHeaderProps & {
|
||||
keycloak: Keycloak;
|
||||
brand: BrandLogo;
|
||||
avatar?: AvatarProps;
|
||||
features?: {
|
||||
|
@ -33,6 +52,7 @@ type KeycloakMastheadProps = PageHeaderProps & {
|
|||
};
|
||||
|
||||
const KeycloakMasthead = ({
|
||||
keycloak,
|
||||
brand: { href: brandHref, ...brandProps },
|
||||
avatar,
|
||||
features: {
|
||||
|
@ -46,7 +66,6 @@ const KeycloakMasthead = ({
|
|||
...rest
|
||||
}: KeycloakMastheadProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { keycloak } = useKeycloak()!;
|
||||
const extraItems = [];
|
||||
if (hasManageAccount) {
|
||||
extraItems.push(
|
|
@ -86,9 +86,6 @@ importers:
|
|||
keycloak-js:
|
||||
specifier: workspace:*
|
||||
version: link:../../libs/keycloak-js
|
||||
keycloak-masthead:
|
||||
specifier: workspace:*
|
||||
version: link:../../libs/keycloak-masthead
|
||||
lodash-es:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
|
@ -388,46 +385,6 @@ importers:
|
|||
specifier: ^0.3.4
|
||||
version: 0.3.4
|
||||
|
||||
js/libs/keycloak-masthead:
|
||||
dependencies:
|
||||
'@patternfly/react-core':
|
||||
specifier: ^5.2.3
|
||||
version: 5.2.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@patternfly/react-styles':
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1
|
||||
keycloak-js:
|
||||
specifier: workspace:*
|
||||
version: link:../keycloak-js
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
react-dom:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^18.2.79
|
||||
version: 18.2.79
|
||||
'@types/react-dom':
|
||||
specifier: ^18.2.25
|
||||
version: 18.2.25
|
||||
'@vitejs/plugin-react-swc':
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0(vite@5.2.9(@types/node@20.12.7)(lightningcss@1.24.1)(terser@5.30.3))
|
||||
rollup-plugin-peer-deps-external:
|
||||
specifier: ^2.2.4
|
||||
version: 2.2.4(rollup@4.14.3)
|
||||
vite:
|
||||
specifier: ^5.2.9
|
||||
version: 5.2.9(@types/node@20.12.7)(lightningcss@1.24.1)(terser@5.30.3)
|
||||
vite-plugin-checker:
|
||||
specifier: ^0.6.4
|
||||
version: 0.6.4(eslint@8.57.0)(optionator@0.9.3)(typescript@5.4.5)(vite@5.2.9(@types/node@20.12.7)(lightningcss@1.24.1)(terser@5.30.3))(vue-tsc@1.8.27(typescript@5.4.5))
|
||||
vite-plugin-dts:
|
||||
specifier: ^3.8.3
|
||||
version: 3.8.3(@types/node@20.12.7)(rollup@4.14.3)(typescript@5.4.5)(vite@5.2.9(@types/node@20.12.7)(lightningcss@1.24.1)(terser@5.30.3))
|
||||
|
||||
js/libs/ui-shared:
|
||||
dependencies:
|
||||
'@keycloak/keycloak-admin-client':
|
||||
|
@ -439,9 +396,15 @@ importers:
|
|||
'@patternfly/react-icons':
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@patternfly/react-styles':
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.1
|
||||
i18next:
|
||||
specifier: ^23.11.2
|
||||
version: 23.11.2
|
||||
keycloak-js:
|
||||
specifier: workspace:*
|
||||
version: link:../keycloak-js
|
||||
lodash-es:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
|
|
Loading…
Reference in a new issue