Skip to content

Commit

Permalink
refactor(app): refactor app dropdownmenu and inputfield for RTP (#14655)
Browse files Browse the repository at this point in the history
  • Loading branch information
ncdiehl11 authored Mar 15, 2024
1 parent 5efa903 commit 30d9548
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 141 deletions.
166 changes: 128 additions & 38 deletions app/src/atoms/InputField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ import {
COLOR_WARNING_DARK,
COLORS,
DIRECTION_COLUMN,
DISPLAY_INLINE_BLOCK,
Flex,
RESPONSIVENESS,
SPACING,
TEXT_ALIGN_RIGHT,
TYPOGRAPHY,
TEXT_ALIGN_RIGHT,
} from '@opentrons/components'

export const INPUT_TYPE_NUMBER = 'number' as const
Expand All @@ -36,6 +35,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 +63,12 @@ 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 TYPOGRAPHY.textAlignLeft
| typeof TYPOGRAPHY.textAlignCenter
/** small or medium input field height, relevant only */
size?: 'medium' | 'small'
}

export function InputField(props: InputFieldProps): JSX.Element {
Expand All @@ -80,20 +87,39 @@ export function InputField(props: InputFieldProps): JSX.Element {
}

function Input(props: InputFieldProps): JSX.Element {
const {
placeholder,
textAlign = TYPOGRAPHY.textAlignLeft,
size = 'small',
title,
...inputProps
} = props
const error = props.error != null
const value = props.isIndeterminate ?? false ? '' : props.value ?? ''
const placeHolder = props.isIndeterminate ?? false ? '-' : props.placeholder

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.borderRadius4};
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,29 @@ function Input(props: InputFieldProps): JSX.Element {
-webkit-appearance: none;
margin: 0;
}
@media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} {
height: ${size === 'small' ? '4.25rem' : '5rem'};
box-shadow: ${error ? BORDERS.shadowBig : 'none'};
font-size: ${TYPOGRAPHY.fontSize28};
padding: ${SPACING.spacing16} ${SPACING.spacing24};
border: 2px ${BORDERS.styleSolid} ${error ? COLORS.red50 : COLORS.grey50};
&: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};
}
}
`

const FORM_BOTTOM_SPACE_STYLE = css`
Expand All @@ -133,6 +189,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 @@ -141,38 +212,57 @@ function Input(props: InputFieldProps): JSX.Element {
}
`

const UNITS_STYLE = css`
color: ${props.disabled ? COLORS.grey40 : COLORS.grey50};
font-size: ${TYPOGRAPHY.fontSizeLabel};
font-weight: ${TYPOGRAPHY.fontWeightSemiBold};
line-height: ${TYPOGRAPHY.lineHeight12};
align-text: ${TEXT_ALIGN_RIGHT};
@media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} {
color: ${props.disabled ? COLORS.grey40 : COLORS.grey50};
font-size: ${TYPOGRAPHY.fontSize22};
font-weight: ${TYPOGRAPHY.fontWeightRegular};
line-height: ${TYPOGRAPHY.lineHeight28};
justify-content: ${textAlign};
}
`

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
color={COLORS.grey50}
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 flexDirection={DIRECTION_COLUMN} width="100%">
{props.title != null ? (
<Flex css={TITLE_STYLE}>{props.title}</Flex>
) : null}
<Flex width="100%" flexDirection={DIRECTION_COLUMN} css={OUTER_CSS}>
<Flex
css={INPUT_FIELD}
alignItems={ALIGN_CENTER}
as="label"
for={inputProps.id}
>
<input
{...inputProps}
data-testid={props.id}
value={value}
placeholder={placeHolder}
/>
{props.units != null ? (
<Flex css={UNITS_STYLE}>{props.units}</Flex>
) : null}
</Flex>
<Flex
color={COLORS.grey60}
fontSize={TYPOGRAPHY.fontSizeLabel}
paddingTop={SPACING.spacing4}
flexDirection={DIRECTION_COLUMN}
>
{props.caption != null ? (
<Flex css={FORM_BOTTOM_SPACE_STYLE}>{props.caption}</Flex>
) : null}
{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
93 changes: 71 additions & 22 deletions app/src/atoms/MenuList/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,44 +26,91 @@ export interface DropdownMenuProps {
filterOptions: DropdownOption[]
onClick: (value: string) => void
currentOption: DropdownOption
width?: string
dropdownType?: 'rounded' | 'neutral'
title?: string
}

// 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',
title,
} = props
const [showDropdownMenu, setShowDropdownMenu] = React.useState<boolean>(false)
const toggleSetShowDropdownMenu = (): void =>
const toggleSetShowDropdownMenu = (): void => {
setShowDropdownMenu(!showDropdownMenu)
}
const dropDownMenuWrapperRef = useOnClickOutside<HTMLDivElement>({
onClickOutside: () => setShowDropdownMenu(false),
onClickOutside: () => {
setShowDropdownMenu(false)
},
})

const DROPDOWN_STYLE = css`
flex-direction: ${DIRECTION_ROW};
background-color: ${COLORS.white};
cursor: pointer;
padding: ${SPACING.spacing8} ${SPACING.spacing12};
border: 1px ${BORDERS.styleSolid}
${showDropdownMenu ? COLORS.blue50 : COLORS.grey50};
border-radius: ${dropdownType === 'rounded'
? BORDERS.borderRadiusFull
: BORDERS.borderRadius4};
align-items: ${ALIGN_CENTER};
justify-content: ${JUSTIFY_SPACE_BETWEEN};
width: ${width};
&:hover {
border: 1px ${BORDERS.styleSolid}
${showDropdownMenu ? COLORS.blue50 : COLORS.grey55};
}
&: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_COLUMN}>
{title !== null ? (
<StyledText as="labelSemiBold" paddingBottom={SPACING.spacing8}>
{title}
</StyledText>
) : null}
<Flex
flexDirection={DIRECTION_ROW}
alignItems={ALIGN_CENTER}
justifyContent={JUSTIFY_SPACE_BETWEEN}
width="9.125rem"
onClick={toggleSetShowDropdownMenu}
border={BORDERS.lineBorder}
borderRadius={BORDERS.borderRadius8}
padding={SPACING.spacing8}
backgroundColor={COLORS.white}
css={css`
cursor: pointer;
`}
onClick={(e: MouseEvent) => {
e.preventDefault()
toggleSetShowDropdownMenu()
}}
css={DROPDOWN_STYLE}
ref={dropDownMenuWrapperRef}
>
<StyledText css={TYPOGRAPHY.pSemiBold}>{currentOption.name}</StyledText>
<Icon
height={TYPOGRAPHY.lineHeight16}
name={showDropdownMenu ? 'chevron-up' : 'chevron-down'}
/>
{showDropdownMenu ? (
<Icon height="0.75rem" name="menu-down" transform="rotate(180deg)" />
) : (
<Icon height="0.75rem" name="menu-down" />
)}
</Flex>
{showDropdownMenu && (
<Flex
ref={dropDownMenuWrapperRef}
zIndex={2}
borderRadius={BORDERS.borderRadius8}
boxShadow="0px 1px 3px rgba(0, 0, 0, 0.2)"
Expand All @@ -72,6 +119,8 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element {
top="8.5rem"
left={SPACING.spacing16}
flexDirection={DIRECTION_COLUMN}
width={width}
ref={dropDownMenuWrapperRef}
>
{filterOptions.map((option, index) => (
<MenuItem
Expand All @@ -86,6 +135,6 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element {
))}
</Flex>
)}
</>
</Flex>
)
}
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
Loading

0 comments on commit 30d9548

Please sign in to comment.