diff --git a/react/ActionMenu/index.jsx b/react/ActionMenu/index.jsx index f77df3bd04..e414bc6750 100644 --- a/react/ActionMenu/index.jsx +++ b/react/ActionMenu/index.jsx @@ -11,6 +11,8 @@ import Radio from '../Radio' import { spacingProp } from '../Stack' import { usePopper } from 'react-popper' import createDepreciationLogger from '../helpers/createDepreciationLogger' +import { useSetFlagshipUI } from '../hooks/useSetFlagshipUi/useSetFlagshipUI' +import { useTheme } from '@material-ui/core' const ActionMenuWrapper = ({ inline, @@ -101,6 +103,26 @@ const ActionMenu = ({ anchorElRef, containerElRef }) => { + const theme = useTheme() + const sidebar = document.querySelector('[class*="sidebar"]') + + useSetFlagshipUI( + { + bottomBackground: theme.palette.background.paper, + bottomTheme: 'dark', + topOverlay: getCssVariableValue('overlay'), + topBackground: theme.palette.background.paper, + topTheme: 'light' + }, + { + bottomBackground: theme.palette.background[sidebar ? 'default' : 'paper'], + bottomTheme: 'dark', + topOverlay: 'transparent', + topBackground: theme.palette.background.paper, + topTheme: 'dark' + } + ) + if (placement) logDepecratedPlacement( ' is deprecated, use instead' diff --git a/react/BarContextProvider/index.spec.jsx b/react/BarContextProvider/index.spec.jsx index 86c7dd230b..fe5a7445d2 100644 --- a/react/BarContextProvider/index.spec.jsx +++ b/react/BarContextProvider/index.spec.jsx @@ -172,27 +172,4 @@ describe('BarContextProvider', () => { expect(queryByText(mockVoidWebviewService)).toBeInTheDocument() }) - - it('should not try to provide a cozy-intent context if one is provided', () => { - // Set Web context - window.cozy.isFlagshipApp = false - - const client = createMockClient({}) - const mockStore = configureStore() - const store = mockStore(x => x) - - const { queryByText } = render( - - - locales}> - - - - - - - ) - - expect(queryByText(mockWebviewService.foo)).not.toBeInTheDocument() - }) }) diff --git a/react/Dialog/index.jsx b/react/Dialog/index.jsx index f6f6adbfbb..8411a8cdef 100644 --- a/react/Dialog/index.jsx +++ b/react/Dialog/index.jsx @@ -4,6 +4,8 @@ import { RemoveScroll } from 'react-remove-scroll' import useBreakpoints from '../hooks/useBreakpoints' import { useCozyTheme } from '../CozyTheme' import themesStyles from '../../stylus/settings/palette.styl' +import { useSetFlagshipUI } from '../hooks/useSetFlagshipUi/useSetFlagshipUI' +import { useTheme } from '@material-ui/core' const Dialog = props => { const { isMobile, isTablet } = useBreakpoints() @@ -21,12 +23,47 @@ const Dialog = props => { (props.open || props.opened) && shouldBlockScroll ? RemoveScroll : React.Fragment + const cozyTheme = useCozyTheme() + const theme = useTheme() + const cozBar = document.querySelector('.coz-bar-wrapper') + const sidebar = document.getElementById('sidebar') - const theme = useCozyTheme() + useSetFlagshipUI( + props.fullScreen + ? { + bottomBackground: theme.palette.background.paper, + bottomTheme: 'dark', + topBackground: theme.palette.background.paper, + topTheme: 'dark' + } + : { + bottomBackground: theme.palette.background.default, + bottomTheme: 'light', + bottomOverlay: 'rgba(0, 0, 0, 0.5)', + topOverlay: 'rgba(0, 0, 0, 0.5)', + topBackground: theme.palette.background.paper, + topTheme: 'light' + }, + { + bottomBackground: theme.palette.background[sidebar ? 'default' : 'paper'], + bottomTheme: 'dark', + bottomOverlay: 'transparent', + topOverlay: 'transparent', + topBackground: + cozBar && getComputedStyle(cozBar).getPropertyValue('background-color'), + topTheme: + cozBar && cozBar.classList.contains('coz-theme-primary') + ? 'light' + : 'dark' + } + ) return ( - + ) } diff --git a/react/SelectionBar/index.jsx b/react/SelectionBar/index.jsx index 3230d0d28a..2c7c5cac4b 100644 --- a/react/SelectionBar/index.jsx +++ b/react/SelectionBar/index.jsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import PropTypes from 'prop-types' import cx from 'classnames' @@ -11,6 +11,8 @@ import useBreakpoints from '../hooks/useBreakpoints' import styles from './styles.styl' import CrossIcon from 'cozy-ui/transpiled/react/Icons/Cross' +import { useWebviewIntent } from 'cozy-intent' +import { useTheme } from '@material-ui/core' /* @@ -39,6 +41,37 @@ const SelectionBar = ({ actions, selected, hideSelectionBar }) => { action.displayCondition === undefined || action.displayCondition(selected) ) }) + const webviewIntent = useWebviewIntent() + const theme = useTheme() + + // This component is always rendered but hidden with CSS if there is no selection + // That is why we do not use useSetFlagship API here because that hook can not accept changing values + useEffect(() => { + if (!webviewIntent || !theme) return + + selectedCount === 0 && + webviewIntent && + webviewIntent.call('setFlagshipUI', { + bottomBackground: theme.palette.background.default, + bottomTheme: 'dark' + }) + + selectedCount > 0 && + webviewIntent && + webviewIntent.call('setFlagshipUI', { + bottomBackground: theme.palette.grey[700], + bottomTheme: 'light' + }) + + return () => + webviewIntent && + theme && + webviewIntent.call('setFlagshipUI', { + bottomBackground: theme.palette.background.default, + bottomTheme: 'dark' + }) + }, [selectedCount, webviewIntent]) + return (
( - -) +const Sidebar = ({ children, className, ...restProps }) => { + const theme = useTheme() + + useSetFlagshipUI( + { + bottomBackground: theme.palette.background.default, + bottomTheme: 'dark' + }, + { + bottomBackground: theme.palette.background.paper + } + ) + + return ( + + ) +} Sidebar.propTypes = { children: PropTypes.node, diff --git a/react/Viewer/index.jsx b/react/Viewer/index.jsx index e35fbdcf91..894c350986 100644 --- a/react/Viewer/index.jsx +++ b/react/Viewer/index.jsx @@ -12,6 +12,8 @@ import ViewerByFile from './ViewerByFile' import { isValidForPanel } from './helpers' import PanelContent from './Panel/PanelContent' import FooterContent from './Footer/FooterContent' +import { useSetFlagshipUI } from '../hooks/useSetFlagshipUi/useSetFlagshipUI' +import { useTheme } from '@material-ui/core' const KEY_CODE_LEFT = 37 const KEY_CODE_RIGHT = 39 @@ -149,6 +151,19 @@ const ViewerInformationsWrapper = ({ validForPanel, toolbarRef }) => { + const theme = useTheme() + const sidebar = document.querySelector('[class*="sidebar"]') + + useSetFlagshipUI( + { + bottomBackground: theme.palette.background.paper, + bottomTheme: 'dark' + }, + { + bottomBackground: theme.palette.background[sidebar ? 'default' : 'paper'] + } + ) + return ( <> {!disableFooter && ( diff --git a/react/hooks/useSetFlagshipUi/useSetFlagshipUI.js b/react/hooks/useSetFlagshipUi/useSetFlagshipUI.js new file mode 100644 index 0000000000..ab0b22c516 --- /dev/null +++ b/react/hooks/useSetFlagshipUi/useSetFlagshipUI.js @@ -0,0 +1,25 @@ +import { useEffect } from 'react' +import { useWebviewIntent } from 'cozy-intent' +import pickBy from 'lodash/pickBy' +import identity from 'lodash/identity' +import isEmpty from 'lodash/isEmpty' +import isObject from 'lodash/isObject' + +export const useSetFlagshipUI = (onMount, onUnmount) => { + const webviewIntent = useWebviewIntent() + + useEffect(() => { + const parsedOnMount = isObject(onMount) && pickBy(onMount, identity) + const parsedOnUnmount = isObject(onUnmount) && pickBy(onUnmount, identity) + + webviewIntent && + !isEmpty(parsedOnMount) && + webviewIntent.call('setFlagshipUI', parsedOnMount) + + return () => { + webviewIntent && + !isEmpty(parsedOnUnmount) && + webviewIntent.call('setFlagshipUI', parsedOnUnmount) + } + }, [webviewIntent]) +} diff --git a/react/hooks/useSetFlagshipUi/useSetFlagshipUI.spec.js b/react/hooks/useSetFlagshipUi/useSetFlagshipUI.spec.js new file mode 100644 index 0000000000..b5e681a43b --- /dev/null +++ b/react/hooks/useSetFlagshipUi/useSetFlagshipUI.spec.js @@ -0,0 +1,201 @@ +import { renderHook } from '@testing-library/react-hooks' +import { useSetFlagshipUI } from './useSetFlagshipUI' +import { useWebviewIntent } from 'cozy-intent' + +jest.mock('cozy-intent') + +const mockCall = jest.fn() +const mockCSSValue = '#fff' + +beforeEach(() => { + useWebviewIntent.mockImplementation(() => ({ call: mockCall })) +}) + +afterEach(() => { + mockCall.mockClear() +}) + +it('should call webviewIntent with the correct params, once at mount and once at unmount', () => { + const { unmount } = renderHook(() => + useSetFlagshipUI( + { + bottomBackground: mockCSSValue, + bottomOverlay: 'transparent', + bottomTheme: 'dark', + topBackground: mockCSSValue, + topOverlay: 'transparent', + topTheme: 'dark' + }, + { + bottomBackground: mockCSSValue, + bottomOverlay: 'transparent', + bottomTheme: 'light', + topBackground: mockCSSValue, + topOverlay: 'transparent', + topTheme: 'light' + } + ) + ) + + expect(mockCall).toHaveBeenNthCalledWith(1, 'setFlagshipUI', { + bottomBackground: mockCSSValue, + bottomOverlay: 'transparent', + bottomTheme: 'dark', + topBackground: mockCSSValue, + topOverlay: 'transparent', + topTheme: 'dark' + }) + + unmount() + + expect(mockCall).toHaveBeenNthCalledWith(2, 'setFlagshipUI', { + bottomBackground: mockCSSValue, + bottomOverlay: 'transparent', + bottomTheme: 'light', + topBackground: mockCSSValue, + topOverlay: 'transparent', + topTheme: 'light' + }) +}) + +it('should call webviewIntent with the correct params with few args, once at mount and once at unmount', () => { + const { unmount } = renderHook(() => + useSetFlagshipUI( + { bottomBackground: mockCSSValue }, + { bottomBackground: mockCSSValue } + ) + ) + + expect(mockCall).toHaveBeenNthCalledWith(1, 'setFlagshipUI', { + bottomBackground: mockCSSValue + }) + + unmount() + + expect(mockCall).toHaveBeenNthCalledWith(2, 'setFlagshipUI', { + bottomBackground: mockCSSValue + }) +}) + +it('should call nothing with no webviewIntent and not throw', () => { + useWebviewIntent.mockImplementation(() => undefined) + + const { unmount } = renderHook(() => + useSetFlagshipUI( + { bottomBackground: mockCSSValue }, + { bottomBackground: mockCSSValue } + ) + ) + + expect(mockCall).not.toHaveBeenCalled() + + unmount() + + expect(mockCall).not.toHaveBeenCalled() +}) + +it('should refuse to call bad args', () => { + const { unmount } = renderHook(() => + useSetFlagshipUI( + { + bottomBackground: false, + bottomOverlay: 0, + bottomTheme: '', + topBackground: null, + topOverlay: undefined, + topTheme: NaN + }, + { + bottomBackground: false, + bottomOverlay: 0, + bottomTheme: '', + topBackground: null, + topOverlay: undefined, + topTheme: NaN + } + ) + ) + + expect(mockCall).not.toHaveBeenCalled() + + unmount() + + expect(mockCall).not.toHaveBeenCalled() +}) + +it('should refuse to call when no arg in second arg', () => { + const { unmount } = renderHook(() => + useSetFlagshipUI({ bottomBackground: mockCSSValue }) + ) + + expect(mockCall).toHaveBeenNthCalledWith(1, 'setFlagshipUI', { + bottomBackground: mockCSSValue + }) + + unmount() + + expect(mockCall).toHaveBeenCalledTimes(1) +}) + +it('should refuse to call when no arg at all', () => { + const { unmount } = renderHook(() => useSetFlagshipUI()) + + expect(mockCall).not.toHaveBeenCalled() + + unmount() + + expect(mockCall).not.toHaveBeenCalled() +}) + +it('should refuse to call when empty arg', () => { + const { unmount } = renderHook(() => useSetFlagshipUI({}, {})) + + expect(mockCall).not.toHaveBeenCalled() + + unmount() + + expect(mockCall).not.toHaveBeenCalled() +}) + +it('should refuse to call when invalid arg type', () => { + const { unmount } = renderHook(() => useSetFlagshipUI('foo', 10)) + + expect(mockCall).not.toHaveBeenCalled() + + unmount() + + expect(mockCall).not.toHaveBeenCalled() +}) + +it('should sanitize bad objects', () => { + const { unmount } = renderHook(() => + useSetFlagshipUI( + { + bottomBackground: false, + bottomOverlay: 0, + bottomTheme: '', + topBackground: 'red', + topOverlay: undefined, + topTheme: NaN + }, + { + bottomBackground: false, + bottomOverlay: 0, + bottomTheme: '', + topBackground: 'blue', + topOverlay: undefined, + topTheme: NaN + } + ) + ) + + expect(mockCall).toHaveBeenNthCalledWith(1, 'setFlagshipUI', { + topBackground: 'red' + }) + + unmount() + + expect(mockCall).toHaveBeenNthCalledWith(2, 'setFlagshipUI', { + topBackground: 'blue' + }) +})