diff --git a/extensions/default/src/ViewerLayout/ViewerHeader.tsx b/extensions/default/src/ViewerLayout/ViewerHeader.tsx index e2c0a34e6..9f6425776 100644 --- a/extensions/default/src/ViewerLayout/ViewerHeader.tsx +++ b/extensions/default/src/ViewerLayout/ViewerHeader.tsx @@ -43,6 +43,10 @@ function ViewerHeader({ appConfig }: withAppTypes<{ appConfig: AppTypes.Config } 'ohif.aboutModal' ) as Types.MenuComponentCustomization; + const AppearanceModal = customizationService.getCustomization( + 'ohif.appearanceModal' + ) as Types.MenuComponentCustomization; + const UserPreferencesModal = customizationService.getCustomization( 'ohif.userPreferencesModal' ) as Types.MenuComponentCustomization; @@ -71,6 +75,19 @@ function ViewerHeader({ appConfig }: withAppTypes<{ appConfig: AppTypes.Config } }, ]; + if (AppearanceModal) { + menuOptions.splice(1, 0, { + title: AppearanceModal.menuTitle ?? t('Header:Appearance'), + icon: 'ColorChange', + onClick: () => + show({ + content: AppearanceModal, + title: AppearanceModal.title ?? t('AppearanceModal:Appearance'), + containerClassName: AppearanceModal.containerClassName ?? 'max-w-md', + }), + }); + } + if (appConfig.oidc) { menuOptions.push({ title: t('Header:Logout'), diff --git a/extensions/default/src/customizations/appearanceModalCustomization.tsx b/extensions/default/src/customizations/appearanceModalCustomization.tsx new file mode 100644 index 000000000..9230dcd6b --- /dev/null +++ b/extensions/default/src/customizations/appearanceModalCustomization.tsx @@ -0,0 +1,143 @@ +import React from 'react'; +import { + AppearanceModal, + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, + Button, + useActiveTheme, + themePresets, +} from '@ohif/ui-next'; +import { useTranslation } from 'react-i18next'; + +function AppearanceModalDefault() { + const { activeTheme, setActiveTheme, customCss, applyCustomTheme, clearCustomTheme } = + useActiveTheme(); + const { t } = useTranslation('AppearanceModal'); + + const [draftCss, setDraftCss] = React.useState(() => customCss); + const [isCustomOpen, setIsCustomOpen] = React.useState(() => activeTheme === 'custom'); + const [parseFailed, setParseFailed] = React.useState(false); + + const handleToggleCustom = () => { + setIsCustomOpen(!isCustomOpen); + }; + + const handleSave = () => { + setParseFailed(!applyCustomTheme(draftCss)); + }; + + const handleClear = () => { + clearCustomTheme(); + setDraftCss(''); + setParseFailed(false); + setIsCustomOpen(false); + }; + + const handlePresetChange = (value: string) => { + if (value === 'custom') { + // Selecting "Custom" restores the saved custom theme; the draft is only + // committed through the explicit Apply button. + setIsCustomOpen(true); + if (customCss) { + applyCustomTheme(customCss); + } + } else { + setIsCustomOpen(false); + setActiveTheme(value); + } + }; + + const handleTextChange = (e: React.ChangeEvent) => { + setDraftCss(e.target.value); + setParseFailed(false); + }; + + return ( + + +
+ {t('Theme')} +
+ +
+ +
+ +
+
+ + {isCustomOpen && ( +
+