diff --git a/app/src/App/OnDeviceDisplayApp.tsx b/app/src/App/OnDeviceDisplayApp.tsx index 835e005d256..1459ff5071f 100644 --- a/app/src/App/OnDeviceDisplayApp.tsx +++ b/app/src/App/OnDeviceDisplayApp.tsx @@ -32,6 +32,7 @@ import { RobotDashboard } from '../pages/RobotDashboard' import { RobotSettingsDashboard } from '../pages/RobotSettingsDashboard' import { ProtocolDashboard } from '../pages/ProtocolDashboard' import { ProtocolDetails } from '../pages/ProtocolDetails' +import { QuickTransferFlow } from '../organisms/QuickTransferFlow' import { RunningProtocol } from '../pages/RunningProtocol' import { RunSummary } from '../pages/RunSummary' import { UpdateRobot } from '../pages/UpdateRobot/UpdateRobot' @@ -73,6 +74,7 @@ export const ON_DEVICE_DISPLAY_PATHS = [ '/network-setup/wifi', '/protocols', '/protocols/:protocolId', + '/quick-transfer', '/robot-settings', '/robot-settings/rename-robot', '/robot-settings/update-robot', @@ -109,6 +111,8 @@ function getPathComponent( return case '/protocols/:protocolId': return + case `/quick-transfer`: + return case '/robot-settings': return case '/robot-settings/rename-robot': diff --git a/app/src/assets/localization/en/index.ts b/app/src/assets/localization/en/index.ts index c74aab09de5..51acf92db53 100644 --- a/app/src/assets/localization/en/index.ts +++ b/app/src/assets/localization/en/index.ts @@ -21,6 +21,7 @@ import protocol_details from './protocol_details.json' import protocol_info from './protocol_info.json' import protocol_list from './protocol_list.json' import protocol_setup from './protocol_setup.json' +import quick_transfer from './quick_transfer.json' import robot_calibration from './robot_calibration.json' import robot_controls from './robot_controls.json' import run_details from './run_details.json' @@ -51,6 +52,7 @@ export const en = { protocol_info, protocol_list, protocol_setup, + quick_transfer, robot_calibration, robot_controls, run_details, diff --git a/app/src/assets/localization/en/quick_transfer.json b/app/src/assets/localization/en/quick_transfer.json new file mode 100644 index 00000000000..45732a28114 --- /dev/null +++ b/app/src/assets/localization/en/quick_transfer.json @@ -0,0 +1,18 @@ +{ + "create_new_transfer": "Create new quick transfer", + "select_attached_pipette": "Select attached pipette", + "select_dest_labware": "Select destination labware", + "select_dest_wells": "Select destination wells", + "select_source_labware": "Select source labware", + "select_source_wells": "Select source wells", + "select_tip_rack": "Select tip rack", + "set_aspirate_volume": "Set aspirate volume", + "set_dispense_volume": "Set dispense volume", + "set_transfer_volume": "Set transfer volume", + "use_deck_slots": "Quick transfers use deck slots B2-D2. These slots hold a tip rack, a source labware, and a destination labware.Make sure that your deck configuration is up to date to avoid collisions.", + "tip_rack": "Tip rack", + "labware": "Labware", + "pipette_currently_attached": "Quick transfer options depend on the pipettes currently attached to your robot.", + "well_selection": "Well selection", + "well_ratio": "Quick transfers with multiple source wells can either be one-to-one (select {{wells}} for this transfer) or consolidate (select 1 destination well)." +} diff --git a/app/src/organisms/ChildNavigation/ChildNavigation.stories.tsx b/app/src/organisms/ChildNavigation/ChildNavigation.stories.tsx index da15b3af90e..cddbb2cd7a3 100644 --- a/app/src/organisms/ChildNavigation/ChildNavigation.stories.tsx +++ b/app/src/organisms/ChildNavigation/ChildNavigation.stories.tsx @@ -15,6 +15,13 @@ const Template: Story> = args => ( export const Default = Template.bind({}) Default.args = { header: 'Header', + onClickBack: () => {}, +} + +export const TitleNoBackButton = Template.bind({}) +TitleNoBackButton.args = { + header: 'Header', + onClickBack: undefined, } export const TitleWithNormalSmallButton = Template.bind({}) @@ -22,6 +29,16 @@ TitleWithNormalSmallButton.args = { header: 'Header', buttonText: 'ButtonText', onClickButton: () => {}, + onClickBack: () => {}, +} + +export const TitleWithNormalSmallButtonDisabled = Template.bind({}) +TitleWithNormalSmallButtonDisabled.args = { + header: 'Header', + buttonText: 'ButtonText', + onClickButton: () => {}, + onClickBack: () => {}, + buttonIsDisabled: true, } export const TitleWithLinkButton = Template.bind({}) @@ -32,6 +49,7 @@ TitleWithLinkButton.args = { iconName: 'information', iconPlacement: 'startIcon', onClickButton: () => {}, + onClickBack: () => {}, } export const TitleWithTwoButtons = Template.bind({}) @@ -47,4 +65,5 @@ TitleWithTwoButtons.args = { buttonText: 'ButtonText', onClickButton: () => {}, secondaryButtonProps, + onClickBack: () => {}, } diff --git a/app/src/organisms/ChildNavigation/__tests__/ChildNavigation.test.tsx b/app/src/organisms/ChildNavigation/__tests__/ChildNavigation.test.tsx index 8f53b640187..8e2a1c7ec0e 100644 --- a/app/src/organisms/ChildNavigation/__tests__/ChildNavigation.test.tsx +++ b/app/src/organisms/ChildNavigation/__tests__/ChildNavigation.test.tsx @@ -72,4 +72,26 @@ describe('ChildNavigation', () => { fireEvent.click(secondaryButton) expect(mockOnClickSecondaryButton).toHaveBeenCalled() }) + it.fails( + 'should not render back button if onClickBack does not exist', + () => { + props = { + ...props, + onClickBack: undefined, + } + render(props) + screen.getByTestId('ChildNavigation_Back_Button') + } + ) + it('should render button as disabled', () => { + props = { + ...props, + buttonText: 'mock button', + onClickButton: mockOnClickButton, + buttonIsDisabled: true, + } + render(props) + const button = screen.getByTestId('ChildNavigation_Primary_Button') + expect(button).toBeDisabled() + }) }) diff --git a/app/src/organisms/ChildNavigation/index.tsx b/app/src/organisms/ChildNavigation/index.tsx index afe3c1f7508..e076f7191af 100644 --- a/app/src/organisms/ChildNavigation/index.tsx +++ b/app/src/organisms/ChildNavigation/index.tsx @@ -20,20 +20,21 @@ import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants' import { SmallButton } from '../../atoms/buttons' import { InlineNotification } from '../../atoms/InlineNotification' -import type { IconName } from '@opentrons/components' +import type { IconName, StyleProps } from '@opentrons/components' import type { InlineNotificationProps } from '../../atoms/InlineNotification' import type { IconPlacement, SmallButtonTypes, } from '../../atoms/buttons/SmallButton' -interface ChildNavigationProps { +interface ChildNavigationProps extends StyleProps { header: string - onClickBack: React.MouseEventHandler + onClickBack?: React.MouseEventHandler buttonText?: React.ReactNode inlineNotification?: InlineNotificationProps onClickButton?: React.MouseEventHandler buttonType?: SmallButtonTypes + buttonIsDisabled?: boolean iconName?: IconName iconPlacement?: IconPlacement secondaryButtonProps?: React.ComponentProps @@ -49,6 +50,8 @@ export function ChildNavigation({ iconName, iconPlacement, secondaryButtonProps, + buttonIsDisabled, + ...styleProps }: ChildNavigationProps): JSX.Element { return ( - - - + {onClickBack != null ? ( + + + + ) : null} {header} @@ -87,6 +93,8 @@ export function ChildNavigation({ onClick={onClickButton} iconName={iconName} iconPlacement={iconPlacement} + disabled={buttonIsDisabled} + data-testid="ChildNavigation_Primary_Button" /> ) : null} diff --git a/app/src/organisms/QuickTransferFlow/CreateNewTransfer.tsx b/app/src/organisms/QuickTransferFlow/CreateNewTransfer.tsx new file mode 100644 index 00000000000..f1b795e5fc3 --- /dev/null +++ b/app/src/organisms/QuickTransferFlow/CreateNewTransfer.tsx @@ -0,0 +1,74 @@ +import * as React from 'react' +import { useTranslation, Trans } from 'react-i18next' +import { + Flex, + SPACING, + StyledText, + DeckConfigurator, + TYPOGRAPHY, + DIRECTION_COLUMN, +} from '@opentrons/components' +import { SmallButton } from '../../atoms/buttons' +import { useDeckConfigurationQuery } from '@opentrons/react-api-client' +import { ChildNavigation } from '../ChildNavigation' + +interface CreateNewTransferProps { + onNext: () => void + exitButtonProps: React.ComponentProps +} + +export function CreateNewTransfer(props: CreateNewTransferProps): JSX.Element { + const { i18n, t } = useTranslation(['quick_transfer', 'shared']) + const deckConfig = useDeckConfigurationQuery().data ?? [] + return ( + + + + + + + ), + }} + /> + + + {}} + handleClickRemove={() => {}} + additionalStaticFixtures={[ + { location: 'cutoutB2', label: t('tip_rack') }, + { location: 'cutoutC2', label: t('labware') }, + { location: 'cutoutD2', label: t('labware') }, + ]} + /> + + + + + ) +} diff --git a/app/src/organisms/QuickTransferFlow/__tests__/CreateNewTransfer.test.tsx b/app/src/organisms/QuickTransferFlow/__tests__/CreateNewTransfer.test.tsx new file mode 100644 index 00000000000..abeba9a2b1d --- /dev/null +++ b/app/src/organisms/QuickTransferFlow/__tests__/CreateNewTransfer.test.tsx @@ -0,0 +1,62 @@ +import * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' +import { DeckConfigurator } from '@opentrons/components' + +import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '../../../i18n' +import { CreateNewTransfer } from '../CreateNewTransfer' + +import type * as OpentronsComponents from '@opentrons/components' + +vi.mock('@opentrons/components', async importOriginal => { + const actual = await importOriginal() + return { + ...actual, + DeckConfigurator: vi.fn(), + } +}) +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} + +describe('CreateNewTransfer', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + onNext: vi.fn(), + exitButtonProps: { + buttonType: 'tertiaryLowLight', + buttonText: 'Exit', + onClick: vi.fn(), + }, + } + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders the create new transfer screen and header', () => { + render(props) + screen.getByText('Create new quick transfer') + screen.getByText( + 'Quick transfers use deck slots B2-D2. These slots hold a tip rack, a source labware, and a destination labware.' + ) + screen.getByText( + 'Make sure that your deck configuration is up to date to avoid collisions.' + ) + expect(vi.mocked(DeckConfigurator)).toHaveBeenCalled() + }) + it('renders exit and continue buttons and they work as expected', () => { + render(props) + const exitBtn = screen.getByText('Exit') + fireEvent.click(exitBtn) + expect(props.exitButtonProps.onClick).toHaveBeenCalled() + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + expect(props.onNext).toHaveBeenCalled() + }) +}) diff --git a/app/src/organisms/QuickTransferFlow/constants.ts b/app/src/organisms/QuickTransferFlow/constants.ts new file mode 100644 index 00000000000..3241759a044 --- /dev/null +++ b/app/src/organisms/QuickTransferFlow/constants.ts @@ -0,0 +1,9 @@ +export const ACTIONS = { + SELECT_PIPETTE: 'SELECT_PIPETTE', + SELECT_TIP_RACK: 'SELECT_TIP_RACK', + SET_SOURCE_LABWARE: 'SET_SOURCE_LABWARE', + SET_SOURCE_WELLS: 'SET_SOURCE_WELLS', + SET_DEST_LABWARE: 'SET_DEST_LABWARE', + SET_DEST_WELLS: 'SET_DEST_WELLS', + SET_VOLUME: 'SET_VOLUME', +} as const diff --git a/app/src/organisms/QuickTransferFlow/index.tsx b/app/src/organisms/QuickTransferFlow/index.tsx new file mode 100644 index 00000000000..4031c7aa7bf --- /dev/null +++ b/app/src/organisms/QuickTransferFlow/index.tsx @@ -0,0 +1,167 @@ +import * as React from 'react' +import { useHistory } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import { Flex, StepMeter, SPACING } from '@opentrons/components' +import { SmallButton } from '../../atoms/buttons' +import { ChildNavigation } from '../ChildNavigation' +import { CreateNewTransfer } from './CreateNewTransfer' + +import type { + QuickTransferSetupState, + QuickTransferWizardAction, +} from './types' + +const QUICK_TRANSFER_WIZARD_STEPS = 8 + +// const initialQuickTransferState: QuickTransferSetupState = {} +export function reducer( + state: QuickTransferSetupState, + action: QuickTransferWizardAction +): QuickTransferSetupState { + switch (action.type) { + case 'SELECT_PIPETTE': { + return { + pipette: action.pipette, + } + } + case 'SELECT_TIP_RACK': { + return { + pipette: state.pipette, + tipRack: action.tipRack, + } + } + case 'SET_SOURCE_LABWARE': { + return { + pipette: state.pipette, + tipRack: state.tipRack, + source: action.labware, + } + } + case 'SET_SOURCE_WELLS': { + return { + pipette: state.pipette, + tipRack: state.tipRack, + source: state.source, + sourceWells: action.wells, + } + } + case 'SET_DEST_LABWARE': { + return { + pipette: state.pipette, + tipRack: state.tipRack, + source: state.source, + sourceWells: state.sourceWells, + destination: action.labware, + } + } + case 'SET_DEST_WELLS': { + return { + pipette: state.pipette, + tipRack: state.tipRack, + source: state.source, + sourceWells: state.sourceWells, + destination: state.destination, + destinationWells: action.wells, + } + } + case 'SET_VOLUME': { + return { + pipette: state.pipette, + tipRack: state.tipRack, + source: state.source, + sourceWells: state.sourceWells, + destination: state.destination, + destinationWells: state.destinationWells, + volume: action.volume, + } + } + } +} + +export const QuickTransferFlow = (): JSX.Element => { + const history = useHistory() + const { i18n, t } = useTranslation(['quick_transfer', 'shared']) + // const [state, dispatch] = React.useReducer(reducer, initialQuickTransferState) + const [currentStep, setCurrentStep] = React.useState(1) + const [continueIsDisabled] = React.useState(false) + + // every child component will take state as a prop, an anonymous + // dispatch function related to that step (except create new), + // and a function to disable the continue button + + const exitButtonProps: React.ComponentProps = { + buttonType: 'tertiaryLowLight', + buttonText: i18n.format(t('shared:exit'), 'capitalize'), + onClick: () => { + history.push('protocols') + }, + } + const ORDERED_STEP_HEADERS: string[] = [ + t('create_new_transfer'), + t('select_attached_pipette'), + t('select_tip_rack'), + t('select_source_labware'), + t('select_source_wells'), + t('select_dest_labware'), + t('select_dest_wells'), + t('set_transfer_volume'), + ] + + const header = ORDERED_STEP_HEADERS[currentStep - 1] + let modalContent: JSX.Element | null = null + if (currentStep === 1) { + modalContent = ( + setCurrentStep(prevStep => prevStep + 1)} + exitButtonProps={exitButtonProps} + /> + ) + } else { + modalContent = null + } + + // until each page is wired up, show header title with empty screen + + return ( + <> + + {modalContent == null ? ( + + { + setCurrentStep(prevStep => prevStep - 1) + } + } + buttonText={i18n.format(t('shared:continue'), 'capitalize')} + onClickButton={() => { + if (currentStep === 8) { + history.push('protocols') + } else { + setCurrentStep(prevStep => prevStep + 1) + } + }} + buttonIsDisabled={continueIsDisabled} + secondaryButtonProps={{ + buttonType: 'tertiaryLowLight', + buttonText: i18n.format(t('shared:exit'), 'capitalize'), + onClick: () => { + history.push('protocols') + }, + }} + top={SPACING.spacing8} + /> + {modalContent} + + ) : ( + modalContent + )} + + ) +} diff --git a/app/src/organisms/QuickTransferFlow/types.ts b/app/src/organisms/QuickTransferFlow/types.ts new file mode 100644 index 00000000000..2087a98be37 --- /dev/null +++ b/app/src/organisms/QuickTransferFlow/types.ts @@ -0,0 +1,51 @@ +import { ACTIONS } from './constants' +import type { PipetteData } from '@opentrons/api-client' +import type { LabwareDefinition1 } from '@opentrons/shared-data' + +export interface QuickTransferSetupState { + pipette?: PipetteData + tipRack?: LabwareDefinition1 + source?: LabwareDefinition1 + sourceWells?: string[] + destination?: LabwareDefinition1 + destinationWells?: string[] + volume?: number +} + +export type QuickTransferWizardAction = + | SelectPipetteAction + | SelectTipRackAction + | SetSourceLabwareAction + | SetSourceWellsAction + | SetDestLabwareAction + | SetDestWellsAction + | SetVolumeAction + +interface SelectPipetteAction { + type: typeof ACTIONS.SELECT_PIPETTE + pipette: PipetteData +} +interface SelectTipRackAction { + type: typeof ACTIONS.SELECT_TIP_RACK + tipRack: LabwareDefinition1 +} +interface SetSourceLabwareAction { + type: typeof ACTIONS.SET_SOURCE_LABWARE + labware: LabwareDefinition1 +} +interface SetSourceWellsAction { + type: typeof ACTIONS.SET_SOURCE_WELLS + wells: string[] +} +interface SetDestLabwareAction { + type: typeof ACTIONS.SET_DEST_LABWARE + labware: LabwareDefinition1 +} +interface SetDestWellsAction { + type: typeof ACTIONS.SET_DEST_WELLS + wells: string[] +} +interface SetVolumeAction { + type: typeof ACTIONS.SET_VOLUME + volume: number +} diff --git a/app/src/pages/ProtocolDashboard/index.tsx b/app/src/pages/ProtocolDashboard/index.tsx index e326ab7176c..dd4aa89b1ac 100644 --- a/app/src/pages/ProtocolDashboard/index.tsx +++ b/app/src/pages/ProtocolDashboard/index.tsx @@ -1,4 +1,5 @@ import * as React from 'react' +import { useHistory } from 'react-router-dom' import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' @@ -38,6 +39,7 @@ import type { ProtocolResource } from '@opentrons/shared-data' export function ProtocolDashboard(): JSX.Element { const protocols = useAllProtocolsQuery() const runs = useNotifyAllRunsQuery() + const history = useHistory() const { t } = useTranslation('protocol_info') const dispatch = useDispatch() const [navMenuIsOpened, setNavMenuIsOpened] = React.useState(false) @@ -58,6 +60,9 @@ export function ProtocolDashboard(): JSX.Element { const pinnedProtocolIds = useSelector(getPinnedProtocolIds) ?? [] const pinnedProtocols: ProtocolResource[] = [] + // TODO(sb, 4/15/24): The quick transfer button is going to be moved to a new quick transfer + // tab before the feature is released. Because of this, we're not adding test cov + // for this button in ProtocolDashboard const enableQuickTransferFF = useFeatureFlag('enableQuickTransfer') // We only need to grab out the pinned protocol data once all the protocols load @@ -280,7 +285,7 @@ export function ProtocolDashboard(): JSX.Element { buttonText={t('quick_transfer')} iconName="plus" onClick={() => { - console.log('launch quick transfer flow') + history.push('/quick-transfer') }} /> )} diff --git a/components/src/hardware-sim/DeckConfigurator/DeckConfigurator.stories.tsx b/components/src/hardware-sim/DeckConfigurator/DeckConfigurator.stories.tsx index dc900541fd6..f29b2cffc02 100644 --- a/components/src/hardware-sim/DeckConfigurator/DeckConfigurator.stories.tsx +++ b/components/src/hardware-sim/DeckConfigurator/DeckConfigurator.stories.tsx @@ -71,6 +71,12 @@ const deckConfig: CutoutConfig[] = [ }, ] +const staticFixtures = [ + { location: 'cutoutB2', label: 'Tip rack' }, + { location: 'cutoutC2', label: 'Labware' }, + { location: 'cutoutD2', label: 'Labware' }, +] + export const Default = Template.bind({}) Default.args = { deckConfig, @@ -85,3 +91,12 @@ ReadOnly.args = { handleClickRemove: cutoutId => console.log(`remove at ${cutoutId}`), readOnly: true, } + +export const ReadOnlyWithStaticFixtures = Template.bind({}) +ReadOnlyWithStaticFixtures.args = { + deckConfig, + handleClickAdd: () => {}, + handleClickRemove: () => {}, + readOnly: true, + additionalStaticFixtures: staticFixtures, +} diff --git a/components/src/hardware-sim/DeckConfigurator/StaticFixture.tsx b/components/src/hardware-sim/DeckConfigurator/StaticFixture.tsx new file mode 100644 index 00000000000..a3722d51269 --- /dev/null +++ b/components/src/hardware-sim/DeckConfigurator/StaticFixture.tsx @@ -0,0 +1,56 @@ +import * as React from 'react' + +import { Btn, Text } from '../../primitives' +import { TYPOGRAPHY } from '../../ui-style-constants' +import { RobotCoordsForeignObject } from '../Deck/RobotCoordsForeignObject' +import { + CONFIG_STYLE_READ_ONLY, + FIXTURE_HEIGHT, + MIDDLE_SLOT_FIXTURE_WIDTH, + Y_ADJUSTMENT, + COLUMN_2_X_ADJUSTMENT, +} from './constants' + +import type { CutoutId, DeckDefinition } from '@opentrons/shared-data' + +interface StaticFixtureProps { + deckDefinition: DeckDefinition + fixtureLocation: CutoutId + label: string +} + +/** + * this component allows us to add static labeled fixtures to the center column of a deck + * config map + */ + +export function StaticFixture(props: StaticFixtureProps): JSX.Element { + const { deckDefinition, fixtureLocation, label } = props + + const staticCutout = deckDefinition.locations.cutouts.find( + cutout => cutout.id === fixtureLocation + ) + + /** + * deck definition cutout position is the position of the single slot located within that cutout + * so, to get the position of the cutout itself we must add an adjustment to the slot position + */ + const [xSlotPosition = 0, ySlotPosition = 0] = staticCutout?.position ?? [] + const y = ySlotPosition + Y_ADJUSTMENT + const x = xSlotPosition + COLUMN_2_X_ADJUSTMENT + + return ( + + {}}> + {label} + + + ) +} diff --git a/components/src/hardware-sim/DeckConfigurator/constants.ts b/components/src/hardware-sim/DeckConfigurator/constants.ts index 53faef10b7e..388dcdedecc 100644 --- a/components/src/hardware-sim/DeckConfigurator/constants.ts +++ b/components/src/hardware-sim/DeckConfigurator/constants.ts @@ -9,10 +9,12 @@ import { RESPONSIVENESS, SPACING } from '../../ui-style-constants' * Position is relative to deck definition slot positions and a custom stroke applied to the single slot fixture SVG */ export const FIXTURE_HEIGHT = 102.0 +export const MIDDLE_SLOT_FIXTURE_WIDTH = 158.5 export const SINGLE_SLOT_FIXTURE_WIDTH = 243.5 export const STAGING_AREA_FIXTURE_WIDTH = 314.5 export const COLUMN_1_X_ADJUSTMENT = -100 +export const COLUMN_2_X_ADJUSTMENT = -15.5 export const COLUMN_3_X_ADJUSTMENT = -15.5 export const Y_ADJUSTMENT = -8 diff --git a/components/src/hardware-sim/DeckConfigurator/index.tsx b/components/src/hardware-sim/DeckConfigurator/index.tsx index 9378471d8e0..8477b20e875 100644 --- a/components/src/hardware-sim/DeckConfigurator/index.tsx +++ b/components/src/hardware-sim/DeckConfigurator/index.tsx @@ -18,6 +18,7 @@ import { EmptyConfigFixture } from './EmptyConfigFixture' import { StagingAreaConfigFixture } from './StagingAreaConfigFixture' import { TrashBinConfigFixture } from './TrashBinConfigFixture' import { WasteChuteConfigFixture } from './WasteChuteConfigFixture' +import { StaticFixture } from './StaticFixture' import type { CutoutId, DeckConfiguration } from '@opentrons/shared-data' @@ -30,6 +31,7 @@ interface DeckConfiguratorProps { readOnly?: boolean showExpansion?: boolean children?: React.ReactNode + additionalStaticFixtures?: Array<{ location: CutoutId; label: string }> } export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { @@ -41,6 +43,7 @@ export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { darkFill = COLORS.black90, readOnly = false, showExpansion = true, + additionalStaticFixtures, children, } = props const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) @@ -143,6 +146,14 @@ export function DeckConfigurator(props: DeckConfiguratorProps): JSX.Element { fixtureLocation={cutoutId} /> ))} + {additionalStaticFixtures?.map(staticFixture => ( + + ))}