From 357a3a50fa8d5e73483b1f52003d9deb72867226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?= <60678893+juliajforesti@users.noreply.github.com> Date: Tue, 1 Aug 2023 10:51:37 -0300 Subject: [PATCH] feat: high-contrast theme (#29805) --- .changeset/smooth-planes-cough.md | 7 + .../GenericUpsellModal/GenericUpsellModal.tsx | 4 +- .../providers/UserProvider/UserProvider.tsx | 12 +- .../sidebar/header/hooks/useAccountItems.tsx | 43 ++-- .../sidebar/header/hooks/useThemeItems.tsx | 33 --- .../sidebar/header/hooks/useUserMenu.tsx | 24 +- .../preferences/PreferencesGlobalSection.tsx | 21 +- apps/meteor/client/views/account/routes.tsx | 8 + .../client/views/account/sidebarItems.tsx | 15 +- .../themes/HighContrastUpsellModal.tsx | 41 ++++ .../client/views/account/themes/ThemePage.tsx | 103 +++++++++ .../client/views/account/themes/themeItems.ts | 24 ++ .../rocketchat-i18n/i18n/en.i18n.json | 11 + .../images/high-contrast-upsell-modal.png | Bin 0 -> 13392 bytes .../server/methods/saveUserPreferences.ts | 3 +- .../ui-theming/src/PaletteStyleTag.tsx | 15 +- .../ui-theming/src/hooks/useThemeMode.ts | 25 ++- ee/packages/ui-theming/src/palette.ts | 10 +- .../ui-theming/src/paletteHighContrast.ts | 210 ++++++++++++++++++ ee/packages/ui-theming/src/types/themes.ts | 3 + .../v1/users/UsersSetPreferenceParamsPOST.ts | 3 +- 21 files changed, 513 insertions(+), 102 deletions(-) create mode 100644 .changeset/smooth-planes-cough.md delete mode 100644 apps/meteor/client/sidebar/header/hooks/useThemeItems.tsx create mode 100644 apps/meteor/client/views/account/themes/HighContrastUpsellModal.tsx create mode 100644 apps/meteor/client/views/account/themes/ThemePage.tsx create mode 100644 apps/meteor/client/views/account/themes/themeItems.ts create mode 100644 apps/meteor/public/images/high-contrast-upsell-modal.png create mode 100644 ee/packages/ui-theming/src/paletteHighContrast.ts create mode 100644 ee/packages/ui-theming/src/types/themes.ts diff --git a/.changeset/smooth-planes-cough.md b/.changeset/smooth-planes-cough.md new file mode 100644 index 00000000000..9ad4239f034 --- /dev/null +++ b/.changeset/smooth-planes-cough.md @@ -0,0 +1,7 @@ +--- +'@rocket.chat/ui-theming': minor +'@rocket.chat/rest-typings': minor +'@rocket.chat/meteor': minor +--- + +feat: high-contrast theme diff --git a/apps/meteor/client/components/GenericUpsellModal/GenericUpsellModal.tsx b/apps/meteor/client/components/GenericUpsellModal/GenericUpsellModal.tsx index 15c47db711b..79215ef7c10 100644 --- a/apps/meteor/client/components/GenericUpsellModal/GenericUpsellModal.tsx +++ b/apps/meteor/client/components/GenericUpsellModal/GenericUpsellModal.tsx @@ -15,7 +15,7 @@ type GenericUpsellModalProps = { icon?: IconName; img: ComponentProps['src']; onCancel?: () => void; - onClose?: () => void; + onClose: () => void; onConfirm?: () => void; annotation?: ReactNode; } & ComponentProps; @@ -30,8 +30,8 @@ const GenericUpsellModal = ({ icon, description, onCancel, + onClose, onConfirm, - onClose = onCancel, annotation, ...props }: GenericUpsellModalProps) => { diff --git a/apps/meteor/client/providers/UserProvider/UserProvider.tsx b/apps/meteor/client/providers/UserProvider/UserProvider.tsx index d4f982d3350..fa5eca49cae 100644 --- a/apps/meteor/client/providers/UserProvider/UserProvider.tsx +++ b/apps/meteor/client/providers/UserProvider/UserProvider.tsx @@ -1,6 +1,6 @@ import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; import { useLocalStorage } from '@rocket.chat/fuselage-hooks'; -import { UserContext, useSetting } from '@rocket.chat/ui-contexts'; +import { UserContext, useEndpoint, useSetting } from '@rocket.chat/ui-contexts'; import type { LoginService, SubscriptionWithRoom } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import type { ContextType, ReactElement, ReactNode } from 'react'; @@ -10,6 +10,7 @@ import { Subscriptions, ChatRoom } from '../../../app/models/client'; import { getUserPreference } from '../../../app/utils/client'; import { sdk } from '../../../app/utils/client/lib/SDKClient'; import { afterLogoutCleanUpCallback } from '../../../lib/callbacks/afterLogoutCleanUpCallback'; +import { useIsEnterprise } from '../../hooks/useIsEnterprise'; import { useReactiveValue } from '../../hooks/useReactiveValue'; import { createReactiveSubscriptionFactory } from '../../lib/createReactiveSubscriptionFactory'; import { useEmailVerificationWarning } from './hooks/useEmailVerificationWarning'; @@ -66,6 +67,9 @@ const UserProvider = ({ children }: UserProviderProps): ReactElement => { const user = useReactiveValue(getUser); const [language, setLanguage] = useLocalStorage('userLanguage', user?.language ?? 'en'); + const { data: license } = useIsEnterprise(); + const setUserPreferences = useEndpoint('POST', '/v1/users.setPreferences'); + const loginMethod: LoginMethods = (isLdapEnabled && 'loginWithLDAP') || (isCrowdEnabled && 'loginWithCrowd') || 'loginWithPassword'; useLDAPAndCrowdCollisionWarning(); @@ -166,6 +170,12 @@ const UserProvider = ({ children }: UserProviderProps): ReactElement => { } }, [user?.language, language, setLanguage]); + useEffect(() => { + if (!license?.isEnterprise && user?.settings?.preferences?.themeAppearence === 'high-contrast') { + setUserPreferences({ data: { themeAppearence: 'light' } }); + } + }, [license?.isEnterprise, setUserPreferences, user?.settings?.preferences?.themeAppearence]); + return ; }; diff --git a/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx b/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx index e4757c2d48b..10966e4b859 100644 --- a/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx +++ b/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx @@ -1,29 +1,28 @@ import { Badge } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { defaultFeaturesPreview, useFeaturePreviewList } from '@rocket.chat/ui-client'; -import { useLogout, useRoute, useTranslation } from '@rocket.chat/ui-contexts'; +import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem'; export const useAccountItems = (): GenericMenuItemProps[] => { const t = useTranslation(); - const accountRoute = useRoute('account-index'); - const featurePreviewRoute = useRoute('feature-preview'); - const { unseenFeatures, featurePreviewEnabled } = useFeaturePreviewList(); + const router = useRouter(); - const logout = useLogout(); + const { unseenFeatures, featurePreviewEnabled } = useFeaturePreviewList(); const handleMyAccount = useMutableCallback(() => { - accountRoute.push({}); + router.navigate('/account'); }); - - const handleFeaturePreview = useMutableCallback(() => { - featurePreviewRoute.push(); + const handleThemes = useMutableCallback(() => { + router.navigate('/account/theme'); }); - - const handleLogout = useMutableCallback(() => { - logout(); + const handlePreferences = useMutableCallback(() => { + router.navigate('/account/preferences'); + }); + const handleFeaturePreview = useMutableCallback(() => { + router.navigate('/account/feature-preview'); }); const featurePreviewItem = { @@ -42,17 +41,23 @@ export const useAccountItems = (): GenericMenuItemProps[] => { return [ { - id: 'my-account', + id: 'profile', icon: 'user', - content: t('My_Account'), + content: t('Profile'), onClick: handleMyAccount, }, - ...(featurePreviewEnabled && defaultFeaturesPreview.length > 0 ? [featurePreviewItem] : []), { - id: 'logout', - icon: 'sign-out', - content: t('Logout'), - onClick: handleLogout, + id: 'theme', + icon: 'palette', + content: t('Theme'), + onClick: handleThemes, }, + { + id: 'preferences', + icon: 'customize', + content: t('Preferences'), + onClick: handlePreferences, + }, + ...(featurePreviewEnabled && defaultFeaturesPreview.length > 0 ? [featurePreviewItem] : []), ]; }; diff --git a/apps/meteor/client/sidebar/header/hooks/useThemeItems.tsx b/apps/meteor/client/sidebar/header/hooks/useThemeItems.tsx deleted file mode 100644 index d571acde162..00000000000 --- a/apps/meteor/client/sidebar/header/hooks/useThemeItems.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { RadioButton } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; -import { useThemeMode } from '@rocket.chat/ui-theming/src/hooks/useThemeMode'; -import React from 'react'; - -import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem'; - -export const useThemeItems = (): GenericMenuItemProps[] => { - const t = useTranslation(); - - const [selectedTheme, setTheme] = useThemeMode(); - - return [ - { - id: 'light', - icon: 'sun', - content: t('Theme_light'), - addon: , - }, - { - id: 'dark', - icon: 'moon', - content: t('Theme_dark'), - addon: , - }, - { - id: 'auto', - icon: 'desktop', - content: t('Theme_match_system'), - addon: , - }, - ]; -}; diff --git a/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx b/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx index 232011dbe31..111065b0ac4 100644 --- a/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx +++ b/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx @@ -1,19 +1,31 @@ import type { IUser } from '@rocket.chat/core-typings'; -import { useTranslation } from '@rocket.chat/ui-contexts'; +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useLogout, useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; +import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem'; import UserMenuHeader from '../UserMenuHeader'; import { useAccountItems } from './useAccountItems'; import { useStatusItems } from './useStatusItems'; -import { useThemeItems } from './useThemeItems'; export const useUserMenu = (user: IUser) => { const t = useTranslation(); const statusItems = useStatusItems(user); - const themeItems = useThemeItems(); const accountItems = useAccountItems(); + const logout = useLogout(); + const handleLogout = useMutableCallback(() => { + logout(); + }); + + const logoutItem: GenericMenuItemProps = { + id: 'logout', + icon: 'sign-out', + content: t('Logout'), + onClick: handleLogout, + }; + return [ { title: , @@ -24,11 +36,11 @@ export const useUserMenu = (user: IUser) => { items: statusItems, }, { - title: t('Theme'), - items: themeItems, + title: t('Account'), + items: accountItems, }, { - items: accountItems, + items: [logoutItem], }, ]; }; diff --git a/apps/meteor/client/views/account/preferences/PreferencesGlobalSection.tsx b/apps/meteor/client/views/account/preferences/PreferencesGlobalSection.tsx index edc7e64b9dc..7c2410deb9f 100644 --- a/apps/meteor/client/views/account/preferences/PreferencesGlobalSection.tsx +++ b/apps/meteor/client/views/account/preferences/PreferencesGlobalSection.tsx @@ -1,5 +1,5 @@ import type { SelectOption } from '@rocket.chat/fuselage'; -import { Select, Accordion, Field, FieldGroup, MultiSelect } from '@rocket.chat/fuselage'; +import { Accordion, Field, FieldGroup, MultiSelect } from '@rocket.chat/fuselage'; import { useUserPreference, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { useMemo } from 'react'; @@ -11,7 +11,6 @@ const PreferencesGlobalSection = ({ onChange, commitRef, ...props }: FormSection const t = useTranslation(); const userDontAskAgainList = useUserPreference<{ action: string; label: string }[]>('dontAskAgainList'); - const themePreference = useUserPreference<'light' | 'dark' | 'auto'>('themeAppearence'); const options = useMemo( () => (userDontAskAgainList || []).map(({ action, label }) => [action, label]) as SelectOption[], @@ -23,26 +22,18 @@ const PreferencesGlobalSection = ({ onChange, commitRef, ...props }: FormSection const { values, handlers, commit } = useForm( { dontAskAgainList: selectedOptions, - themeAppearence: themePreference, }, onChange, ); - const { dontAskAgainList, themeAppearence } = values as { + const { dontAskAgainList } = values as { dontAskAgainList: string[]; - themeAppearence: string; }; - const { handleDontAskAgainList, handleThemeAppearence } = handlers; + const { handleDontAskAgainList } = handlers; commitRef.current.global = commit; - const themeOptions: SelectOption[] = [ - ['auto', t('Theme_match_system')], - ['light', t('Theme_light')], - ['dark', t('Theme_dark')], - ]; - return ( @@ -57,12 +48,6 @@ const PreferencesGlobalSection = ({ onChange, commitRef, ...props }: FormSection /> - - {t('Theme_Appearence')} - -