From c594cf2e6e48223649fd3130a6391b28aa5bf48c Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Wed, 3 May 2023 16:22:13 -0400 Subject: [PATCH] feat(app): labware and modules setup hifi designs (#12582) * Add deck map icon [RCORE-733] * Apply hifi designs to labware setup page [RCORE-733] * Code formatting fixes [RCORE-733] * Fix small button text color [RCORE-734] * Create shared back button component [RCORE-734] * Use new back button in setup headers and remove continue button [RCORE-734] * Create FloatingActionButton component and use it for Map View button [RCORE-734] * Add connection-status icon [RCORE-734] * Fix chip component styling [RCORE-734] * Create new InlineNotification component [RCORE-734] * Apply hifi styling to module setup page [RCORE-734] * Add back info icon [RCORE-734] * Remove onClick parameter for inline notification [RCORE-734] * Move ODDBackButton to molecules and add stories and tests [RCORE-734] * Fix imports, formatting, and general code style improvements [RCORE-734] * Use new typography [RCORE-734] * Fix mangled directory names [NOTICKET] --- .../localization/en/protocol_setup.json | 2 +- app/src/atoms/Chip/__tests__/Chip.test.tsx | 4 +- app/src/atoms/Chip/index.tsx | 6 +- .../InlineNotification.stories.tsx | 39 +++ .../__tests__/InlineNotification.test.tsx | 66 +++++ app/src/atoms/InlineNotification/index.tsx | 110 +++++++++ .../FloatingActionButton.stories.tsx | 29 +++ .../OnDeviceDisplay/FloatingActionButton.tsx | 91 +++++++ .../buttons/OnDeviceDisplay/SmallButton.tsx | 4 +- .../__tests__/FloatingActionButton.test.tsx | 76 ++++++ .../__tests__/SmallButton.test.tsx | 8 +- .../atoms/buttons/OnDeviceDisplay/index.ts | 1 + .../ODDBackButton/ODDBackButton.stories.tsx | 18 ++ .../__tests__/ODDBackButton.test.tsx | 32 +++ app/src/molecules/ODDBackButton/index.tsx | 42 ++++ .../index.tsx | 0 .../InstrumentMountItem.tsx/LabeledMount.tsx | 83 ------- .../InstrumentMountItem.tsx/index.tsx | 92 ------- .../ProtocolSetupInstruments/index.tsx | 11 +- .../__tests__/ProtocolSetupLabware.test.tsx | 7 +- .../organisms/ProtocolSetupLabware/index.tsx | 89 ++++--- .../index.tsx | 18 +- .../__tests__/ProtocolSetupLiquids.test.tsx | 4 - .../organisms/ProtocolSetupLiquids/index.tsx | 11 +- .../__tests__/ProtocolSetupModules.test.tsx | 14 +- .../organisms/ProtocolSetupModules/index.tsx | 229 ++++++++---------- .../OnDeviceDisplay/InstrumentDetail.tsx | 2 +- .../__snapshots__/icons.test.tsx.snap | 48 ++++ components/src/icons/icon-data.ts | 9 + components/src/ui-style-constants/borders.ts | 4 +- 30 files changed, 750 insertions(+), 399 deletions(-) create mode 100644 app/src/atoms/InlineNotification/InlineNotification.stories.tsx create mode 100644 app/src/atoms/InlineNotification/__tests__/InlineNotification.test.tsx create mode 100644 app/src/atoms/InlineNotification/index.tsx create mode 100644 app/src/atoms/buttons/OnDeviceDisplay/FloatingActionButton.stories.tsx create mode 100644 app/src/atoms/buttons/OnDeviceDisplay/FloatingActionButton.tsx create mode 100644 app/src/atoms/buttons/OnDeviceDisplay/__tests__/FloatingActionButton.test.tsx create mode 100644 app/src/molecules/ODDBackButton/ODDBackButton.stories.tsx create mode 100644 app/src/molecules/ODDBackButton/__tests__/ODDBackButton.test.tsx create mode 100644 app/src/molecules/ODDBackButton/index.tsx rename app/src/organisms/{InstrumentInfo.tsx => InstrumentInfo}/index.tsx (100%) delete mode 100644 app/src/organisms/InstrumentMountItem.tsx/LabeledMount.tsx delete mode 100644 app/src/organisms/InstrumentMountItem.tsx/index.tsx diff --git a/app/src/assets/localization/en/protocol_setup.json b/app/src/assets/localization/en/protocol_setup.json index dbf61a8b2a1..eef76b2215a 100644 --- a/app/src/assets/localization/en/protocol_setup.json +++ b/app/src/assets/localization/en/protocol_setup.json @@ -119,7 +119,7 @@ "multiple_modules_missing": "Multiple modules missing", "module_mismatch_error": "Module mismatch error", "module_mismatch_title": "This robot has connected modules that are not specified in this protocol", - "module_mismatch_body": "Make sure the modules connected to this robot are of the right type and generation.", + "module_mismatch_body": "Make sure the modules connected to this robot are of the right type and generation", "modules": "Modules", "modules_connected": "{{count}} module connected", "modules_connected_plural": "{{count}} modules connected", diff --git a/app/src/atoms/Chip/__tests__/Chip.test.tsx b/app/src/atoms/Chip/__tests__/Chip.test.tsx index baf92fd9aa5..c0e39893d8b 100644 --- a/app/src/atoms/Chip/__tests__/Chip.test.tsx +++ b/app/src/atoms/Chip/__tests__/Chip.test.tsx @@ -66,7 +66,7 @@ describe('Chip', () => { expect(chip).toHaveStyle(`background-color: ${String(COLORS.yellow_three)}`) expect(chipText).toHaveStyle(`color: ${String(COLORS.yellow_one)}`) const icon = getByLabelText('icon_mockWarning') - expect(icon).toHaveStyle(`color: ${String(COLORS.yellow_two)}`) + expect(icon).toHaveStyle(`color: ${String(COLORS.yellow_one)}`) }) it('should render text, icon, no bgcolor with warning colors and bg false', () => { @@ -81,7 +81,7 @@ describe('Chip', () => { expect(chip).toHaveStyle(`background-color: ${String(COLORS.transparent)}`) expect(chipText).toHaveStyle(`color: ${String(COLORS.yellow_one)}`) const icon = getByLabelText('icon_mockWarning') - expect(icon).toHaveStyle(`color: ${String(COLORS.yellow_two)}`) + expect(icon).toHaveStyle(`color: ${String(COLORS.yellow_one)}`) }) it('should render text, icon, bgcolor with neutral colors', () => { diff --git a/app/src/atoms/Chip/index.tsx b/app/src/atoms/Chip/index.tsx index eeee2bce063..d1e3ad94148 100644 --- a/app/src/atoms/Chip/index.tsx +++ b/app/src/atoms/Chip/index.tsx @@ -59,7 +59,7 @@ const CHIP_PROPS_BY_TYPE: Record< warning: { backgroundColor: COLORS.yellow_three, borderRadius: BORDERS.size_six, - iconColor: COLORS.yellow_two, + iconColor: COLORS.yellow_one, textColor: COLORS.yellow_one, }, } @@ -98,8 +98,8 @@ export function Chip({ /> )} diff --git a/app/src/atoms/InlineNotification/InlineNotification.stories.tsx b/app/src/atoms/InlineNotification/InlineNotification.stories.tsx new file mode 100644 index 00000000000..0466edc254e --- /dev/null +++ b/app/src/atoms/InlineNotification/InlineNotification.stories.tsx @@ -0,0 +1,39 @@ +import * as React from 'react' +import { InlineNotification } from '.' +import type { Story, Meta } from '@storybook/react' + +export default { + title: 'ODD/Atoms/InlineNotification', + argTypes: { + hug: { + control: { + type: 'boolean', + }, + defaultValue: false, + }, + type: { + control: { + type: 'select', + options: ['alert', 'error', 'neutral', 'success'], + }, + defaultValue: 'success', + }, + onCloseClick: { + control: { + type: 'boolean', + }, + defaultValue: true, + }, + }, +} as Meta + +const Template: Story< + React.ComponentProps +> = args => + +export const InlineNotificationComponent = Template.bind({}) +InlineNotificationComponent.args = { + heading: 'awesome', + message: 'you did it', + type: 'success', +} diff --git a/app/src/atoms/InlineNotification/__tests__/InlineNotification.test.tsx b/app/src/atoms/InlineNotification/__tests__/InlineNotification.test.tsx new file mode 100644 index 00000000000..50dbe6968d6 --- /dev/null +++ b/app/src/atoms/InlineNotification/__tests__/InlineNotification.test.tsx @@ -0,0 +1,66 @@ +import * as React from 'react' +import { fireEvent } from '@testing-library/react' +import { renderWithProviders } from '@opentrons/components' +import { i18n } from '../../../i18n' +import { InlineNotification } from '..' + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +describe('InlineNotification', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + type: 'success', + heading: 'TITLE', + } + }) + it('renders success inline notification', () => { + const { getByText, getByLabelText } = render(props) + getByLabelText('icon_success') + getByText('TITLE') + }) + it('renders success inline notification with exit button and when click dismisses inline notification', () => { + props = { + type: 'success', + heading: 'TITLE', + onCloseClick: jest.fn(), + } + const { getByText, getByLabelText } = render(props) + getByText('TITLE') + const btn = getByLabelText('close_icon') + fireEvent.click(btn) + expect(props.onCloseClick).toHaveBeenCalled() + }) + it('renders alert inline notification', () => { + props = { + type: 'alert', + heading: 'TITLE', + } + const { getByText, getByLabelText } = render(props) + getByLabelText('icon_alert') + getByText('TITLE') + }) + it('renders error inline notification', () => { + props = { + type: 'error', + heading: 'TITLE', + } + const { getByText, getByLabelText } = render(props) + getByLabelText('icon_error') + getByText('TITLE') + }) + it('renders neutral inline notification', () => { + props = { + type: 'neutral', + heading: 'TITLE', + } + const { getByText, getByLabelText } = render(props) + getByLabelText('icon_neutral') + getByText('TITLE') + }) +}) diff --git a/app/src/atoms/InlineNotification/index.tsx b/app/src/atoms/InlineNotification/index.tsx new file mode 100644 index 00000000000..7dc4c7ec65c --- /dev/null +++ b/app/src/atoms/InlineNotification/index.tsx @@ -0,0 +1,110 @@ +import * as React from 'react' +import { + Icon, + JUSTIFY_SPACE_BETWEEN, + IconProps, + Flex, + DIRECTION_ROW, + ALIGN_CENTER, + COLORS, + SPACING, + TYPOGRAPHY, + BORDERS, + Btn, +} from '@opentrons/components' +import { StyledText } from '../text' + +import type { StyleProps } from '@opentrons/components' + +type InlineNotificationType = 'alert' | 'error' | 'neutral' | 'success' + +export interface InlineNotificationProps extends StyleProps { + /** name constant of the icon to display */ + type: InlineNotificationType + /** InlineNotification contents */ + heading: string + message?: string + /** Optional dynamic width based on contents */ + hug?: boolean + /** optional handler to show close button/clear alert */ + onCloseClick?: (() => void) | React.MouseEventHandler +} + +const INLINE_NOTIFICATION_PROPS_BY_TYPE: Record< + InlineNotificationType, + { icon: IconProps; backgroundColor: string; color: string } +> = { + alert: { + icon: { name: 'ot-alert' }, + backgroundColor: COLORS.yellow_three, + color: COLORS.yellow_two, + }, + error: { + icon: { name: 'ot-alert' }, + backgroundColor: COLORS.red_three, + color: COLORS.red_two, + }, + neutral: { + icon: { name: 'information' }, + backgroundColor: COLORS.darkBlack_twenty, + color: COLORS.darkBlackEnabled, + }, + success: { + icon: { name: 'ot-check' }, + backgroundColor: COLORS.green_three, + color: COLORS.green_two, + }, +} + +export function InlineNotification( + props: InlineNotificationProps +): JSX.Element { + const { heading, hug = false, onCloseClick, message, type } = props + const fullHeading = `${heading}${message ? '. ' : ''}` + const fullmessage = `${message}.` + const inlineNotificationProps = INLINE_NOTIFICATION_PROPS_BY_TYPE[type] + const iconProps = { + ...inlineNotificationProps.icon, + size: '1.75rem', + color: INLINE_NOTIFICATION_PROPS_BY_TYPE[type].color, + } + return ( + + + + + + {fullHeading} + + {message && fullmessage} + + + {onCloseClick && ( + + + + )} + + ) +} diff --git a/app/src/atoms/buttons/OnDeviceDisplay/FloatingActionButton.stories.tsx b/app/src/atoms/buttons/OnDeviceDisplay/FloatingActionButton.stories.tsx new file mode 100644 index 00000000000..4cf234db430 --- /dev/null +++ b/app/src/atoms/buttons/OnDeviceDisplay/FloatingActionButton.stories.tsx @@ -0,0 +1,29 @@ +import * as React from 'react' +import { FloatingActionButton } from '.' +import type { Story, Meta } from '@storybook/react' +import { ICON_DATA_BY_NAME } from '@opentrons/components/src/icons/icon-data' + +export default { + title: 'ODD/Atoms/Buttons/FloatingActionButton', + argTypes: { + iconName: { + control: { + type: 'select', + options: Object.keys(ICON_DATA_BY_NAME), + }, + defaultValue: undefined, + }, + onClick: { action: 'clicked' }, + }, +} as Meta + +const FloatingActionButtonTemplate: Story< + React.ComponentProps +> = args => +export const FloatingActionButtonComponent = FloatingActionButtonTemplate.bind( + {} +) +FloatingActionButtonComponent.args = { + buttonText: 'Button text', + disabled: false, +} diff --git a/app/src/atoms/buttons/OnDeviceDisplay/FloatingActionButton.tsx b/app/src/atoms/buttons/OnDeviceDisplay/FloatingActionButton.tsx new file mode 100644 index 00000000000..2c3e2dace4f --- /dev/null +++ b/app/src/atoms/buttons/OnDeviceDisplay/FloatingActionButton.tsx @@ -0,0 +1,91 @@ +import * as React from 'react' +import { useTranslation } from 'react-i18next' +import { css } from 'styled-components' + +import { + Btn, + Flex, + Icon, + ALIGN_CENTER, + BORDERS, + COLORS, + DIRECTION_ROW, + POSITION_FIXED, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' +import { StyledText } from '../../text' + +import type { IconName, StyleProps } from '@opentrons/components' + +interface FloatingActionButtonProps extends StyleProps { + buttonText?: React.ReactNode + disabled?: boolean + iconName?: IconName + onClick: React.MouseEventHandler +} + +export function FloatingActionButton( + props: FloatingActionButtonProps +): JSX.Element { + const { t } = useTranslation('protocol_setup') + const { + buttonText = t('map_view'), + disabled = false, + iconName = 'deck-map', + ...buttonProps + } = props + + const contentColor = disabled ? COLORS.darkBlack_sixty : COLORS.white + const FLOATING_ACTION_BUTTON_STYLE = css` + background-color: ${COLORS.highlightPurple_one}; + border-radius: ${BORDERS.size_five}; + box-shadow: ${BORDERS.shadowBig}; + color: ${contentColor}; + cursor: default; + + &:active { + background-color: ${COLORS.highlightPurple_one_pressed}; + } + + &:focus-visible { + border-color: ${COLORS.fundamentalsFocus}; + border-width: ${SPACING.spacing2}; + box-shadow: ${BORDERS.shadowBig}; + } + + &:disabled { + background-color: ${COLORS.darkBlack_twenty}; + color: ${contentColor}; + } + ` + + return ( + + + + {buttonText} + + + ) +} diff --git a/app/src/atoms/buttons/OnDeviceDisplay/SmallButton.tsx b/app/src/atoms/buttons/OnDeviceDisplay/SmallButton.tsx index ce7096eabb1..2f78cadec82 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/SmallButton.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/SmallButton.tsx @@ -79,14 +79,14 @@ export function SmallButton(props: SmallButtonProps): JSX.Element { disabledColor: `${COLORS.darkBlack_sixty}`, }, tertiaryHighLight: { - defaultColor: `${COLORS.darkBlack_seventy}`, + defaultColor: COLORS.darkBlackEnabled, defaultBackgroundColor: `${COLORS.blueEnabled}${COLORS.opacity0HexCode}`, activeBackgroundColor: `${COLORS.darkBlack_twenty}`, disabledBackgroundColor: `${COLORS.blueEnabled}${COLORS.opacity0HexCode}`, disabledColor: `${COLORS.darkBlack_sixty}`, }, tertiaryLowLight: { - defaultColor: COLORS.darkBlackEnabled, + defaultColor: `${COLORS.darkBlack_seventy}`, defaultBackgroundColor: ` ${COLORS.blueEnabled}${COLORS.opacity0HexCode}`, activeBackgroundColor: `${COLORS.darkBlack_twenty}`, disabledBackgroundColor: `${COLORS.blueEnabled}${COLORS.opacity0HexCode}`, diff --git a/app/src/atoms/buttons/OnDeviceDisplay/__tests__/FloatingActionButton.test.tsx b/app/src/atoms/buttons/OnDeviceDisplay/__tests__/FloatingActionButton.test.tsx new file mode 100644 index 00000000000..219515a60ea --- /dev/null +++ b/app/src/atoms/buttons/OnDeviceDisplay/__tests__/FloatingActionButton.test.tsx @@ -0,0 +1,76 @@ +import * as React from 'react' +import { + renderWithProviders, + BORDERS, + COLORS, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' + +import { FloatingActionButton } from '..' + +const render = (props: React.ComponentProps) => { + return renderWithProviders()[0] +} + +describe('FloatingActionButton', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + buttonText: 'floating action', + onClick: jest.fn(), + } + }) + + it('renders floating action button with text', () => { + const { getByRole } = render(props) + const button = getByRole('button') + expect(button).toHaveStyle( + `background-color: ${COLORS.highlightPurple_one}` + ) + expect(button).toHaveStyle(`padding: 0.75rem ${SPACING.spacing5}`) + expect(button).toHaveStyle(`font-size: ${TYPOGRAPHY.fontSize28}`) + expect(button).toHaveStyle(`font-weight: ${TYPOGRAPHY.fontWeightSemiBold}`) + expect(button).toHaveStyle(`line-height: ${TYPOGRAPHY.lineHeight36}`) + expect(button).toHaveStyle(`border-radius: ${BORDERS.size_five}`) + expect(button).toHaveStyle( + `text-transform: ${TYPOGRAPHY.textTransformNone}` + ) + expect(button).toHaveStyle(`box-shadow: ${BORDERS.shadowBig}`) + expect(button).toHaveStyle(`color: ${COLORS.white}`) + }) + + it('renders unselected floating action button with text and disabled', () => { + props.disabled = true + const { getByRole } = render(props) + const button = getByRole('button') + expect(button).toBeDisabled() + expect(button).toHaveStyle(`background-color: #16212d33`) + expect(button).toHaveStyle(`color: #16212d99`) + }) + + it('applies the correct states to the unselected floating action button - active', () => { + const { getByRole } = render(props) + const button = getByRole('button') + expect(button).toHaveStyleRule( + 'background-color', + `${COLORS.highlightPurple_one_pressed}`, + { + modifier: ':active', + } + ) + }) + + it('applies the correct states to the unselected floating action button - focus-visible', () => { + const { getByRole } = render(props) + const button = getByRole('button') + expect(button).toHaveStyleRule( + 'border-color', + `${COLORS.fundamentalsFocus}`, + { + modifier: ':focus-visible', + } + ) + }) +}) diff --git a/app/src/atoms/buttons/OnDeviceDisplay/__tests__/SmallButton.test.tsx b/app/src/atoms/buttons/OnDeviceDisplay/__tests__/SmallButton.test.tsx index 093aa61d79e..66a1f1ff871 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/__tests__/SmallButton.test.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/__tests__/SmallButton.test.tsx @@ -54,9 +54,7 @@ describe('SmallButton', () => { buttonType: 'tertiaryHighLight', } const { getByRole } = render(props) - expect(getByRole('button')).toHaveStyle( - `color: ${COLORS.darkBlackEnabled}${COLORS.opacity70HexCode}` - ) + expect(getByRole('button')).toHaveStyle(`color: ${COLORS.darkBlackEnabled}`) }) it('renders the tertiary low light', () => { props = { @@ -64,7 +62,9 @@ describe('SmallButton', () => { buttonType: 'tertiaryLowLight', } const { getByRole } = render(props) - expect(getByRole('button')).toHaveStyle(`color: ${COLORS.darkBlackEnabled}`) + expect(getByRole('button')).toHaveStyle( + `color: ${COLORS.darkBlackEnabled}${COLORS.opacity70HexCode}` + ) }) it('renders the button as disabled', () => { props = { diff --git a/app/src/atoms/buttons/OnDeviceDisplay/index.ts b/app/src/atoms/buttons/OnDeviceDisplay/index.ts index b7c47cf8e1e..4a11db86efc 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/index.ts +++ b/app/src/atoms/buttons/OnDeviceDisplay/index.ts @@ -1,3 +1,4 @@ +export { FloatingActionButton } from './FloatingActionButton' export { LargeButton } from './LargeButton' export { MediumButton } from './MediumButton' export { RadioButton } from './RadioButton' diff --git a/app/src/molecules/ODDBackButton/ODDBackButton.stories.tsx b/app/src/molecules/ODDBackButton/ODDBackButton.stories.tsx new file mode 100644 index 00000000000..c139db1b7ff --- /dev/null +++ b/app/src/molecules/ODDBackButton/ODDBackButton.stories.tsx @@ -0,0 +1,18 @@ +import * as React from 'react' +import { ODDBackButton } from '.' +import type { Story, Meta } from '@storybook/react' + +export default { + title: 'ODD/Molecules/ODDBackButton', + argTypes: { + onClick: { action: 'clicked' }, + }, +} as Meta + +const ODDBackButtonTemplate: Story< + React.ComponentProps +> = args => +export const ODDBackButtonComponent = ODDBackButtonTemplate.bind({}) +ODDBackButtonComponent.args = { + label: 'Previous location', +} diff --git a/app/src/molecules/ODDBackButton/__tests__/ODDBackButton.test.tsx b/app/src/molecules/ODDBackButton/__tests__/ODDBackButton.test.tsx new file mode 100644 index 00000000000..be004400d91 --- /dev/null +++ b/app/src/molecules/ODDBackButton/__tests__/ODDBackButton.test.tsx @@ -0,0 +1,32 @@ +import * as React from 'react' +import { renderWithProviders, COLORS } from '@opentrons/components' +import { ODDBackButton } from '..' + +const render = (props: React.ComponentProps) => { + return renderWithProviders()[0] +} + +describe('ODDBackButton', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + label: 'button label', + onClick: jest.fn(), + } + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + it('should render text and icon', () => { + const { getByText, getByTestId, getByRole } = render(props) + getByText('button label') + expect(getByTestId('back_icon')).toBeInTheDocument() + const button = getByRole('button') + expect(button).toHaveStyle(`background-color: ${COLORS.transparent}`) + button.click() + expect(props.onClick).toHaveBeenCalled() + }) +}) diff --git a/app/src/molecules/ODDBackButton/index.tsx b/app/src/molecules/ODDBackButton/index.tsx new file mode 100644 index 00000000000..a6de8279c11 --- /dev/null +++ b/app/src/molecules/ODDBackButton/index.tsx @@ -0,0 +1,42 @@ +import * as React from 'react' + +import { + Btn, + Flex, + Icon, + ALIGN_CENTER, + COLORS, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' + +export function ODDBackButton( + props: React.HTMLProps +): JSX.Element { + const { onClick, label } = props + + return ( + + + + + {label} + + ) +} diff --git a/app/src/organisms/InstrumentInfo.tsx/index.tsx b/app/src/organisms/InstrumentInfo/index.tsx similarity index 100% rename from app/src/organisms/InstrumentInfo.tsx/index.tsx rename to app/src/organisms/InstrumentInfo/index.tsx diff --git a/app/src/organisms/InstrumentMountItem.tsx/LabeledMount.tsx b/app/src/organisms/InstrumentMountItem.tsx/LabeledMount.tsx deleted file mode 100644 index 0837361260d..00000000000 --- a/app/src/organisms/InstrumentMountItem.tsx/LabeledMount.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import * as React from 'react' -import styled from 'styled-components' -import { - ALIGN_CENTER, - Flex, - SPACING, - TEXT_TRANSFORM_CAPITALIZE, - TYPOGRAPHY, - COLORS, - JUSTIFY_SPACE_BETWEEN, - Icon, - DIRECTION_COLUMN, - ALIGN_FLEX_START, - BORDERS, -} from '@opentrons/components' -import type { Mount } from '../../redux/pipettes/types' -import { StyledText } from '../../atoms/text' -import { useTranslation } from 'react-i18next' - -const MountButton = styled.button<{ isAttached: boolean }>` - display: flex; - width: 100%; - flex-direction: ${DIRECTION_COLUMN}; - align-items: ${ALIGN_FLEX_START}; - padding: ${SPACING.spacing5}; - border-radius: ${BORDERS.size_three}; - background-color: ${({ isAttached }) => - isAttached ? COLORS.green_three : COLORS.light_one}; - &:hover, - &:active, - &:focus { - background-color: ${({ isAttached }) => - isAttached ? COLORS.green_three_pressed : COLORS.light_one_pressed}; - } -` -interface LabeledMountProps { - mount: Mount | 'extension' - instrumentName: string | null - handleClick: React.MouseEventHandler -} - -export function LabeledMount(props: LabeledMountProps): JSX.Element { - const { t } = useTranslation('device_details') - const { mount, instrumentName, handleClick } = props - - return ( - - - - - {t('mount', { side: mount })} - - - {instrumentName == null ? t('empty') : instrumentName} - - - - - - ) -} diff --git a/app/src/organisms/InstrumentMountItem.tsx/index.tsx b/app/src/organisms/InstrumentMountItem.tsx/index.tsx deleted file mode 100644 index d732a890d2a..00000000000 --- a/app/src/organisms/InstrumentMountItem.tsx/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import * as React from 'react' -import { useHistory } from 'react-router-dom' - -import { - LEFT, - NINETY_SIX_CHANNEL, - SINGLE_MOUNT_PIPETTES, -} from '@opentrons/shared-data' -import type { Mount } from '../../redux/pipettes/types' -import { ChoosePipette } from '../PipetteWizardFlows/ChoosePipette' -import { FLOWS } from '../PipetteWizardFlows/constants' -import { PipetteWizardFlows } from '../PipetteWizardFlows' -import { GripperWizardFlows } from '../GripperWizardFlows' -import { GRIPPER_FLOW_TYPES } from '../GripperWizardFlows/constants' -import type { InstrumentData } from '@opentrons/api-client' -import type { SelectablePipettes } from '../PipetteWizardFlows/types' -import { LabeledMount } from './LabeledMount' - -interface InstrumentMountItemProps { - mount: Mount | 'extension' - attachedInstrument: InstrumentData | null - setWizardProps: ( - props: - | React.ComponentProps - | React.ComponentProps - | null - ) => void -} - -export function InstrumentMountItem( - props: InstrumentMountItemProps -): JSX.Element { - const history = useHistory() - const { mount, attachedInstrument, setWizardProps } = props - - const [showChoosePipetteModal, setShowChoosePipetteModal] = React.useState( - false - ) - const [ - selectedPipette, - setSelectedPipette, - ] = React.useState(SINGLE_MOUNT_PIPETTES) - - const handleClick: React.MouseEventHandler = () => { - if (attachedInstrument == null && mount !== 'extension') { - setShowChoosePipetteModal(true) - } else if (attachedInstrument == null && mount === 'extension') { - setWizardProps({ - flowType: GRIPPER_FLOW_TYPES.ATTACH, - attachedGripper: attachedInstrument, - closeFlow: () => setWizardProps(null), - }) - } else { - history.push(`/instruments/${mount}`) - } - } - return ( - <> - - {showChoosePipetteModal ? ( - { - setWizardProps({ - mount: - selectedPipette === NINETY_SIX_CHANNEL - ? LEFT - : (mount as Mount), - flowType: FLOWS.ATTACH, - selectedPipette, - setSelectedPipette, - closeFlow: () => { - setWizardProps(null) - setShowChoosePipetteModal(false) - }, - }) - setShowChoosePipetteModal(false) - }} - setSelectedPipette={setSelectedPipette} - selectedPipette={selectedPipette} - exit={() => { - setShowChoosePipetteModal(false) - }} - mount={mount as Mount} - /> - ) : null} - - ) -} diff --git a/app/src/organisms/ProtocolSetupInstruments/index.tsx b/app/src/organisms/ProtocolSetupInstruments/index.tsx index beb1f8cc0f2..87d32bdf8a3 100644 --- a/app/src/organisms/ProtocolSetupInstruments/index.tsx +++ b/app/src/organisms/ProtocolSetupInstruments/index.tsx @@ -14,7 +14,7 @@ import { useAllPipetteOffsetCalibrationsQuery, useInstrumentsQuery, } from '@opentrons/react-api-client' -import { BackButton } from '../../atoms/buttons' +import { ODDBackButton } from '../../molecules/ODDBackButton' import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' import { ProtocolInstrumentMountItem } from '../InstrumentMountItem' @@ -55,11 +55,10 @@ export function ProtocolSetupInstruments({ width="100%" gridGap={SPACING.spacing3} > - - setSetupScreen('prepare to run')}> - {t('instruments')} - - + setSetupScreen('prepare to run')} + /> { getByText('Labware') getByText('Labware Name') getByText('Location') - getByRole('button', { name: 'Deck Map' }) + getByRole('button', { name: 'Map View' }) }) - it('correctly navigates with the nav buttons', () => { + it('correctly navigates with the nav button', () => { const [{ getAllByRole }] = render() getAllByRole('button')[0].click() expect(mockSetSetupScreen).toHaveBeenCalledWith('prepare to run') @@ -108,8 +108,7 @@ describe('ProtocolSetupLabware', () => { it('should launch and close the deck map', () => { const [{ getByRole, getByText, getByTestId }] = render() - getByRole('button', { name: 'Deck Map' }).click() - getByText('Map View') + getByRole('button', { name: 'Map View' }).click() getByTestId('ModalHeader_icon_close_Map View').click() getByText('Labware') }) diff --git a/app/src/organisms/ProtocolSetupLabware/index.tsx b/app/src/organisms/ProtocolSetupLabware/index.tsx index 92331e878fb..44ac853acad 100644 --- a/app/src/organisms/ProtocolSetupLabware/index.tsx +++ b/app/src/organisms/ProtocolSetupLabware/index.tsx @@ -30,9 +30,9 @@ import { useModulesQuery, } from '@opentrons/react-api-client' +import { FloatingActionButton } from '../../atoms/buttons/OnDeviceDisplay' import { StyledText } from '../../atoms/text' -import { BackButton } from '../../atoms/buttons' -import { DeckMapButton } from '../ProtocolSetupModules' // Probably should move this to atoms/buttons as well +import { ODDBackButton } from '../../molecules/ODDBackButton' import { Portal } from '../../App/portal' import { Modal } from '../../molecules/Modal' @@ -241,7 +241,7 @@ export function ProtocolSetupLabware({ {getLabwareDisplayName(selectedLabware)} @@ -250,21 +250,26 @@ export function ProtocolSetupLabware({ ) : null} - - setSetupScreen('prepare to run')}> - {t('labware')} - - + setSetupScreen('prepare to run')} + /> - - + + {'Location'} - + {'Labware Name'} @@ -280,7 +285,7 @@ export function ProtocolSetupLabware({ ) : null })} - setShowDeckMapModal(true)} /> + setShowDeckMapModal(true)} /> ) } @@ -322,26 +327,24 @@ function LabwareLatch({ return ( - + {t('labware_latch')} {statusText != null && icon != null ? ( <> - + {t(statusText)} + {setupTextTranslator('labware_latch_instructions')} ) @@ -405,8 +410,7 @@ function RowLabware({ | HeaterShakerOpenLatchCreateCommand let latchStatus: LatchStatus = 'unknown' if ( - matchedModule != null && - matchedModule.attachedModuleMatch != null && + matchedModule?.attachedModuleMatch != null && matchedModule.attachedModuleMatch.moduleType === HEATERSHAKER_MODULE_TYPE ) { latchStatus = matchedModule.attachedModuleMatch.data.labwareLatchStatus @@ -441,12 +445,12 @@ function RowLabware({ - + {getLabwareDisplayLocation( robotSideAnalysis, @@ -456,15 +460,26 @@ function RowLabware({ - + {getLabwareDisplayName(definition)} - {nickName} + + {nickName} + {isOnHeaterShaker ? moduleInstructions : null} {isOnHeaterShaker ? ( diff --git a/app/src/organisms/ProtocolSetupLabwarePositionCheck/index.tsx b/app/src/organisms/ProtocolSetupLabwarePositionCheck/index.tsx index 06bc7018daf..938b813eef9 100644 --- a/app/src/organisms/ProtocolSetupLabwarePositionCheck/index.tsx +++ b/app/src/organisms/ProtocolSetupLabwarePositionCheck/index.tsx @@ -3,17 +3,15 @@ import { DIRECTION_COLUMN, Flex, JUSTIFY_CENTER, - JUSTIFY_SPACE_BETWEEN, SPACING, } from '@opentrons/components' import * as React from 'react' import { useTranslation } from 'react-i18next' -import { BackButton } from '../../atoms/buttons' -import { ContinueButton } from '../ProtocolSetupModules' import { MediumButton } from '../../atoms/buttons/OnDeviceDisplay' +import { ODDBackButton } from '../../molecules/ODDBackButton' +import { useLaunchLPC } from '../LabwarePositionCheck/useLaunchLPC' import type { SetupScreens } from '../../pages/OnDeviceDisplay/ProtocolSetup' -import { useLaunchLPC } from '../LabwarePositionCheck/useLaunchLPC' export interface ProtocolSetupLabwarePositionCheckProps { runId: string @@ -35,14 +33,10 @@ export function ProtocolSetupLabwarePositionCheck({ width="100%" gridGap={SPACING.spacing3} > - - setSetupScreen('labware')}> - {t('labware_position_check')} - - - setSetupScreen('liquids')} /> - - + setSetupScreen('prepare to run')} + /> -const mockBackButton = BackButton as jest.MockedFunction const mockgetTotalVolumePerLiquidId = getTotalVolumePerLiquidId as jest.MockedFunction< typeof getTotalVolumePerLiquidId > @@ -58,7 +56,6 @@ describe('ProtocolSetupLiquids', () => { MOCK_PROTOCOL_ANALYSIS as CompletedProtocolAnalysis ) mockLiquidDetails.mockReturnValue(
mock liquid details
) - mockBackButton.mockReturnValue(
mock back button
) mockgetTotalVolumePerLiquidId.mockReturnValue(50) }) @@ -67,7 +64,6 @@ describe('ProtocolSetupLiquids', () => { getByText('mock liquid 1') getByText('mock liquid 2') getAllByText('50 µL') - getByText('mock back button') getByLabelText('Liquids_1').click() getByText('mock liquid details') }) diff --git a/app/src/organisms/ProtocolSetupLiquids/index.tsx b/app/src/organisms/ProtocolSetupLiquids/index.tsx index 600c2277cb5..ab27758442c 100644 --- a/app/src/organisms/ProtocolSetupLiquids/index.tsx +++ b/app/src/organisms/ProtocolSetupLiquids/index.tsx @@ -16,8 +16,8 @@ import { parseLabwareInfoByLiquidId, } from '@opentrons/api-client' import { MICRO_LITERS, RunTimeCommand } from '@opentrons/shared-data' -import { BackButton } from '../../atoms/buttons' import { StyledText } from '../../atoms/text' +import { ODDBackButton } from '../../molecules/ODDBackButton' import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' import { getTotalVolumePerLiquidId } from '../Devices/ProtocolRun/SetupLiquids/utils' import { LiquidDetails } from './LiquidDetails' @@ -41,11 +41,10 @@ export function ProtocolSetupLiquids({ ) return ( <> - - setSetupScreen('prepare to run')}> - {t('liquids')} - - + setSetupScreen('prepare to run')} + /> { getByText('Module Name') getByText('Location') getByText('Status') - getByRole('button', { name: 'Modules' }) - getByRole('button', { name: 'Setup Instructions' }) - getByRole('button', { name: 'Deck Map' }) + getByText('Setup Instructions') + getByRole('button', { name: 'Map View' }) }) it('should launch deck map on button click', () => { - const [{ getByRole, getByText }] = render() + const [{ getByRole }] = render() - getByRole('button', { name: 'Deck Map' }).click() - getByText('Map View') + getByRole('button', { name: 'Map View' }).click() }) it('should launch setup instructions modal on button click', () => { - const [{ getByRole, getByText }] = render() + const [{ getByText }] = render() - getByRole('button', { name: 'Setup Instructions' }).click() + getByText('Setup Instructions').click() getByText('TODO: setup instructions modal') }) }) diff --git a/app/src/organisms/ProtocolSetupModules/index.tsx b/app/src/organisms/ProtocolSetupModules/index.tsx index 15182411e6d..c8e53981d5d 100644 --- a/app/src/organisms/ProtocolSetupModules/index.tsx +++ b/app/src/organisms/ProtocolSetupModules/index.tsx @@ -2,18 +2,19 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' import { - Btn, Flex, Icon, Module, RobotWorkSpace, ALIGN_CENTER, + ALIGN_FLEX_END, + BORDERS, COLORS, DIRECTION_COLUMN, + DIRECTION_ROW, JUSTIFY_SPACE_BETWEEN, SPACING, TYPOGRAPHY, - BORDERS, } from '@opentrons/components' import { getDeckDefFromRobotType, @@ -25,10 +26,15 @@ import { } from '@opentrons/shared-data' import { Portal } from '../../App/portal' -import { Banner } from '../../atoms/Banner' -import { BackButton } from '../../atoms/buttons' +import { + FloatingActionButton, + SmallButton, +} from '../../atoms/buttons/OnDeviceDisplay' +import { Chip } from '../../atoms/Chip' +import { InlineNotification } from '../../atoms/InlineNotification' import { Modal } from '../../molecules/Modal' import { StyledText } from '../../atoms/text' +import { ODDBackButton } from '../../molecules/ODDBackButton' import { useAttachedModules } from '../../organisms/Devices/hooks' import { ModuleInfo } from '../../organisms/Devices/ModuleInfo' import { MultipleModulesModal } from '../../organisms/Devices/ProtocolRun/SetupModules/MultipleModulesModal' @@ -63,27 +69,32 @@ function RowModule({ setShowMultipleModulesModal, }: RowModuleProps): JSX.Element { const { t } = useTranslation('protocol_setup') + const attachedModuleMatch = !!module.attachedModuleMatch return ( isDuplicateModuleModel ? setShowMultipleModulesModal(true) : null } > - + {getModuleDisplayName(module.moduleDef.model)} - + {/* TODO(bh, 2023-02-07): adjust slot location when hi-fi designs finalized */} {t('slot_location', { @@ -93,93 +104,26 @@ function RowModule({ : module.slotName, })} - - - - {module.attachedModuleMatch != null - ? t('module_connected') - : t('module_disconnected')} - {isDuplicateModuleModel ? ( - + ) : null} + ) } -function SetupInstructionsButton( - props: React.HTMLProps -): JSX.Element { - const { t } = useTranslation('protocol_setup') - return ( - - - - {t('setup_instructions')} - - - ) -} - -export function ContinueButton( - props: React.HTMLProps -): JSX.Element { - const { t } = useTranslation('shared') - return ( - - - {t('continue')} - - - ) -} - -export function DeckMapButton( - props: React.HTMLProps -): JSX.Element { - const { t } = useTranslation('protocol_setup') - return ( - - - {t('deck_map')} - - - ) -} - interface ProtocolSetupModulesProps { runId: string setSetupScreen: React.Dispatch> @@ -283,59 +227,80 @@ export function ProtocolSetupModules({ ) : null} - - setSetupScreen('prepare to run')}> - {t('modules')} - - + setSetupScreen('prepare to run')} + /> + setShowSetupInstructionsModal(true)} /> {isModuleMismatch && !clearModuleMismatchBanner ? ( - setClearModuleMismatchBanner(true)} - > - {`${t('module_mismatch_error')}. ${t('module_mismatch_body')}`} - + { + e.stopPropagation() + setClearModuleMismatchBanner(true) + }} + heading={t('module_mismatch_error')} + message={t('module_mismatch_body')} + /> ) : null} - - - {'Module Name'} - - - {'Location'} - - - {'Status'} + + + + {'Module Name'} + + + {'Location'} + + + {'Status'} + - - {attachedProtocolModuleMatches.map(module => { - // check for duplicate module model in list of modules for protocol - const isDuplicateModuleModel = protocolModulesInfo - // filter out current module - .filter(otherModule => otherModule.moduleId !== module.moduleId) - // check for existence of another module of same model - .some( - otherModule => - otherModule.moduleDef.model === module.moduleDef.model + {attachedProtocolModuleMatches.map(module => { + // check for duplicate module model in list of modules for protocol + const isDuplicateModuleModel = protocolModulesInfo + // filter out current module + .filter(otherModule => otherModule.moduleId !== module.moduleId) + // check for existence of another module of same model + .some( + otherModule => + otherModule.moduleDef.model === module.moduleDef.model + ) + return ( + ) - return ( - - ) - })} + })} + - setShowDeckMapModal(true)} /> + setShowDeckMapModal(true)} /> ) } diff --git a/app/src/pages/OnDeviceDisplay/InstrumentDetail.tsx b/app/src/pages/OnDeviceDisplay/InstrumentDetail.tsx index d12d402296f..67159dba20d 100644 --- a/app/src/pages/OnDeviceDisplay/InstrumentDetail.tsx +++ b/app/src/pages/OnDeviceDisplay/InstrumentDetail.tsx @@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom' import { useInstrumentsQuery } from '@opentrons/react-api-client' import { DIRECTION_COLUMN, Flex, SPACING } from '@opentrons/components' import { BackButton } from '../../atoms/buttons/BackButton' -import { InstrumentInfo } from '../../organisms/InstrumentInfo.tsx' +import { InstrumentInfo } from '../../organisms/InstrumentInfo' import type { InstrumentData } from '@opentrons/api-client' diff --git a/components/src/__tests__/__snapshots__/icons.test.tsx.snap b/components/src/__tests__/__snapshots__/icons.test.tsx.snap index 4375b493ed9..c9be3285513 100644 --- a/components/src/__tests__/__snapshots__/icons.test.tsx.snap +++ b/components/src/__tests__/__snapshots__/icons.test.tsx.snap @@ -717,6 +717,30 @@ exports[`icons comment renders correctly 1`] = ` `; +exports[`icons connection-status renders correctly 1`] = ` +.c0.spin { + -webkit-animation: GLFYz 0.8s steps(8) infinite; + animation: GLFYz 0.8s steps(8) infinite; + -webkit-transform-origin: center; + -ms-transform-origin: center; + transform-origin: center; +} + + +`; + exports[`icons content-copy renders correctly 1`] = ` .c0.spin { -webkit-animation: GLFYz 0.8s steps(8) infinite; @@ -813,6 +837,30 @@ exports[`icons cursor-move renders correctly 1`] = ` `; +exports[`icons deck-map renders correctly 1`] = ` +.c0.spin { + -webkit-animation: GLFYz 0.8s steps(8) infinite; + animation: GLFYz 0.8s steps(8) infinite; + -webkit-transform-origin: center; + -ms-transform-origin: center; + transform-origin: center; +} + + +`; + exports[`icons delete renders correctly 1`] = ` .c0.spin { -webkit-animation: GLFYz 0.8s steps(8) infinite; diff --git a/components/src/icons/icon-data.ts b/components/src/icons/icon-data.ts index 95d6d5e01d9..3aa72269142 100644 --- a/components/src/icons/icon-data.ts +++ b/components/src/icons/icon-data.ts @@ -39,6 +39,15 @@ export const ICON_DATA_BY_NAME = { path: 'M18 7.209L16.791 6 12 10.791 7.209 6 6 7.209 10.791 12 6 16.791 7.209 18 12 13.209 16.791 18 18 16.791 13.209 12z', }, + 'connection-status': { + viewBox: '0 0 24 24', + path: 'M 6, 12 a 6,6 0 1,1 12,0 a 6,6 0 1,1 -12,0', + }, + 'deck-map': { + viewBox: '0 0 60 48', + path: + 'M20.5713 8.57141H8.57129V15.4286H20.5713V8.57141Z M36.0856 8.57141H24.0856V15.4286H36.0856V8.57141Z M39.5999 8.57141H51.5999V15.4286H39.5999V8.57141Z M20.5713 20.5714H8.57129V27.4286H20.5713V20.5714Z M24.0856 20.5714H36.0856V27.4286H24.0856V20.5714Z M51.5999 20.5714H39.5999V27.4286H51.5999V20.5714Z M8.57129 32.5886H20.5713V39.4457H8.57129V32.5886Z M36.0856 32.5886H24.0856V39.4457H36.0856V32.5886Z M39.5999 32.5886H51.5999V39.4457H39.5999V32.5886Z', + }, refresh: { viewBox: '0 0 200 200', path: diff --git a/components/src/ui-style-constants/borders.ts b/components/src/ui-style-constants/borders.ts index 4375a9c6f08..0142a8aea2f 100644 --- a/components/src/ui-style-constants/borders.ts +++ b/components/src/ui-style-constants/borders.ts @@ -42,5 +42,5 @@ export const bigDropShadow = '0 3px 6px rgba(255, 0, 0, 1)' export const smallDropShadow = '0px 3px 6px rgba(0, 0, 0, 0.23)' // touch screen -export const shadowBig = '0px 3px 6px rgba(0, 0, 0, 0.23)' -export const shadowSmall = '0px 0px 40px rgba(0, 0, 0, 0.4)' +export const shadowBig = '0px 3px 6px rgba(0,0,0,0.23)' +export const shadowSmall = '0px 0px 40px rgba(0,0,0,0.4)'