From c4cb035269f55e17b6de73a9f593971c61ca6137 Mon Sep 17 00:00:00 2001 From: Nick Diehl <47604184+ncdiehl11@users.noreply.github.com> Date: Mon, 25 Mar 2024 14:17:18 -0400 Subject: [PATCH] feat(app): populate ChooseRobotSlideout with runtime parameters (#14706) closes [AUTH-98](https://opentrons.atlassian.net/browse/AUTH-98) closes [AUTH-99](https://opentrons.atlassian.net/browse/AUTH-101) closes [AUTH-101](https://opentrons.atlassian.net/browse/AUTH-101) --- .../localization/en/protocol_details.json | 2 + app/src/atoms/InputField/index.tsx | 55 +++-- app/src/atoms/MenuList/DropdownMenu.tsx | 111 ++++++---- app/src/atoms/SelectField/Select.tsx | 24 ++- app/src/atoms/SelectField/index.tsx | 29 ++- .../__tests__/ChooseRobotSlideout.test.tsx | 75 +++++++ .../organisms/ChooseRobotSlideout/index.tsx | 194 +++++++++++++++++- .../index.tsx | 65 +++++- app/src/organisms/ProtocolDetails/index.tsx | 1 + components/src/helix-design-system/borders.ts | 1 + 10 files changed, 485 insertions(+), 72 deletions(-) diff --git a/app/src/assets/localization/en/protocol_details.json b/app/src/assets/localization/en/protocol_details.json index 757e9caab3d..cc6aa6c1e41 100644 --- a/app/src/assets/localization/en/protocol_details.json +++ b/app/src/assets/localization/en/protocol_details.json @@ -33,6 +33,7 @@ "modules": "modules", "name": "Name", "no_available_robots_found": "No available robots found", + "no_custom_values": "No custom values specified", "no_parameters": "No parameters specified in this protocol", "no_summary": "no summary specified for this protocol.", "not_connected": "not connected", @@ -58,6 +59,7 @@ "range": "Range", "read_less": "read less", "read_more": "read more", + "restore_defaults": "Restore default values", "right_mount": "right mount", "robot_configuration": "robot configuration", "robot_is_busy_with_protocol": "{{robotName}} is busy with {{protocolName}} in {{runStatus}} state. Do you want to clear it and proceed?", diff --git a/app/src/atoms/InputField/index.tsx b/app/src/atoms/InputField/index.tsx index d67710557f6..a3f4d656133 100644 --- a/app/src/atoms/InputField/index.tsx +++ b/app/src/atoms/InputField/index.tsx @@ -13,6 +13,7 @@ import { TYPOGRAPHY, TEXT_ALIGN_RIGHT, } from '@opentrons/components' +import { StyledText } from '../text' export const INPUT_TYPE_NUMBER = 'number' as const export const INPUT_TYPE_TEXT = 'text' as const @@ -183,7 +184,7 @@ function Input(props: InputFieldProps): JSX.Element { ` const FORM_BOTTOM_SPACE_STYLE = css` - padding-bottom: ${SPACING.spacing4}; + padding: ${SPACING.spacing4} 0rem; @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { padding-bottom: 0; } @@ -230,40 +231,60 @@ function Input(props: InputFieldProps): JSX.Element { return ( {props.title != null ? ( - {props.title} + + {props.title} + ) : null} { + if (props.id != null) { + document.getElementById(props.id)?.focus() + } + }} > event.currentTarget.blur()} // prevent value change with scrolling /> {props.units != null ? ( {props.units} ) : null} - + {props.error} + + ) : null} + + {props.caption != null ? ( + - {props.caption != null ? ( - {props.caption} - ) : null} - {props.secondaryCaption != null ? ( - {props.secondaryCaption} - ) : null} - {props.error} - - + {props.caption} + + ) : null} + {props.secondaryCaption != null ? ( + + {props.secondaryCaption} + + ) : null} ) } diff --git a/app/src/atoms/MenuList/DropdownMenu.tsx b/app/src/atoms/MenuList/DropdownMenu.tsx index 9693efa920a..cdd64634a13 100644 --- a/app/src/atoms/MenuList/DropdownMenu.tsx +++ b/app/src/atoms/MenuList/DropdownMenu.tsx @@ -13,6 +13,7 @@ import { BORDERS, Icon, useOnClickOutside, + POSITION_RELATIVE, } from '@opentrons/components' import { StyledText } from '../text' import { MenuItem } from './MenuItem' @@ -22,13 +23,16 @@ export interface DropdownOption { value: string } +export type DropdownBorder = 'rounded' | 'neutral' + export interface DropdownMenuProps { filterOptions: DropdownOption[] onClick: (value: string) => void currentOption: DropdownOption width?: string - dropdownType?: 'rounded' | 'neutral' + dropdownType?: DropdownBorder title?: string + caption?: string | null } // TODO: (smb: 4/15/22) refactor this to use html select for accessibility @@ -41,6 +45,7 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { width = '9.125rem', dropdownType = 'rounded', title, + caption, } = props const [showDropdownMenu, setShowDropdownMenu] = React.useState(false) const toggleSetShowDropdownMenu = (): void => { @@ -65,6 +70,7 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { align-items: ${ALIGN_CENTER}; justify-content: ${JUSTIFY_SPACE_BETWEEN}; width: ${width}; + height: 2.25rem; &:hover { border: 1px ${BORDERS.styleSolid} @@ -88,53 +94,76 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { ` return ( - + {title !== null ? ( - + {title} ) : null} - { - e.preventDefault() - toggleSetShowDropdownMenu() - }} - css={DROPDOWN_STYLE} - ref={dropDownMenuWrapperRef} - > - {currentOption.name} - {showDropdownMenu ? ( - - ) : ( - - )} - - {showDropdownMenu && ( + { + e.preventDefault() + toggleSetShowDropdownMenu() + }} + css={DROPDOWN_STYLE} > - {filterOptions.map((option, index) => ( - { - onClick(option.value) - setShowDropdownMenu(false) - }} - > - {option.name} - - ))} + + {currentOption.name} + + {showDropdownMenu ? ( + + ) : ( + + )} - )} + {showDropdownMenu && ( + + {filterOptions.map((option, index) => ( + { + onClick(option.value) + setShowDropdownMenu(false) + }} + > + {option.name} + + ))} + + )} + + {caption != null ? ( + + {caption} + + ) : null} ) } diff --git a/app/src/atoms/SelectField/Select.tsx b/app/src/atoms/SelectField/Select.tsx index f0f49389b92..0c1d4ebdf61 100644 --- a/app/src/atoms/SelectField/Select.tsx +++ b/app/src/atoms/SelectField/Select.tsx @@ -20,6 +20,7 @@ import type { CSSObjectWithLabel, DropdownIndicatorProps, } from 'react-select' +import type { DropdownBorder } from '../MenuList/DropdownMenu' export interface SelectOption { value: string @@ -31,29 +32,36 @@ export type SelectProps = ReactSelectProps interface SelectComponentProps extends SelectProps { width?: string + dropdownType?: DropdownBorder } const VOID_STYLE: unknown = undefined const NO_STYLE_FN = (): CSSObjectWithLabel => VOID_STYLE as CSSObjectWithLabel export function Select(props: SelectComponentProps): JSX.Element { + const { dropdownType, menuIsOpen, width } = props const CLEAR_DEFAULT_STYLES_AND_SET_NEW_STYLES: StylesConfig = { clearIndicator: NO_STYLE_FN, control: (styles: CSSObjectWithLabel) => ({ ...styles, - borderRadius: BORDERS.borderRadiusFull, - border: BORDERS.lineBorder, - width: props.width ?? 'auto', + borderRadius: + dropdownType === 'rounded' + ? BORDERS.borderRadiusFull + : BORDERS.borderRadius4, + border: `1px ${BORDERS.styleSolid} ${ + menuIsOpen ? COLORS.blue50 : COLORS.grey50 + }`, + width: width ?? 'auto', height: SPACING.spacing16, borderColor: COLORS.grey30, boxShadow: 'none', padding: SPACING.spacing6, flexDirection: DIRECTION_ROW, '&:hover': { - borderColor: COLORS.grey60, + borderColor: COLORS.grey50, }, '&:active': { - borderColor: COLORS.grey60, + borderColor: COLORS.blue50, }, }), container: (styles: CSSObjectWithLabel) => ({ @@ -83,7 +91,7 @@ export function Select(props: SelectComponentProps): JSX.Element { menu: (styles: CSSObjectWithLabel) => ({ ...styles, backgroundColor: COLORS.white, - width: props.width != null ? props.width : 'auto', + width: width != null ? width : 'auto', boxShadowcha: '0px 1px 3px rgba(0, 0, 0, 0.2)', borderRadius: '4px 4px 0px 0px', marginTop: SPACING.spacing4, @@ -155,9 +163,9 @@ function DropdownIndicator( width={SPACING.spacing20} > {Boolean(props.selectProps.menuIsOpen) ? ( - + ) : ( - + )} diff --git a/app/src/atoms/SelectField/index.tsx b/app/src/atoms/SelectField/index.tsx index 646ecd60378..c277432ec93 100644 --- a/app/src/atoms/SelectField/index.tsx +++ b/app/src/atoms/SelectField/index.tsx @@ -1,8 +1,15 @@ import * as React from 'react' import find from 'lodash/find' import { Select } from './Select' -import { COLORS, Flex, TYPOGRAPHY } from '@opentrons/components' +import { + COLORS, + DIRECTION_COLUMN, + Flex, + TYPOGRAPHY, + SPACING, +} from '@opentrons/components' import { css } from 'styled-components' +import { StyledText } from '../text' import type { SelectProps, SelectOption } from './Select' import type { ActionMeta, MultiValue, SingleValue } from 'react-select' @@ -24,6 +31,8 @@ export interface SelectFieldProps { menuPosition?: SelectProps['menuPosition'] /** render function for the option label passed to react-select */ formatOptionLabel?: SelectProps['formatOptionLabel'] + /** optional title */ + title?: React.ReactNode /** optional caption. hidden when `error` is given */ caption?: React.ReactNode /** if included, use error style and display error instead of caption */ @@ -40,10 +49,12 @@ export interface SelectFieldProps { isSearchable?: boolean /** optional width to specify the width of the select field and dropdown menu */ width?: string + dropdownType?: 'rounded' | 'neutral' } const CAPTION_STYLE = css` font-size: ${TYPOGRAPHY.fontSizeCaption}; + padding-top: ${SPACING.spacing4}; &.error { color: ${COLORS.red50}; font-weight: ${TYPOGRAPHY.fontWeightSemiBold}; @@ -64,6 +75,8 @@ export function SelectField(props: SelectFieldProps): JSX.Element { onLoseFocus, isSearchable = true, width, + title, + dropdownType = 'rounded', } = props const allOptions = options.flatMap(og => 'options' in og ? og.options : [og] @@ -72,7 +85,16 @@ export function SelectField(props: SelectFieldProps): JSX.Element { const caption = error != null || props.caption return ( - <> + + {title != null ? ( + + {title} + + ) : null}