Skip to content

Commit

Permalink
refactor(app): refactor app dropdown and input fields
Browse files Browse the repository at this point in the history
Modify style and functionality of DropdownMenu and InputField in app in preparation for runtime
parameters. In future work, we will prepare these components for use in our shared components
library.
  • Loading branch information
ncdiehl11 committed Mar 13, 2024
1 parent 1e66720 commit 1fd4b14
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 111 deletions.
149 changes: 115 additions & 34 deletions app/src/atoms/InputField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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' */
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -103,19 +129,26 @@ function Input(props: InputFieldProps): JSX.Element {
flex: 1 1 auto;
width: 100%;
height: ${SPACING.spacing16};
text-align: ${textAlign};
}
& input:focus {
outline: none;
}
&: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};
}
Expand All @@ -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`
Expand All @@ -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} {
Expand All @@ -142,37 +214,46 @@ function Input(props: InputFieldProps): JSX.Element {
`

return (
<Flex width="100%" flexDirection={DIRECTION_COLUMN}>
<Flex css={INPUT_FIELD}>
<input
{...props}
data-testid={props.id}
value={value}
placeholder={placeHolder}
/>
{props.units != null && (
<Flex
display={DISPLAY_INLINE_BLOCK}
textAlign={TEXT_ALIGN_RIGHT}
alignSelf={ALIGN_CENTER}
color={props.disabled ? COLORS.grey40 : COLORS.grey50}
fontSize={TYPOGRAPHY.fontSizeLabel}
>
{props.units}
</Flex>
)}
</Flex>
<Flex flexDirection={DIRECTION_COLUMN} width="100%">
{props.title != null && <Flex css={TITLE_STYLE}>{props.title}</Flex>}
<Flex
color={COLORS.grey50}
fontSize={TYPOGRAPHY.fontSizeLabel}
paddingTop={SPACING.spacing4}
width="100%"
flexDirection={DIRECTION_COLUMN}
onClick={() =>
props.id !== undefined && document.getElementById(props.id)?.focus()
}
css={OUTER_CSS}
>
<Flex css={FORM_BOTTOM_SPACE_STYLE}>{props.caption}</Flex>
{props.secondaryCaption != null ? (
<Flex css={FORM_BOTTOM_SPACE_STYLE}>{props.secondaryCaption}</Flex>
) : null}
<Flex css={ERROR_TEXT_STYLE}>{props.error}</Flex>
<Flex css={INPUT_FIELD} padding alignItems={ALIGN_CENTER}>
<input

Check failure on line 228 in app/src/atoms/InputField/index.tsx

View workflow job for this annotation

GitHub Actions / js checks

Type '{ "data-testid": string | undefined; value: string | number; placeholder: string | undefined; disabled?: boolean | undefined; onChange?: ChangeEventHandler<HTMLInputElement> | undefined; name?: string | undefined; id?: string | undefined; units?: ReactNode; error?: string | null | undefined; title?: string | null | undefined; caption?: string | null | undefined; secondaryCaption?: string | null | undefined; type?: "number" | "password" | "text" | undefined; onClick?: ((event: MouseEvent<HTMLInputElement, MouseEvent>) => unknown) | undefined; onFocus?: ((event: FocusEvent<HTMLInputElement, Element>) => unknown) | undefined; onBlur?: ((event: FocusEvent<HTMLInputElement, Element>) => unknown) | undefined; readOnly?: boolean | undefined; tabIndex?: number | undefined; autoFocus?: boolean | undefined; isIndeterminate?: boolean | undefined; max?: number | undefined; min?: number | undefined; textAlign?: "left" | "right" | "center" | undefined; size?: "medium" | "small" | undefined; }' is not assignable to type 'DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>'.

Check failure on line 228 in app/src/atoms/InputField/index.tsx

View workflow job for this annotation

GitHub Actions / js checks

Type '{ "data-testid": string | undefined; value: string | number; placeholder: string | undefined; disabled?: boolean | undefined; onChange?: ChangeEventHandler<HTMLInputElement> | undefined; name?: string | undefined; id?: string | undefined; units?: ReactNode; error?: string | null | undefined; title?: string | null | undefined; caption?: string | null | undefined; secondaryCaption?: string | null | undefined; type?: "number" | "password" | "text" | undefined; onClick?: ((event: MouseEvent<HTMLInputElement, MouseEvent>) => unknown) | undefined; onFocus?: ((event: FocusEvent<HTMLInputElement, Element>) => unknown) | undefined; onBlur?: ((event: FocusEvent<HTMLInputElement, Element>) => unknown) | undefined; readOnly?: boolean | undefined; tabIndex?: number | undefined; autoFocus?: boolean | undefined; isIndeterminate?: boolean | undefined; max?: number | undefined; min?: number | undefined; textAlign?: "left" | "right" | "center" | undefined; size?: "medium" | "small" | undefined; }' is not assignable to type 'DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>'.
{...props}
data-testid={props.id}
value={value}
placeholder={placeHolder}
/>
{props.units != null && (
<Flex
display={DISPLAY_INLINE_BLOCK}
textAlign={TEXT_ALIGN_RIGHT}
color={props.disabled ? COLORS.grey40 : COLORS.grey50}
fontSize={TYPOGRAPHY.fontSizeLabel}
>
{props.units}
</Flex>
)}
</Flex>
<Flex
color={COLORS.grey60}
fontSize={TYPOGRAPHY.fontSizeLabel}
paddingTop={SPACING.spacing4}
flexDirection={DIRECTION_COLUMN}
>
<Flex css={FORM_BOTTOM_SPACE_STYLE}>{props.caption}</Flex>
{props.secondaryCaption != null ? (
<Flex css={FORM_BOTTOM_SPACE_STYLE}>{props.secondaryCaption}</Flex>
) : null}
<Flex css={ERROR_TEXT_STYLE}>{props.error}</Flex>
</Flex>
</Flex>
</Flex>
)
Expand Down
78 changes: 60 additions & 18 deletions app/src/atoms/MenuList/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,52 +26,94 @@ 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<boolean>(false)
const toggleSetShowDropdownMenu = (): void =>
setShowDropdownMenu(!showDropdownMenu)
const dropDownMenuWrapperRef = useOnClickOutside<HTMLDivElement>({
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 (
<>
<Flex
flexDirection={DIRECTION_ROW}
alignItems={ALIGN_CENTER}
justifyContent={JUSTIFY_SPACE_BETWEEN}
width="9.125rem"
onClick={toggleSetShowDropdownMenu}
border={BORDERS.lineBorder}
borderRadius={BORDERS.radiusRoundEdge}
padding={SPACING.spacing8}
backgroundColor={COLORS.white}
css={css`
cursor: pointer;
`}
onClick={(e: MouseEvent) => {
e.preventDefault()
toggleSetShowDropdownMenu()
}}
css={DROPDOWN_STYLE}
>
<StyledText css={TYPOGRAPHY.pSemiBold}>{currentOption.name}</StyledText>
<Icon
height={TYPOGRAPHY.lineHeight16}
name={showDropdownMenu ? 'chevron-up' : 'chevron-down'}
/>
{showDropdownMenu ? (
<Icon
height={TYPOGRAPHY.lineHeight16}
name="menu-down"
transform="rotate(180deg)"
/>
) : (
<Icon height={TYPOGRAPHY.lineHeight16} name="menu-down" />
)}
</Flex>
{showDropdownMenu && (
<Flex
ref={dropDownMenuWrapperRef}
zIndex={2}
borderRadius={BORDERS.radiusSoftCorners}
borderRadius={BORDERS.borderRadiusSize2}
boxShadow="0px 1px 3px rgba(0, 0, 0, 0.2)"
position={POSITION_ABSOLUTE}
backgroundColor={COLORS.white}
top="8.5rem"
left={SPACING.spacing16}
flexDirection={DIRECTION_COLUMN}
width={width}
>
{filterOptions.map((option, index) => (
<MenuItem
Expand Down
6 changes: 1 addition & 5 deletions app/src/atoms/MenuList/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,7 @@ export const MenuItem = styled.button<ButtonProps>`
${SPACING.spacing12};
&:hover {
background-color: ${COLORS.grey10};
}
&:active {
background-color: ${COLORS.grey30};
background-color: ${COLORS.blue10};
}
&:disabled {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,6 @@ export function RenameRobotSlideout({
<StyledText as="p" marginBottom={SPACING.spacing16}>
{t('rename_robot_input_limitation_detail')}
</StyledText>
<StyledText
as="label"
css={TYPOGRAPHY.labelSemiBold}
marginBottom={SPACING.spacing8}
>
{t('robot_name')}
</StyledText>
<Controller
control={control}
name="newRobotName"
Expand All @@ -208,6 +201,7 @@ export function RenameRobotSlideout({
value={field.value}
error={fieldState.error?.message && ' '}
onBlur={field.onBlur}
title={t('robot_name')}
/>
)}
/>
Expand Down
1 change: 1 addition & 0 deletions app/src/organisms/ModuleCard/TestShakeSlideout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ export const TestShakeSlideout = (
>
<InputField
data-testid="TestShakeSlideout_shake_input"
id="TestShakeSlideout_shake_input"
autoFocus
units={RPM}
value={shakeValue != null ? Math.round(shakeValue) : null}
Expand Down
Loading

0 comments on commit 1fd4b14

Please sign in to comment.