Skip to content

Commit

Permalink
feat(protocol-designer, components): revamp form errors and fix logic…
Browse files Browse the repository at this point in the history
… for rendering (#16576)

closes AUTH-972
  • Loading branch information
jerader authored Oct 24, 2024
1 parent a0086cc commit eb83705
Show file tree
Hide file tree
Showing 13 changed files with 76 additions and 19 deletions.
16 changes: 9 additions & 7 deletions components/src/atoms/InputField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export interface InputFieldProps {
leftIcon?: IconName
showDeleteIcon?: boolean
onDelete?: () => void
hasBackgroundError?: boolean
}

export const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
Expand All @@ -83,6 +84,7 @@ export const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
tooltipText,
tabIndex = 0,
showDeleteIcon = false,
hasBackgroundError = false,
...inputProps
} = props
const hasError = props.error != null
Expand All @@ -103,11 +105,13 @@ export const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(

const INPUT_FIELD = css`
display: flex;
background-color: ${COLORS.white};
background-color: ${hasBackgroundError ? COLORS.red30 : COLORS.white};
border-radius: ${BORDERS.borderRadius4};
padding: ${SPACING.spacing8};
border: 1px ${BORDERS.styleSolid}
${hasError ? COLORS.red50 : COLORS.grey50};
border: ${hasBackgroundError
? 'none'
: `1px ${BORDERS.styleSolid}
${hasError ? COLORS.red50 : COLORS.grey50}`};
font-size: ${TYPOGRAPHY.fontSizeP};
width: 100%;
height: ${size === 'small' ? '2rem' : '2.75rem'};
Expand Down Expand Up @@ -321,10 +325,7 @@ export const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
</StyledText>
) : null}
{hasError ? (
<StyledText
desktopStyle="bodyDefaultRegular"
css={ERROR_TEXT_STYLE}
>
<StyledText desktopStyle="captionRegular" css={ERROR_TEXT_STYLE}>
{props.error}
</StyledText>
) : null}
Expand All @@ -335,6 +336,7 @@ export const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
)

const StyledInput = styled.input`
background-color: transparent;
&::placeholder {
color: ${COLORS.grey40};
}
Expand Down
8 changes: 8 additions & 0 deletions components/src/molecules/DropdownMenu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export interface DropdownMenuProps {
tabIndex?: number
/** optional error */
error?: string | null
/** focus handler */
onFocus?: React.FocusEventHandler<HTMLButtonElement>
/** blur handler */
onBlur?: React.FocusEventHandler<HTMLButtonElement>
}

// TODO: (smb: 4/15/22) refactor this to use html select for accessibility
Expand All @@ -79,6 +83,8 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element {
tooltipText,
tabIndex = 0,
error,
onFocus,
onBlur,
} = props
const [targetProps, tooltipProps] = useHoverTooltip()
const [showDropdownMenu, setShowDropdownMenu] = React.useState<boolean>(false)
Expand Down Expand Up @@ -222,6 +228,8 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element {
e.preventDefault()
toggleSetShowDropdownMenu()
}}
onFocus={onFocus}
onBlur={onBlur}
css={DROPDOWN_STYLE}
tabIndex={tabIndex}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export function DropdownStepFormField(
tooltipContent,
addPadding = true,
width = '17.5rem',
onFieldFocus,
onFieldBlur,
} = props
const { t } = useTranslation('tooltip')
const availableOptionId = options.find(opt => opt.value === value)
Expand All @@ -35,6 +37,8 @@ export function DropdownStepFormField(
dropdownType="neutral"
filterOptions={options}
title={title}
onBlur={onFieldBlur}
onFocus={onFieldFocus}
currentOption={
availableOptionId ?? { name: 'Choose option', value: '' }
}
Expand Down
1 change: 1 addition & 0 deletions protocol-designer/src/organisms/Alerts/FormAlerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ function FormAlertsComponent(props: FormAlertsProps): JSX.Element | null {
: undefined
}
width="100%"
iconMarginLeft={SPACING.spacing4}
>
<Flex flexDirection={DIRECTION_COLUMN} gridGap={SPACING.spacing4}>
<StyledText desktopStyle="bodyDefaultSemiBold">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type WellSelectionFieldProps = FieldProps & {
nozzles: string | null
pipetteId?: string | null
labwareId?: string | null
hasFormError?: boolean
}

export const WellSelectionField = (
Expand All @@ -45,6 +46,7 @@ export const WellSelectionField = (
disabled,
errorToShow,
tooltipContent,
hasFormError,
} = props
const { t, i18n } = useTranslation(['form', 'tooltip'])
const dispatch = useDispatch()
Expand Down Expand Up @@ -90,7 +92,6 @@ export const WellSelectionField = (
? t(`step_edit_form.wellSelectionLabel.columns_${name}`)
: t(`step_edit_form.wellSelectionLabel.wells_${name}`)
const [targetProps, tooltipProps] = useHoverTooltip()

return (
<>
<Flex flexDirection={DIRECTION_COLUMN} padding={SPACING.spacing16}>
Expand All @@ -116,6 +117,7 @@ export const WellSelectionField = (
error={errorToShow}
value={primaryWellCount}
onClick={handleOpen}
hasBackgroundError={hasFormError}
/>
</Flex>
{createPortal(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,15 @@ import {
TemperatureTools,
ThermocyclerTools,
} from './StepTools'
import { getSaveStepSnackbarText } from './utils'
import {
getSaveStepSnackbarText,
getVisibleFormErrors,
getVisibleFormWarnings,
} from './utils'
import type { StepFieldName } from '../../../../steplist/fieldLevel'
import type { FormData, StepType } from '../../../../form-types'
import type { FieldPropsByName, FocusHandlers, StepFormProps } from './types'
import { getFormLevelErrorsForUnsavedForm } from '../../../../step-forms/selectors'

type StepFormMap = {
[K in StepType]?: React.ComponentType<StepFormProps> | null
Expand Down Expand Up @@ -91,6 +96,9 @@ export function StepFormToolbox(props: StepFormToolboxProps): JSX.Element {
const timelineWarningsForSelectedStep = useSelector(
getTimelineWarningsForSelectedStep
)
const formLevelErrorsForUnsavedForm = useSelector(
getFormLevelErrorsForUnsavedForm
)
const timeline = useSelector(getRobotStateTimeline)
const [toolboxStep, setToolboxStep] = useState<number>(
// progress to step 2 if thermocycler form is populated
Expand All @@ -103,6 +111,16 @@ export function StepFormToolbox(props: StepFormToolboxProps): JSX.Element {
showFormErrorsAndWarnings,
setShowFormErrorsAndWarnings,
] = useState<boolean>(false)
const visibleFormWarnings = getVisibleFormWarnings({
focusedField,
dirtyFields: dirtyFields ?? [],
errors: formWarningsForSelectedStep,
})
const visibleFormErrors = getVisibleFormErrors({
focusedField,
dirtyFields: dirtyFields ?? [],
errors: formLevelErrorsForUnsavedForm,
})
const [isRename, setIsRename] = useState<boolean>(false)
const icon = stepIconsByType[formData.stepType]

Expand All @@ -126,7 +144,7 @@ export function StepFormToolbox(props: StepFormToolboxProps): JSX.Element {
formData.stepType === 'mix' ||
formData.stepType === 'thermocycler'
const numWarnings =
formWarningsForSelectedStep.length + timelineWarningsForSelectedStep.length
visibleFormWarnings.length + timelineWarningsForSelectedStep.length
const numErrors = timeline.errors?.length ?? 0

const handleSaveClick = (): void => {
Expand Down Expand Up @@ -229,6 +247,7 @@ export function StepFormToolbox(props: StepFormToolboxProps): JSX.Element {
propsForFields,
focusHandlers,
toolboxStep,
visibleFormErrors,
}}
/>
</Toolbox>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {
import type { StepFormProps } from '../../types'

export function MixTools(props: StepFormProps): JSX.Element {
const { propsForFields, formData, toolboxStep } = props
const { propsForFields, formData, toolboxStep, visibleFormErrors } = props
const pipettes = useSelector(getPipetteEntities)
const enableReturnTip = useSelector(getEnableReturnTip)
const labwares = useSelector(getLabwareEntities)
Expand Down Expand Up @@ -89,6 +89,11 @@ export function MixTools(props: StepFormProps): JSX.Element {
labwareId={formData.labware}
pipetteId={formData.pipette}
nozzles={String(propsForFields.nozzles.value) ?? null}
hasFormError={
visibleFormErrors?.some(error =>
error.dependentFields.includes('labware')
) ?? false
}
/>
<Divider marginY="0" />
<VolumeField {...propsForFields.volume} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const makeAddFieldNamePrefix = (prefix: string) => (
): StepFieldName => `${prefix}_${fieldName}`

export function MoveLiquidTools(props: StepFormProps): JSX.Element {
const { toolboxStep, propsForFields, formData } = props
const { toolboxStep, propsForFields, formData, visibleFormErrors } = props
const { t, i18n } = useTranslation(['protocol_steps', 'form'])
const { path } = formData
const [tab, setTab] = useState<'aspirate' | 'dispense'>('aspirate')
Expand Down Expand Up @@ -126,6 +126,11 @@ export function MoveLiquidTools(props: StepFormProps): JSX.Element {
labwareId={String(propsForFields.aspirate_labware.value)}
pipetteId={formData.pipette}
nozzles={String(propsForFields.nozzles.value) ?? null}
hasFormError={
visibleFormErrors?.some(error =>
error.dependentFields.includes('aspirate_labware')
) ?? false
}
/>
<Divider marginY="0" />
<LabwareField {...propsForFields.dispense_labware} />
Expand All @@ -136,6 +141,11 @@ export function MoveLiquidTools(props: StepFormProps): JSX.Element {
labwareId={String(propsForFields.dispense_labware.value)}
pipetteId={formData.pipette}
nozzles={String(propsForFields.nozzles.value) ?? null}
hasFormError={
visibleFormErrors?.some(error =>
error.dependentFields.includes('dispense_wells')
) ?? false
}
/>
)}
<Divider marginY="0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ describe('MagnetTools', () => {
dirtyFields: [],
focusedField: null,
},
visibleFormErrors: [],
toolboxStep: 1,
propsForFields: {
magnetAction: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('TemperatureTools', () => {
dirtyFields: [],
focusedField: null,
},
visibleFormErrors: [],
toolboxStep: 1,
propsForFields: {
moduleId: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,12 @@ function StepFormManager(props: StepFormManagerProps): JSX.Element | null {
if (fieldName === focusedField) {
setFocusedField(null)
}
if (!dirtyFields.includes(fieldName)) {
setDirtyFields([...dirtyFields, fieldName])
}
setDirtyFields(prevDirtyFields => {
if (!prevDirtyFields.includes(fieldName)) {
return [...prevDirtyFields, fieldName]
}
return prevDirtyFields
})
}
const stepId = formData?.id
const handleDelete = (): void => {
Expand Down Expand Up @@ -144,7 +147,6 @@ function StepFormManager(props: StepFormManagerProps): JSX.Element | null {
) {
handleSave = confirmAddPauseUntilHeaterShakerTempStep
}

return (
<>
{/* TODO: update these modals to match new modal design */}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { FormData, StepFieldName } from '../../../../form-types'
import type { StepFormErrors } from '../../../../steplist'
export interface FocusHandlers {
focusedField: StepFieldName | null
dirtyFields: StepFieldName[]
Expand All @@ -24,4 +25,5 @@ export interface StepFormProps {
focusHandlers: FocusHandlers
propsForFields: FieldPropsByName
toolboxStep: number
visibleFormErrors: StepFormErrors
}
6 changes: 3 additions & 3 deletions protocol-designer/src/steplist/formLevel/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ export interface FormError {
dependentFields: StepFieldName[]
}
const INCOMPATIBLE_ASPIRATE_LABWARE: FormError = {
title: 'Selected aspirate labware is incompatible with selected pipette',
title: 'Selected aspirate labware is incompatible with pipette',
dependentFields: ['aspirate_labware', 'pipette'],
}
const INCOMPATIBLE_DISPENSE_LABWARE: FormError = {
title: 'Selected dispense labware is incompatible with selected pipette',
title: 'Selected dispense labware is incompatible with pipette',
dependentFields: ['dispense_labware', 'pipette'],
}
const INCOMPATIBLE_LABWARE: FormError = {
title: 'Selected labware is incompatible with selected pipette',
title: 'Selected labware is incompatible with pipette',
dependentFields: ['labware', 'pipette'],
}
const PAUSE_TYPE_REQUIRED: FormError = {
Expand Down

0 comments on commit eb83705

Please sign in to comment.