Skip to content

Commit

Permalink
feat(app): labware and modules setup hifi designs (#12582)
Browse files Browse the repository at this point in the history
* Add deck map icon [RCORE-733]

* Apply hifi designs to labware setup page [RCORE-733]

* Code formatting fixes [RCORE-733]

* Fix small button text color [RCORE-734]

* Create shared back button component [RCORE-734]

* Use new back button in setup headers and remove continue button [RCORE-734]

* Create FloatingActionButton component and use it for Map View button [RCORE-734]

* Add connection-status icon [RCORE-734]

* Fix chip component styling [RCORE-734]

* Create new InlineNotification component [RCORE-734]

* Apply hifi styling to module setup page [RCORE-734]

* Add back info icon [RCORE-734]

* Remove onClick parameter for inline notification [RCORE-734]

* Move ODDBackButton to molecules and add stories and tests [RCORE-734]

* Fix imports, formatting, and general code style improvements [RCORE-734]

* Use new typography [RCORE-734]

* Fix mangled directory names [NOTICKET]
  • Loading branch information
ewagoner authored May 3, 2023
1 parent dc58668 commit c594cf2
Show file tree
Hide file tree
Showing 30 changed files with 750 additions and 399 deletions.
2 changes: 1 addition & 1 deletion app/src/assets/localization/en/protocol_setup.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
"multiple_modules_missing": "Multiple modules missing",
"module_mismatch_error": "Module mismatch error",
"module_mismatch_title": "This robot has connected modules that are not specified in this protocol",
"module_mismatch_body": "Make sure the modules connected to this robot are of the right type and generation.",
"module_mismatch_body": "Make sure the modules connected to this robot are of the right type and generation",
"modules": "Modules",
"modules_connected": "{{count}} module connected",
"modules_connected_plural": "{{count}} modules connected",
Expand Down
4 changes: 2 additions & 2 deletions app/src/atoms/Chip/__tests__/Chip.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('Chip', () => {
expect(chip).toHaveStyle(`background-color: ${String(COLORS.yellow_three)}`)
expect(chipText).toHaveStyle(`color: ${String(COLORS.yellow_one)}`)
const icon = getByLabelText('icon_mockWarning')
expect(icon).toHaveStyle(`color: ${String(COLORS.yellow_two)}`)
expect(icon).toHaveStyle(`color: ${String(COLORS.yellow_one)}`)
})

it('should render text, icon, no bgcolor with warning colors and bg false', () => {
Expand All @@ -81,7 +81,7 @@ describe('Chip', () => {
expect(chip).toHaveStyle(`background-color: ${String(COLORS.transparent)}`)
expect(chipText).toHaveStyle(`color: ${String(COLORS.yellow_one)}`)
const icon = getByLabelText('icon_mockWarning')
expect(icon).toHaveStyle(`color: ${String(COLORS.yellow_two)}`)
expect(icon).toHaveStyle(`color: ${String(COLORS.yellow_one)}`)
})

it('should render text, icon, bgcolor with neutral colors', () => {
Expand Down
6 changes: 3 additions & 3 deletions app/src/atoms/Chip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const CHIP_PROPS_BY_TYPE: Record<
warning: {
backgroundColor: COLORS.yellow_three,
borderRadius: BORDERS.size_six,
iconColor: COLORS.yellow_two,
iconColor: COLORS.yellow_one,
textColor: COLORS.yellow_one,
},
}
Expand Down Expand Up @@ -98,8 +98,8 @@ export function Chip({
/>
)}
<StyledText
fontSize="1.25rem"
lineHeight="1.6875rem"
fontSize={TYPOGRAPHY.fontSize22}
lineHeight={TYPOGRAPHY.lineHeight28}
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
color={CHIP_PROPS_BY_TYPE[type].textColor}
>
Expand Down
39 changes: 39 additions & 0 deletions app/src/atoms/InlineNotification/InlineNotification.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as React from 'react'
import { InlineNotification } from '.'
import type { Story, Meta } from '@storybook/react'

export default {
title: 'ODD/Atoms/InlineNotification',
argTypes: {
hug: {
control: {
type: 'boolean',
},
defaultValue: false,
},
type: {
control: {
type: 'select',
options: ['alert', 'error', 'neutral', 'success'],
},
defaultValue: 'success',
},
onCloseClick: {
control: {
type: 'boolean',
},
defaultValue: true,
},
},
} as Meta

const Template: Story<
React.ComponentProps<typeof InlineNotification>
> = args => <InlineNotification {...args} />

export const InlineNotificationComponent = Template.bind({})
InlineNotificationComponent.args = {
heading: 'awesome',
message: 'you did it',
type: 'success',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as React from 'react'
import { fireEvent } from '@testing-library/react'
import { renderWithProviders } from '@opentrons/components'
import { i18n } from '../../../i18n'
import { InlineNotification } from '..'

const render = (props: React.ComponentProps<typeof InlineNotification>) => {
return renderWithProviders(<InlineNotification {...props} />, {
i18nInstance: i18n,
})[0]
}

describe('InlineNotification', () => {
let props: React.ComponentProps<typeof InlineNotification>

beforeEach(() => {
props = {
type: 'success',
heading: 'TITLE',
}
})
it('renders success inline notification', () => {
const { getByText, getByLabelText } = render(props)
getByLabelText('icon_success')
getByText('TITLE')
})
it('renders success inline notification with exit button and when click dismisses inline notification', () => {
props = {
type: 'success',
heading: 'TITLE',
onCloseClick: jest.fn(),
}
const { getByText, getByLabelText } = render(props)
getByText('TITLE')
const btn = getByLabelText('close_icon')
fireEvent.click(btn)
expect(props.onCloseClick).toHaveBeenCalled()
})
it('renders alert inline notification', () => {
props = {
type: 'alert',
heading: 'TITLE',
}
const { getByText, getByLabelText } = render(props)
getByLabelText('icon_alert')
getByText('TITLE')
})
it('renders error inline notification', () => {
props = {
type: 'error',
heading: 'TITLE',
}
const { getByText, getByLabelText } = render(props)
getByLabelText('icon_error')
getByText('TITLE')
})
it('renders neutral inline notification', () => {
props = {
type: 'neutral',
heading: 'TITLE',
}
const { getByText, getByLabelText } = render(props)
getByLabelText('icon_neutral')
getByText('TITLE')
})
})
110 changes: 110 additions & 0 deletions app/src/atoms/InlineNotification/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import * as React from 'react'
import {
Icon,
JUSTIFY_SPACE_BETWEEN,
IconProps,
Flex,
DIRECTION_ROW,
ALIGN_CENTER,
COLORS,
SPACING,
TYPOGRAPHY,
BORDERS,
Btn,
} from '@opentrons/components'
import { StyledText } from '../text'

import type { StyleProps } from '@opentrons/components'

type InlineNotificationType = 'alert' | 'error' | 'neutral' | 'success'

export interface InlineNotificationProps extends StyleProps {
/** name constant of the icon to display */
type: InlineNotificationType
/** InlineNotification contents */
heading: string
message?: string
/** Optional dynamic width based on contents */
hug?: boolean
/** optional handler to show close button/clear alert */
onCloseClick?: (() => void) | React.MouseEventHandler<HTMLButtonElement>
}

const INLINE_NOTIFICATION_PROPS_BY_TYPE: Record<
InlineNotificationType,
{ icon: IconProps; backgroundColor: string; color: string }
> = {
alert: {
icon: { name: 'ot-alert' },
backgroundColor: COLORS.yellow_three,
color: COLORS.yellow_two,
},
error: {
icon: { name: 'ot-alert' },
backgroundColor: COLORS.red_three,
color: COLORS.red_two,
},
neutral: {
icon: { name: 'information' },
backgroundColor: COLORS.darkBlack_twenty,
color: COLORS.darkBlackEnabled,
},
success: {
icon: { name: 'ot-check' },
backgroundColor: COLORS.green_three,
color: COLORS.green_two,
},
}

export function InlineNotification(
props: InlineNotificationProps
): JSX.Element {
const { heading, hug = false, onCloseClick, message, type } = props
const fullHeading = `${heading}${message ? '. ' : ''}`
const fullmessage = `${message}.`
const inlineNotificationProps = INLINE_NOTIFICATION_PROPS_BY_TYPE[type]
const iconProps = {
...inlineNotificationProps.icon,
size: '1.75rem',
color: INLINE_NOTIFICATION_PROPS_BY_TYPE[type].color,
}
return (
<Flex
alignItems={ALIGN_CENTER}
backgroundColor={INLINE_NOTIFICATION_PROPS_BY_TYPE[type].backgroundColor}
borderRadius={BORDERS.size_three}
data-testid={`InlineNotification_${type}`}
flexDirection={DIRECTION_ROW}
gridGap="0.75rem"
justifyContent={JUSTIFY_SPACE_BETWEEN}
padding={`0.75rem ${SPACING.spacing4}`}
width={hug ? 'max-content' : '100%'}
>
<Icon {...iconProps} aria-label={`icon_${type}`} />
<Flex flex="1" alignItems={ALIGN_CENTER}>
<StyledText
fontSize={TYPOGRAPHY.fontSize22}
fontWeight={TYPOGRAPHY.fontWeightRegular}
lineHeight={TYPOGRAPHY.lineHeight28}
>
<span
css={`
font-weight: ${TYPOGRAPHY.fontWeightSemiBold};
`}
>
{fullHeading}
</span>
{message && fullmessage}
</StyledText>
</Flex>
{onCloseClick && (
<Btn
data-testid="InlineNotification_close-button"
onClick={onCloseClick}
>
<Icon aria-label="close_icon" name="close" size="3rem" />
</Btn>
)}
</Flex>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as React from 'react'
import { FloatingActionButton } from '.'
import type { Story, Meta } from '@storybook/react'
import { ICON_DATA_BY_NAME } from '@opentrons/components/src/icons/icon-data'

export default {
title: 'ODD/Atoms/Buttons/FloatingActionButton',
argTypes: {
iconName: {
control: {
type: 'select',
options: Object.keys(ICON_DATA_BY_NAME),
},
defaultValue: undefined,
},
onClick: { action: 'clicked' },
},
} as Meta

const FloatingActionButtonTemplate: Story<
React.ComponentProps<typeof FloatingActionButton>
> = args => <FloatingActionButton {...args} />
export const FloatingActionButtonComponent = FloatingActionButtonTemplate.bind(
{}
)
FloatingActionButtonComponent.args = {
buttonText: 'Button text',
disabled: false,
}
91 changes: 91 additions & 0 deletions app/src/atoms/buttons/OnDeviceDisplay/FloatingActionButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { css } from 'styled-components'

import {
Btn,
Flex,
Icon,
ALIGN_CENTER,
BORDERS,
COLORS,
DIRECTION_ROW,
POSITION_FIXED,
SPACING,
TYPOGRAPHY,
} from '@opentrons/components'
import { StyledText } from '../../text'

import type { IconName, StyleProps } from '@opentrons/components'

interface FloatingActionButtonProps extends StyleProps {
buttonText?: React.ReactNode
disabled?: boolean
iconName?: IconName
onClick: React.MouseEventHandler
}

export function FloatingActionButton(
props: FloatingActionButtonProps
): JSX.Element {
const { t } = useTranslation('protocol_setup')
const {
buttonText = t('map_view'),
disabled = false,
iconName = 'deck-map',
...buttonProps
} = props

const contentColor = disabled ? COLORS.darkBlack_sixty : COLORS.white
const FLOATING_ACTION_BUTTON_STYLE = css`
background-color: ${COLORS.highlightPurple_one};
border-radius: ${BORDERS.size_five};
box-shadow: ${BORDERS.shadowBig};
color: ${contentColor};
cursor: default;
&:active {
background-color: ${COLORS.highlightPurple_one_pressed};
}
&:focus-visible {
border-color: ${COLORS.fundamentalsFocus};
border-width: ${SPACING.spacing2};
box-shadow: ${BORDERS.shadowBig};
}
&:disabled {
background-color: ${COLORS.darkBlack_twenty};
color: ${contentColor};
}
`

return (
<Btn
bottom={SPACING.spacing5}
css={FLOATING_ACTION_BUTTON_STYLE}
disabled={disabled}
fontSize={TYPOGRAPHY.fontSize28}
fontWeight={TYPOGRAPHY.fontWeightSemiBold}
lineHeight={TYPOGRAPHY.lineHeight36}
padding={`0.75rem ${SPACING.spacing5}`}
position={POSITION_FIXED}
right={SPACING.spacing5}
{...buttonProps}
>
<Flex
alignItems={ALIGN_CENTER}
flexDirection={DIRECTION_ROW}
gridGap={SPACING.spacing3}
>
<Icon
color={contentColor}
height="3rem"
name={iconName}
width="3.75rem"
/>
<StyledText>{buttonText}</StyledText>
</Flex>
</Btn>
)
}
Loading

0 comments on commit c594cf2

Please sign in to comment.