From 81e57dea7e99182347c3d95a4f324c18562b9b1d Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 6 Oct 2023 20:47:38 +0530 Subject: [PATCH 1/5] color fix Signed-off-by: Abhinav Kumar --- packages/react/src/components/Button/Button.js | 2 +- packages/react/src/components/Icon/Icon.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/Button/Button.js b/packages/react/src/components/Button/Button.js index 0405dc8a0..2d8e823c8 100644 --- a/packages/react/src/components/Button/Button.js +++ b/packages/react/src/components/Button/Button.js @@ -96,7 +96,7 @@ const Button = ({ } &.ghost { background: none; - color: ${theme?.palette?.[color]?.main || '#1A2027'}; + color: ${theme?.palette?.[color]?.main || 'currentColor'}; border: none; } &.disabled.ghost { diff --git a/packages/react/src/components/Icon/Icon.js b/packages/react/src/components/Icon/Icon.js index 1e56484e9..c26f6dffe 100644 --- a/packages/react/src/components/Icon/Icon.js +++ b/packages/react/src/components/Icon/Icon.js @@ -24,7 +24,7 @@ const Icon = ({ size = 24, name, className = '', style = {}, ...props }) => { y="0" width={size} height={size} - color="inherit" + color="currentColor" className={`ec-icon ${classNames}`} style={styleOverrides} {...props} From 51896b42181e0f68491e01cf24b44e1acbb00c9e Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 6 Oct 2023 20:48:21 +0530 Subject: [PATCH 2/5] added toastbar component Signed-off-by: Abhinav Kumar --- .../react/src/components/ToastBar/ToastBar.js | 86 +++++++++++++++++++ .../components/ToastBar/ToastBar.stories.js | 76 ++++++++++++++++ .../components/ToastBar/ToastBarProvider.js | 30 +++++++ .../src/components/ToastBar/ToastContainer.js | 41 +++++++++ .../react/src/components/ToastBar/index.js | 2 + packages/react/src/context/ToastContext.js | 5 ++ .../react/src/hooks/useToastBarDispatch.js | 7 ++ packages/react/src/theme/DefaultTheme.js | 2 +- 8 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 packages/react/src/components/ToastBar/ToastBar.js create mode 100644 packages/react/src/components/ToastBar/ToastBar.stories.js create mode 100644 packages/react/src/components/ToastBar/ToastBarProvider.js create mode 100644 packages/react/src/components/ToastBar/ToastContainer.js create mode 100644 packages/react/src/components/ToastBar/index.js create mode 100644 packages/react/src/context/ToastContext.js create mode 100644 packages/react/src/hooks/useToastBarDispatch.js diff --git a/packages/react/src/components/ToastBar/ToastBar.js b/packages/react/src/components/ToastBar/ToastBar.js new file mode 100644 index 000000000..05843bf64 --- /dev/null +++ b/packages/react/src/components/ToastBar/ToastBar.js @@ -0,0 +1,86 @@ +/* eslint-disable no-shadow */ +import React, { useMemo, useRef, useEffect } from 'react'; +import { css, useTheme, keyframes } from '@emotion/react'; +import alpha from 'color-alpha'; +import { appendClassNames } from '../../lib/appendClassNames'; +import useComponentOverrides from '../../theme/useComponentOverrides'; +import { Box } from '../Box'; +import { Icon } from '../Icon'; +import { ActionButton } from '../ActionButton'; + +const ToastBar = ({ toast, onClose }) => { + const { type, payload, time = 2000 } = toast; + const toastRef = useRef(); + const theme = useTheme(); + const { classNames, styleOverrides } = useComponentOverrides('ToastBar'); + const { iconName, bgColor, color } = useMemo(() => { + const color = theme.palette?.[type]?.main; + const bgColor = alpha(color, 0.3); + let iconName = 'success'; + switch (type) { + case 'info': + iconName = 'info'; + break; + case 'warning': + iconName = 'warning'; + break; + case 'error': + iconName = 'warning'; + break; + case 'success': + default: + iconName = 'check'; + } + return { iconName, color, bgColor }; + }, [theme.palette, type]); + + const animation = keyframes` + 0% { + opacity: 0; + } + 20% { + opacity: 1; + } + 80% { + opacity: 1; + } + 100% { + opacity: 0; + } + `; + + const ToastBarCSS = css` + display: flex; + flex-direction: row; + gap: 2em; + align-items: center; + justify-content: space-between; + width: fit-content; + max-width: 20rem; + color: ${color}; + background-color: ${bgColor}; + border-radius: 0.25em; + padding: 0.75em 1em; + z-index: ${theme.zIndex?.toastbar}; + animation: ${animation} ${time}ms ease-in-out forwards; + `; + + useEffect(() => { + setTimeout(onClose, time); + }, [onClose, time]); + + return ( + + + {payload} + + + ); +}; + +export default ToastBar; diff --git a/packages/react/src/components/ToastBar/ToastBar.stories.js b/packages/react/src/components/ToastBar/ToastBar.stories.js new file mode 100644 index 000000000..7afd80c34 --- /dev/null +++ b/packages/react/src/components/ToastBar/ToastBar.stories.js @@ -0,0 +1,76 @@ +import React from 'react'; +import { ThemeProvider } from '@emotion/react'; +import { ToastBar, ToastBarProvider } from '.'; +import DefaultTheme from '../../theme/DefaultTheme'; +import { Button } from '../Button'; +import { useToastBarDispatch } from '../../hooks/useToastBarDispatch'; + +export default { + title: 'Components/ToastBar', + component: ToastBar, +}; + +const ToastBarContainer = () => { + const dispatchToast = useToastBarDispatch(); + return ( + <> + + + + + + ); +}; + +const Template = (args) => ( + + + + + +); + +export const Default = Template.bind({}); +Default.args = { + position: 'bottom right', +}; diff --git a/packages/react/src/components/ToastBar/ToastBarProvider.js b/packages/react/src/components/ToastBar/ToastBarProvider.js new file mode 100644 index 000000000..f0f9062eb --- /dev/null +++ b/packages/react/src/components/ToastBar/ToastBarProvider.js @@ -0,0 +1,30 @@ +import React, { useState, useCallback, useMemo } from 'react'; +import ToastContext from '../../context/ToastContext'; +import ToastContainer from './ToastContainer'; + +const ToastBarProvider = ({ position = 'bottom left', children }) => { + const [toasts, setToasts] = useState([]); + const dispatchToast = useCallback( + (toast) => { + setToasts((prevToasts) => [toast, ...prevToasts]); + }, + [setToasts] + ); + const contextValue = useMemo( + () => ({ + toasts, + dispatchToast, + position, + setToasts, + }), + [toasts, dispatchToast, position, setToasts] + ); + return ( + + {children} + + + ); +}; + +export default ToastBarProvider; diff --git a/packages/react/src/components/ToastBar/ToastContainer.js b/packages/react/src/components/ToastBar/ToastContainer.js new file mode 100644 index 000000000..b8ae8fcff --- /dev/null +++ b/packages/react/src/components/ToastBar/ToastContainer.js @@ -0,0 +1,41 @@ +import React, { useContext, useMemo, useCallback } from 'react'; +import { css, useTheme } from '@emotion/react'; +import ToastContext from '../../context/ToastContext'; +import { Box } from '../Box'; +import ToastBar from './ToastBar'; + +const ToastContainer = () => { + const theme = useTheme(); + const ToastContainerCss = css` + // position: fixed; + z-index: ${theme.zIndex.toastbar}; + `; + + const { position, toasts, setToasts } = useContext(ToastContext); + const positionStyle = useMemo(() => { + const positions = position.split(/\s+/); + const styleAnchor = {}; + positions.forEach((pos) => { + styleAnchor[pos] = `2rem`; + }); + return styleAnchor; + }, [position]); + const currentToast = useMemo(() => { + const toast = toasts[toasts.length - 1]; + return toast; + }, [toasts]); + + const onClose = useCallback(() => { + setToasts(toasts.slice(0, toasts.length - 1)); + }, [setToasts, toasts]); + if (!currentToast) { + return null; + } + return ( + + + + ); +}; + +export default ToastContainer; diff --git a/packages/react/src/components/ToastBar/index.js b/packages/react/src/components/ToastBar/index.js new file mode 100644 index 000000000..1e572ca23 --- /dev/null +++ b/packages/react/src/components/ToastBar/index.js @@ -0,0 +1,2 @@ +export { default as ToastBar } from './ToastBar'; +export { default as ToastBarProvider } from './ToastBarProvider'; diff --git a/packages/react/src/context/ToastContext.js b/packages/react/src/context/ToastContext.js new file mode 100644 index 000000000..6e12c1de9 --- /dev/null +++ b/packages/react/src/context/ToastContext.js @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +const ToastContext = createContext(); + +export default ToastContext; diff --git a/packages/react/src/hooks/useToastBarDispatch.js b/packages/react/src/hooks/useToastBarDispatch.js new file mode 100644 index 000000000..c34abf505 --- /dev/null +++ b/packages/react/src/hooks/useToastBarDispatch.js @@ -0,0 +1,7 @@ +import { useContext } from 'react'; +import ToastContext from '../context/ToastContext'; + +export const useToastBarDispatch = () => { + const { dispatchToast } = useContext(ToastContext); + return dispatchToast; +}; diff --git a/packages/react/src/theme/DefaultTheme.js b/packages/react/src/theme/DefaultTheme.js index 2035e902e..32f742291 100644 --- a/packages/react/src/theme/DefaultTheme.js +++ b/packages/react/src/theme/DefaultTheme.js @@ -138,7 +138,7 @@ const DefaultTheme = { header: 1100, popup: 1200, modal: 1300, - snackbar: 1400, + toastbar: 1400, tooltip: 1500, }, }; From a1f32fb908172425089b116115dcccce0bef91c3 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 6 Oct 2023 21:37:55 +0530 Subject: [PATCH 3/5] added missing icons Signed-off-by: Abhinav Kumar --- packages/react/src/components/Icon/icons/Check.js | 14 ++++++++++++++ .../react/src/components/Icon/icons/ErrorCircle.js | 14 ++++++++++++++ packages/react/src/components/Icon/icons/index.js | 4 ++++ packages/react/src/components/ToastBar/ToastBar.js | 6 +++--- .../src/components/ToastBar/ToastContainer.js | 2 +- packages/react/tools/icons-generator.js | 2 ++ 6 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 packages/react/src/components/Icon/icons/Check.js create mode 100644 packages/react/src/components/Icon/icons/ErrorCircle.js diff --git a/packages/react/src/components/Icon/icons/Check.js b/packages/react/src/components/Icon/icons/Check.js new file mode 100644 index 000000000..c5974b119 --- /dev/null +++ b/packages/react/src/components/Icon/icons/Check.js @@ -0,0 +1,14 @@ +import React from 'react'; + +const Check = (props) => ( + + + +); + +export default Check; diff --git a/packages/react/src/components/Icon/icons/ErrorCircle.js b/packages/react/src/components/Icon/icons/ErrorCircle.js new file mode 100644 index 000000000..b5bd3ef6b --- /dev/null +++ b/packages/react/src/components/Icon/icons/ErrorCircle.js @@ -0,0 +1,14 @@ +import React from 'react'; + +const ErrorCircle = (props) => ( + + + +); + +export default ErrorCircle; diff --git a/packages/react/src/components/Icon/icons/index.js b/packages/react/src/components/Icon/icons/index.js index c15aa924b..d4eb69852 100644 --- a/packages/react/src/components/Icon/icons/index.js +++ b/packages/react/src/components/Icon/icons/index.js @@ -32,6 +32,8 @@ import Italic from './Italic'; import StarFilled from './StarFilled'; import Trash from './Trash'; import Kebab from './Kebab'; +import Check from './Check'; +import ErrorCircle from './ErrorCircle'; const icons = { file: File, @@ -68,6 +70,8 @@ const icons = { 'star-filled': StarFilled, trash: Trash, kebab: Kebab, + check: Check, + 'error-circle': ErrorCircle, }; export default icons; diff --git a/packages/react/src/components/ToastBar/ToastBar.js b/packages/react/src/components/ToastBar/ToastBar.js index 05843bf64..921e52576 100644 --- a/packages/react/src/components/ToastBar/ToastBar.js +++ b/packages/react/src/components/ToastBar/ToastBar.js @@ -22,10 +22,10 @@ const ToastBar = ({ toast, onClose }) => { iconName = 'info'; break; case 'warning': - iconName = 'warning'; + iconName = 'report'; break; case 'error': - iconName = 'warning'; + iconName = 'error-circle'; break; case 'success': default: @@ -52,7 +52,7 @@ const ToastBar = ({ toast, onClose }) => { const ToastBarCSS = css` display: flex; flex-direction: row; - gap: 2em; + gap: 1em; align-items: center; justify-content: space-between; width: fit-content; diff --git a/packages/react/src/components/ToastBar/ToastContainer.js b/packages/react/src/components/ToastBar/ToastContainer.js index b8ae8fcff..078a30eca 100644 --- a/packages/react/src/components/ToastBar/ToastContainer.js +++ b/packages/react/src/components/ToastBar/ToastContainer.js @@ -7,7 +7,7 @@ import ToastBar from './ToastBar'; const ToastContainer = () => { const theme = useTheme(); const ToastContainerCss = css` - // position: fixed; + position: fixed; z-index: ${theme.zIndex.toastbar}; `; diff --git a/packages/react/tools/icons-generator.js b/packages/react/tools/icons-generator.js index 4b11beaf1..135d8d395 100644 --- a/packages/react/tools/icons-generator.js +++ b/packages/react/tools/icons-generator.js @@ -37,6 +37,8 @@ const iconsList = [ 'star-filled', 'trash', 'kebab', + 'check', + 'error-circle', ]; const svgDirPath = path.join( __dirname, From 556e7ef03fc1a6610603c23fb6bfd3d717434461 Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Date: Fri, 6 Oct 2023 21:49:25 +0530 Subject: [PATCH 4/5] replaced custom toastbar Signed-off-by: Abhinav Kumar --- packages/react/src/components/ChatInput/ChatInput.js | 3 +-- packages/react/src/components/Message/Message.js | 8 +------- .../src/components/ReportMessage/ReportWindowButtons.js | 4 +--- packages/react/src/components/ToastBar/ToastBar.js | 4 ++-- packages/react/src/hooks/useRCAuth.js | 2 +- packages/react/src/hooks/useRCAuth4Google.js | 2 +- packages/react/src/index.js | 6 +++--- packages/react/src/stories/EmbeddedChat.stories.js | 2 +- .../react/src/stories/EmbeddedChatAuthToken.stories.js | 2 +- packages/react/src/stories/EmbeddedChatTheme.stories.js | 2 +- .../src/stories/EmbeddedChatWithoutHeader.stories.js | 2 +- 11 files changed, 14 insertions(+), 23 deletions(-) diff --git a/packages/react/src/components/ChatInput/ChatInput.js b/packages/react/src/components/ChatInput/ChatInput.js index 613c328c5..924a2887f 100644 --- a/packages/react/src/components/ChatInput/ChatInput.js +++ b/packages/react/src/components/ChatInput/ChatInput.js @@ -5,7 +5,6 @@ import React, { useEffect, useCallback, } from 'react'; -import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar'; import { css } from '@emotion/react'; import styles from './ChatInput.module.css'; import RCContext from '../../context/RCInstance'; @@ -29,6 +28,7 @@ import { Icon } from '../Icon'; import { CommandsList } from '../CommandList'; import { ActionButton } from '../ActionButton'; import useComponentOverrides from '../../theme/useComponentOverrides'; +import { useToastBarDispatch } from '../../hooks/useToastBarDispatch'; const ChatInput = () => { const { styleOverrides, classNames } = useComponentOverrides('ChatInput'); @@ -158,7 +158,6 @@ const ChatInput = () => { dispatchToastMessage({ type: 'error', message: 'Error sending message, login again', - position: toastPosition, }); } else { replaceMessage(pendingMessage._id, res.message); diff --git a/packages/react/src/components/Message/Message.js b/packages/react/src/components/Message/Message.js index 539d36dcd..237e33a23 100644 --- a/packages/react/src/components/Message/Message.js +++ b/packages/react/src/components/Message/Message.js @@ -1,7 +1,6 @@ import React, { useContext, useMemo } from 'react'; import PropTypes from 'prop-types'; import Cookies from 'js-cookie'; -import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar'; import { format } from 'date-fns'; import { css } from '@emotion/react'; import { Attachments } from '../Attachments'; @@ -22,6 +21,7 @@ import { MessageMetrics } from './MessageMetrics'; import { MessageToolbox } from './MessageToolbox'; import { Avatar } from '../Avatar'; import { MessageDivider } from './MessageDivider'; +import { useToastBarDispatch } from '../../hooks/useToastBarDispatch'; const MessageCss = css` display: flex; @@ -77,14 +77,12 @@ const Message = ({ dispatchToastMessage({ type: 'success', message: 'Message starred', - position: toastPosition, }); } else { await RCInstance.unstarMessage(msg._id); dispatchToastMessage({ type: 'success', message: 'Message unstarred', - position: toastPosition, }); } }; @@ -98,13 +96,11 @@ const Message = ({ dispatchToastMessage({ type: 'error', message: 'Error pinning message', - position: toastPosition, }); } else { dispatchToastMessage({ type: 'success', message: isPinned ? 'Message unpinned' : 'Message pinned', - position: toastPosition, }); } }; @@ -116,13 +112,11 @@ const Message = ({ dispatchToastMessage({ type: 'success', message: 'Message deleted successfully', - position: toastPosition, }); } else { dispatchToastMessage({ type: 'error', message: 'Error in deleting message', - position: toastPosition, }); } }; diff --git a/packages/react/src/components/ReportMessage/ReportWindowButtons.js b/packages/react/src/components/ReportMessage/ReportWindowButtons.js index 0cf9fb8e7..ac28ccf84 100644 --- a/packages/react/src/components/ReportMessage/ReportWindowButtons.js +++ b/packages/react/src/components/ReportMessage/ReportWindowButtons.js @@ -1,11 +1,11 @@ import React, { useContext } from 'react'; -import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar'; import PropTypes from 'prop-types'; import { useMessageStore, useToastStore } from '../../store'; import RCContext from '../../context/RCInstance'; import { Button } from '../Button'; import { Icon } from '../Icon'; import { Modal } from '../Modal'; +import { useToastBarDispatch } from '../../hooks/useToastBarDispatch'; const ReportWindowButtons = ({ children, reportDescription, messageId }) => { const [toggleReportMessage, setMessageToReport] = useMessageStore((state) => [ @@ -28,13 +28,11 @@ const ReportWindowButtons = ({ children, reportDescription, messageId }) => { dispatchToastMessage({ type: 'success', message: 'Message reported successfully', - position: toastPosition, }); } else { dispatchToastMessage({ type: 'error', message: 'Error in reporting message', - position: toastPosition, }); } diff --git a/packages/react/src/components/ToastBar/ToastBar.js b/packages/react/src/components/ToastBar/ToastBar.js index 921e52576..ddbba2af5 100644 --- a/packages/react/src/components/ToastBar/ToastBar.js +++ b/packages/react/src/components/ToastBar/ToastBar.js @@ -9,7 +9,7 @@ import { Icon } from '../Icon'; import { ActionButton } from '../ActionButton'; const ToastBar = ({ toast, onClose }) => { - const { type, payload, time = 2000 } = toast; + const { type, message, time = 2000 } = toast; const toastRef = useRef(); const theme = useTheme(); const { classNames, styleOverrides } = useComponentOverrides('ToastBar'); @@ -77,7 +77,7 @@ const ToastBar = ({ toast, onClose }) => { style={styleOverrides} > - {payload} + {message} ); diff --git a/packages/react/src/hooks/useRCAuth.js b/packages/react/src/hooks/useRCAuth.js index 7ac08b8f2..2b4eec957 100644 --- a/packages/react/src/hooks/useRCAuth.js +++ b/packages/react/src/hooks/useRCAuth.js @@ -1,5 +1,4 @@ import { useContext } from 'react'; -import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar'; import RCContext from '../context/RCInstance'; import { useToastStore, @@ -7,6 +6,7 @@ import { totpModalStore, loginModalStore, } from '../store'; +import { useToastBarDispatch } from './useToastBarDispatch'; export const useRCAuth = () => { const { RCInstance } = useContext(RCContext); diff --git a/packages/react/src/hooks/useRCAuth4Google.js b/packages/react/src/hooks/useRCAuth4Google.js index 6386c20f8..19a2ce310 100644 --- a/packages/react/src/hooks/useRCAuth4Google.js +++ b/packages/react/src/hooks/useRCAuth4Google.js @@ -1,8 +1,8 @@ import { useContext } from 'react'; -import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar'; import RCContext from '../context/RCInstance'; import { useGoogleLogin } from './useGoogleLogin'; import { useToastStore, useUserStore, totpModalStore } from '../store'; +import { useToastBarDispatch } from './useToastBarDispatch'; export const useRCAuth4Google = (GOOGLE_CLIENT_ID) => { const { signIn } = useGoogleLogin(GOOGLE_CLIENT_ID); diff --git a/packages/react/src/index.js b/packages/react/src/index.js index 069a0e54a..b75b5e770 100644 --- a/packages/react/src/index.js +++ b/packages/react/src/index.js @@ -1,6 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; -import { ToastBarProvider } from '@rocket.chat/fuselage-toastbar'; // eslint-disable-next-line import/no-extraneous-dependencies import { css, ThemeProvider } from '@emotion/react'; import { EmbeddedChatApi } from '@embeddedchat/api'; @@ -13,6 +12,7 @@ import DefaultTheme from './theme/DefaultTheme'; import { deleteToken, getToken, saveToken } from './lib/auth'; import { Box } from './components/Box'; import useComponentOverrides from './theme/useComponentOverrides'; +import { ToastBarProvider } from './components/ToastBar'; export const EmbeddedChat = ({ isClosable = false, @@ -25,7 +25,7 @@ export const EmbeddedChat = ({ channelName, anonymousMode = false, headerColor = '#fff', - toastBarPosition = 'bottom-end', + toastBarPosition = 'bottom right', showRoles = false, showAvatar = false, enableThreads = false, @@ -136,7 +136,7 @@ export const EmbeddedChat = ({ return ( - + {attachmentWindowOpen ? : null} Date: Fri, 6 Oct 2023 21:51:06 +0530 Subject: [PATCH 5/5] fixed typo Signed-off-by: Abhinav Kumar --- .../react/src/components/ToastBar/ToastBar.stories.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react/src/components/ToastBar/ToastBar.stories.js b/packages/react/src/components/ToastBar/ToastBar.stories.js index 7afd80c34..8ec057aa1 100644 --- a/packages/react/src/components/ToastBar/ToastBar.stories.js +++ b/packages/react/src/components/ToastBar/ToastBar.stories.js @@ -17,7 +17,7 @@ const ToastBarContainer = () => {