diff --git a/.storybook/preview.js b/.storybook/preview.js index 986552baa88..8586896bc12 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -1,12 +1,24 @@ -export const parameters = { - actions: { argTypesRegex: '^on[A-Z].*' }, -} - import React from 'react' import { I18nextProvider } from 'react-i18next' import { GlobalStyle } from '../app/src/atoms/GlobalStyle' import { i18n } from '../app/src/i18n' +export const customViewports = { + onDeviceDisplay: { + name: 'Touchscreen', + type: 'tablet', + styles: { + width: '1024px', + height: '600px', + }, + }, +} + +export const parameters = { + actions: { argTypesRegex: '^on[A-Z].*' }, + viewport: { viewports: customViewports }, +} + // Global decorator to apply the styles to all stories export const decorators = [ Story => ( diff --git a/app/src/atoms/MenuList/MenuItem.stories.tsx b/app/src/atoms/MenuList/MenuItem.stories.tsx index 3aea7b3c66a..559fd0001c8 100644 --- a/app/src/atoms/MenuList/MenuItem.stories.tsx +++ b/app/src/atoms/MenuList/MenuItem.stories.tsx @@ -14,4 +14,11 @@ const Template: Story> = args => ( export const Primary = Template.bind({}) Primary.args = { children: 'Example menu btn', + disabled: false, +} + +Primary.parameters = { + viewport: { + defaultViewport: 'onDeviceDisplay', + }, } diff --git a/app/src/atoms/MenuList/MenuItem.tsx b/app/src/atoms/MenuList/MenuItem.tsx index 902acdf378a..84b790a51b3 100644 --- a/app/src/atoms/MenuList/MenuItem.tsx +++ b/app/src/atoms/MenuList/MenuItem.tsx @@ -1,30 +1,56 @@ import styled from 'styled-components' import { SPACING, - Btn, COLORS, - TEXT_ALIGN_LEFT, TYPOGRAPHY, + ALIGN_CENTER, + RESPONSIVENESS, + StyleProps, } from '@opentrons/components' -import type { PrimitiveComponent } from '@opentrons/components' - -type BtnComponent = PrimitiveComponent<'button'> - -export const MenuItem: BtnComponent = styled(Btn)` - text-align: ${TEXT_ALIGN_LEFT}; +interface ButtonProps extends StyleProps { + /** optional isAlert boolean to turn the background red, only seen in ODD */ + isAlert?: boolean +} +export const MenuItem = styled.button` + text-align: ${TYPOGRAPHY.textAlignLeft}; font-size: ${TYPOGRAPHY.fontSizeP}; background-color: ${COLORS.transparent}; color: ${COLORS.darkBlackEnabled}; padding: ${SPACING.spacing3} 0.75rem ${SPACING.spacing3} 0.75rem; - &:hover { + &:hover, + &:active { background-color: ${COLORS.lightBlue}; } - &:disabled, - &.disabled { + &:disabled { background-color: ${COLORS.transparent}; color: ${COLORS.black}${COLORS.opacity50HexCode}; } + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + align-items: ${ALIGN_CENTER}; + text-align: ${TYPOGRAPHY.textAlignCenter}; + font-size: ${TYPOGRAPHY.fontSize28}; + background-color: ${({ isAlert }) => + isAlert ? COLORS.errorEnabled : COLORS.transparent}; + color: ${({ isAlert }) => + isAlert ? COLORS.white : COLORS.darkBlackEnabled}; + padding: 1.625rem 1.5rem; + height: 4.875rem; + line-height: ${TYPOGRAPHY.lineHeight36}; + &:hover, + &:active { + background-color: ${({ isAlert }) => + isAlert ? COLORS.errorEnabled : COLORS.darkBlack_twenty}; + } + + &:disabled { + background-color: ${({ isAlert }) => + isAlert ? COLORS.errorEnabled : COLORS.transparent}; + color: ${({ isAlert }) => + isAlert ? COLORS.white : COLORS.darkBlack_sixty}; + } + } ` diff --git a/app/src/atoms/MenuList/MenuList.stories.tsx b/app/src/atoms/MenuList/MenuList.stories.tsx index 7d5699289e8..f1964d0cf0e 100644 --- a/app/src/atoms/MenuList/MenuList.stories.tsx +++ b/app/src/atoms/MenuList/MenuList.stories.tsx @@ -1,48 +1,27 @@ import * as React from 'react' -import { css } from 'styled-components' -import { - Flex, - TYPOGRAPHY, - COLORS, - TEXT_ALIGN_LEFT, - SPACING, -} from '@opentrons/components' import { MenuList } from './index' +import { MenuItem } from './MenuItem' import type { Story, Meta } from '@storybook/react' export default { title: 'App/Atoms/MenuList', component: MenuList, + onClick: { action: 'clicked' }, } as Meta const Template: Story> = args => ( ) -const style = css` - width: auto; - text-align: ${TEXT_ALIGN_LEFT}; - font-size: ${TYPOGRAPHY.fontSizeP}; - padding-bottom: ${TYPOGRAPHY.fontSizeH6}; - background-color: transparent; - color: ${COLORS.darkBlackEnabled}; - padding-left: ${TYPOGRAPHY.fontSizeLabel}; - padding-right: ${TYPOGRAPHY.fontSizeLabel}; - padding-top: ${SPACING.spacing3}; - - &:hover { - background-color: ${COLORS.lightBlue}; - } - - &:disabled, - &.disabled { - color: ${COLORS.darkGreyDisabled}; - } -` -const btn = {'Example menu btn'} - +const menuBtn = 'example menu btn' export const Primary = Template.bind({}) Primary.args = { - buttons: [btn, btn], + children: ( + <> + {menuBtn} + {menuBtn} + {menuBtn} + + ), } diff --git a/app/src/atoms/MenuList/__tests__/MenuList.test.tsx b/app/src/atoms/MenuList/__tests__/MenuList.test.tsx index 4381a43e82b..6540c619540 100644 --- a/app/src/atoms/MenuList/__tests__/MenuList.test.tsx +++ b/app/src/atoms/MenuList/__tests__/MenuList.test.tsx @@ -12,12 +12,22 @@ describe('MenuList', () => { let props: React.ComponentProps beforeEach(() => { props = { - buttons: [mockBtn], + children: mockBtn, } }) - it('renders a child', () => { + it('renders a child not on device', () => { const { getByText } = render(props) getByText('mockBtn') }) + it('renders isOnDevice child, clicking background overlay calls onClick', () => { + props = { + ...props, + isOnDevice: true, + onClick: jest.fn(), + } + const { getByLabelText } = render(props) + getByLabelText('BackgroundOverlay_ModalShell').click() + expect(props.onClick).toHaveBeenCalled() + }) }) diff --git a/app/src/atoms/MenuList/index.tsx b/app/src/atoms/MenuList/index.tsx index b04f3ab1e86..e90513070d3 100644 --- a/app/src/atoms/MenuList/index.tsx +++ b/app/src/atoms/MenuList/index.tsx @@ -3,17 +3,37 @@ import { COLORS, POSITION_ABSOLUTE, DIRECTION_COLUMN, - ButtonProps, Flex, SPACING, + BORDERS, + JUSTIFY_CENTER, } from '@opentrons/components' +import { ModalShell } from '../../molecules/Modal' interface MenuListProps { - buttons: Array + children: React.ReactNode + isOnDevice?: boolean + onClick?: React.MouseEventHandler } export const MenuList = (props: MenuListProps): JSX.Element | null => { - return ( + const { children, isOnDevice = false, onClick = null } = props + return isOnDevice && onClick != null ? ( + + + {children} + + + ) : ( { top="2.6rem" right={`calc(50% + ${String(SPACING.spacing2)})`} flexDirection={DIRECTION_COLUMN} + width="max-content" > - {props.buttons} + {children} ) } diff --git a/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx b/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx index b1dce3ccfda..50c8acb616d 100644 --- a/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx +++ b/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx @@ -67,28 +67,26 @@ export const ModuleOverflowMenu = ( return ( - { - return ( - - item.onClick(item.isSecondary)} - data-testid={`module_setting_${String(module.moduleModel)}`} - disabled={item.disabledReason || isDisabled} - whiteSpace="nowrap" - > - {item.setSetting} - - {item.menuButtons} - - ) - } - ), - ]} - /> + + {menuOverflowItemsByModuleType[module.moduleType].map( + (item: any, index: number) => { + return ( + + item.onClick(item.isSecondary)} + data-testid={`module_setting_${String(module.moduleModel)}`} + disabled={item.disabledReason || isDisabled} + whiteSpace="nowrap" + > + {item.setSetting} + + {item.menuButtons} + + ) + } + )} + ) } diff --git a/app/src/organisms/OnDeviceDisplay/Navigation/NavigationMenu.tsx b/app/src/organisms/OnDeviceDisplay/Navigation/NavigationMenu.tsx index 77b0d6bffb3..4d6cbce3269 100644 --- a/app/src/organisms/OnDeviceDisplay/Navigation/NavigationMenu.tsx +++ b/app/src/organisms/OnDeviceDisplay/Navigation/NavigationMenu.tsx @@ -1,21 +1,11 @@ import * as React from 'react' import { useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' -import { - ALIGN_CENTER, - BORDERS, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_CENTER, - TYPOGRAPHY, - SPACING, - SIZE_2, -} from '@opentrons/components' +import { COLORS, Flex, Icon, SPACING, SIZE_2 } from '@opentrons/components' import { StyledText } from '../../../atoms/text' -import { ModalShell } from '../../../molecules/Modal' +import { MenuList } from '../../../atoms/MenuList' +import { MenuItem } from '../../../atoms/MenuList/MenuItem' import { home, ROBOT } from '../../../redux/robot-controls' import { restartRobot } from '../../../redux/robot-admin' import { useLights } from '../../Devices/hooks' @@ -34,85 +24,51 @@ export function NavigationMenu(props: NavigationMenuProps): JSX.Element { const dispatch = useDispatch() return ( - - + dispatch(home(robotName, ROBOT))} > - dispatch(home(robotName, ROBOT))} - > + - + {t('home_gantry')} - dispatch(restartRobot(robotName))} - > + + dispatch(restartRobot(robotName))}> + - + {t('robot_controls:restart_label')} - + + + - + {i18n.format( t(lightsOn ? 'lights_off' : 'lights_on'), 'capitalize' )} - - + + ) } diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx index 14c89d68f06..2489e1dcb88 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx @@ -3,29 +3,20 @@ import { useDispatch, useSelector } from 'react-redux' import { useQueryClient } from 'react-query' import { useHistory } from 'react-router-dom' import { useTranslation } from 'react-i18next' -import { - ALIGN_CENTER, - BORDERS, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_CENTER, - SPACING, - TYPOGRAPHY, -} from '@opentrons/components' +import { Flex, Icon, SPACING } from '@opentrons/components' import { deleteProtocol, deleteRun, getProtocol } from '@opentrons/api-client' import { useCreateRunMutation, useHost } from '@opentrons/react-api-client' import { MAXIMUM_PINNED_PROTOCOLS } from '../../../App/constants' import { StyledText } from '../../../atoms/text' -import { ModalShell } from '../../../molecules/Modal' +import { MenuList } from '../../../atoms/MenuList' +import { MenuItem } from '../../../atoms/MenuList/MenuItem' import { SmallModalChildren } from '../../../molecules/Modal/OnDeviceDisplay' import { useToaster } from '../../../organisms/ToasterOven' import { getPinnedProtocolIds, updateConfigValue } from '../../../redux/config' -import type { Dispatch } from '../../../redux/types' import type { UseLongPressResult } from '@opentrons/components' +import type { Dispatch } from '../../../redux/types' export function LongPressModal(props: { longpress: UseLongPressResult @@ -137,73 +128,30 @@ export function LongPressModal(props: { handleCloseMaxPinsAlert={() => longpress?.setIsLongPressed(false)} /> ) : ( - - - - - + + + + + {t('run_protocol')} - - - + + + + + {pinned ? t('unpin_protocol') : t('pin_protocol')} - - - - {t('delete_protocol')} - + + + + + {t('delete_protocol')} - - + + )} )