Skip to content

Commit

Permalink
refactor(protocol-designer, components): infoItem to nicely accommoda… (
Browse files Browse the repository at this point in the history
#14850)

…te multiple tipracks

closes AUTH-314
  • Loading branch information
jerader authored and Carlos-fernandez committed May 20, 2024
1 parent 628c571 commit 5bc704f
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 68 deletions.
24 changes: 0 additions & 24 deletions components/src/instrument/InfoItem.tsx

This file was deleted.

110 changes: 68 additions & 42 deletions components/src/instrument/InstrumentInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,77 +1,103 @@
import * as React from 'react'

import { LEFT, RIGHT } from '@opentrons/shared-data'
import { InfoItem } from './InfoItem'
import { InstrumentDiagram } from './InstrumentDiagram'
import styles from './instrument.module.css'
import { Flex } from '../primitives'
import { SPACING } from '../ui-style-constants'
import { SPACING, TYPOGRAPHY } from '../ui-style-constants'
import { StyledText } from '../atoms'
import { DIRECTION_COLUMN, JUSTIFY_CENTER } from '../styles'
import { InstrumentDiagram } from './InstrumentDiagram'

import type { Mount } from '../robot-types'
import type { InstrumentDiagramProps } from './InstrumentDiagram'

import styles from './instrument.module.css'

export interface InstrumentInfoProps {
/** 'left' or 'right' */
mount: Mount
/** if true, show labels 'LEFT PIPETTE' / 'RIGHT PIPETTE' */
showMountLabel?: boolean | null
/** human-readable description, eg 'p300 Single-channel' */
description: string
/** paired tiprack models */
tiprackModels?: string[]
/** if disabled, pipette & its info are grayed out */
isDisabled: boolean
/** specs of mounted pipette */
pipetteSpecs?: InstrumentDiagramProps['pipetteSpecs'] | null
/** classes to apply */
className?: string
/** classes to apply to the info group child */
infoClassName?: string
/** paired tiprack models */
tiprackModels?: string[]
/** children to display under the info */
children?: React.ReactNode
/** if true, show labels 'LEFT PIPETTE' / 'RIGHT PIPETTE' */
showMountLabel?: boolean | null
}

const MAX_WIDTH = '14rem'

export function InstrumentInfo(props: InstrumentInfoProps): JSX.Element {
const has96Channel = props.pipetteSpecs?.channels === 96
const {
mount,
showMountLabel,
description,
tiprackModels,
pipetteSpecs,
children,
} = props

const has96Channel = pipetteSpecs?.channels === 96
return (
<Flex justifyContent={JUSTIFY_CENTER} gridGap={SPACING.spacing16}>
{props.mount === RIGHT && props.pipetteSpecs && (
{mount === RIGHT && pipetteSpecs ? (
<InstrumentDiagram
pipetteSpecs={props.pipetteSpecs}
pipetteSpecs={pipetteSpecs}
className={styles.pipette_icon}
mount={props.mount}
mount={mount}
/>
)}
) : null}
{/* NOTE: the color is our legacy c-font-dark, which matches the other colors in this component **/}
<Flex
flexDirection={DIRECTION_COLUMN}
color="#4a4a4a"
gridGap={SPACING.spacing8}
>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing4}>
<StyledText
as="h3"
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
textTransform={TYPOGRAPHY.textTransformCapitalize}
>
{showMountLabel && !has96Channel ? `${mount} pipette` : 'pipette'}
</StyledText>
<StyledText as="p" width="max-content" maxWidth={MAX_WIDTH}>
{description}
</StyledText>
</Flex>

<Flex flexDirection={DIRECTION_COLUMN}>
<InfoItem
title={
props.showMountLabel && !has96Channel
? `${props.mount} pipette`
: 'pipette'
}
value={props.description}
/>
{props.tiprackModels != null
? props.tiprackModels.map((model, index) => (
<InfoItem
key={`tiprackModel-${index}`}
title={index === 0 ? 'tip racks' : null}
value={model}
/>
))
: null}
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing4}>
<StyledText as="h3" fontWeight={TYPOGRAPHY.fontWeightSemiBold}>
{'Tip rack'}
</StyledText>
<ul>
{tiprackModels != null && tiprackModels.length > 0 ? (
tiprackModels.map((model, index) => (
<li key={`${model}_${index}`}>
<StyledText as="p" width="max-content" maxWidth={MAX_WIDTH}>
{model}
</StyledText>
</li>
))
) : (
<StyledText as="p" width="max-content" maxWidth={MAX_WIDTH}>
{'None'}
</StyledText>
)}
</ul>
</Flex>
</Flex>

{props.children}
{props.mount === LEFT && props.pipetteSpecs && (
{children}
{mount === LEFT && pipetteSpecs ? (
<InstrumentDiagram
pipetteSpecs={props.pipetteSpecs}
pipetteSpecs={pipetteSpecs}
className={styles.pipette_icon}
mount={props.mount}
mount={mount}
/>
)}
) : null}
</Flex>
)
}
54 changes: 54 additions & 0 deletions components/src/instrument/__tests__/InstrumentInfo.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as React from 'react'
import { screen } from '@testing-library/react'
import { describe, beforeEach, it, vi } from 'vitest'
import { LEFT, RIGHT, fixtureP1000SingleV2Specs } from '@opentrons/shared-data'
import { renderWithProviders } from '../../testing/utils'
import { InstrumentInfo } from '../InstrumentInfo'
import { InstrumentDiagram } from '../InstrumentDiagram'

vi.mock('../InstrumentDiagram')
const render = (props: React.ComponentProps<typeof InstrumentInfo>) => {
return renderWithProviders(<InstrumentInfo {...props} />)[0]
}

describe('InstrumentInfo', () => {
let props: React.ComponentProps<typeof InstrumentInfo>

beforeEach(() => {
props = {
mount: LEFT,
description: 'mock description',
pipetteSpecs: fixtureP1000SingleV2Specs,
tiprackModels: ['mock1', 'mock2'],
showMountLabel: true,
}
vi.mocked(InstrumentDiagram).mockReturnValue(
<div>mock instrumentDiagram</div>
)
})
it('renders a p1000 pipette with 2 tiprack models for left mount', () => {
render(props)
screen.getByText('mock instrumentDiagram')
screen.getByText('left pipette')
screen.getByText('mock description')
screen.getByText('Tip rack')
screen.getByText('mock1')
screen.getByText('mock2')
})
it('renders a p1000 pipette with 1 tiprack model for right mount', () => {
props.mount = RIGHT
props.tiprackModels = ['mock1']
render(props)
screen.getByText('mock instrumentDiagram')
screen.getByText('right pipette')
screen.getByText('mock description')
screen.getByText('Tip rack')
screen.getByText('mock1')
})
it('renders none for pip and tiprack if none are selected', () => {
props.pipetteSpecs = undefined
props.tiprackModels = undefined
render(props)
screen.getByText('None')
})
})
1 change: 0 additions & 1 deletion components/src/instrument/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './InfoItem'
export * from './InstrumentDiagram'
export * from './InstrumentGroup'
export * from './InstrumentInfo'
Expand Down
1 change: 0 additions & 1 deletion protocol-designer/src/step-forms/selectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,6 @@ export const getPipettesForInstrumentGroup: Selector<
mount: pipetteOnDeck.mount,
pipetteSpecs: pipetteSpec,
description: _getPipetteDisplayName(pipetteOnDeck.name),
isDisabled: false,
tiprackModels: tiprackDefs?.map((def: LabwareDefinition2) =>
getLabwareDisplayName(def)
),
Expand Down

0 comments on commit 5bc704f

Please sign in to comment.