diff --git a/packages/react/src/store/index.js b/packages/react/src/store/index.js index bddd0bd1d6..7264932058 100644 --- a/packages/react/src/store/index.js +++ b/packages/react/src/store/index.js @@ -11,3 +11,4 @@ export { default as useMentionsStore } from './mentionsStore'; export { default as usePinnedMessageStore } from './pinnedMessageStore'; export { default as useStarredMessageStore } from './starredMessageStore'; export { default as useSidebarStore } from './sidebarStore'; +export { default as useThemeStore } from './themeStore'; diff --git a/packages/react/src/store/themeStore.js b/packages/react/src/store/themeStore.js new file mode 100644 index 0000000000..4da0305089 --- /dev/null +++ b/packages/react/src/store/themeStore.js @@ -0,0 +1,9 @@ +import { create } from 'zustand'; + +const useThemeStore = create((set) => ({ + isDarkMode: false, + setIsDarkMode: (isDarkMode) => set(() => ({ isDarkMode })), + toggleTheme: () => set((state) => ({ isDarkMode: !state.isDarkMode })), +})); + +export default useThemeStore; diff --git a/packages/react/src/views/ChatHeader/ChatHeader.js b/packages/react/src/views/ChatHeader/ChatHeader.js index 9143598d30..2ffbb9945e 100644 --- a/packages/react/src/views/ChatHeader/ChatHeader.js +++ b/packages/react/src/views/ChatHeader/ChatHeader.js @@ -24,6 +24,7 @@ import { useStarredMessageStore, useFileStore, useSidebarStore, + useThemeStore, } from '../../store'; import { DynamicHeader } from '../DynamicHeader'; import useFetchChatData from '../../hooks/useFetchChatData'; @@ -40,7 +41,7 @@ const ChatHeader = ({ className = '', style = {}, optionConfig = { - surfaceItems: ['minmax', 'close'], + surfaceItems: ['minmax', 'theme', 'close'], menuItems: [ 'thread', 'mentions', @@ -103,6 +104,10 @@ const ChatHeader = ({ const filtered = useMessageStore((state) => state.filtered); const setFilter = useMessageStore((state) => state.setFilter); + // Theme toggle + const isDarkMode = useThemeStore((state) => state.isDarkMode); + const toggleTheme = useThemeStore((state) => state.toggleTheme); + const isThreadOpen = useMessageStore((state) => state.isThreadOpen); const threadMainMessage = useMessageStore((state) => state.threadMainMessage); @@ -244,6 +249,13 @@ const ChatHeader = ({ iconName: 'cross', visible: isClosable, }, + theme: { + label: isDarkMode ? 'Light Mode' : 'Dark Mode', + id: 'theme', + onClick: toggleTheme, + iconName: isDarkMode ? 'sun' : 'moon', + visible: true, + }, thread: { label: 'Threads', id: 'thread', @@ -312,6 +324,8 @@ const ChatHeader = ({ fullScreen, isClosable, isUserAuthenticated, + isDarkMode, + toggleTheme, handleLogout, setFullScreen, setClosableState, diff --git a/packages/react/src/views/EmbeddedChat.js b/packages/react/src/views/EmbeddedChat.js index f3b94c7b48..690b771aa3 100644 --- a/packages/react/src/views/EmbeddedChat.js +++ b/packages/react/src/views/EmbeddedChat.js @@ -18,7 +18,12 @@ import { import { ChatLayout } from './ChatLayout'; import { ChatHeader } from './ChatHeader'; import { RCInstanceProvider } from '../context/RCInstance'; -import { useUserStore, useLoginStore, useMessageStore } from '../store'; +import { + useUserStore, + useLoginStore, + useMessageStore, + useThemeStore, +} from '../store'; import DefaultTheme from '../theme/DefaultTheme'; import { getTokenStorage } from '../lib/auth'; import { styles } from './EmbeddedChat.styles'; @@ -64,6 +69,15 @@ const EmbeddedChat = (props) => { const { classNames, styleOverrides } = useComponentOverrides('EmbeddedChat'); const [fullScreen, setFullScreen] = useState(false); const [isSynced, setIsSynced] = useState(!remoteOpt); + + // Theme store for runtime theme toggle + const isDarkMode = useThemeStore((state) => state.isDarkMode); + const setIsDarkMode = useThemeStore((state) => state.setIsDarkMode); + + // Initialize theme store from prop on mount + useEffect(() => { + setIsDarkMode(dark); + }, [dark, setIsDarkMode]); const { getToken, saveToken, deleteToken } = getTokenStorage(secure); const { setIsUserAuthenticated, @@ -225,11 +239,14 @@ const EmbeddedChat = (props) => { if (!isSynced) return null; return ( - + ( + + + +); + +export default Moon; diff --git a/packages/ui-elements/src/components/Icon/icons/Sun.js b/packages/ui-elements/src/components/Icon/icons/Sun.js new file mode 100644 index 0000000000..a5210afcd5 --- /dev/null +++ b/packages/ui-elements/src/components/Icon/icons/Sun.js @@ -0,0 +1,14 @@ +import React from 'react'; + +const Sun = (props) => ( + + + +); + +export default Sun; diff --git a/packages/ui-elements/src/components/Icon/icons/index.js b/packages/ui-elements/src/components/Icon/icons/index.js index 234251edab..5930fface6 100644 --- a/packages/ui-elements/src/components/Icon/icons/index.js +++ b/packages/ui-elements/src/components/Icon/icons/index.js @@ -64,6 +64,8 @@ import Avatar from './Avatar'; import FormatText from './FormatText'; import Cog from './Cog'; import Team from './Team'; +import Sun from './Sun'; +import Moon from './Moon'; const icons = { file: File, @@ -132,6 +134,8 @@ const icons = { avatar: Avatar, 'format-text': FormatText, cog: Cog, + sun: Sun, + moon: Moon, }; export default icons;