From 3b8c393be854c9640b1e373dc88940c1c56a9b2d Mon Sep 17 00:00:00 2001 From: Pavel Prichodko <14926950+prichodko@users.noreply.github.com> Date: Mon, 18 Apr 2022 15:48:34 +0200 Subject: [PATCH] feat(react): allow theme customization --- .../src/contexts/theme-context.tsx | 59 +++++++++++++++ .../src/modules/community/index.tsx | 51 +++++++------ packages/status-react/src/styles/themes.ts | 75 ------------------- packages/status-react/src/types/config.tsx | 11 ++- 4 files changed, 96 insertions(+), 100 deletions(-) create mode 100644 packages/status-react/src/contexts/theme-context.tsx delete mode 100644 packages/status-react/src/styles/themes.ts diff --git a/packages/status-react/src/contexts/theme-context.tsx b/packages/status-react/src/contexts/theme-context.tsx new file mode 100644 index 00000000..58c0fc8a --- /dev/null +++ b/packages/status-react/src/contexts/theme-context.tsx @@ -0,0 +1,59 @@ +import React, { + createContext, + useContext, + useLayoutEffect, + useMemo, +} from 'react' + +import { createTheme, darkTheme, theme as defaultTheme } from '../styles/config' + +import type { Theme } from '../styles/config' +import type { Config } from '../types/config' + +const ThemeContext = createContext(undefined) + +interface Props { + theme: Config['theme'] + children: React.ReactNode +} + +export const ThemeProvider = (props: Props) => { + const { children, theme = 'light' } = props + + const appTheme = useMemo(() => { + if (!theme || theme === 'light') { + return defaultTheme + } + + if (theme === 'dark') { + return darkTheme + } + + return createTheme({ + colors: theme.colors, + fonts: theme.fonts, + }) + }, [theme]) + + useLayoutEffect(() => { + document.body.classList.add(appTheme) + + return () => { + document.body.classList.remove(appTheme) + } + }, [appTheme]) + + return ( + {children} + ) +} + +export const useTheme = () => { + const context = useContext(ThemeContext) + + if (!context) { + throw new Error('useTheme must be used within a ThemeProvider') + } + + return context +} diff --git a/packages/status-react/src/modules/community/index.tsx b/packages/status-react/src/modules/community/index.tsx index 2f465831..45711fb5 100644 --- a/packages/status-react/src/modules/community/index.tsx +++ b/packages/status-react/src/modules/community/index.tsx @@ -10,6 +10,7 @@ import { MessengerProvider } from '~/src/contexts/messengerProvider' import { ModalProvider } from '~/src/contexts/modalProvider' import { NarrowProvider } from '~/src/contexts/narrowProvider' import { ScrollProvider } from '~/src/contexts/scrollProvider' +import { ThemeProvider } from '~/src/contexts/theme-context' import { ToastProvider } from '~/src/contexts/toastProvider' import { styled } from '~/src/styles/config' import { GlobalStyle } from '~/src/styles/GlobalStyle' @@ -33,30 +34,32 @@ export const Community = (props: Props) => { return ( - - - - - - - - - - - -
- - - - - - - - - + + + + + + + + + + + + +
+ + + + + + + + + + ) diff --git a/packages/status-react/src/styles/themes.ts b/packages/status-react/src/styles/themes.ts deleted file mode 100644 index f1cd3054..00000000 --- a/packages/status-react/src/styles/themes.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type { Theme } from '../types/theme' - -export const lightTheme: Theme = { - primary: '#000', - secondary: '#939BA1', - tertiary: '#4360DF', - bodyBackgroundColor: '#fff', - sectionBackgroundColor: '#F6F8FA', - bodyBackgroundGradient: - 'linear-gradient(0deg, #FFFFFF 50%, rgba(255, 255, 255, 0) 102.57%)', - guestNameColor: '#887AF9', - iconColor: '#D37EF4', - iconUserColor: '#717199', - iconTextColor: 'rgba(255, 255, 255, 0.7)', - logoColor: '#51D0F0', - activeChannelBackground: '#E9EDF1', - notificationColor: '#4360DF', - inputColor: '#EEF2F5', - border: '#EEF2F5', - buttonBg: 'rgba(67, 96, 223, 0.1)', - buttonBgHover: 'rgba(67, 96, 223, 0.2)', - buttonNoBg: 'rgba(255, 45, 85, 0.1)', - buttonNoBgHover: 'rgba(255, 45, 85, 0.2)', - skeletonLight: '#F6F8FA', - skeletonDark: '#E9EDF1', - redColor: '#FF2D55', - greenColor: '#4EBC60', - greenBg: 'rgba(78, 188, 96, 0.1)', - mentionColor: '#0DA4C9', - mentionHover: '#BDE7F2', - mentionBg: '#E5F8FD', - mentionBgHover: '#D4F3FA', - shadow: - '0px 2px 4px rgba(0, 34, 51, 0.16), 0px 4px 12px rgba(0, 34, 51, 0.08)', - reactionBg: '#eceffc', - blueBg: 'rgba(67, 96, 223, 0.1)', -} - -export const darkTheme: Theme = { - primary: '#fff', - secondary: '#909090', - tertiary: '#88B0FF', - bodyBackgroundColor: '#000', - sectionBackgroundColor: '#252525', - bodyBackgroundGradient: - 'linear-gradient(0deg, #000 50%, rgba(255, 255, 255, 0) 102.57%)', - guestNameColor: '#887AF9', - iconColor: '#D37EF4', - iconUserColor: '#717199', - logoColor: '#51D0F0', - iconTextColor: 'rgba(0, 0, 0, 0.7)', - activeChannelBackground: '#2C2C2C', - notificationColor: '#887AF9', - inputColor: '#373737', - border: '#373737', - buttonBg: 'rgba(134, 158, 255, 0.2)', - buttonBgHover: 'rgba(67, 96, 223, 0.3)', - buttonNoBg: 'rgba(255, 92, 123, 0.2)', - buttonNoBgHover: 'rgba(255, 45, 85, 0.3)', - skeletonLight: '#2E2F31', - skeletonDark: '#141414', - redColor: '#FF5C7B', - greenColor: '#60C370', - greenBg: 'rgba(96, 195, 112, 0.2)', - mentionColor: '#51D0F0', - mentionHover: '#004E60', - mentionBg: '#004050', - mentionBgHover: '#002D39', - shadow: - '0px 2px 4px rgba(0, 34, 51, 0.16), 0px 4px 12px rgba(0, 34, 51, 0.08)', - reactionBg: '#373737', - blueBg: 'rgba(134, 158, 255, 0.3)', -} - -export default { lightTheme, darkTheme } diff --git a/packages/status-react/src/types/config.tsx b/packages/status-react/src/types/config.tsx index c882d4bb..70ebe3f1 100644 --- a/packages/status-react/src/types/config.tsx +++ b/packages/status-react/src/types/config.tsx @@ -3,10 +3,19 @@ import type { BrowserRouter, HashRouter, MemoryRouter } from 'react-router-dom' export type Environment = 'production' | 'test' +type CustomTheme = { + colors: { + [key in keyof Theme['colors']]: string + } + fonts: { + [key in keyof Theme['colors']]: string + } +} + export interface Config { publicKey: string environment?: Environment - theme?: Theme + theme?: 'light' | 'dark' | CustomTheme router?: typeof BrowserRouter | typeof MemoryRouter | typeof HashRouter options?: { enableSidebar: boolean