Skip to content

Commit

Permalink
refactor(app): Desktop implementation for SelectRecoveryOption (#15691)
Browse files Browse the repository at this point in the history
This PR got away from me a little bit (and I still have to test it - the
sum of these two is why it's in draft). It's probably best to view it
commit by commit.

Overall, the point is to implement the desktop styling for the
SelectRecoveryOption component of error recovery, here:
https://www.figma.com/design/8dMeu8MuPfXoORtOV6cACO/Feature%3A-Error-Recovery-August-Release?node-id=4829-78111&t=bTwOek0mZSA61ugm-4
. This is a little weird because it is exactly semantically equivalent
to the ODD panel, but has a very different styling - two columns instead
of one. It also uses the radio buttons that we use only in OT-2 tip
length calibration method selection, which are old and use cssmodules
and need a cleanup.

The approach here was to add some fundamental structure components to
InterventionModal that will render two columns on desktop (as the
TwoColumn structure, including min-size and wrap) and one column on the
ODD at full width. Then, we can build on top of that in ErrorRecovery,
with the big difference being that the ErrorRecovery component also
folds in the standard ER footer (or will - already had to find/replace a
bunch of stuff, going to move other components over to specifying their
footers through the wrapper as we update their styles).

There were also some incidental refactors. By commit, 
- 981e828 : we have these utility
components for visualizing structure components; make them handle
sometimes being size limited and sometimes not being
- a7917e7 : Add a wrapper around the
RadioGroup component. We're going to need to update these styles further
(that's a todo) but we don't want to, or I don't want to, have to mess
with cssmodules. Also I think @TamarZanzouri is touching this stuff at
the same time, so keep it isolated and droppable. Did I let the worms in
my brain drive and make a typescript wrapper for getting the change
events to take an inferred union of button values? Yes. Also note the
one change to the underlying component to specify IDs so that we can use
aria label linking in the passed components, which is both good practice
and allows tests to work later.
- e835f86 The error recovery component
for viewing failed and next commands was bundled with presenting text in
the left column, which is not what we want this panel to have, so split
it out.
- 699cb79 The first fun one: Add a
component that can responsively present one or two columns, and by
"responsively" i mean abuse css mediaquery to only do it on touchscreen
and maintain the two-column responsive presentation through desktop
resizes. Best appreciated in storybook.
- 52150cb Simultaneous refactor and
feature - we had a RecoveryContentWrapper but it was really just a
single column, this isn't helpful, add a single and twocolumn version
(lots of find and replace since I changed the name) and then add a
OneOrTwoColumn on top of that. These wrappers also encompass rendering
footers because the footer needs to be in different places in one or two
column
- 00f38e8 Use all that stuff to render
SelectRecoveryOption! Note that the tests now run on both presentations
and it all works because of that aria label stuff.

## Todo
- [x] Visual tests, at all (this is why it's a draft)
  • Loading branch information
sfoster1 authored Jul 18, 2024
1 parent fa23071 commit 8eb14ae
Show file tree
Hide file tree
Showing 30 changed files with 664 additions and 295 deletions.
36 changes: 5 additions & 31 deletions app/src/molecules/InterventionModal/OneColumn.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,13 @@
import * as React from 'react'

import {
LegacyStyledText,
Box,
Flex,
BORDERS,
RESPONSIVENESS,
SPACING,
ALIGN_CENTER,
JUSTIFY_CENTER,
} from '@opentrons/components'
import { Box, RESPONSIVENESS } from '@opentrons/components'

import { OneColumn as OneColumnComponent } from './'
import { StandInContent } from './story-utils/StandIn'

import type { Meta, StoryObj } from '@storybook/react'

function StandInContent(): JSX.Element {
return (
<Flex
border={'4px dashed #A864FFFF'}
borderRadius={BORDERS.borderRadius8}
margin={SPACING.spacing16}
height="104px"
backgroundColor="#A864FF19"
alignItems={ALIGN_CENTER}
justifyContent={JUSTIFY_CENTER}
>
<LegacyStyledText as="h1">
This is a standin for some other component
</LegacyStyledText>
</Flex>
)
}

const meta: Meta<React.ComponentProps<OneColumnComponent>> = {
const meta: Meta<React.ComponentProps<typeof OneColumnComponent>> = {
title: 'App/Molecules/InterventionModal/OneColumn',
component: OneColumnComponent,
render: args => (
Expand All @@ -46,14 +20,14 @@ const meta: Meta<React.ComponentProps<OneColumnComponent>> = {
`}
>
<OneColumnComponent>
<StandInContent />
<StandInContent>This is a standin for another component</StandInContent>
</OneColumnComponent>
</Box>
),
}

export default meta

export type Story = StoryObj<OneColumnComponent>
export type Story = StoryObj<typeof OneColumnComponent>

export const ExampleOneColumn: Story = { args: {} }
25 changes: 21 additions & 4 deletions app/src/molecules/InterventionModal/OneColumn.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
import * as React from 'react'

import { Box } from '@opentrons/components'
import {
Flex,
DIRECTION_COLUMN,
JUSTIFY_SPACE_BETWEEN,
} from '@opentrons/components'
import type { StyleProps } from '@opentrons/components'

export interface OneColumnProps {
export interface OneColumnProps extends StyleProps {
children: React.ReactNode
}

export function OneColumn({ children }: OneColumnProps): JSX.Element {
return <Box width="100%">{children}</Box>
export function OneColumn({
children,
...styleProps
}: OneColumnProps): JSX.Element {
return (
<Flex
flexDirection={DIRECTION_COLUMN}
justifyContent={JUSTIFY_SPACE_BETWEEN}
width="100%"
{...styleProps}
>
{children}
</Flex>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import * as React from 'react'

import { OneColumnOrTwoColumn } from './'

import { StandInContent } from './story-utils/StandIn'
import { VisibleContainer } from './story-utils/VisibleContainer'
import { css } from 'styled-components'
import {
RESPONSIVENESS,
Flex,
ALIGN_CENTER,
JUSTIFY_SPACE_AROUND,
DIRECTION_COLUMN,
} from '@opentrons/components'

import type { Meta, StoryObj } from '@storybook/react'

function Wrapper(props: {}): JSX.Element {
return (
<OneColumnOrTwoColumn>
<StandInContent>
<Flex
flexDirection={DIRECTION_COLUMN}
alignItems={ALIGN_CENTER}
justifyContent={JUSTIFY_SPACE_AROUND}
height="100%"
>
This component is the only one shown on the ODD.
</Flex>
</StandInContent>
<StandInContent>
<Flex
flexDirection={DIRECTION_COLUMN}
alignItems={ALIGN_CENTER}
justifyContent={JUSTIFY_SPACE_AROUND}
height="100%"
>
This component is shown in the right column on desktop.
</Flex>
</StandInContent>
</OneColumnOrTwoColumn>
)
}

const meta: Meta<React.ComponentProps<typeof Wrapper>> = {
title: 'App/Molecules/InterventionModal/OneColumnOrTwoColumn',
component: Wrapper,
decorators: [
Story => (
<VisibleContainer
css={css`
min-width: min(max-content, 100vw);
@media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} {
width: 500px;
}
`}
>
<Story />
</VisibleContainer>
),
],
}

export default meta

type Story = StoryObj<typeof Wrapper>

export const OneOrTwoColumn: Story = {}
55 changes: 55 additions & 0 deletions app/src/molecules/InterventionModal/OneColumnOrTwoColumn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react'

import { css } from 'styled-components'
import {
Flex,
Box,
DIRECTION_ROW,
SPACING,
WRAP,
RESPONSIVENESS,
} from '@opentrons/components'
import type { StyleProps } from '@opentrons/components'
import { TWO_COLUMN_ELEMENT_MIN_WIDTH } from './constants'

export interface OneColumnOrTwoColumnProps extends StyleProps {
children: [React.ReactNode, React.ReactNode]
}

export function OneColumnOrTwoColumn({
children: [leftOrSingleElement, optionallyDisplayedRightElement],
...styleProps
}: OneColumnOrTwoColumnProps): JSX.Element {
return (
<Flex
flexDirection={DIRECTION_ROW}
gap={SPACING.spacing40}
flexWrap={WRAP}
{...styleProps}
>
<Box
flex="1"
css={css`
min-width: ${TWO_COLUMN_ELEMENT_MIN_WIDTH};
@media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} {
min-width: none;
width: 100%;
}
`}
>
{leftOrSingleElement}
</Box>
<Box
flex="1"
minWidth={TWO_COLUMN_ELEMENT_MIN_WIDTH}
css={css`
@media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} {
display: none;
}
`}
>
{optionallyDisplayedRightElement}
</Box>
</Flex>
)
}
16 changes: 12 additions & 4 deletions app/src/molecules/InterventionModal/TwoColumn.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import * as React from 'react'

import { Flex, Box, DIRECTION_ROW, SPACING, WRAP } from '@opentrons/components'
import type { StyleProps } from '@opentrons/components'
import { TWO_COLUMN_ELEMENT_MIN_WIDTH } from './constants'

export interface TwoColumnProps {
export interface TwoColumnProps extends StyleProps {
children: [React.ReactNode, React.ReactNode]
}

export function TwoColumn({
children: [leftElement, rightElement],
...styleProps
}: TwoColumnProps): JSX.Element {
return (
<Flex flexDirection={DIRECTION_ROW} gap={SPACING.spacing40} flexWrap={WRAP}>
<Box flex="1" minWidth="17.1875rem">
<Flex
flexDirection={DIRECTION_ROW}
gap={SPACING.spacing40}
flexWrap={WRAP}
{...styleProps}
>
<Box flex="1" minWidth={TWO_COLUMN_ELEMENT_MIN_WIDTH}>
{leftElement}
</Box>
<Box flex="1" minWidth="17.1875rem">
<Box flex="1" minWidth={TWO_COLUMN_ELEMENT_MIN_WIDTH}>
{rightElement}
</Box>
</Flex>
Expand Down
1 change: 1 addition & 0 deletions app/src/molecules/InterventionModal/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const TWO_COLUMN_ELEMENT_MIN_WIDTH = '17.1875rem' as const
2 changes: 2 additions & 0 deletions app/src/molecules/InterventionModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { IconName } from '@opentrons/components'
import { ModalContentOneColSimpleButtons } from './ModalContentOneColSimpleButtons'
import { TwoColumn } from './TwoColumn'
import { OneColumn } from './OneColumn'
import { OneColumnOrTwoColumn } from './OneColumnOrTwoColumn'
import { ModalContentMixed } from './ModalContentMixed'
import { DescriptionContent } from './DescriptionContent'
import { DeckMapContent } from './DeckMapContent'
Expand All @@ -31,6 +32,7 @@ export {
ModalContentOneColSimpleButtons,
TwoColumn,
OneColumn,
OneColumnOrTwoColumn,
ModalContentMixed,
DescriptionContent,
DeckMapContent,
Expand Down
10 changes: 8 additions & 2 deletions app/src/molecules/InterventionModal/story-utils/StandIn.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import * as React from 'react'
import { Box, BORDERS } from '@opentrons/components'

export function StandInContent(): JSX.Element {
export function StandInContent({
children,
}: {
children?: React.ReactNode
}): JSX.Element {
return (
<Box
border={'4px dashed #A864FFFF'}
borderRadius={BORDERS.borderRadius8}
height="104px"
backgroundColor="#A864FF19"
/>
>
{children}
</Box>
)
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import * as React from 'react'

import { Box, BORDERS, SPACING } from '@opentrons/components'
import type { StyleProps } from '@opentrons/components'

export interface VisibleContainerProps {
export interface VisibleContainerProps extends StyleProps {
children: JSX.Element | JSX.Element[]
}

export function VisibleContainer({
children,
...styleProps
}: VisibleContainerProps): JSX.Element {
return (
<Box
Expand All @@ -18,6 +20,7 @@ export function VisibleContainer({
maxWidth="100vp"
maxHeight="100vp"
padding={SPACING.spacing32}
{...styleProps}
>
{children}
</Box>
Expand Down
9 changes: 6 additions & 3 deletions app/src/organisms/ErrorRecoveryFlows/RecoveryDoorOpen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import {
} from '@opentrons/components'
import { RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR } from '@opentrons/api-client'

import { RecoveryContentWrapper, RecoveryFooterButtons } from './shared'
import {
RecoverySingleColumnContentWrapper,
RecoveryFooterButtons,
} from './shared'

import type { RecoveryContentProps } from './types'

Expand All @@ -31,7 +34,7 @@ export function RecoveryDoorOpen({
const { t } = useTranslation('error_recovery')

return (
<RecoveryContentWrapper>
<RecoverySingleColumnContentWrapper>
<Flex
padding={SPACING.spacing40}
gridGap={SPACING.spacing24}
Expand Down Expand Up @@ -70,7 +73,7 @@ export function RecoveryDoorOpen({
isLoadingPrimaryBtnAction={isResumeRecoveryLoading}
/>
</Flex>
</RecoveryContentWrapper>
</RecoverySingleColumnContentWrapper>
)
}

Expand Down
6 changes: 3 additions & 3 deletions app/src/organisms/ErrorRecoveryFlows/RecoveryError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from '@opentrons/components'

import { RECOVERY_MAP } from './constants'
import { RecoveryContentWrapper } from './shared'
import { RecoverySingleColumnContentWrapper } from './shared'

import type { RecoveryContentProps } from './types'
import { SmallButton } from '../../atoms/buttons'
Expand Down Expand Up @@ -168,7 +168,7 @@ export function ErrorContent({
btnOnClick: () => void
}): JSX.Element | null {
return (
<RecoveryContentWrapper>
<RecoverySingleColumnContentWrapper>
<Flex
padding={SPACING.spacing40}
gridGap={SPACING.spacing24}
Expand Down Expand Up @@ -196,6 +196,6 @@ export function ErrorContent({
<Flex justifyContent={JUSTIFY_END}>
<SmallButton onClick={btnOnClick} buttonText={btnText} />
</Flex>
</RecoveryContentWrapper>
</RecoverySingleColumnContentWrapper>
)
}
Loading

0 comments on commit 8eb14ae

Please sign in to comment.