From ee347d3839bec4b8bb9f9d7651ed019f49d54cbf Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Thu, 16 Mar 2023 14:17:51 -0400 Subject: [PATCH 01/25] Use new tabbede buttons for navigation [RCORE-684] --- app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx index 905d3dfdd90..b418ea803d4 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx @@ -19,9 +19,9 @@ import { import { ProtocolMetadata } from '@opentrons/shared-data' import { BackButton, - QuaternaryButton, TertiaryButton, } from '../../../atoms/buttons' +import { TabbedButton } from '../../../atoms/buttons/OnDeviceDisplay' import { StyledText } from '../../../atoms/text' import { Deck } from './Deck' import { Hardware } from './Hardware' @@ -83,12 +83,10 @@ const ProtocolSectionTabs = (props: ProtocolSectionTabsProps): JSX.Element => { return ( {protocolSectionTabOptions.map(option => { - const Button = - option === props.currentOption ? TertiaryButton : QuaternaryButton return ( - + ) })} From a3d5e57426ade172fee5a3397740084867f3b515 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Mon, 20 Mar 2023 16:42:44 -0400 Subject: [PATCH 02/25] Added medium button hifi component [RCORE-684] --- .../OnDeviceDisplay/MediumButton.stories.tsx | 49 +++++++ .../buttons/OnDeviceDisplay/MediumButton.tsx | 131 ++++++++++++++++++ .../__tests__/MediumButton.test.tsx | 81 +++++++++++ .../atoms/buttons/OnDeviceDisplay/index.ts | 1 + 4 files changed, 262 insertions(+) create mode 100644 app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx create mode 100644 app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx create mode 100644 app/src/atoms/buttons/OnDeviceDisplay/__tests__/MediumButton.test.tsx diff --git a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx new file mode 100644 index 00000000000..bfffbf7af1d --- /dev/null +++ b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx @@ -0,0 +1,49 @@ +import * as React from 'react' +import { MediumButton } from '.' +import type { Story, Meta } from '@storybook/react' + +export default { + title: 'ODD/Atoms/Buttons/MediumButton', + argTypes: { onClick: { action: 'clicked' } }, +} as Meta + +const MediumButtonTemplate: Story< + React.ComponentProps +> = args => + +export const PrimaryMediumButton = MediumButtonTemplate.bind({}) +PrimaryMediumButton.args = { + buttonText: 'Button text', + buttonType: 'primary', + disabled: false, +} +export const SecondaryMediumButton = MediumButtonTemplate.bind({}) +SecondaryMediumButton.args = { + buttonText: 'Button text', + buttonType: 'secondary', + disabled: false, +} +export const AlertMediumButton = MediumButtonTemplate.bind({}) +AlertMediumButton.args = { + buttonText: 'Button text', + buttonType: 'alert', + disabled: false, +} +export const AlertSecondaryMediumButton = MediumButtonTemplate.bind({}) +AlertSecondaryMediumButton.args = { + buttonText: 'Button text', + buttonType: 'alertSecondary', + disabled: false, +} +export const TertiaryMediumButton = MediumButtonTemplate.bind({}) +TertiaryMediumButton.args = { + buttonText: 'Button text', + buttonType: 'tertiary', + disabled: false, +} +export const TertiaryLightMediumButton = MediumButtonTemplate.bind({}) +TertiaryLightMediumButton.args = { + buttonText: 'Button text', + buttonType: 'tertiaryLight', + disabled: false, +} diff --git a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx new file mode 100644 index 00000000000..d11a7029f8e --- /dev/null +++ b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx @@ -0,0 +1,131 @@ +import * as React from 'react' +import { css } from 'styled-components' +import { + TYPOGRAPHY, + COLORS, + SPACING, + BORDERS, + NewPrimaryBtn, + styleProps, + DIRECTION_ROW, +} from '@opentrons/components' +import { StyledText } from '../../text' +import type { StyleProps } from '@opentrons/components' + +type MediumButtonTypes = + | 'primary' + | 'secondary' + | 'alert' + | 'alertSecondary' + | 'tertiary' + | 'tertiaryLight' +interface MediumButtonProps extends StyleProps { + onClick: () => void + buttonType?: MediumButtonTypes + buttonText: React.ReactNode + disabled?: boolean +} + +export function MediumButton(props: MediumButtonProps): JSX.Element { + const { onClick, buttonType = 'primary', buttonText, disabled = false } = props + const buttonProps = { + onClick, + disabled, + } + + const MEDIUM_BUTTON_PROPS_BY_TYPE: Record< + MediumButtonTypes, + { + defaultBackgroundColor: string + activeBackgroundColor: string + defaultColor: string + } + > = { + alert: { + defaultColor: COLORS.white, + defaultBackgroundColor: COLORS.red_two, + activeBackgroundColor: '#b91f20', + }, + alertSecondary: { + defaultColor: COLORS.red_one, + defaultBackgroundColor: COLORS.red_three, + activeBackgroundColor: '#ccabac', + }, + primary: { + defaultColor: COLORS.white, + defaultBackgroundColor: COLORS.blueEnabled, + activeBackgroundColor: '#045dd0', + }, + secondary: { + defaultColor: COLORS.darkBlackEnabled, + defaultBackgroundColor: COLORS.foundationalBlue, + activeBackgroundColor: '#94afd4', + }, + tertiary: { + defaultColor: COLORS.darkBlack_hundred, + defaultBackgroundColor: COLORS.white, + activeBackgroundColor: COLORS.darkBlack_twenty, + }, + tertiaryLight: { + defaultColor: COLORS.darkBlack_seventy, + defaultBackgroundColor: COLORS.white, + activeBackgroundColor: COLORS.darkBlack_twenty, + }, + } + + const MEDIUM_BUTTON_STYLE = css` + background-color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType] + .defaultBackgroundColor}; + border-radius: ${BORDERS.size_four}; + box-shadow: none; + color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType].defaultColor}; + cursor: default; + padding: ${SPACING.spacingM} ${SPACING.spacingXXL}; + text-align: ${TYPOGRAPHY.textAlignLeft}; + text-transform: ${TYPOGRAPHY.textTransformNone}; + + ${styleProps} + + &:focus { + background-color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType] + .defaultBackgroundColor}; + box-shadow: none; + } + &:hover { + border: none; + box-shadow: none; + background-color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType] + .defaultBackgroundColor}; + color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType].defaultColor}; + } + &:focus-visible { + box-shadow: 0 0 0 ${SPACING.spacingS} ${COLORS.fundamentalsFocus}; + } + + &:active { + background-color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType] + .activeBackgroundColor}; + } + + &:disabled { + background-color: ${COLORS.darkBlack_twenty}; + color: ${COLORS.darkBlack_sixty}; + } + ` + return ( + + + {buttonText} + + + ) +} diff --git a/app/src/atoms/buttons/OnDeviceDisplay/__tests__/MediumButton.test.tsx b/app/src/atoms/buttons/OnDeviceDisplay/__tests__/MediumButton.test.tsx new file mode 100644 index 00000000000..e345fa18e6f --- /dev/null +++ b/app/src/atoms/buttons/OnDeviceDisplay/__tests__/MediumButton.test.tsx @@ -0,0 +1,81 @@ +import * as React from 'react' +import { renderWithProviders, COLORS } from '@opentrons/components' + +import { MediumButton } from '../MediumButton' + +const render = (props: React.ComponentProps) => { + return renderWithProviders()[0] +} + +describe('MediumButton', () => { + let props: React.ComponentProps + beforeEach(() => { + props = { + onClick: jest.fn(), + buttonType: 'primary', + buttonText: 'Medium button', + } + }) + it('renders the default button and it works as expected', () => { + const { getByText, getByRole } = render(props) + getByText('Medium button').click() + expect(props.onClick).toHaveBeenCalled() + expect(getByRole('button')).toHaveStyle( + `background-color: ${COLORS.blueEnabled}` + ) + }) + it('renders the alert button', () => { + props = { + ...props, + buttonType: 'alert', + } + const { getByRole } = render(props) + expect(getByRole('button')).toHaveStyle( + `background-color: ${COLORS.red_two}` + ) + }) + it('renders the secondary button', () => { + props = { + ...props, + buttonType: 'secondary', + } + const { getByRole } = render(props) + expect(getByRole('button')).toHaveStyle( + `background-color: ${COLORS.foundationalBlue}` + ) + }) + it('renders the secondary alert button', () => { + props = { + ...props, + buttonType: 'alertSecondary', + } + const { getByRole } = render(props) + expect(getByRole('button')).toHaveStyle( + `background-color: ${COLORS.red_three}` + ) + }) + it('renders the tertiary button', () => { + props = { + ...props, + buttonType: 'tertiary', + } + const { getByRole } = render(props) + expect(getByRole('button')).toHaveStyle(`background-color: ${COLORS.white}`) + }) + it('renders the tertiary light button', () => { + props = { + ...props, + buttonType: 'tertiaryLight', + } + const { getByRole } = render(props) + expect(getByRole('button')).toHaveStyle(`background-color: ${COLORS.white}`) + }) + it('renders the button as disabled', () => { + props = { + ...props, + disabled: true, + } + const { getByRole } = render(props) + expect(getByRole('button')).toBeDisabled() + }) +}) diff --git a/app/src/atoms/buttons/OnDeviceDisplay/index.ts b/app/src/atoms/buttons/OnDeviceDisplay/index.ts index 67cb396fe21..07f98f219af 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/index.ts +++ b/app/src/atoms/buttons/OnDeviceDisplay/index.ts @@ -1,4 +1,5 @@ export { LargeButton } from './LargeButton' +export { MediumButton } from './MediumButton' export { MediumButtonRounded } from './MediumButtonRounded' export { SmallButton } from './SmallButton' export { TabbedButton } from './TabbedButton' From 985e0b2039d83e3699378e2f01e575b884df527b Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Mon, 20 Mar 2023 17:25:17 -0400 Subject: [PATCH 03/25] Add icon to medium buttons [RCORE-684] --- .../OnDeviceDisplay/MediumButton.stories.tsx | 6 ++ .../buttons/OnDeviceDisplay/MediumButton.tsx | 96 +++++++++++++------ .../__tests__/MediumButton.test.tsx | 8 ++ 3 files changed, 80 insertions(+), 30 deletions(-) diff --git a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx index bfffbf7af1d..a6179212c8e 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx @@ -47,3 +47,9 @@ TertiaryLightMediumButton.args = { buttonType: 'tertiaryLight', disabled: false, } +export const CustomIconMediumButton = MediumButtonTemplate.bind({}) +CustomIconMediumButton.args = { + buttonText: 'Button text', + buttonType: 'primary', + iconName: 'restart', +} diff --git a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx index d11a7029f8e..630760361bb 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx @@ -1,16 +1,19 @@ import * as React from 'react' import { css } from 'styled-components' import { - TYPOGRAPHY, - COLORS, - SPACING, + ALIGN_CENTER, BORDERS, + COLORS, + DIRECTION_ROW, + Flex, + Icon, NewPrimaryBtn, + SPACING, styleProps, - DIRECTION_ROW, + TYPOGRAPHY, } from '@opentrons/components' import { StyledText } from '../../text' -import type { StyleProps } from '@opentrons/components' +import type { IconName, StyleProps } from '@opentrons/components' type MediumButtonTypes = | 'primary' @@ -20,56 +23,70 @@ type MediumButtonTypes = | 'tertiary' | 'tertiaryLight' interface MediumButtonProps extends StyleProps { - onClick: () => void - buttonType?: MediumButtonTypes buttonText: React.ReactNode + buttonType?: MediumButtonTypes disabled?: boolean + iconName?: IconName + onClick: () => void } export function MediumButton(props: MediumButtonProps): JSX.Element { - const { onClick, buttonType = 'primary', buttonText, disabled = false } = props - const buttonProps = { + const { + buttonText, + buttonType = 'primary', + disabled = false, + iconName, onClick, + } = props + const buttonProps = { disabled, + onClick, } const MEDIUM_BUTTON_PROPS_BY_TYPE: Record< MediumButtonTypes, { - defaultBackgroundColor: string activeBackgroundColor: string + defaultBackgroundColor: string defaultColor: string + iconColor: string } > = { alert: { - defaultColor: COLORS.white, - defaultBackgroundColor: COLORS.red_two, activeBackgroundColor: '#b91f20', + defaultBackgroundColor: COLORS.red_two, + defaultColor: COLORS.white, + iconColor: COLORS.white, }, alertSecondary: { - defaultColor: COLORS.red_one, - defaultBackgroundColor: COLORS.red_three, activeBackgroundColor: '#ccabac', + defaultBackgroundColor: COLORS.red_three, + defaultColor: COLORS.red_one, + iconColor: COLORS.red_one, }, primary: { - defaultColor: COLORS.white, - defaultBackgroundColor: COLORS.blueEnabled, activeBackgroundColor: '#045dd0', + defaultBackgroundColor: COLORS.blueEnabled, + defaultColor: COLORS.white, + iconColor: COLORS.white, }, secondary: { - defaultColor: COLORS.darkBlackEnabled, - defaultBackgroundColor: COLORS.foundationalBlue, activeBackgroundColor: '#94afd4', + defaultBackgroundColor: COLORS.foundationalBlue, + defaultColor: COLORS.darkBlackEnabled, + iconColor: COLORS.blueEnabled, }, tertiary: { - defaultColor: COLORS.darkBlack_hundred, - defaultBackgroundColor: COLORS.white, activeBackgroundColor: COLORS.darkBlack_twenty, + defaultBackgroundColor: COLORS.white, + defaultColor: COLORS.darkBlack_hundred, + iconColor: COLORS.darkBlack_hundred, }, tertiaryLight: { - defaultColor: COLORS.darkBlack_seventy, - defaultBackgroundColor: COLORS.white, activeBackgroundColor: COLORS.darkBlack_twenty, + defaultBackgroundColor: COLORS.white, + defaultColor: COLORS.darkBlack_seventy, + iconColor: COLORS.darkBlack_seventy, }, } @@ -92,10 +109,10 @@ export function MediumButton(props: MediumButtonProps): JSX.Element { box-shadow: none; } &:hover { - border: none; - box-shadow: none; background-color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType] .defaultBackgroundColor}; + border: none; + box-shadow: none; color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType].defaultColor}; } &:focus-visible { @@ -119,13 +136,32 @@ export function MediumButton(props: MediumButtonProps): JSX.Element { aria-label={`MediumButton_${buttonType}`} flexDirection={DIRECTION_ROW} > - - {buttonText} - + {iconName !== null && ( + + )} + + {buttonText} + + ) } diff --git a/app/src/atoms/buttons/OnDeviceDisplay/__tests__/MediumButton.test.tsx b/app/src/atoms/buttons/OnDeviceDisplay/__tests__/MediumButton.test.tsx index e345fa18e6f..b1e7a1f349f 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/__tests__/MediumButton.test.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/__tests__/MediumButton.test.tsx @@ -78,4 +78,12 @@ describe('MediumButton', () => { const { getByRole } = render(props) expect(getByRole('button')).toBeDisabled() }) + it('renders custom icon in the button', () => { + props = { + ...props, + iconName: 'restart', + } + const { getByLabelText } = render(props) + getByLabelText('MediumButton_restart') + }) }) From fb4a6ef9c9b10f91f72cccffbdb874471c2b30bc Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Mon, 20 Mar 2023 20:42:55 -0400 Subject: [PATCH 04/25] Add new 'Start setup' text [RCORE-684] --- app/src/assets/localization/en/protocol_details.json | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/assets/localization/en/protocol_details.json b/app/src/assets/localization/en/protocol_details.json index 69dca68a3ec..cdc40836941 100644 --- a/app/src/assets/localization/en/protocol_details.json +++ b/app/src/assets/localization/en/protocol_details.json @@ -29,6 +29,7 @@ "robot_is_busy_with_protocol": "{{robotName}} is busy with {{protocolName}} in {{runStatus}} state. Do you want to clear it and proceed?", "run_protocol": "Run protocol", "show_in_folder": "Show in folder", + "start_setup": "Start setup", "unavailable_robot_not_listed": "{{count}} unavailable robot is not listed.", "unavailable_robot_not_listed_plural": "{{count}} unavailable robots are not listed.", "unavailable_or_busy_robot_not_listed": "{{count}} unavailable or busy robot is not listed.", From 3c8a4915cda3af4a84e1247a6795f78de75d067a Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Mon, 20 Mar 2023 21:21:26 -0400 Subject: [PATCH 05/25] Removed unnecessary medium rounded button component [RCORE-684] --- .../MediumButtonRounded.stories.tsx | 17 ---- .../OnDeviceDisplay/MediumButtonRounded.tsx | 37 --------- .../__tests__/MediumButtonRounded.test.tsx | 80 ------------------- .../atoms/buttons/OnDeviceDisplay/index.ts | 1 - 4 files changed, 135 deletions(-) delete mode 100644 app/src/atoms/buttons/OnDeviceDisplay/MediumButtonRounded.stories.tsx delete mode 100644 app/src/atoms/buttons/OnDeviceDisplay/MediumButtonRounded.tsx delete mode 100644 app/src/atoms/buttons/OnDeviceDisplay/__tests__/MediumButtonRounded.test.tsx diff --git a/app/src/atoms/buttons/OnDeviceDisplay/MediumButtonRounded.stories.tsx b/app/src/atoms/buttons/OnDeviceDisplay/MediumButtonRounded.stories.tsx deleted file mode 100644 index e71099ae25c..00000000000 --- a/app/src/atoms/buttons/OnDeviceDisplay/MediumButtonRounded.stories.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react' -import { MediumButtonRounded } from '.' -import type { Story, Meta } from '@storybook/react' - -export default { - title: 'ODD/Atoms/Buttons/MediumButtonRounded', - argTypes: { onClick: { action: 'clicked' } }, -} as Meta - -const MediumButtonRoundedTemplate: Story< - React.ComponentProps -> = args => -export const MediumRounded = MediumButtonRoundedTemplate.bind({}) -MediumRounded.args = { - children: 'Button text', - title: 'medium button rounded', -} diff --git a/app/src/atoms/buttons/OnDeviceDisplay/MediumButtonRounded.tsx b/app/src/atoms/buttons/OnDeviceDisplay/MediumButtonRounded.tsx deleted file mode 100644 index 5c5e2514042..00000000000 --- a/app/src/atoms/buttons/OnDeviceDisplay/MediumButtonRounded.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import styled from 'styled-components' -import { - TYPOGRAPHY, - COLORS, - SPACING, - BORDERS, - NewPrimaryBtn, - styleProps, -} from '@opentrons/components' - -export const MediumButtonRounded = styled(NewPrimaryBtn)` - background-color: ${COLORS.blueEnabled}; - border-radius: ${BORDERS.size_six}; - box-shadow: none; - height: '4.25rem'; - font-size: ${TYPOGRAPHY.fontSize28}; - font-weight: ${TYPOGRAPHY.fontWeightSemiBold}; - line-height: ${TYPOGRAPHY.lineHeight36}; - padding: ${SPACING.spacing4} ${SPACING.spacing6}; - text-transform: ${TYPOGRAPHY.textTransformNone}; - width: '13.375rem'; - - ${styleProps} - - &:focus-visible { - box-shadow: 0 0 0 3px ${COLORS.fundamentalsFocus}; - } - - &:active { - background-color: ${COLORS.blueEnabled}; - } - - &:disabled { - background-color: ${COLORS.darkBlack_twenty}; - color: ${COLORS.darkBlack_sixty}; - } -` diff --git a/app/src/atoms/buttons/OnDeviceDisplay/__tests__/MediumButtonRounded.test.tsx b/app/src/atoms/buttons/OnDeviceDisplay/__tests__/MediumButtonRounded.test.tsx deleted file mode 100644 index 033180808d9..00000000000 --- a/app/src/atoms/buttons/OnDeviceDisplay/__tests__/MediumButtonRounded.test.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import * as React from 'react' -import { - renderWithProviders, - COLORS, - SPACING, - TYPOGRAPHY, -} from '@opentrons/components' - -import { MediumButtonRounded } from '..' - -const render = (props: React.ComponentProps) => { - return renderWithProviders()[0] -} - -describe('MediumButtonRounded', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - children: 'medium button rounded', - } - }) - - it('renders medium button rounded with text', () => { - const { getByText } = render(props) - const button = getByText('medium button rounded') - expect(button).toHaveStyle( - `background-color: ${String(COLORS.blueEnabled)}` - ) - expect(button).toHaveStyle( - `padding: ${String(SPACING.spacing4)} ${String(SPACING.spacing6)}` - ) - expect(button).toHaveStyle(`font-size: ${String(TYPOGRAPHY.fontSize28)}`) - expect(button).toHaveStyle( - `font-weight: ${String(TYPOGRAPHY.fontWeightSemiBold)}` - ) - expect(button).toHaveStyle( - `line-height: ${String(TYPOGRAPHY.lineHeight36)}` - ) - expect(button).toHaveStyle(`border-radius: 60px`) - expect(button).toHaveStyle( - `text-transform: ${String(TYPOGRAPHY.textTransformNone)}` - ) - expect(button).toHaveStyle(`box-shadow: none`) - expect(button).toHaveStyle(`color: ${String(COLORS.white)}`) - }) - - it('renders medium button rounded with text and disabled', () => { - props.disabled = true - const { getByText } = render(props) - const button = getByText('medium button rounded') - expect(button).toBeDisabled() - expect(button).toHaveStyle(`background-color: #16212d33`) - expect(button).toHaveStyle(`color: #16212d99`) - }) - - it('applies the correct states to the medium button rounded - active', () => { - const { getByText } = render(props) - const button = getByText('medium button rounded') - expect(button).toHaveStyleRule( - 'background-color', - `${COLORS.blueEnabled}`, - { - modifier: ':active', - } - ) - }) - - it('applies the correct states to the medium button rounded - focus-visible', () => { - const { getByText } = render(props) - const button = getByText('medium button rounded') - expect(button).toHaveStyleRule( - 'box-shadow', - `0 0 0 3px ${String(COLORS.fundamentalsFocus)}`, - { - modifier: ':focus-visible', - } - ) - }) -}) diff --git a/app/src/atoms/buttons/OnDeviceDisplay/index.ts b/app/src/atoms/buttons/OnDeviceDisplay/index.ts index 07f98f219af..b9b39e1adb1 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/index.ts +++ b/app/src/atoms/buttons/OnDeviceDisplay/index.ts @@ -1,5 +1,4 @@ export { LargeButton } from './LargeButton' export { MediumButton } from './MediumButton' -export { MediumButtonRounded } from './MediumButtonRounded' export { SmallButton } from './SmallButton' export { TabbedButton } from './TabbedButton' From d5e3ca5f29e8ab6161047380eefcd76efefda84a Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Mon, 20 Mar 2023 21:30:57 -0400 Subject: [PATCH 06/25] Improve linting on LargeButton component [RCORE-684] --- app/src/atoms/buttons/OnDeviceDisplay/LargeButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/atoms/buttons/OnDeviceDisplay/LargeButton.tsx b/app/src/atoms/buttons/OnDeviceDisplay/LargeButton.tsx index 3c004ed7f47..37a7d81bf83 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/LargeButton.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/LargeButton.tsx @@ -118,7 +118,7 @@ export function LargeButton(props: LargeButtonProps): JSX.Element { name={iconName ?? 'play'} aria-label={`LargeButton_${iconName ?? 'play'}`} color={ - disabled + disabled === true ? COLORS.darkBlack_sixty : LARGE_BUTTON_PROPS_BY_TYPE[buttonType].iconColor } From da6e48f00c28b834f8b1914f441cc6036354ae7c Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Mon, 20 Mar 2023 21:32:20 -0400 Subject: [PATCH 07/25] Add new typography for protocol header [RCORE-684] --- components/src/ui-style-constants/typography.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/src/ui-style-constants/typography.ts b/components/src/ui-style-constants/typography.ts index 7aef6722755..15ba6682ac2 100644 --- a/components/src/ui-style-constants/typography.ts +++ b/components/src/ui-style-constants/typography.ts @@ -2,6 +2,7 @@ import { css } from 'styled-components' import { COLORS } from './' // Font Sizes +export const fontSize38 = '2.375rem' // 38px export const fontSize28 = '1.75rem' // 28px export const fontSize22 = '1.375rem' // 22px export const fontSize20 = '1.25rem' // 20px @@ -17,11 +18,13 @@ export const fontSizeCaption = '0.625rem' // 10px // Font Weights export const fontWeightBold = 800 +export const fontWeightNearlyBold = 700 export const fontWeightSemiBold = 600 export const fontWeightRegular = 400 export const fontWeightLight = 300 // Line Heights +export const lineHeight48 = '3rem' // 48px export const lineHeight36 = '2.25rem' // 36px export const lineHeight28 = '1.75rem' // 28px export const lineHeight24 = '1.5rem' // 24px From 8fc6238f4cab3a9be009bcf357707b31820a8b8f Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Mon, 20 Mar 2023 22:34:57 -0400 Subject: [PATCH 08/25] Set max number of pins as an app constant [RCORE-684] --- app/src/App/constants.ts | 3 +++ .../OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx | 4 +--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/App/constants.ts b/app/src/App/constants.ts index c155812bbfe..73380a6da99 100644 --- a/app/src/App/constants.ts +++ b/app/src/App/constants.ts @@ -1,3 +1,6 @@ // defines a constant for the nav bar width - used in run log component to calculate centering export const NAV_BAR_WIDTH = '5.625rem' export const SLEEP_NEVER_MS = 604800000 + +// What is the maximum number of protocols one can pin? This many. +export const MAXIMUM_PINNED_PROTOCOLS = 8 diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx index 30647329872..c10dbc9a8d1 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx @@ -18,6 +18,7 @@ import { // TODO useDeleteProtocolMutation, } from '@opentrons/react-api-client' +import { MAXIMUM_PINNED_PROTOCOLS } from '../../../App/constants' import { StyledText } from '../../../atoms/text' import { ModalShell } from '../../../molecules/Modal' import { getPinnedProtocolIds, updateConfigValue } from '../../../redux/config' @@ -27,9 +28,6 @@ import type { Dispatch } from '../../../redux/types' import type { UseLongPressResult } from '@opentrons/components' import type { ProtocolResource } from '@opentrons/shared-data' -// What is the maximum number of protocols one can pin? This many. -const MAXIMUM_PINNED_PROTOCOLS = 8 - export function LongPressModal(props: { longpress: UseLongPressResult protocol: ProtocolResource From 76cdf07127531f12c57062158d39c8c912d26140 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Mon, 20 Mar 2023 23:14:40 -0400 Subject: [PATCH 09/25] Generalize Too Many Pins modal [RCORE-684] --- .../ProtocolDashboard/LongPressModal.tsx | 6 ++++-- .../{ProtocolDashboard => }/TooManyPinsModal.tsx | 14 ++++---------- .../__tests__/TooManyPinsModal.test.tsx | 15 +++++---------- 3 files changed, 13 insertions(+), 22 deletions(-) rename app/src/pages/OnDeviceDisplay/{ProtocolDashboard => }/TooManyPinsModal.tsx (83%) rename app/src/pages/OnDeviceDisplay/{ProtocolDashboard => }/__tests__/TooManyPinsModal.test.tsx (51%) diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx index c10dbc9a8d1..235d308afde 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx @@ -22,7 +22,7 @@ import { MAXIMUM_PINNED_PROTOCOLS } from '../../../App/constants' import { StyledText } from '../../../atoms/text' import { ModalShell } from '../../../molecules/Modal' import { getPinnedProtocolIds, updateConfigValue } from '../../../redux/config' -import { TooManyPinsModal } from './TooManyPinsModal' +import { TooManyPinsModal } from '../TooManyPinsModal' import type { Dispatch } from '../../../redux/types' import type { UseLongPressResult } from '@opentrons/components' @@ -101,7 +101,9 @@ export function LongPressModal(props: { return ( <> {showMaxPinsAlert ? ( - + longpress?.setIsLongPressed(false)} + /> ) : ( void }): JSX.Element { - const { longpress } = props + const { handleCloseMaxPinsAlert } = props const { t } = useTranslation('protocol_info') - const handleCloseMaxPinsAlert = (): void => { - longpress.setIsLongPressed(false) - } - return ( { const reactRouterDom = jest.requireActual('react-router-dom') return { @@ -16,10 +13,10 @@ jest.mock('react-router-dom', () => { } }) -const render = (longPress: UseLongPressResult) => { +const render = () => { return renderWithProviders( - + {}}/> , { i18nInstance: i18n, @@ -29,9 +26,7 @@ const render = (longPress: UseLongPressResult) => { describe('Too Many Pins Modal', () => { it('should have a close button', () => { - const { result } = renderHook(() => useLongPress()) - result.current.isLongPressed = true - const [{ getByText }] = render(result.current) + const [{ getByText }] = render() getByText('Got it') }) }) From 384991ee62240df3c0784663a34a5a97532e9c29 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Tue, 21 Mar 2023 00:26:15 -0400 Subject: [PATCH 10/25] Apply HiFi stylings to Too Many Pins Modal ORCORE-684] --- .../assets/localization/en/protocol_info.json | 4 ++-- .../TooManyPinsModal.stories.tsx | 13 ++++++++++++ .../OnDeviceDisplay/TooManyPinsModal.tsx | 21 +++++++++++-------- .../__tests__/TooManyPinsModal.test.tsx | 4 ++-- 4 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 app/src/pages/OnDeviceDisplay/TooManyPinsModal.stories.tsx diff --git a/app/src/assets/localization/en/protocol_info.json b/app/src/assets/localization/en/protocol_info.json index 7615373ee11..9587571a436 100644 --- a/app/src/assets/localization/en/protocol_info.json +++ b/app/src/assets/localization/en/protocol_info.json @@ -3,6 +3,7 @@ "choose_file": "Choose File...", "choose_protocol_file": "Choose File", "choose_snippet_type": "Choose the Labware Offset Data Python Snippet based on target execution environment.", + "close": "Close", "continue_proceed_to_calibrate": "Proceed to Calibrate", "continue_verify_calibrations": "Verify pipette and labware calibrations", "creation_method": "Creation Method", @@ -14,7 +15,6 @@ "estimated_run_time": "Estimated Run Time", "error_message_no_steps": "This protocol has no steps in it - there's nothing for your robot to do! Your protocol needs at least one aspirate/dispense to import properly", "get_labware_offset_data": "Get Labware Offset Data", - "got_it": "Got it", "import_a_file": "Import a protocol to get started", "instrument_not_attached": "Not attached", "instrument_cal_data_title": "Calibration data", @@ -41,7 +41,7 @@ "required_cal_data_title": "Calibration Data", "run_protocol": "Run protocol", "simulation_in_progress": "Simulation in Progress", - "too_many_pins_header": "You’ve hit your limit of pinned protocols!", + "too_many_pins_header": "You've hit your max!", "too_many_pins_body": "Remove a protocol in order to add more protocols to your pinned list.", "unpin_protocol": "Unpin protocol", "update_robot_for_custom_labware": "You have custom labware definitions saved to your app, but this robot needs to be updated before you can use these definitions with Python protocols", diff --git a/app/src/pages/OnDeviceDisplay/TooManyPinsModal.stories.tsx b/app/src/pages/OnDeviceDisplay/TooManyPinsModal.stories.tsx new file mode 100644 index 00000000000..2946381a745 --- /dev/null +++ b/app/src/pages/OnDeviceDisplay/TooManyPinsModal.stories.tsx @@ -0,0 +1,13 @@ +import * as React from 'react' +import { TooManyPinsModal } from './TooManyPinsModal' +import type { Story, Meta } from '@storybook/react' + +export default { + title: 'ODD/Organisms/Modals/TooManyPinsModal', + argTypes: { onClick: { action: 'clicked' } }, +} as Meta + +const TooManyPinsModalTemplate: Story< + React.ComponentProps +> = args => +export const TooManyPinsModalx = TooManyPinsModalTemplate.bind({}) diff --git a/app/src/pages/OnDeviceDisplay/TooManyPinsModal.tsx b/app/src/pages/OnDeviceDisplay/TooManyPinsModal.tsx index 19c34480456..61bb75a0aa9 100644 --- a/app/src/pages/OnDeviceDisplay/TooManyPinsModal.tsx +++ b/app/src/pages/OnDeviceDisplay/TooManyPinsModal.tsx @@ -21,7 +21,6 @@ export function TooManyPinsModal(props: { return ( @@ -31,16 +30,19 @@ export function TooManyPinsModal(props: { padding={SPACING.spacingXXL} > {t('too_many_pins_header')} {t('too_many_pins_body')} @@ -55,11 +57,12 @@ export function TooManyPinsModal(props: { > - {t('got_it')} + {t('close')} diff --git a/app/src/pages/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx b/app/src/pages/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx index 7a6b35f979b..b998f9d5b6a 100644 --- a/app/src/pages/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx +++ b/app/src/pages/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx @@ -16,7 +16,7 @@ jest.mock('react-router-dom', () => { const render = () => { return renderWithProviders( - {}}/> + {}} /> , { i18nInstance: i18n, @@ -27,6 +27,6 @@ const render = () => { describe('Too Many Pins Modal', () => { it('should have a close button', () => { const [{ getByText }] = render() - getByText('Got it') + getByText('Close') }) }) From 9cd661c8828d0c26bb79e7852231498e1bda156d Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Tue, 21 Mar 2023 10:08:16 -0400 Subject: [PATCH 11/25] Added back chevron icon svg [RCORE-684] --- .../__snapshots__/icons.test.tsx.snap | 24 +++++++++++++++++++ components/src/icons/icon-data.ts | 5 ++++ 2 files changed, 29 insertions(+) diff --git a/components/src/__tests__/__snapshots__/icons.test.tsx.snap b/components/src/__tests__/__snapshots__/icons.test.tsx.snap index dcbf91f7451..ef24615a39e 100644 --- a/components/src/__tests__/__snapshots__/icons.test.tsx.snap +++ b/components/src/__tests__/__snapshots__/icons.test.tsx.snap @@ -141,6 +141,30 @@ exports[`icons arrow-right renders correctly 1`] = ` `; +exports[`icons back 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 book-open-page-variant 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 010da1b62d5..cfaff2c8307 100644 --- a/components/src/icons/icon-data.ts +++ b/components/src/icons/icon-data.ts @@ -14,6 +14,11 @@ export const ICON_DATA_BY_NAME = { path: 'M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z', }, + back: { + viewBox: '0 0 19 34', + path: + 'M16.6667 33.6668L0 17.0002L16.6667 0.333496L19 2.7085L4.70833 17.0002L19 31.2918L16.6667 33.6668Z', + }, circle: { viewBox: '0 0 24 24', path: From 69e672e54f74ae764d266583b819ad2ceb1fdb3b Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Tue, 21 Mar 2023 10:30:03 -0400 Subject: [PATCH 12/25] Added optional width to medium buttons [RCORE-684] --- .../atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx | 7 +++++++ app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx index a6179212c8e..412ac579f12 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx @@ -53,3 +53,10 @@ CustomIconMediumButton.args = { buttonType: 'primary', iconName: 'restart', } +export const CustomWideIconMediumButton = MediumButtonTemplate.bind({}) +CustomWideIconMediumButton.args = { + buttonText: 'Pin protocol', + buttonType: 'secondary', + iconName: 'push-pin', + width: '30.375rem', +} diff --git a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx index 630760361bb..40b46f5a645 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx @@ -28,6 +28,7 @@ interface MediumButtonProps extends StyleProps { disabled?: boolean iconName?: IconName onClick: () => void + width?: string } export function MediumButton(props: MediumButtonProps): JSX.Element { @@ -37,6 +38,7 @@ export function MediumButton(props: MediumButtonProps): JSX.Element { disabled = false, iconName, onClick, + width, } = props const buttonProps = { disabled, @@ -135,13 +137,14 @@ export function MediumButton(props: MediumButtonProps): JSX.Element { css={MEDIUM_BUTTON_STYLE} aria-label={`MediumButton_${buttonType}`} flexDirection={DIRECTION_ROW} + width={width} > - {iconName !== null && ( + {iconName !== undefined && ( Date: Tue, 21 Mar 2023 10:40:41 -0400 Subject: [PATCH 13/25] Protocol details page hifi design [RCORE-684] --- .../OnDeviceDisplay/ProtocolDetails/index.tsx | 229 +++++++++++++----- 1 file changed, 166 insertions(+), 63 deletions(-) diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx index b418ea803d4..ef4b8f18d9a 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx @@ -1,14 +1,21 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' +import { useDispatch, useSelector } from 'react-redux' import { useHistory, useParams } from 'react-router-dom' import { format } from 'date-fns' import { + ALIGN_CENTER, + BORDERS, + Btn, COLORS, DIRECTION_COLUMN, + DIRECTION_ROW, Flex, + JUSTIFY_SPACE_BETWEEN, Icon, - SIZE_2, + NewPrimaryBtn, SPACING, + truncateString, TYPOGRAPHY, } from '@opentrons/components' import { @@ -16,50 +23,88 @@ import { useDeleteProtocolMutation, useProtocolQuery, } from '@opentrons/react-api-client' -import { ProtocolMetadata } from '@opentrons/shared-data' +import { ProtocolResource } from '@opentrons/shared-data' +import { MAXIMUM_PINNED_PROTOCOLS } from '../../../App/constants' import { - BackButton, - TertiaryButton, -} from '../../../atoms/buttons' -import { TabbedButton } from '../../../atoms/buttons/OnDeviceDisplay' + MediumButton, + TabbedButton, +} from '../../../atoms/buttons/OnDeviceDisplay' +import { Chip } from '../../../atoms/chip' import { StyledText } from '../../../atoms/text' +import { getPinnedProtocolIds, updateConfigValue } from '../../../redux/config' import { Deck } from './Deck' import { Hardware } from './Hardware' import { Labware } from './Labware' +import { TooManyPinsModal } from '../TooManyPinsModal' +import type { Dispatch } from '../../../redux/types' import type { OnDeviceRouteParams } from '../../../App/types' -type ProtocolType = 'json' | 'python' -type CreationMethod = 'Protocol Designer' | 'Python' - -const getCreationMethod = (protocolType: ProtocolType): CreationMethod => - protocolType === 'json' ? 'Protocol Designer' : 'Python' - const ProtocolHeader = (props: { title: string - date: number | null - protocolType: ProtocolType handleRunProtocol: () => void }): JSX.Element => { + const history = useHistory() const { t } = useTranslation(['protocol_info, protocol_details', 'shared']) - const { title, date, protocolType, handleRunProtocol } = props + const { title, handleRunProtocol } = props + + const [truncate, setTruncate] = React.useState(true) + const toggleTruncate = (): void => setTruncate(value => !value) + + let displayedTitle = title + + if (title.length > 92 && truncate) { + displayedTitle = truncateString(title, 92, 69) + } return ( - - - {title} - - {t('protocol_details:run_protocol')} - + + + history.goBack()}> + + + + + + + + {displayedTitle} + + - - {`${t('protocol_info:date_added')}: ${ - date != null - ? format(new Date(date), 'MM/dd/yyyy') - : t('shared:no_data') - }`} - {`${t( - 'protocol_details:creation_method' - )}: ${getCreationMethod(protocolType)}`} + + + + {t('protocol_details:start_setup')} + + ) @@ -84,7 +129,11 @@ const ProtocolSectionTabs = (props: ProtocolSectionTabsProps): JSX.Element => { {protocolSectionTabOptions.map(option => { return ( - props.setCurrentOption(option)}> + props.setCurrentOption(option)} + > {option} ) @@ -96,25 +145,50 @@ const ProtocolSectionTabs = (props: ProtocolSectionTabsProps): JSX.Element => { const Summary = (props: { author: string | null description: string | null + date: string | null }): JSX.Element => { const { t } = useTranslation('protocol_details') return ( - - {`${t('author')}: `} + + {`${t( + 'author' + )}: `} {props.author} - {props.description} + + {props.description} + + + {`${t('protocol_info:date_added')}: ${ + props.date != null + ? format(new Date(props.date), 'MM/dd/yyyy k:mm') + : t('shared:no_data') + }`} + ) } interface ProtocolSectionContentProps { protocolId: string - metadata: ProtocolMetadata + protocolData: ProtocolResource currentOption: TabOption } const ProtocolSectionContent = ( @@ -125,8 +199,9 @@ const ProtocolSectionContent = ( case 'Summary': protocolSection = ( ) break @@ -146,22 +221,43 @@ const ProtocolSectionContent = ( } export function ProtocolDetails(): JSX.Element | null { - const { t } = useTranslation('protocol_details') + const { t } = useTranslation(['protocol_details', 'protocol_info']) const { protocolId } = useParams() + const dispatch = useDispatch() const history = useHistory() const [currentOption, setCurrentOption] = React.useState( protocolSectionTabOptions[0] ) + const [showMaxPinsAlert, setShowMaxPinsAlert] = React.useState(false) const { data: protocolRecord } = useProtocolQuery(protocolId, { staleTime: Infinity, }) + let pinnedProtocolIds = useSelector(getPinnedProtocolIds) ?? [] + const pinned = pinnedProtocolIds.includes(protocolId) + const { createRun } = useCreateRunMutation({ onSuccess: data => { const runId: string = data.data.id history.push(`/protocols/${runId}/setup`) }, }) + + const handlePinClick = (): void => { + if (!pinned) { + if (pinnedProtocolIds.length === MAXIMUM_PINNED_PROTOCOLS) { + setShowMaxPinsAlert(true) + } else { + pinnedProtocolIds.push(protocolId) + } + } else { + pinnedProtocolIds = pinnedProtocolIds.filter(p => p !== protocolId) + } + dispatch( + updateConfigValue('protocols.pinnedProtocolIds', pinnedProtocolIds) + ) + } + const handleRunProtocol = (): void => { createRun({ protocolId }) } @@ -169,19 +265,20 @@ export function ProtocolDetails(): JSX.Element | null { const { deleteProtocol } = useDeleteProtocolMutation(protocolId) if (protocolRecord == null) return null - const displayName = protocolRecord?.data.metadata.protocolName ?? protocolRecord?.data.files[0].name return ( - + {showMaxPinsAlert && ( + setShowMaxPinsAlert(false)} + /> + )} - - + + { deleteProtocol() history.goBack() }} - > - - - {t('delete_protocol')} - - + width="30.375rem" + /> ) From 2ca0e44d175b83b7d586cbb156abf1f0006933b1 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Tue, 21 Mar 2023 11:14:54 -0400 Subject: [PATCH 14/25] Hardware and Labware hifi styling [RCORE-684] --- .../ProtocolDetails/Hardware.tsx | 73 +++++++++++++------ .../ProtocolDetails/Labware.tsx | 8 +- .../__tests__/Hardware.test.tsx | 9 +-- 3 files changed, 56 insertions(+), 34 deletions(-) diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/Hardware.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/Hardware.tsx index cd4ded74ede..306d37e3553 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/Hardware.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/Hardware.tsx @@ -1,11 +1,12 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import { COLORS, SPACING, TYPOGRAPHY } from '@opentrons/components' +import { BORDERS, COLORS, SPACING, TYPOGRAPHY } from '@opentrons/components' import { getModuleDisplayName, getPipetteNameSpecs, } from '@opentrons/shared-data' +import { StyledText } from '../../../atoms/text' import { useRequiredProtocolHardware } from '../../Protocols/hooks' import type { ProtocolHardware } from '../../Protocols/hooks' import type { TFunction } from 'react-i18next' @@ -17,7 +18,7 @@ const Table = styled('table')` width: 100%; border-spacing: 0 ${SPACING.spacing2}; margin: ${SPACING.spacing4} 0; - text-align: left; + text-align: ${TYPOGRAPHY.textAlignLeft}; ` const TableHeader = styled('th')` text-transform: ${TYPOGRAPHY.textTransformCapitalize}; @@ -27,22 +28,24 @@ const TableHeader = styled('th')` ` const TableRow = styled('tr')` + background-color: ${COLORS.light_one}; border: 1px ${COLORS.white} solid; - height: 4rem; + height: 4.75rem; ` const TableDatum = styled('td')` + font-size: ${TYPOGRAPHY.fontSize22}; + font-weight: ${TYPOGRAPHY.lineHeight28}; padding: ${SPACING.spacing2}; white-space: break-spaces; text-overflow: wrap; - text-transform: ${TYPOGRAPHY.textTransformCapitalize}; &:first-child { - border-top-left-radius: 10px; - border-bottom-left-radius: 10px; + border-top-left-radius: ${BORDERS.size_four}; + border-bottom-left-radius: ${BORDERS.size_four}; } &:last-child { - border-top-right-radius: 10px; - border-bottom-right-radius: 10px; + border-top-right-radius: ${BORDERS.size_four}; + border-bottom-right-radius: ${BORDERS.size_four}; } ` @@ -73,27 +76,51 @@ export const Hardware = (props: { protocolId: string }): JSX.Element => { - {t('location')} - {t('hardware')} - {t('connection_status')} + + + {t('location')} + + + + + {t('hardware')} + + {requiredProtocolHardware.map((hardware, id) => { - const isConnected = hardware.connected return ( - - {getHardwareLocation(hardware, t)} - {getHardwareName(hardware)} + + + + {getHardwareLocation(hardware, t)} + + - {isConnected ? t('connected') : t('not_connected')} + + {getHardwareName(hardware)} + ) diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/Labware.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/Labware.tsx index 40c90a39755..c798cd6df56 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/Labware.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/Labware.tsx @@ -33,6 +33,7 @@ const TableHeader = styled('th')` ` const TableRow = styled('tr')` + background-color: ${COLORS.light_one}; border: 1px ${COLORS.white} solid; height: 4.75rem; ` @@ -105,12 +106,7 @@ export const Labware = (props: { protocolId: string }): JSX.Element => { li => getLabwareDisplayName(li.definition) === name )?.definition return ( - + { const { getByRole } = render(props)[0] getByRole('columnheader', { name: 'location' }) getByRole('columnheader', { name: 'hardware' }) - getByRole('columnheader', { name: 'connection status' }) }) it('should render the correct location, name, and connected status in each table row', () => { const { getByRole } = render(props)[0] - getByRole('row', { name: 'left mount P10 Single-Channel GEN1 connected' }) + getByRole('row', { name: 'left mount P10 Single-Channel GEN1' }) getByRole('row', { - name: 'right mount P1000 Single-Channel GEN1 not connected', + name: 'right mount P1000 Single-Channel GEN1', }) - getByRole('row', { name: 'Slot 1 Heater-Shaker Module GEN1 connected' }) - getByRole('row', { name: 'Slot 3 Temperature Module GEN2 not connected' }) + getByRole('row', { name: 'Slot 1 Heater-Shaker Module GEN1' }) + getByRole('row', { name: 'Slot 3 Temperature Module GEN2' }) }) }) From ea946f28cd97c7144330a8c35c2204228cbda47e Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Tue, 21 Mar 2023 12:05:52 -0400 Subject: [PATCH 15/25] Fixed case of Chip import [RCORE-684] --- app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx index ef4b8f18d9a..68c7672fdcb 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx @@ -29,7 +29,7 @@ import { MediumButton, TabbedButton, } from '../../../atoms/buttons/OnDeviceDisplay' -import { Chip } from '../../../atoms/chip' +import { Chip } from '../../../atoms/Chip' import { StyledText } from '../../../atoms/text' import { getPinnedProtocolIds, updateConfigValue } from '../../../redux/config' import { Deck } from './Deck' From 8775c181d6be4ce80b8df4e5943aebbdfebcbb10 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Tue, 21 Mar 2023 15:32:12 -0400 Subject: [PATCH 16/25] Fixed TooManyPins storybook name [RCORE-684] --- app/src/pages/OnDeviceDisplay/TooManyPinsModal.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/pages/OnDeviceDisplay/TooManyPinsModal.stories.tsx b/app/src/pages/OnDeviceDisplay/TooManyPinsModal.stories.tsx index 2946381a745..9bc3034a1c8 100644 --- a/app/src/pages/OnDeviceDisplay/TooManyPinsModal.stories.tsx +++ b/app/src/pages/OnDeviceDisplay/TooManyPinsModal.stories.tsx @@ -10,4 +10,4 @@ export default { const TooManyPinsModalTemplate: Story< React.ComponentProps > = args => -export const TooManyPinsModalx = TooManyPinsModalTemplate.bind({}) +export const TooManyPins = TooManyPinsModalTemplate.bind({}) From 7ba2748301868d0da6db3cfb229e3a1c5df67ee7 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Tue, 21 Mar 2023 17:51:34 -0400 Subject: [PATCH 17/25] Added unit tests for core protocol details odd page [RCORE-684] --- .../__tests__/ProtocolDetails.test.tsx | 113 ++++++++++++++++++ .../OnDeviceDisplay/ProtocolDetails/index.tsx | 2 +- 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx new file mode 100644 index 00000000000..7dc8c849578 --- /dev/null +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx @@ -0,0 +1,113 @@ +import * as React from 'react' +import { Route } from 'react-router' +import { MemoryRouter } from 'react-router-dom' +import '@testing-library/jest-dom' +import { renderWithProviders } from '@opentrons/components' +import { + useCreateRunMutation, + useDeleteProtocolMutation, + useProtocolQuery, +} from '@opentrons/react-api-client' +import { i18n } from '../../../../i18n' +import { ProtocolDetails } from '..' + +jest.mock('@opentrons/react-api-client') + +const mockCreateRun = jest.fn((id: string) => {}) +const mockDeleteProtocol = jest.fn((id: string) => {}) +const mockUseCreateRunMutation = useCreateRunMutation as jest.MockedFunction< + typeof useCreateRunMutation +> +const mockUseDeleteProtocolMutation = useDeleteProtocolMutation as jest.MockedFunction< + typeof useDeleteProtocolMutation +> +const mockUseProtocolQuery = useProtocolQuery as jest.MockedFunction< + typeof useProtocolQuery +> + +const render = (path = '/protocols/fakeProtocolId') => { + return renderWithProviders( + + + + + , + { + i18nInstance: i18n, + } + ) +} + +describe('ODDProtocolDetails', () => { + beforeEach(() => { + mockUseCreateRunMutation.mockReturnValue({ + createRun: mockCreateRun, + } as any) + mockUseDeleteProtocolMutation.mockReturnValue({ + deleteProtocol: mockDeleteProtocol, + } as any) + mockUseProtocolQuery.mockReturnValue({ + data: { + data: { + id: 'mockProtocol1', + createdAt: '2022-05-03T21:36:12.494778+00:00', + protocolType: 'json', + metadata: { + protocolName: + 'Nextera XT DNA Library Prep Kit Protocol: Part 1/4 - Tagment Genomic DNA and Amplify Libraries', + author: 'engineering testing division', + description: 'A short mock protocol', + created: 1606853851893, + tags: ['unitTest'], + }, + analysisSummaries: [], + files: [], + key: '26ed5a82-502f-4074-8981-57cdda1d066d', + }, + }, + } as any) + }) + + it('renders protocol truncated name that expands when clicked', () => { + const [{ getByText }] = render() + const name = getByText( + 'Nextera XT DNA Library Prep Kit Protocol: Part 1/4 - Tagment Genomic ...nd Amplify Libraries' + ) + name.click() + getByText( + 'Nextera XT DNA Library Prep Kit Protocol: Part 1/4 - Tagment Genomic DNA and Amplify Libraries' + ) + }) + it('renders the start setup button', () => { + const [{ getByRole }] = render() + getByRole('button', { name: 'Start setup' }) + }) + it('renders the protocol author', () => { + const [{ getByText }] = render() + getByText('engineering testing division') + }) + it('renders the protocol description', () => { + const [{ getByText }] = render() + getByText('A short mock protocol') + }) + it('renders the protocol date added', () => { + const [{ getByText }] = render() + getByText('Date Added: 05/03/2022') + }) + it('renders the pin protocol button', () => { + const [{ getByText }] = render() + getByText('Pin protocol') + }) + it('renders the delete protocol button', () => { + const [{ getByText }] = render() + getByText('Delete protocol') + }) + it('renders the navigation buttons', () => { + const [{ getByRole }] = render() + getByRole('button', { name: 'Summary' }) + getByRole('button', { name: 'Hardware' }) + getByRole('button', { name: 'Labware' }) + getByRole('button', { name: 'Liquids' }) + getByRole('button', { name: 'Initial Deck Layout' }) + }) +}) diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx index 68c7672fdcb..16f778c1c98 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx @@ -307,7 +307,7 @@ export function ProtocolDetails(): JSX.Element | null { width="30.375rem" /> { From c28ed8666b4616faa3b44f10aaa606850ac3d713 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Tue, 21 Mar 2023 21:13:32 -0400 Subject: [PATCH 18/25] Made date/time test timezone independent [RCORE-684] --- .../ProtocolDetails/__tests__/ProtocolDetails.test.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx index 7dc8c849578..b582f84e35b 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx @@ -1,6 +1,7 @@ import * as React from 'react' import { Route } from 'react-router' import { MemoryRouter } from 'react-router-dom' +import { format } from 'date-fns' import '@testing-library/jest-dom' import { renderWithProviders } from '@opentrons/components' import { @@ -92,7 +93,12 @@ describe('ODDProtocolDetails', () => { }) it('renders the protocol date added', () => { const [{ getByText }] = render() - getByText('Date Added: 05/03/2022') + getByText( + `Date Added: ${format( + new Date('2022-05-03T21:36:12.494778+00:00'), + 'MM/dd/yyyy k:mm' + )}` + ) }) it('renders the pin protocol button', () => { const [{ getByText }] = render() From d79030bdeb8e30a7f215c978f7081bf35ce03ef1 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Wed, 22 Mar 2023 22:17:48 -0400 Subject: [PATCH 19/25] Naming and code style changes from PR feedback. No logic changes. [RCORE-684] --- .../assets/localization/en/protocol_info.json | 1 - .../buttons/OnDeviceDisplay/LargeButton.tsx | 2 +- .../OnDeviceDisplay/MediumButton.stories.tsx | 4 +- .../buttons/OnDeviceDisplay/MediumButton.tsx | 16 ++++--- .../__tests__/MediumButton.test.tsx | 4 +- .../ProtocolDetails/Hardware.tsx | 10 ++++- .../ProtocolDetails/Labware.tsx | 3 +- .../OnDeviceDisplay/ProtocolDetails/index.tsx | 44 +++++++++++-------- .../OnDeviceDisplay/TooManyPinsModal.tsx | 7 +-- .../__tests__/TooManyPinsModal.test.tsx | 2 +- .../src/ui-style-constants/typography.ts | 2 +- 11 files changed, 57 insertions(+), 38 deletions(-) diff --git a/app/src/assets/localization/en/protocol_info.json b/app/src/assets/localization/en/protocol_info.json index 9587571a436..dfb9fe53010 100644 --- a/app/src/assets/localization/en/protocol_info.json +++ b/app/src/assets/localization/en/protocol_info.json @@ -3,7 +3,6 @@ "choose_file": "Choose File...", "choose_protocol_file": "Choose File", "choose_snippet_type": "Choose the Labware Offset Data Python Snippet based on target execution environment.", - "close": "Close", "continue_proceed_to_calibrate": "Proceed to Calibrate", "continue_verify_calibrations": "Verify pipette and labware calibrations", "creation_method": "Creation Method", diff --git a/app/src/atoms/buttons/OnDeviceDisplay/LargeButton.tsx b/app/src/atoms/buttons/OnDeviceDisplay/LargeButton.tsx index 37a7d81bf83..3c004ed7f47 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/LargeButton.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/LargeButton.tsx @@ -118,7 +118,7 @@ export function LargeButton(props: LargeButtonProps): JSX.Element { name={iconName ?? 'play'} aria-label={`LargeButton_${iconName ?? 'play'}`} color={ - disabled === true + disabled ? COLORS.darkBlack_sixty : LARGE_BUTTON_PROPS_BY_TYPE[buttonType].iconColor } diff --git a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx index 412ac579f12..27740bb2964 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.stories.tsx @@ -38,13 +38,13 @@ AlertSecondaryMediumButton.args = { export const TertiaryMediumButton = MediumButtonTemplate.bind({}) TertiaryMediumButton.args = { buttonText: 'Button text', - buttonType: 'tertiary', + buttonType: 'tertiaryHigh', disabled: false, } export const TertiaryLightMediumButton = MediumButtonTemplate.bind({}) TertiaryLightMediumButton.args = { buttonText: 'Button text', - buttonType: 'tertiaryLight', + buttonType: 'tertiaryHighLight', disabled: false, } export const CustomIconMediumButton = MediumButtonTemplate.bind({}) diff --git a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx index 40b46f5a645..c9c1d983665 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx @@ -20,8 +20,8 @@ type MediumButtonTypes = | 'secondary' | 'alert' | 'alertSecondary' - | 'tertiary' - | 'tertiaryLight' + | 'tertiaryHigh' + | 'tertiaryHighLight' interface MediumButtonProps extends StyleProps { buttonText: React.ReactNode buttonType?: MediumButtonTypes @@ -55,36 +55,40 @@ export function MediumButton(props: MediumButtonProps): JSX.Element { } > = { alert: { + // TODO(ew, 3/22/23): replaces these hex codes with the color constants activeBackgroundColor: '#b91f20', defaultBackgroundColor: COLORS.red_two, defaultColor: COLORS.white, iconColor: COLORS.white, }, alertSecondary: { + // TODO(ew, 3/22/23): replaces these hex codes with the color constants activeBackgroundColor: '#ccabac', defaultBackgroundColor: COLORS.red_three, defaultColor: COLORS.red_one, iconColor: COLORS.red_one, }, primary: { + // TODO(ew, 3/22/23): replaces these hex codes with the color constants activeBackgroundColor: '#045dd0', defaultBackgroundColor: COLORS.blueEnabled, defaultColor: COLORS.white, iconColor: COLORS.white, }, secondary: { + // TODO(ew, 3/22/23): replaces these hex codes with the color constants activeBackgroundColor: '#94afd4', defaultBackgroundColor: COLORS.foundationalBlue, defaultColor: COLORS.darkBlackEnabled, iconColor: COLORS.blueEnabled, }, - tertiary: { + tertiaryHigh: { activeBackgroundColor: COLORS.darkBlack_twenty, defaultBackgroundColor: COLORS.white, defaultColor: COLORS.darkBlack_hundred, iconColor: COLORS.darkBlack_hundred, }, - tertiaryLight: { + tertiaryHighLight: { activeBackgroundColor: COLORS.darkBlack_twenty, defaultBackgroundColor: COLORS.white, defaultColor: COLORS.darkBlack_seventy, @@ -153,8 +157,8 @@ export function MediumButton(props: MediumButtonProps): JSX.Element { ? COLORS.darkBlack_sixty : MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType].iconColor } - width="1.875rem" - height="1.875rem" + width={SPACING.spacingXL} + height={SPACING.spacingXL} /> )} { it('renders the tertiary button', () => { props = { ...props, - buttonType: 'tertiary', + buttonType: 'tertiaryHigh', } const { getByRole } = render(props) expect(getByRole('button')).toHaveStyle(`background-color: ${COLORS.white}`) @@ -65,7 +65,7 @@ describe('MediumButton', () => { it('renders the tertiary light button', () => { props = { ...props, - buttonType: 'tertiaryLight', + buttonType: 'tertiaryHighLight', } const { getByRole } = render(props) expect(getByRole('button')).toHaveStyle(`background-color: ${COLORS.white}`) diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/Hardware.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/Hardware.tsx index 306d37e3553..4946d2d80e6 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/Hardware.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/Hardware.tsx @@ -1,7 +1,13 @@ import * as React from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import { BORDERS, COLORS, SPACING, TYPOGRAPHY } from '@opentrons/components' +import { + BORDERS, + COLORS, + SPACING, + TYPOGRAPHY, + WRAP, +} from '@opentrons/components' import { getModuleDisplayName, getPipetteNameSpecs, @@ -38,7 +44,7 @@ const TableDatum = styled('td')` font-weight: ${TYPOGRAPHY.lineHeight28}; padding: ${SPACING.spacing2}; white-space: break-spaces; - text-overflow: wrap; + text-overflow: ${WRAP}; &:first-child { border-top-left-radius: ${BORDERS.size_four}; border-bottom-left-radius: ${BORDERS.size_four}; diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/Labware.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/Labware.tsx index c798cd6df56..859fc741c66 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/Labware.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/Labware.tsx @@ -10,6 +10,7 @@ import { Icon, SPACING, TYPOGRAPHY, + WRAP, } from '@opentrons/components' import { getLabwareDisplayName } from '@opentrons/shared-data' @@ -43,7 +44,7 @@ const TableDatum = styled('td')` font-weight: ${TYPOGRAPHY.lineHeight28}; padding: ${SPACING.spacing2}; white-space: break-spaces; - text-overflow: wrap; + text-overflow: ${WRAP}; &:first-child { border-top-left-radius: ${BORDERS.size_four}; border-bottom-left-radius: ${BORDERS.size_four}; diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx index 16f778c1c98..aedff9e815b 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx @@ -74,13 +74,17 @@ const ProtocolHeader = (props: { color={COLORS.darkBlack_hundred} /> - + @@ -98,7 +102,7 @@ const ProtocolHeader = (props: { > @@ -125,14 +129,15 @@ interface ProtocolSectionTabsProps { setCurrentOption: (option: TabOption) => void } const ProtocolSectionTabs = (props: ProtocolSectionTabsProps): JSX.Element => { + const { currentOption, setCurrentOption } = props return ( {protocolSectionTabOptions.map(option => { return ( props.setCurrentOption(option)} + onClick={() => setCurrentOption(option)} > {option} @@ -147,6 +152,7 @@ const Summary = (props: { description: string | null date: string | null }): JSX.Element => { + const { author, description, date } = props const { t } = useTranslation('protocol_details') return ( @@ -159,17 +165,18 @@ const Summary = (props: { {`${t( 'author' )}: `} - {props.author} + {author} - {props.description} + {description} {`${t('protocol_info:date_added')}: ${ - props.date != null - ? format(new Date(props.date), 'MM/dd/yyyy k:mm') + date != null + ? format(new Date(date), 'MM/dd/yyyy k:mm') : t('shared:no_data') }`} @@ -194,27 +201,28 @@ interface ProtocolSectionContentProps { const ProtocolSectionContent = ( props: ProtocolSectionContentProps ): JSX.Element => { + const { protocolId, protocolData, currentOption } = props let protocolSection - switch (props.currentOption) { + switch (currentOption) { case 'Summary': protocolSection = ( ) break case 'Hardware': - protocolSection = + protocolSection = break case 'Labware': - protocolSection = + protocolSection = break case 'Liquids': break case 'Initial Deck Layout': - protocolSection = + protocolSection = break } return {protocolSection} @@ -302,14 +310,14 @@ export function ProtocolDetails(): JSX.Element | null { : t('protocol_info:pin_protocol') } buttonType="secondary" - iconName={'push-pin'} + iconName="push-pin" onClick={handlePinClick} width="30.375rem" /> { deleteProtocol() history.goBack() diff --git a/app/src/pages/OnDeviceDisplay/TooManyPinsModal.tsx b/app/src/pages/OnDeviceDisplay/TooManyPinsModal.tsx index 61bb75a0aa9..6e2101c2eb8 100644 --- a/app/src/pages/OnDeviceDisplay/TooManyPinsModal.tsx +++ b/app/src/pages/OnDeviceDisplay/TooManyPinsModal.tsx @@ -16,7 +16,7 @@ export function TooManyPinsModal(props: { handleCloseMaxPinsAlert: () => void }): JSX.Element { const { handleCloseMaxPinsAlert } = props - const { t } = useTranslation('protocol_info') + const { t } = useTranslation(['protocol_info', 'shared']) return ( @@ -61,8 +61,9 @@ export function TooManyPinsModal(props: { fontWeight={TYPOGRAPHY.fontWeightSemiBold} lineHeight={TYPOGRAPHY.lineHeight28} textAlign={TYPOGRAPHY.textAlignCenter} + textTransform={TYPOGRAPHY.textTransformCapitalize} > - {t('close')} + {t('shared:close')} diff --git a/app/src/pages/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx b/app/src/pages/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx index b998f9d5b6a..4d9a2a39fa9 100644 --- a/app/src/pages/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx +++ b/app/src/pages/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx @@ -27,6 +27,6 @@ const render = () => { describe('Too Many Pins Modal', () => { it('should have a close button', () => { const [{ getByText }] = render() - getByText('Close') + getByText('close') }) }) diff --git a/components/src/ui-style-constants/typography.ts b/components/src/ui-style-constants/typography.ts index 15ba6682ac2..7d962d35c33 100644 --- a/components/src/ui-style-constants/typography.ts +++ b/components/src/ui-style-constants/typography.ts @@ -18,7 +18,7 @@ export const fontSizeCaption = '0.625rem' // 10px // Font Weights export const fontWeightBold = 800 -export const fontWeightNearlyBold = 700 +export const fontWeightLevel2_bold = 700 export const fontWeightSemiBold = 600 export const fontWeightRegular = 400 export const fontWeightLight = 300 From 7e14e772a2f0e3601e73963754103fd1323a7b16 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Wed, 22 Mar 2023 22:48:39 -0400 Subject: [PATCH 20/25] Moved the TooManyPins modal into its forever home [RCORE-684] --- .../Modal}/OnDeviceDisplay/TooManyPinsModal.stories.tsx | 2 +- .../Modal}/OnDeviceDisplay/TooManyPinsModal.tsx | 4 ++-- .../OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx | 2 +- app/src/molecules/Modal/OnDeviceDisplay/index.ts | 1 + .../OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx | 2 +- app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) rename app/src/{pages => molecules/Modal}/OnDeviceDisplay/TooManyPinsModal.stories.tsx (89%) rename app/src/{pages => molecules/Modal}/OnDeviceDisplay/TooManyPinsModal.tsx (95%) rename app/src/{pages => molecules/Modal}/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx (94%) create mode 100644 app/src/molecules/Modal/OnDeviceDisplay/index.ts diff --git a/app/src/pages/OnDeviceDisplay/TooManyPinsModal.stories.tsx b/app/src/molecules/Modal/OnDeviceDisplay/TooManyPinsModal.stories.tsx similarity index 89% rename from app/src/pages/OnDeviceDisplay/TooManyPinsModal.stories.tsx rename to app/src/molecules/Modal/OnDeviceDisplay/TooManyPinsModal.stories.tsx index 9bc3034a1c8..eea2d12dc23 100644 --- a/app/src/pages/OnDeviceDisplay/TooManyPinsModal.stories.tsx +++ b/app/src/molecules/Modal/OnDeviceDisplay/TooManyPinsModal.stories.tsx @@ -3,7 +3,7 @@ import { TooManyPinsModal } from './TooManyPinsModal' import type { Story, Meta } from '@storybook/react' export default { - title: 'ODD/Organisms/Modals/TooManyPinsModal', + title: 'ODD/Molecules/Modals/TooManyPinsModal', argTypes: { onClick: { action: 'clicked' } }, } as Meta diff --git a/app/src/pages/OnDeviceDisplay/TooManyPinsModal.tsx b/app/src/molecules/Modal/OnDeviceDisplay/TooManyPinsModal.tsx similarity index 95% rename from app/src/pages/OnDeviceDisplay/TooManyPinsModal.tsx rename to app/src/molecules/Modal/OnDeviceDisplay/TooManyPinsModal.tsx index 6e2101c2eb8..8e21ad28c3e 100644 --- a/app/src/pages/OnDeviceDisplay/TooManyPinsModal.tsx +++ b/app/src/molecules/Modal/OnDeviceDisplay/TooManyPinsModal.tsx @@ -9,8 +9,8 @@ import { BORDERS, } from '@opentrons/components' -import { StyledText } from '../../atoms/text' -import { ModalShell } from '../../molecules/Modal' +import { StyledText } from '../../../atoms/text' +import { ModalShell } from '../../../molecules/Modal' export function TooManyPinsModal(props: { handleCloseMaxPinsAlert: () => void diff --git a/app/src/pages/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx b/app/src/molecules/Modal/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx similarity index 94% rename from app/src/pages/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx rename to app/src/molecules/Modal/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx index 4d9a2a39fa9..49014ba0d40 100644 --- a/app/src/pages/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx +++ b/app/src/molecules/Modal/OnDeviceDisplay/__tests__/TooManyPinsModal.test.tsx @@ -3,7 +3,7 @@ import { MemoryRouter } from 'react-router-dom' import { renderWithProviders } from '@opentrons/components' -import { i18n } from '../../../i18n' +import { i18n } from '../../../../i18n' import { TooManyPinsModal } from '../TooManyPinsModal' jest.mock('react-router-dom', () => { diff --git a/app/src/molecules/Modal/OnDeviceDisplay/index.ts b/app/src/molecules/Modal/OnDeviceDisplay/index.ts new file mode 100644 index 00000000000..232929bbbed --- /dev/null +++ b/app/src/molecules/Modal/OnDeviceDisplay/index.ts @@ -0,0 +1 @@ +export { TooManyPinsModal } from './TooManyPinsModal' diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx index 235d308afde..165a854765f 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDashboard/LongPressModal.tsx @@ -21,8 +21,8 @@ import { import { MAXIMUM_PINNED_PROTOCOLS } from '../../../App/constants' import { StyledText } from '../../../atoms/text' import { ModalShell } from '../../../molecules/Modal' +import { TooManyPinsModal } from '../../../molecules/Modal/OnDeviceDisplay' import { getPinnedProtocolIds, updateConfigValue } from '../../../redux/config' -import { TooManyPinsModal } from '../TooManyPinsModal' import type { Dispatch } from '../../../redux/types' import type { UseLongPressResult } from '@opentrons/components' diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx index aedff9e815b..24cca2f3736 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx @@ -31,11 +31,11 @@ import { } from '../../../atoms/buttons/OnDeviceDisplay' import { Chip } from '../../../atoms/Chip' import { StyledText } from '../../../atoms/text' +import { TooManyPinsModal } from '../../../molecules/Modal/OnDeviceDisplay' import { getPinnedProtocolIds, updateConfigValue } from '../../../redux/config' import { Deck } from './Deck' import { Hardware } from './Hardware' import { Labware } from './Labware' -import { TooManyPinsModal } from '../TooManyPinsModal' import type { Dispatch } from '../../../redux/types' import type { OnDeviceRouteParams } from '../../../App/types' From 5f3d30f766c6e0fa0d589be797c1fb1c4621cf4a Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Wed, 22 Mar 2023 22:58:01 -0400 Subject: [PATCH 21/25] Removed box shadow from TabbedButton focus state [RCORE-684] --- .../buttons/OnDeviceDisplay/TabbedButton.tsx | 3 ++- .../__tests__/TabbedButton.test.tsx | 22 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/src/atoms/buttons/OnDeviceDisplay/TabbedButton.tsx b/app/src/atoms/buttons/OnDeviceDisplay/TabbedButton.tsx index 3e8c60d0f3b..c1aab95c55e 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/TabbedButton.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/TabbedButton.tsx @@ -13,6 +13,7 @@ const FOREGROUND_STYLES = css` &:focus, &:hover { background-color: ${COLORS.highlightPurple_one}; + box-shadow: none; } &:active { @@ -27,6 +28,7 @@ const BACKGROUND_STYLES = css` &:focus, &:hover { background-color: ${COLORS.highlightPurple_two}; + box-shadow: none; } &:active { @@ -54,7 +56,6 @@ export const TabbedButton = styled(NewPrimaryBtn)` ${styleProps} - &:focus, &:focus-visible { box-shadow: 0 0 0 3px ${COLORS.fundamentalsFocus}; } diff --git a/app/src/atoms/buttons/OnDeviceDisplay/__tests__/TabbedButton.test.tsx b/app/src/atoms/buttons/OnDeviceDisplay/__tests__/TabbedButton.test.tsx index 3c2e4e401c2..3bcd5d9de78 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/__tests__/TabbedButton.test.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/__tests__/TabbedButton.test.tsx @@ -68,13 +68,21 @@ describe('Background TabbedButton', () => { }) it('applies the correct states to the background tabbed button - focus', () => { + const { getByText } = render(props) + const button = getByText('tabbed button') + expect(button).toHaveStyleRule('box-shadow', 'none', { + modifier: ':focus', + }) + }) + + it('applies the correct states to the background tabbed button - focus-visible', () => { const { getByText } = render(props) const button = getByText('tabbed button') expect(button).toHaveStyleRule( 'box-shadow', `0 0 0 3px ${String(COLORS.fundamentalsFocus)}`, { - modifier: ':focus', + modifier: ':focus-visible', } ) }) @@ -135,14 +143,22 @@ describe('Foreground TabbedButton', () => { ) }) - it('applies the correct states to the foreground tabbed button - focus-', () => { + it('applies the correct states to the foreground tabbed button - focus', () => { + const { getByText } = render(props) + const button = getByText('tabbed button') + expect(button).toHaveStyleRule('box-shadow', 'none', { + modifier: ':focus', + }) + }) + + it('applies the correct states to the foreground tabbed button - focus-visible', () => { const { getByText } = render(props) const button = getByText('tabbed button') expect(button).toHaveStyleRule( 'box-shadow', `0 0 0 3px ${String(COLORS.fundamentalsFocus)}`, { - modifier: ':focus', + modifier: ':focus-visible', } ) }) From 97a3521ec13232ffbf306398b604a22531f47471 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Thu, 23 Mar 2023 08:35:31 -0400 Subject: [PATCH 22/25] Increase test coverage for nav buttons [RAUT-684] --- .../__tests__/ProtocolDetails.test.tsx | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx index b582f84e35b..c0f850d136b 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetails.test.tsx @@ -11,11 +11,20 @@ import { } from '@opentrons/react-api-client' import { i18n } from '../../../../i18n' import { ProtocolDetails } from '..' +import { Deck } from '../Deck' +import { Hardware } from '../Hardware' +import { Labware } from '../Labware' jest.mock('@opentrons/react-api-client') +jest.mock('../Deck') +jest.mock('../Hardware') +jest.mock('../Labware') const mockCreateRun = jest.fn((id: string) => {}) const mockDeleteProtocol = jest.fn((id: string) => {}) +const mockHardware = Hardware as jest.MockedFunction +const mockLabware = Labware as jest.MockedFunction +const mockDeck = Deck as jest.MockedFunction const mockUseCreateRunMutation = useCreateRunMutation as jest.MockedFunction< typeof useCreateRunMutation > @@ -109,11 +118,23 @@ describe('ODDProtocolDetails', () => { getByText('Delete protocol') }) it('renders the navigation buttons', () => { - const [{ getByRole }] = render() - getByRole('button', { name: 'Summary' }) - getByRole('button', { name: 'Hardware' }) - getByRole('button', { name: 'Labware' }) + mockHardware.mockReturnValue(
Mock Hardware
) + mockLabware.mockReturnValue(
Mock Labware
) + mockDeck.mockReturnValue(
Mock Initial Deck Layout
) + const [{ getByRole, getByText }] = render() + const hardwareButton = getByRole('button', { name: 'Hardware' }) + hardwareButton.click() + getByText('Mock Hardware') + const labwareButton = getByRole('button', { name: 'Labware' }) + labwareButton.click() + getByText('Mock Labware') + // Can't test this until liquids section exists getByRole('button', { name: 'Liquids' }) - getByRole('button', { name: 'Initial Deck Layout' }) + const deckButton = getByRole('button', { name: 'Initial Deck Layout' }) + deckButton.click() + getByText('Mock Initial Deck Layout') + const summaryButton = getByRole('button', { name: 'Summary' }) + summaryButton.click() + getByText('A short mock protocol') }) }) From b1ea6dac35cbfcb737bad4f50d929b89f32ad1f4 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Thu, 23 Mar 2023 15:21:28 -0400 Subject: [PATCH 23/25] Changed MediumButton and TabbedButton to use Btn as their base component [RCORE-684] --- app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx | 6 +++--- app/src/atoms/buttons/OnDeviceDisplay/TabbedButton.tsx | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx index c9c1d983665..e3692923f15 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx @@ -3,11 +3,11 @@ import { css } from 'styled-components' import { ALIGN_CENTER, BORDERS, + Btn, COLORS, DIRECTION_ROW, Flex, Icon, - NewPrimaryBtn, SPACING, styleProps, TYPOGRAPHY, @@ -136,7 +136,7 @@ export function MediumButton(props: MediumButtonProps): JSX.Element { } ` return ( - - + ) } diff --git a/app/src/atoms/buttons/OnDeviceDisplay/TabbedButton.tsx b/app/src/atoms/buttons/OnDeviceDisplay/TabbedButton.tsx index c1aab95c55e..839d5290266 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/TabbedButton.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/TabbedButton.tsx @@ -3,13 +3,15 @@ import { Btn, BORDERS, COLORS, - NewPrimaryBtn, SPACING, styleProps, TYPOGRAPHY, } from '@opentrons/components' const FOREGROUND_STYLES = css` + background-color: ${COLORS.highlightPurple_one}; + color: ${COLORS.white}; + &:focus, &:hover { background-color: ${COLORS.highlightPurple_one}; @@ -40,10 +42,9 @@ interface TabbedButtonProps extends React.ComponentProps { foreground?: boolean } -export const TabbedButton = styled(NewPrimaryBtn)` +export const TabbedButton = styled(Btn)` ${props => css` - background-color: ${COLORS.highlightPurple_one}; border-radius: ${BORDERS.size_four}; box-shadow: none; font-size: ${TYPOGRAPHY.fontSize22}; From a33fb8e5180debaa81add8a8070a9cdabada38dc Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Thu, 23 Mar 2023 16:38:35 -0400 Subject: [PATCH 24/25] Adjusted spacing to match final designs. Added TODO comments. [RAUT-684] --- .../pages/OnDeviceDisplay/ProtocolDetails/index.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx index 24cca2f3736..0d4791ab257 100644 --- a/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx +++ b/app/src/pages/OnDeviceDisplay/ProtocolDetails/index.tsx @@ -56,6 +56,9 @@ const ProtocolHeader = (props: { if (title.length > 92 && truncate) { displayedTitle = truncateString(title, 92, 69) } + + // TODO(ew, 3/23/23): put real info in the chip + return ( - + @@ -264,6 +273,7 @@ export function ProtocolDetails(): JSX.Element | null { dispatch( updateConfigValue('protocols.pinnedProtocolIds', pinnedProtocolIds) ) + // TODO(ew, 3/23/23): show user result via snackbar component } const handleRunProtocol = (): void => { From 23ab2569194d7e8f2fb8655fed28c682c203968b Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Tue, 28 Mar 2023 11:33:26 -0400 Subject: [PATCH 25/25] Minor spacing tweaks [RCORE-684] --- .../buttons/OnDeviceDisplay/MediumButton.tsx | 6 +++++- .../OnDeviceDisplay/ProtocolDetails/index.tsx | 20 ++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx index e3692923f15..6a622ad91ec 100644 --- a/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx +++ b/app/src/atoms/buttons/OnDeviceDisplay/MediumButton.tsx @@ -103,7 +103,6 @@ export function MediumButton(props: MediumButtonProps): JSX.Element { box-shadow: none; color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType].defaultColor}; cursor: default; - padding: ${SPACING.spacingM} ${SPACING.spacingXXL}; text-align: ${TYPOGRAPHY.textAlignLeft}; text-transform: ${TYPOGRAPHY.textTransformNone}; @@ -141,6 +140,11 @@ export function MediumButton(props: MediumButtonProps): JSX.Element { css={MEDIUM_BUTTON_STYLE} aria-label={`MediumButton_${buttonType}`} flexDirection={DIRECTION_ROW} + padding={ + iconName !== undefined + ? `${SPACING.spacingM} ${SPACING.spacing5}` + : `${SPACING.spacingM} ${SPACING.spacingXXL}` + } width={width} > - history.goBack()}> - + history.goBack()} + width="2.5rem" + > +