diff --git a/app/src/atoms/InputField/index.tsx b/app/src/atoms/InputField/index.tsx index 7ffb71119d2..e69fe84021e 100644 --- a/app/src/atoms/InputField/index.tsx +++ b/app/src/atoms/InputField/index.tsx @@ -11,8 +11,10 @@ import { Flex, RESPONSIVENESS, SPACING, - TEXT_ALIGN_RIGHT, TYPOGRAPHY, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_RIGHT, } from '@opentrons/components' export const INPUT_TYPE_NUMBER = 'number' as const @@ -36,6 +38,8 @@ export interface InputFieldProps { value?: string | number | null /** if included, InputField will use error style and display error instead of caption */ error?: string | null + /** optional title */ + title?: string | null /** optional caption. hidden when `error` is given */ caption?: string | null /** appears to the right of the caption. Used for character limits, eg '0/45' */ @@ -62,6 +66,13 @@ export interface InputFieldProps { /** if input type is number, these are the min and max values */ max?: number min?: number + /** horizontal text alignment for title, input, and (sub)captions */ + textAlign?: + | typeof TEXT_ALIGN_LEFT + | typeof TEXT_ALIGN_CENTER + | typeof TEXT_ALIGN_RIGHT + /** small or medium input field height, relevant only */ + size?: 'medium' | 'small' } export function InputField(props: InputFieldProps): JSX.Element { @@ -83,17 +94,32 @@ function Input(props: InputFieldProps): JSX.Element { const error = props.error != null const value = props.isIndeterminate ?? false ? '' : props.value ?? '' const placeHolder = props.isIndeterminate ?? false ? '-' : props.placeholder + const textAlign = props.textAlign ?? 'left' + const size = props.size ?? 'small' + console.log(error) + + const OUTER_CSS = css` + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + &:focus-within { + filter: ${error + ? 'none' + : `drop-shadow(0px 0px 10px ${COLORS.blue50})`}; + } + } + ` const INPUT_FIELD = css` display: flex; background-color: ${COLORS.white}; - border-radius: ${SPACING.spacing4}; + border-radius: ${BORDERS.borderRadiusSize1}; padding: ${SPACING.spacing8}; border: 1px ${BORDERS.styleSolid} ${error ? COLORS.red50 : COLORS.grey50}; font-size: ${TYPOGRAPHY.fontSizeP}; + width: 100%; + height: 2rem; - &:active { - border: 1px ${BORDERS.styleSolid} ${COLORS.grey50}; + &:active:enabled { + border: 1px ${BORDERS.styleSolid} ${COLORS.blue50}; } & input { @@ -103,6 +129,7 @@ function Input(props: InputFieldProps): JSX.Element { flex: 1 1 auto; width: 100%; height: ${SPACING.spacing16}; + text-align: ${textAlign}; } & input:focus { outline: none; @@ -110,12 +137,18 @@ function Input(props: InputFieldProps): JSX.Element { &:hover { border: 1px ${BORDERS.styleSolid} ${error ? COLORS.red50 : COLORS.grey60}; + } + + &:focus-visible { + border: 1px ${BORDERS.styleSolid} ${error ? COLORS.red50 : COLORS.grey60}; outline: 2px ${BORDERS.styleSolid} ${COLORS.blue50}; outline-offset: 3px; } - &:focus { - border: 1px ${BORDERS.styleSolid} ${error ? COLORS.red50 : COLORS.grey60}; + + &:focus-within { + border: 1px ${BORDERS.styleSolid} ${error ? COLORS.red50 : COLORS.blue50}; } + &:disabled { border: 1px ${BORDERS.styleSolid} ${COLORS.grey30}; } @@ -124,6 +157,30 @@ function Input(props: InputFieldProps): JSX.Element { -webkit-appearance: none; margin: 0; } + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + height: ${size === 'small' ? '4.25rem' : '5rem'}; + box-shadow: ${BORDERS.shadowBig}; + font-size: ${TYPOGRAPHY.fontSize28}; + padding-left: ${SPACING.spacing24}; + border: 2px ${BORDERS.styleSolid} ${error ? COLORS.red50 : COLORS.black90}; + + &:focus-within { + box-shadow: none; + border: ${error ? '2px' : '3px'} ${BORDERS.styleSolid} + ${error ? COLORS.red50 : COLORS.blue50}; + } + & input { + color: ${COLORS.black90}; + flex: 1 1 auto; + width: 100%; + height: 100%; + font-size: ${TYPOGRAPHY.fontSize28}; + line-height: ${TYPOGRAPHY.lineHeight36}; + font-weight: ${TYPOGRAPHY.fontWeightRegular}; + color: ${COLORS.black90}; + } + } ` const FORM_BOTTOM_SPACE_STYLE = css` @@ -133,6 +190,21 @@ function Input(props: InputFieldProps): JSX.Element { } ` + const TITLE_STYLE = css` + color: ${error ? COLORS.red50 : COLORS.black90}; + padding-bottom: ${SPACING.spacing8}; + font-size: ${TYPOGRAPHY.fontSizeLabel}; + font-weight: ${TYPOGRAPHY.fontWeightSemiBold}; + line-height: ${TYPOGRAPHY.lineHeight12}; + align-text: ${textAlign}; + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + font-size: ${TYPOGRAPHY.fontSize22}; + font-weight: ${TYPOGRAPHY.fontWeightRegular}; + line-height: ${TYPOGRAPHY.lineHeight28}; + justify-content: ${textAlign}; + } + ` + const ERROR_TEXT_STYLE = css` color: ${COLORS.red50}; @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { @@ -142,37 +214,46 @@ function Input(props: InputFieldProps): JSX.Element { ` return ( - - - - {props.units != null && ( - - {props.units} - - )} - + + {props.title != null && {props.title}} + props.id !== undefined && document.getElementById(props.id)?.focus() + } + css={OUTER_CSS} > - {props.caption} - {props.secondaryCaption != null ? ( - {props.secondaryCaption} - ) : null} - {props.error} + + + {props.units != null && ( + + {props.units} + + )} + + + {props.caption} + {props.secondaryCaption != null ? ( + {props.secondaryCaption} + ) : null} + {props.error} + ) diff --git a/app/src/atoms/MenuList/DropdownMenu.tsx b/app/src/atoms/MenuList/DropdownMenu.tsx index 5c1fb657cec..b2fb1bd5a9e 100644 --- a/app/src/atoms/MenuList/DropdownMenu.tsx +++ b/app/src/atoms/MenuList/DropdownMenu.tsx @@ -26,12 +26,20 @@ export interface DropdownMenuProps { filterOptions: DropdownOption[] onClick: (value: string) => void currentOption: DropdownOption + width?: string + dropdownType?: 'rounded' | 'neutral' } // TODO: (smb: 4/15/22) refactor this to use html select for accessibility export function DropdownMenu(props: DropdownMenuProps): JSX.Element { - const { filterOptions, onClick, currentOption } = props + const { + filterOptions, + onClick, + currentOption, + width = '9.125rem', + dropdownType = 'rounded', + } = props const [showDropdownMenu, setShowDropdownMenu] = React.useState(false) const toggleSetShowDropdownMenu = (): void => setShowDropdownMenu(!showDropdownMenu) @@ -39,39 +47,73 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { onClickOutside: () => setShowDropdownMenu(false), }) + const DROPDOWN_STYLE = css` + flex-direction: ${DIRECTION_ROW}; + background-color: ${COLORS.white}; + cursor: pointer; + padding: ${SPACING.spacing8}; + border: 1px ${BORDERS.styleSolid} + ${showDropdownMenu ? COLORS.blue50 : COLORS.grey50}; + border-radius: ${dropdownType === 'rounded' + ? BORDERS.radiusRoundEdge + : BORDERS.radiusSoftCorners}; + align-items: ${ALIGN_CENTER}; + justify-content: ${JUSTIFY_SPACE_BETWEEN}; + width: ${width}; + + &:hover { + border: 1px ${BORDERS.styleSolid} + ${showDropdownMenu ? COLORS.blue50 : COLORS.grey50}; + } + + &:active { + border: 1px ${BORDERS.styleSolid} ${COLORS.blue50}; + } + + &:focus-visible { + border: 1px ${BORDERS.styleSolid} ${COLORS.grey55}; + outline: 2px ${BORDERS.styleSolid} ${COLORS.blue50}; + outline-offset: 2px; + } + + &:disabled { + background-color: ${COLORS.transparent}; + color: ${COLORS.grey40}; + } + ` + return ( <> { + e.preventDefault() + toggleSetShowDropdownMenu() + }} + css={DROPDOWN_STYLE} > {currentOption.name} - + {showDropdownMenu ? ( + + ) : ( + + )} {showDropdownMenu && ( {filterOptions.map((option, index) => ( ` ${SPACING.spacing12}; &:hover { - background-color: ${COLORS.grey10}; - } - - &:active { - background-color: ${COLORS.grey30}; + background-color: ${COLORS.blue10}; } &:disabled { diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/RenameRobotSlideout.tsx b/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/RenameRobotSlideout.tsx index 273839e048f..f5fd489c9a0 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/RenameRobotSlideout.tsx +++ b/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/RenameRobotSlideout.tsx @@ -185,13 +185,6 @@ export function RenameRobotSlideout({ {t('rename_robot_input_limitation_detail')} - - {t('robot_name')} - )} /> diff --git a/app/src/organisms/ModuleCard/TestShakeSlideout.tsx b/app/src/organisms/ModuleCard/TestShakeSlideout.tsx index 08d81682e32..e5532febdc8 100644 --- a/app/src/organisms/ModuleCard/TestShakeSlideout.tsx +++ b/app/src/organisms/ModuleCard/TestShakeSlideout.tsx @@ -253,6 +253,7 @@ export const TestShakeSlideout = ( > void @@ -74,10 +52,12 @@ export function SetWifiCred({ setPassword(e.target.value)} type={showPassword ? 'text' : 'password'} - css={SSID_INPUT_FIELD_STYLE} + onBlur={e => e.target.focus()} + autoFocus={true} /> setInputSsid(e.target.value)} type="text" - css={SSID_INPUT_FIELD_STYLE} error={errorMessage} + textAlign="center" + onBlur={e => e.target.focus()} + autoFocus={true} /> e != null && setInputSsid(String(e))} + onChange={e => { + e != null && setInputSsid(String(e)) + }} keyboardRef={keyboardRef} /> diff --git a/app/src/pages/NameRobot/index.tsx b/app/src/pages/NameRobot/index.tsx index 5780a5d8fc8..f861d797a8e 100644 --- a/app/src/pages/NameRobot/index.tsx +++ b/app/src/pages/NameRobot/index.tsx @@ -273,10 +273,10 @@ export function NameRobot(): JSX.Element { id="newRobotName" name="newRobotName" type="text" - readOnly value={field.value} error={fieldState.error?.message && ''} - css={INPUT_FIELD_ODD_STYLE} + textAlign="center" + onBlur={e => e.target.focus()} /> )} />