From 177001f55e33de89243615b60d8f08fe3c6f6ea4 Mon Sep 17 00:00:00 2001 From: koji Date: Mon, 19 Aug 2024 21:36:44 +0900 Subject: [PATCH] feat(protocol-designer, components): add SlotInformation component (#16043) * feat(protocol-designer, components): add SlotInformation component --- .storybook/main.js | 1 + .storybook/preview.jsx | 21 +++-- .../ListItemChildren/ListItemDescriptor.tsx | 4 +- components/src/atoms/index.ts | 1 + .../src/localization/en/shared.json | 5 ++ .../SlotInformation.stories.tsx | 70 +++++++++++++++ .../__tests__/SlotInformation.test.tsx | 65 ++++++++++++++ .../src/organisms/SlotInformation/index.tsx | 88 +++++++++++++++++++ protocol-designer/src/organisms/index.ts | 2 +- 9 files changed, 247 insertions(+), 10 deletions(-) create mode 100644 protocol-designer/src/organisms/SlotInformation/SlotInformation.stories.tsx create mode 100644 protocol-designer/src/organisms/SlotInformation/__tests__/SlotInformation.test.tsx create mode 100644 protocol-designer/src/organisms/SlotInformation/index.tsx diff --git a/.storybook/main.js b/.storybook/main.js index 985486d5d4e..fa6e2d7c0dc 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -2,6 +2,7 @@ module.exports = { stories: [ '../components/**/*.stories.@(js|jsx|ts|tsx)', '../app/**/*.stories.@(js|jsx|ts|tsx)', + '../protocol-designer/**/*.stories.@(js|jsx|ts|tsx)', '../opentrons-ai-client/**/*.stories.@(js|jsx|ts|tsx)', ], diff --git a/.storybook/preview.jsx b/.storybook/preview.jsx index 3ae68f1e980..4d97efe9c4d 100644 --- a/.storybook/preview.jsx +++ b/.storybook/preview.jsx @@ -7,7 +7,7 @@ global.APP_SHELL_REMOTE = { ipcRenderer: { on: (topic, cb) => {}, invoke: (callname, args) => {}, - send: (message, payload) => {} + send: (message, payload) => {}, }, } global._PKG_VERSION_ = '0.0.0-storybook' @@ -29,8 +29,8 @@ export const customViewports = { type: 'desktop', styles: { width: '600px', - height: '450px' - } + height: '450px', + }, }, desktopSmall: { // A size typically used in figma app backgrounds, useful for viewing @@ -39,9 +39,9 @@ export const customViewports = { type: 'desktop', styles: { width: '1024px', - height: '700px' - } - } + height: '700px', + }, + }, } export const parameters = { @@ -50,7 +50,14 @@ export const parameters = { options: { storySort: { method: 'alphabetical', - order: ['Design Tokens', 'Library', 'App', 'ODD', 'AI'], + order: [ + 'Design Tokens', + 'Library', + 'App', + 'ODD', + 'Protocol-Designer', + 'AI', + ], }, }, } diff --git a/components/src/atoms/ListItem/ListItemChildren/ListItemDescriptor.tsx b/components/src/atoms/ListItem/ListItemChildren/ListItemDescriptor.tsx index a6512a895fe..0d62b5c2cda 100644 --- a/components/src/atoms/ListItem/ListItemChildren/ListItemDescriptor.tsx +++ b/components/src/atoms/ListItem/ListItemChildren/ListItemDescriptor.tsx @@ -9,8 +9,8 @@ import { SPACING } from '../../../ui-style-constants' interface ListItemDescriptorProps { type: 'default' | 'mini' - description: JSX.Element - content: JSX.Element + description: JSX.Element | string + content: JSX.Element | string } export const ListItemDescriptor = ( diff --git a/components/src/atoms/index.ts b/components/src/atoms/index.ts index 7925b4cf35b..2967c67ff89 100644 --- a/components/src/atoms/index.ts +++ b/components/src/atoms/index.ts @@ -4,6 +4,7 @@ export * from './CheckboxField' export * from './Chip' export * from './InputField' export * from './ListItem' +export * from './ListItem/ListItemChildren//ListItemDescriptor' export * from './MenuList' export * from './MenuList/MenuItem' export * from './MenuList/OverflowBtn' diff --git a/protocol-designer/src/localization/en/shared.json b/protocol-designer/src/localization/en/shared.json index b5688b40577..1ffd1668da7 100644 --- a/protocol-designer/src/localization/en/shared.json +++ b/protocol-designer/src/localization/en/shared.json @@ -14,16 +14,21 @@ "go_back": "Go back", "import_existing": "Import existing protocol", "import": "Import", + "labware": "Labware", + "liquid": "Liquid", + "module": "Module", "next": "next", "ninety_six_channel": "96-Channel", "no-code-solution": "A no-code solution to create protocols that x, y and z meaning for your lab and workflow.", "no": "No", + "none": "None", "one_channel": "1-Channel", "opentrons_flex": "Opentrons Flex", "opentrons": "Opentrons", "ot2": "Opentrons OT-2", "protocol_designer": "Protocol Designer", "remove": "remove", + "slot_stack_information": "Slot Stack Information", "step_count": "Step {{current}}", "step": "Step {{current}} / {{max}}", "version": "Version # {{version}}", diff --git a/protocol-designer/src/organisms/SlotInformation/SlotInformation.stories.tsx b/protocol-designer/src/organisms/SlotInformation/SlotInformation.stories.tsx new file mode 100644 index 00000000000..bd1972bb540 --- /dev/null +++ b/protocol-designer/src/organisms/SlotInformation/SlotInformation.stories.tsx @@ -0,0 +1,70 @@ +import * as React from 'react' +import { Flex } from '@opentrons/components' +import { I18nextProvider } from 'react-i18next' +import { i18n } from '../../localization' +import { SlotInformation as SlotInformationComponent } from '.' + +import type { Meta, StoryObj } from '@storybook/react' + +const mockLocations = [ + 'A1', + 'A2', + 'A3', + 'B1', + 'B2', + 'B3', + 'C1', + 'C2', + 'C3', + 'D1', + 'D2', + 'D3', +] + +const mockLiquids = ['Mastermix', 'Ethanol', 'Water'] +const mockLabwares = ['96 Well Plate', 'Adapter'] +const mockModules = ['Thermocycler Module Gen2', 'Heater-Shaker Module'] + +const meta: Meta = { + title: 'Protocol-Designer/Organisms/SlotInformation', + component: SlotInformationComponent, + argTypes: { + location: { + control: { + type: 'select', + }, + options: mockLocations, + }, + }, + decorators: [ + Story => ( + + + + + + ), + ], +} + +export default meta + +type Story = StoryObj + +export const SlotInformation: Story = { + args: { + location: 'A1', + liquids: mockLiquids, + labwares: mockLabwares, + modules: mockModules, + }, +} + +export const SlotInformationEmpty: Story = { + args: { + location: 'A1', + liquids: [], + labwares: [], + modules: [], + }, +} diff --git a/protocol-designer/src/organisms/SlotInformation/__tests__/SlotInformation.test.tsx b/protocol-designer/src/organisms/SlotInformation/__tests__/SlotInformation.test.tsx new file mode 100644 index 00000000000..d5f8483c639 --- /dev/null +++ b/protocol-designer/src/organisms/SlotInformation/__tests__/SlotInformation.test.tsx @@ -0,0 +1,65 @@ +import * as React from 'react' +import { describe, it, beforeEach, expect } from 'vitest' +import { screen } from '@testing-library/react' + +import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '../../../localization' + +import { SlotInformation } from '..' + +const mockLiquids = ['Mastermix', 'Ethanol', 'Water'] +const mockLabwares = ['96 Well Plate', 'Adapter'] +const mockModules = ['Thermocycler Module Gen2', 'Heater-Shaker Module'] + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} + +describe('SlotInformation', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + location: 'A1', + liquids: [], + labwares: [], + modules: [], + } + }) + + it('should render DeckInfoLabel and title', () => { + render(props) + screen.getByText('A1') + screen.getByText('Slot Stack Information') + }) + + it('should render liquid, labware, and module', () => { + render(props) + screen.getByText('Liquid') + screen.getByText('Labware') + screen.getByText('Module') + expect(screen.getAllByText('None').length).toBe(3) + }) + + it('should render info of liquid, labware, and module', () => { + props = { + ...props, + liquids: mockLiquids, + labwares: mockLabwares, + modules: mockModules, + } + render(props) + expect(screen.getAllByText('Liquid').length).toBe(mockLiquids.length) + expect(screen.getAllByText('Labware').length).toBe(mockLabwares.length) + expect(screen.getAllByText('Module').length).toBe(mockModules.length) + screen.getByText('Mastermix') + screen.getByText('Ethanol') + screen.getByText('Water') + screen.getByText('96 Well Plate') + screen.getByText('Adapter') + screen.getByText('Thermocycler Module Gen2') + screen.getByText('Heater-Shaker Module') + }) +}) diff --git a/protocol-designer/src/organisms/SlotInformation/index.tsx b/protocol-designer/src/organisms/SlotInformation/index.tsx new file mode 100644 index 00000000000..cdb8d2f3d94 --- /dev/null +++ b/protocol-designer/src/organisms/SlotInformation/index.tsx @@ -0,0 +1,88 @@ +import React from 'react' +import { useTranslation } from 'react-i18next' +import { + ALIGN_CENTER, + DeckInfoLabel, + DIRECTION_COLUMN, + Flex, + ListItem, + ListItemDescriptor, + SPACING, + StyledText, +} from '@opentrons/components' + +interface SlotInformationProps { + location: string + liquids?: string[] + labwares?: string[] + modules?: string[] +} + +export const SlotInformation: React.FC = ({ + location, + liquids = [], + labwares = [], + modules = [], +}) => { + const { t } = useTranslation('shared') + return ( + + + + + {t('slot_stack_information')} + + + + + + + + + ) +} + +interface StackInfoListProps { + title: string + items: string[] +} + +function StackInfoList({ title, items }: StackInfoListProps): JSX.Element { + return ( + <> + {items.length > 0 ? ( + items.map((item, index) => ( + + )) + ) : ( + + )} + + ) +} + +interface StackInfoProps { + title: string + stackInformation?: string +} + +function StackInfo({ title, stackInformation }: StackInfoProps): JSX.Element { + const { t } = useTranslation('shared') + return ( + + + + ) +} diff --git a/protocol-designer/src/organisms/index.ts b/protocol-designer/src/organisms/index.ts index c15c8fdc690..a40b21a0172 100644 --- a/protocol-designer/src/organisms/index.ts +++ b/protocol-designer/src/organisms/index.ts @@ -1 +1 @@ -console.log('organisms for new components') +export * from './SlotInformation'