diff --git a/components/src/atoms/Checkbox/index.tsx b/components/src/atoms/Checkbox/index.tsx index 58da8c1fe4b..db8972d6dda 100644 --- a/components/src/atoms/Checkbox/index.tsx +++ b/components/src/atoms/Checkbox/index.tsx @@ -26,6 +26,10 @@ export interface CheckboxProps { tabIndex?: number /** if disabled is true, mouse events will not trigger onClick callback */ disabled?: boolean + /** optional borderRadius type */ + type?: 'round' | 'neutral' + /** optional width for helix */ + width?: string } export function Checkbox(props: CheckboxProps): JSX.Element { const { @@ -34,18 +38,22 @@ export function Checkbox(props: CheckboxProps): JSX.Element { onClick, tabIndex = 0, disabled = false, + width = FLEX_MAX_CONTENT, + type = 'round', } = props const truncatedLabel = truncateString(labelText, 25) const CHECKBOX_STYLE = css` - width: ${FLEX_MAX_CONTENT}; + width: ${width}; grid-gap: ${SPACING.spacing12}; border: none; align-items: ${ALIGN_CENTER}; flex-direction: ${DIRECTION_ROW}; color: ${isChecked ? COLORS.white : COLORS.black90}; background-color: ${isChecked ? COLORS.blue50 : COLORS.blue35}; - border-radius: ${BORDERS.borderRadiusFull}; + border-radius: ${type === 'round' + ? BORDERS.borderRadiusFull + : BORDERS.borderRadius8}; padding: ${SPACING.spacing12} ${SPACING.spacing16}; justify-content: ${JUSTIFY_SPACE_BETWEEN}; cursor: ${CURSOR_POINTER}; diff --git a/components/src/atoms/CheckboxField/index.tsx b/components/src/atoms/CheckboxField/index.tsx index ae7134fcbb5..08efd6796f6 100644 --- a/components/src/atoms/CheckboxField/index.tsx +++ b/components/src/atoms/CheckboxField/index.tsx @@ -4,7 +4,12 @@ import { SPACING, TYPOGRAPHY } from '../../ui-style-constants' import { COLORS, BORDERS } from '../../helix-design-system' import { Flex, Box } from '../../primitives' import { Icon } from '../../icons' -import { ALIGN_CENTER, CURSOR_POINTER, JUSTIFY_CENTER } from '../../styles' +import { + ALIGN_CENTER, + CURSOR_AUTO, + CURSOR_POINTER, + JUSTIFY_CENTER, +} from '../../styles' export interface CheckboxFieldProps { /** change handler */ @@ -94,7 +99,8 @@ const INNER_STYLE_NO_VALUE = css` } &:disabled { - color: ${COLORS.grey60}; + color: ${COLORS.grey50}; + cursor: ${CURSOR_AUTO}; } ` diff --git a/components/src/molecules/DropdownMenu/index.tsx b/components/src/molecules/DropdownMenu/index.tsx index 924d554cd0d..1bfba999eca 100644 --- a/components/src/molecules/DropdownMenu/index.tsx +++ b/components/src/molecules/DropdownMenu/index.tsx @@ -22,6 +22,7 @@ import { useOnClickOutside } from '../../interaction-enhancers' import { LegacyStyledText } from '../../atoms/StyledText/LegacyStyledText' import { MenuItem } from '../../atoms/MenuList/MenuItem' import { Tooltip } from '../../atoms/Tooltip' +import { StyledText } from '../../atoms/StyledText' import { LiquidIcon } from '../LiquidIcon' /** this is the max height to display 10 items */ @@ -35,6 +36,7 @@ export interface DropdownOption { value: string /** optional dropdown option for adding the liquid color icon */ liquidColor?: string + disabled?: boolean } export type DropdownBorder = 'rounded' | 'neutral' @@ -55,9 +57,11 @@ export interface DropdownMenuProps { /** dropdown item caption */ caption?: string | null /** text for tooltip */ - tooltipText?: string + tooltipText?: string | null /** html tabindex property */ tabIndex?: number + /** optional error */ + error?: string | null } // TODO: (smb: 4/15/22) refactor this to use html select for accessibility @@ -73,6 +77,7 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { caption, tooltipText, tabIndex = 0, + error, } = props const [targetProps, tooltipProps] = useHoverTooltip() const [showDropdownMenu, setShowDropdownMenu] = React.useState(false) @@ -134,13 +139,22 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { setShowDropdownMenu(!showDropdownMenu) } + let defaultBorderColor = COLORS.grey50 + let hoverBorderColor = COLORS.grey55 + if (showDropdownMenu) { + defaultBorderColor = COLORS.blue50 + hoverBorderColor = COLORS.blue50 + } else if (error) { + defaultBorderColor = COLORS.red50 + hoverBorderColor = COLORS.red50 + } + const DROPDOWN_STYLE = css` flex-direction: ${DIRECTION_ROW}; background-color: ${COLORS.white}; cursor: ${CURSOR_POINTER}; padding: ${SPACING.spacing8} ${SPACING.spacing12}; - border: 1px ${BORDERS.styleSolid} - ${showDropdownMenu ? COLORS.blue50 : COLORS.grey50}; + border: 1px ${BORDERS.styleSolid} ${defaultBorderColor}; border-radius: ${dropdownType === 'rounded' ? BORDERS.borderRadiusFull : BORDERS.borderRadius4}; @@ -150,12 +164,11 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { height: 2.25rem; &:hover { - border: 1px ${BORDERS.styleSolid} - ${showDropdownMenu ? COLORS.blue50 : COLORS.grey55}; + border: 1px ${BORDERS.styleSolid} ${hoverBorderColor}; } &:active { - border: 1px ${BORDERS.styleSolid} ${COLORS.blue50}; + border: 1px ${BORDERS.styleSolid} ${error ? COLORS.red50 : COLORS.blue50}; } &:focus-visible { @@ -170,16 +183,16 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { } ` return ( - + {title !== null ? ( - - + + {title} - + {tooltipText != null ? ( <> @@ -208,18 +221,20 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { {currentOption.liquidColor != null ? ( ) : null} - - {currentOption.name} - + + {currentOption.name} + + {showDropdownMenu ? ( @@ -262,14 +277,15 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { )} {caption != null ? ( - + {caption} ) : null} + {error != null ? ( + + {error} + + ) : null} ) } diff --git a/components/src/organisms/Toolbox/index.tsx b/components/src/organisms/Toolbox/index.tsx index 183669414bf..77ac2e12d68 100644 --- a/components/src/organisms/Toolbox/index.tsx +++ b/components/src/organisms/Toolbox/index.tsx @@ -25,6 +25,7 @@ export interface ToolboxProps { closeButtonText?: string side?: 'left' | 'right' horizontalSide?: 'top' | 'bottom' + childrenPadding?: string } export function Toolbox(props: ToolboxProps): JSX.Element { @@ -41,6 +42,7 @@ export function Toolbox(props: ToolboxProps): JSX.Element { confirmButton, side = 'right', horizontalSide = 'bottom', + childrenPadding = SPACING.spacing16, } = props const slideOutRef = React.useRef(null) @@ -108,7 +110,7 @@ export function Toolbox(props: ToolboxProps): JSX.Element { ) : null} + {tooltipContent && ( + {tooltipContent} + )} + + + { + updateValue(!value) + }} + labelText={label ?? ''} + disabled={disabled} + /> + + {value && !disabled && !isIndeterminate ? children : null} + + + ) +} diff --git a/protocol-designer/src/molecules/DropdownStepFormField/index.tsx b/protocol-designer/src/molecules/DropdownStepFormField/index.tsx new file mode 100644 index 00000000000..41faf034b0c --- /dev/null +++ b/protocol-designer/src/molecules/DropdownStepFormField/index.tsx @@ -0,0 +1,34 @@ +import * as React from 'react' +import { DropdownMenu, Flex, SPACING } from '@opentrons/components' +import type { Options } from '@opentrons/components' +import type { FieldProps } from '../../pages/Designer/ProtocolSteps/StepForm/types' + +export interface DropdownStepFormFieldProps extends FieldProps { + options: Options + title: string +} + +export function DropdownStepFormField( + props: DropdownStepFormFieldProps +): JSX.Element { + const { options, value, updateValue, title, errorToShow } = props + const availableOptionId = options.find(opt => opt.value === value) + + return ( + + { + updateValue(value) + }} + /> + + ) +} diff --git a/protocol-designer/src/molecules/index.ts b/protocol-designer/src/molecules/index.ts index ae458bce67b..b12df75dc80 100644 --- a/protocol-designer/src/molecules/index.ts +++ b/protocol-designer/src/molecules/index.ts @@ -1 +1,3 @@ +export * from './CheckboxStepFormField' +export * from './DropdownStepFormField' export * from './SettingsIcon' diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepFormToolbox.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepFormToolbox.tsx index 628b196c998..0ecd1347caa 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepFormToolbox.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepFormToolbox.tsx @@ -93,6 +93,7 @@ export function StepFormToolbox(props: StepFormToolboxProps): JSX.Element { /> */} option.value !== 'offDeck' + ) + } + + if (!useGripper && getHasWasteChute(additionalEquipmentEntities)) { + unoccupiedLabwareLocationsOptions = unoccupiedLabwareLocationsOptions.filter( + option => option.value !== WASTE_CHUTE_CUTOUT + ) + } + + const location: string = value as string + + const bothFieldsSelected = labware != null && value != null + const labwareDisplayName = + labware != null ? labwareEntities[labware]?.def.metadata.displayName : '' + let locationString = `slot ${location}` + if (location != null) { + if (robotState?.modules[location] != null) { + const moduleSlot = robotState?.modules[location].slot ?? '' + locationString = `${getModuleDisplayName( + moduleEntities[location].model + )} in slot ${moduleSlot}` + } else if (robotState?.labware[location] != null) { + const adapterSlot = robotState?.labware[location].slot + locationString = + robotState?.modules[adapterSlot] != null + ? `${getModuleDisplayName( + moduleEntities[adapterSlot].model + )} in slot ${robotState?.modules[adapterSlot].slot}` + : `slot ${robotState?.labware[location].slot}` ?? '' + } + } + return ( + + ) +} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepTools/MoveLabwareTools/MoveLabwareField.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepTools/MoveLabwareTools/MoveLabwareField.tsx new file mode 100644 index 00000000000..fdc24facebd --- /dev/null +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepTools/MoveLabwareTools/MoveLabwareField.tsx @@ -0,0 +1,18 @@ +import * as React from 'react' +import { useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { getMoveLabwareOptions } from '../../../../../../ui/labware/selectors' +import { DropdownStepFormField } from '../../../../../../molecules' +import type { FieldProps } from '../../types' + +export function MoveLabwareField(props: FieldProps): JSX.Element { + const options = useSelector(getMoveLabwareOptions) + const { t } = useTranslation('protocol_steps') + return ( + + ) +} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepTools/MoveLabwareTools/index.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepTools/MoveLabwareTools/index.tsx index 1343331cbaf..db856549b3f 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepTools/MoveLabwareTools/index.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/StepForm/StepTools/MoveLabwareTools/index.tsx @@ -1,5 +1,56 @@ import * as React from 'react' +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import { Box, COLORS, DIRECTION_COLUMN, Flex } from '@opentrons/components' +import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' +import { getRobotType } from '../../../../../../file-data/selectors' +import { CheckboxStepFormField } from '../../../../../../molecules' +import { + getAdditionalEquipment, + getCurrentFormCanBeSaved, +} from '../../../../../../step-forms/selectors' +import { MoveLabwareField } from './MoveLabwareField' +import { LabwareLocationField } from './LabwareLocationField' -export function MoveLabwareTools(): JSX.Element { - return
TODO: wire this up
+import type { StepFormProps } from '../../types' + +export function MoveLabwareTools(props: StepFormProps): JSX.Element { + const { propsForFields } = props + const { t, i18n } = useTranslation(['application', 'form', 'tooltip']) + const robotType = useSelector(getRobotType) + const canSave = useSelector(getCurrentFormCanBeSaved) + const additionalEquipment = useSelector(getAdditionalEquipment) + const isGripperAttached = Object.values(additionalEquipment).some( + equipment => equipment?.name === 'gripper' + ) + + return ( + + {robotType === FLEX_ROBOT_TYPE ? ( + + ) : null} + + + + + + + ) }