diff --git a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties index afa7529ccc2e..c56703517a86 100644 --- a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties +++ b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties @@ -3330,4 +3330,6 @@ organizationsMembersListError=Could not fetch organization members\: {{error}} MANAGED=Managed UNMANAGED=Unmanaged deleteConfirmUsers_one=Delete user {{name}}? -deleteConfirmUsers_other=Delete {{count}} users? \ No newline at end of file +deleteConfirmUsers_other=Delete {{count}} users? +downloadThemeJar=Download theme JAR +themeColorInfo=Here you can set the patternfly color variables and create a "theme jar" file that you can download and put in your providers folder to apply the theme to your realm. \ No newline at end of file diff --git a/js/apps/admin-ui/src/PageHeader.tsx b/js/apps/admin-ui/src/PageHeader.tsx index faeb718a0340..b3ee4d3521c0 100644 --- a/js/apps/admin-ui/src/PageHeader.tsx +++ b/js/apps/admin-ui/src/PageHeader.tsx @@ -25,6 +25,7 @@ import { useAccess } from "./context/access/Access"; import { useRealm } from "./context/realm-context/RealmContext"; import { useWhoAmI } from "./context/whoami/WhoAmI"; import { toDashboard } from "./dashboard/routes/Dashboard"; +import { usePreviewLogo } from "./realm-settings/themes/LogoContext"; import { joinPath } from "./utils/joinPath"; import useToggle from "./utils/useToggle"; @@ -184,12 +185,11 @@ const UserDropdown = () => { export const Header = () => { const { environment, keycloak } = useEnvironment(); const { t } = useTranslation(); - const { realm, realmRepresentation } = useRealm(); + const { realm } = useRealm(); + const contextLogo = usePreviewLogo(); + const customLogo = contextLogo?.logo; const picture = keycloak.tokenParsed?.picture; - const customLogo = JSON.parse( - realmRepresentation?.attributes?.style || "{}", - )?.logo; const logo = customLogo || environment.logo || "/logo.svg"; const url = useHref(toDashboard({ realm })); const logoUrl = environment.logoUrl ? environment.logoUrl : url; diff --git a/js/apps/admin-ui/src/realm-settings/themes/ImageUpload.tsx b/js/apps/admin-ui/src/realm-settings/themes/ImageUpload.tsx index 4eb0ec3eaf54..8ae72908abf1 100644 --- a/js/apps/admin-ui/src/realm-settings/themes/ImageUpload.tsx +++ b/js/apps/admin-ui/src/realm-settings/themes/ImageUpload.tsx @@ -5,9 +5,10 @@ import { Controller, useFormContext } from "react-hook-form"; type ImageUploadProps = { name: string; + onChange?: (file: string) => void; }; -export const ImageUpload = ({ name }: ImageUploadProps) => { +export const ImageUpload = ({ name, onChange }: ImageUploadProps) => { const [dataUri, setDataUri] = useState(""); const [file, setFile] = useState(); const [isLoading, setIsLoading] = useState(false); @@ -26,6 +27,7 @@ export const ImageUpload = ({ name }: ImageUploadProps) => { if (file) { fileToDataUri(file).then((dataUri) => { setDataUri(dataUri); + onChange?.(dataUri); }); } diff --git a/js/apps/admin-ui/src/realm-settings/themes/LogoContext.tsx b/js/apps/admin-ui/src/realm-settings/themes/LogoContext.tsx new file mode 100644 index 000000000000..93bc7946b3b5 --- /dev/null +++ b/js/apps/admin-ui/src/realm-settings/themes/LogoContext.tsx @@ -0,0 +1,23 @@ +import { createNamedContext } from "@keycloak/keycloak-ui-shared"; +import { PropsWithChildren, useContext, useState } from "react"; + +type LogoContextProps = { + logo: string; + setLogo: (logo: string) => void; +}; + +export const LogoPreviewContext = createNamedContext< + LogoContextProps | undefined +>("LogoContext", undefined); + +export const usePreviewLogo = () => useContext(LogoPreviewContext); + +export const LogoContext = ({ children }: PropsWithChildren) => { + const [logo, setLogo] = useState(""); + + return ( + + {children} + + ); +}; diff --git a/js/apps/admin-ui/src/realm-settings/themes/ThemeColors.tsx b/js/apps/admin-ui/src/realm-settings/themes/ThemeColors.tsx index b8ed4ab01931..6cdec6ff1f18 100644 --- a/js/apps/admin-ui/src/realm-settings/themes/ThemeColors.tsx +++ b/js/apps/admin-ui/src/realm-settings/themes/ThemeColors.tsx @@ -9,6 +9,8 @@ import { InputGroup, InputGroupItem, PageSection, + Text, + TextContent, TextInputProps, } from "@patternfly/react-core"; import { useEffect, useMemo } from "react"; @@ -22,6 +24,7 @@ import { useTranslation } from "react-i18next"; import { FixedButtonsGroup } from "../../components/form/FixedButtonGroup"; import { FormAccess } from "../../components/form/FormAccess"; import { ImageUpload } from "./ImageUpload"; +import { usePreviewLogo } from "./LogoContext"; import { darkTheme, lightTheme } from "./PatternflyVars"; import { PreviewWindow } from "./PreviewWindow"; import { ThemeRealmRepresentation } from "./ThemesTab"; @@ -77,6 +80,7 @@ export const ThemeColors = ({ realm, save, theme }: ThemeColorsProps) => { const form = useForm(); const { handleSubmit, watch } = form; const style = watch(); + const contextLogo = usePreviewLogo(); const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); const mapping = useMemo( @@ -131,6 +135,9 @@ export const ThemeColors = ({ realm, save, theme }: ThemeColorsProps) => { return ( + + {t("themeColorInfo")} + {mediaQuery.matches && theme === "light" && ( )} @@ -139,7 +146,10 @@ export const ThemeColors = ({ realm, save, theme }: ThemeColorsProps) => { - + contextLogo?.setLogo(logo)} + /> @@ -161,6 +171,7 @@ export const ThemeColors = ({ realm, save, theme }: ThemeColorsProps) => { diff --git a/js/apps/admin-ui/src/realm-settings/themes/ThemesTab.tsx b/js/apps/admin-ui/src/realm-settings/themes/ThemesTab.tsx index 964c754d7ad6..5c56728539be 100644 --- a/js/apps/admin-ui/src/realm-settings/themes/ThemesTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/themes/ThemesTab.tsx @@ -1,4 +1,5 @@ import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; +import { useEnvironment } from "@keycloak/keycloak-ui-shared"; import { Tab, TabTitleText } from "@patternfly/react-core"; import JSZip from "jszip"; import { useTranslation } from "react-i18next"; @@ -7,12 +8,12 @@ import { useRoutableTab, } from "../../components/routable-tabs/RoutableTabs"; import { useRealm } from "../../context/realm-context/RealmContext"; +import { joinPath } from "../../utils/joinPath"; import useIsFeatureEnabled, { Feature } from "../../utils/useIsFeatureEnabled"; import { ThemesTabType, toThemesTab } from "../routes/ThemesTab"; +import { LogoContext } from "./LogoContext"; import { ThemeColors } from "./ThemeColors"; import { ThemeSettingsTab } from "./ThemeSettings"; -import { joinPath } from "../../utils/joinPath"; -import { useEnvironment } from "@keycloak/keycloak-ui-shared"; type ThemesTabProps = { realm: RealmRepresentation; @@ -55,6 +56,7 @@ export default function ThemesTab({ realm, save }: ThemesTabProps) { parent=keycloak.v2 import=common/quick-theme +logo=${logoName} styles=css/theme-styles.css `, ); @@ -171,7 +173,9 @@ styles=css/login.css css/theme-styles.css data-testid="lightColors-tab" {...lightColorsTab} > - + + + - + + + );