Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ODD): add snackbars and toasts #12378

Merged
merged 17 commits into from
Apr 4, 2023
Merged

feat(ODD): add snackbars and toasts #12378

merged 17 commits into from
Apr 4, 2023

Conversation

ewagoner
Copy link
Contributor

@ewagoner ewagoner commented Mar 28, 2023

Overview

This PR adds Toasts and Snackbars to the ODD. It also refactored the ToasterOven component and hooks so it could bake both types of components.

snackbar: https://www.figma.com/file/OIdG64Q5cgvEw82ish5Eee/Design-System-(Flex)?node-id=1108-55875&t=9rfcF1oaxwy9W6vd-0

toasts: https://www.figma.com/file/OIdG64Q5cgvEw82ish5Eee/Design-System-(Flex)?node-id=1108-55574&t=BP86HBXk18gH2gFv-0

TODO: Add closing animation (also still a TODO for desktop toasts)

Closes RAUT-379

Test Plan

There are complete sets of storybook stories and unit tests for both components.

Changelog

  • Added Snackbar
  • Added Toast for the ODD
  • Refactored ToasterOven and moved it into /organisms
  • Added ToasterOven wrapper around ODD app

Review requests

The storybook stories have all the different styles and configurations.

Risk assessment

Low

Screen.Recording.2023-03-28.at.6.45.59.PM.mov
Screen.Recording.2023-03-28.at.6.46.40.PM.mov

@ewagoner ewagoner requested review from koji, jerader and a team March 28, 2023 22:55
@codecov
Copy link

codecov bot commented Mar 28, 2023

Codecov Report

Merging #12378 (528464b) into edge (82fd757) will decrease coverage by 0.03%.
The diff coverage is 77.01%.

Impacted file tree graph

@@            Coverage Diff             @@
##             edge   #12378      +/-   ##
==========================================
- Coverage   73.81%   73.79%   -0.03%     
==========================================
  Files        2206     2229      +23     
  Lines       60935    61732     +797     
  Branches     6219     6436     +217     
==========================================
+ Hits        44981    45554     +573     
- Misses      14478    14671     +193     
- Partials     1476     1507      +31     
Flag Coverage Δ
app 72.30% <76.74%> (-0.19%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
app/src/App/DesktopApp.tsx 100.00% <ø> (ø)
...p/src/organisms/Devices/hooks/useDownloadRunLog.ts 0.00% <0.00%> (ø)
...DeviceDisplay/ProtocolDashboard/LongPressModal.tsx 48.57% <ø> (ø)
app/src/organisms/ToasterOven/ToasterOven.tsx 27.77% <33.33%> (ø)
app/src/atoms/Toast/index.tsx 76.19% <76.19%> (ø)
app/src/App/OnDeviceDisplayApp.tsx 61.53% <100.00%> (ø)
app/src/atoms/Snackbar/index.tsx 100.00% <100.00%> (ø)
...ices/RobotSettings/AdvancedTab/Troubleshooting.tsx 95.00% <100.00%> (ø)
app/src/organisms/ModuleCard/index.tsx 62.76% <100.00%> (ø)
.../src/organisms/SendProtocolToOT3Slideout/index.tsx 63.63% <100.00%> (ø)
... and 5 more

... and 43 files with indirect coverage changes

@ewagoner ewagoner marked this pull request as ready for review March 28, 2023 23:03
@ewagoner ewagoner requested a review from a team as a code owner March 28, 2023 23:03
Copy link
Member

@shlokamin shlokamin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering if we should name ToasterOven something more generic now that it can render toasts + snacks.

thought for a moment about having them be two different providers, but i think we want them to be shared by the same "oven" so they dont stomp on each other.

app/src/atoms/Toast/index.tsx Outdated Show resolved Hide resolved
@@ -34,9 +36,10 @@ export type ToastType =
export interface ToastProps extends StyleProps {
id: string
message: string
secondaryMessage?: string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is secondaryMessage being used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It's used throughout the ODD designs to pass protocol names where needed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as above re: sharing types. as far as i see the desktop toast doesn't use secondaryMessage

Copy link
Contributor Author

@ewagoner ewagoner Mar 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't yet but the design spec in Figma says "Note in the future: Toast and inline toast are the same component in desktop DS" which I interpreted to mean she would like the component to be shared between apps, using new design and props. I was trying to lay the groundwork for that by sharing as much as possible now so only the HTML will have to be swapped out later. I can totally make them two separate things though and someone later can put them together.

app/src/organisms/ToasterOven/ToasterOven.tsx Outdated Show resolved Hide resolved
app/src/organisms/ToasterOven/ToasterOven.tsx Outdated Show resolved Hide resolved
app/src/organisms/ToasterOven/ToasterOven.tsx Outdated Show resolved Hide resolved
@shlokamin
Copy link
Member

shlokamin commented Mar 29, 2023

@ewagoner
Copy link
Contributor Author

also, getting an error when hitting this storybook link

That's from the button import looking in the wrong place after that recent component work. I pushed a fix to my branch yesterday but maybe storybook didn't get rebuilt? I'll look at that. It works locally for me.

@ewagoner
Copy link
Contributor Author

wondering if we should name ToasterOven something more generic

Open to name suggestions. I thought about changing it to something boring like NotificationProvider or something like that but then realized ToasterOven is an even better name than it was before, because now it can both make toast and bake snacks :)

},
}

export function Toast(props: ToastProps): JSX.Element {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to avoid confusion, i think this component should be explicitly named ODDToast or similar

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. We're not doing that for the other components (see buttons, etc.) where we made ODD versions to replace desktop components. I am importing it into the ToasterOven with the ODD prefix to make it clear there:

import { Toast as ODDToast } from '../../atoms/Toast/OnDeviceDisplay/Toast'

I can just rename it though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to clarify, my concern is not the ODD prefix, but confusion over different components with exactly the same name

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's talk this through in the app & ui channel (ill start a message), i have thoughts but want other folks to be part of this discussion

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was easy to rename it and separate out the props and such so the two are totally separate entities. The ToasterOven is able to deal with whichever type comes through it. I did that work before seeing this, and can adapt it to however that discussion turns out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


type MakeSnackbar = (message: string, options?: MakeSnackbarOptions) => void

type EatSnackbar = () => void
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the use case for eatSnackbar? is the snackbar ever intended to close based on something other than a timeout? eatToast exist to programmatically close based on some event that happens asynchronously

Copy link
Contributor Author

@ewagoner ewagoner Mar 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not using it anywhere yet, but really the only use case is forcing a snackbar to close prematurely so another one could take its place -- there is a limit of one snackbar displayed at a time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took it out. It's premature optimization and one less thing to mock in tests that want to use the ToasterOven but don't care about snacks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noting for posterity that it was actually essential for removing the snackbar from the HTML page when used by the ToasterOven, so this will be making a return in a future PR. In isolation, it closes itself (in tests and storybook) but when placed on a page by the app, it has to be removed by force via an eat method.

@ewagoner ewagoner requested review from shlokamin and koji March 29, 2023 21:03
@ewagoner
Copy link
Contributor Author

Moving this back into Draft while I apply the design refactoring that better combines Desktop and ODD toasts into a single skinnable component.

@ewagoner ewagoner marked this pull request as draft March 31, 2023 13:38
@ewagoner
Copy link
Contributor Author

Ok! I've incorporated the new design and re-unified the props and component. You'll see I cheated just a little because we do want to unit test and storybook the two differently, so the toast component exports two versions that can be imported into test files and storybook stories separately.

@ewagoner ewagoner marked this pull request as ready for review March 31, 2023 15:07
@ewagoner ewagoner marked this pull request as draft March 31, 2023 20:40
@ewagoner
Copy link
Contributor Author

Pulling back into draft mode to implement some design tweaks hashed out with Shlok, Mel, and Koji.

@ewagoner ewagoner marked this pull request as ready for review April 1, 2023 21:15
@ewagoner
Copy link
Contributor Author

ewagoner commented Apr 1, 2023

@shlokamin @koji @mmencarelli I've applied the changes we discussed and reduced the two components down to a single Toast (with one additional abstraction only to account for the useSelector call that we can't do inside storybook).

@koji
Copy link
Contributor

koji commented Apr 1, 2023

@ewagoner
I updated your message because you mentioned different Mel who isn't in app & ui team lol

Copy link
Member

@shlokamin shlokamin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is great, and gets us much much closer to the desired architecture i described in the comment below. thank you!

I scheduled time for us to talk with mel about a naming system to be able to move forward with the proposed architecture.

Two things:

  1. take a look at the storybook guidelines i wrote on Friday afternoon (very new). mel wants us to have one single story with configurable props
  2. there is one place in the toast component where we swap a whole JSX element rather than just swapping styles. we'll need to refactor that (assuming that is possible...) in order to make the proposed design token architecture work

im fine with this merging and addressing the things above in a follow up, whatever works best for you!

Comment on lines 52 to 57
// I really, really wanted to get this done in a single component, but the useSelector
// is a huuuuge pain to try to get to work in storybook. I couldn't mock it, I couldn't
// ignore it, and I couldn't get storybook to work in isolation with it there. So... I
// confined it to the Toast omponent the rest of the app sees and have it determine what
// display it is using at runtime. Storybook then can use the RawToast and just pass in
// what display it wants to see.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is very annoying. i think a good path forward to get around this is to leverage some sort of theme provider like this that we'd make on our own.

we could define it and pull it in at the top level of our app like we do for redux + react router + react query + i18n: https://github.com/Opentrons/opentrons/blob/edge/app/src/index.tsx

then inside of our tests, we can have our renderwithProviders util take in an additional option flag that tells our testing environ,ent which theme provider to use (we can default to desktop app). okay... that takes care of the testing hurdle.

as for storybook, its kinda similar. inside of our stories we can render each ODD component inside of the ODD theme provider like this:

const MediumButtonTemplate: Story<
  React.ComponentProps<typeof MediumButton>
> = args => <ThemeProvider theme="ODD" ><MediumButton {...args} /><ThemeProvider theme="ODD" >

okay... but here come's the hard part. what do with all of the ternaries?? we have to come up with some sort of semantic variable that describes a color that is shared across platforms. ex.

backgroundColor: `${
        showODDStyle ? COLORS.yellow_four : COLORS.errorBackgroundLight
      }`,

// would need to become

backgroundColor: `${COLORS. errorYellow}`,

and COLORS.errorYellow would be provided by the theme provider depending on which platform we're on.

that means we have to come up with a strict mapping of platform to colors, and name them something semantically meaningful. this will be difficult and require close collaboration with @mmencarelli

how does that sound?

app/src/atoms/Toast/index.tsx Outdated Show resolved Hide resolved
@ewagoner
Copy link
Contributor Author

ewagoner commented Apr 3, 2023

  1. take a look at the storybook guidelines i wrote on Friday afternoon (very new). mel wants us to have one single story with configurable props

@shlokamin Those guidelines say If the component has a version that runs on both desktop and ODD, there should be a story for each platform. This is what I've done. Are you saying they should be combined into a single story?

@shlokamin
Copy link
Member

in Toast.stories.tsx i see 8 stories:

export const Success = TemplateWithTimeout.bind({})
Success.args = {
  message: 'Success Toast message',
  type: 'success',
}

export const Warning = TemplateWithTimeout.bind({})
Warning.args = {
  message: 'Warning Toast message',
  type: 'warning',
}

export const Error = TemplateWithTimeout.bind({})
Error.args = {
  message: 'Error Toast message',
  type: 'error',
}

export const Info = TemplateWithTimeout.bind({})
Info.args = {
  message: 'Info Toast message',
  type: 'info',
}


export const SuccessWithoutTimeout = TemplateWithoutTimeout.bind({})
SuccessWithoutTimeout.args = {
  message: 'Success Toast message',
  type: 'success',
  disableTimeout: true,
}

export const WarningWithoutTimeout = TemplateWithoutTimeout.bind({})
WarningWithoutTimeout.args = {
  message: 'Warning Toast message',
  type: 'warning',
  disableTimeout: true,
}

export const ErrorWithoutTimeout = TemplateWithoutTimeout.bind({})
ErrorWithoutTimeout.args = {
  message: 'Error Toast message',
  type: 'error',
  disableTimeout: true,
}

export const InfoWithoutTimeout = TemplateWithoutTimeout.bind({})
InfoWithoutTimeout.args = {
  message: 'Info Toast message',
  type: 'info',
  disableTimeout: true,
}

and in ODDToast.stories.tsx i see another 8:

export const Success = TemplateWithTimeout.bind({})
Success.args = {
  message: 'Success Toast message',
  type: 'success',
}

export const Warning = TemplateWithTimeout.bind({})
Warning.args = {
  message: 'Warning Toast message',
  type: 'warning',
}

export const Error = TemplateWithTimeout.bind({})
Error.args = {
  message: 'Error Toast message',
  type: 'error',
}

export const Info = TemplateWithTimeout.bind({})
Info.args = {
  message: 'Info Toast message',
  type: 'info',
}


export const SuccessWithoutTimeout = TemplateWithoutTimeout.bind({})
SuccessWithoutTimeout.args = {
  message: 'Success Toast message',
  type: 'success',
  disableTimeout: true,
}

export const WarningWithoutTimeout = TemplateWithoutTimeout.bind({})
WarningWithoutTimeout.args = {
  message: 'Warning Toast message',
  type: 'warning',
  disableTimeout: true,
}

export const ErrorWithoutTimeout = TemplateWithoutTimeout.bind({})
ErrorWithoutTimeout.args = {
  message: 'Error Toast message',
  type: 'error',
  disableTimeout: true,
}

export const InfoWithoutTimeout = TemplateWithoutTimeout.bind({})
InfoWithoutTimeout.args = {
  message: 'Info Toast message',
  type: 'info',
  disableTimeout: true,
}

@shlokamin
Copy link
Member

we want one ODD toast story and one desktop story with configurable props

@ewagoner
Copy link
Contributor Author

ewagoner commented Apr 3, 2023

we want one ODD toast story and one desktop story with configurable props

Oh, I see what you mean. I was counting the whole file as one story, and it contained multiple examples of how the configurable props could be initialized, so you could easily see them in action and set them as you want. I'll remove all the examples.

app/src/atoms/Toast/index.tsx Outdated Show resolved Hide resolved
@ewagoner
Copy link
Contributor Author

ewagoner commented Apr 4, 2023

Two things:

@shlokamin I managed to address both things. The storybook stories have been simplified and just contain a single example and the Flex component swapping has been reduced to pure CSS setting.

@ewagoner ewagoner requested review from shlokamin and koji April 4, 2023 13:11
@ewagoner ewagoner merged commit db8929c into edge Apr 4, 2023
@ewagoner ewagoner deleted the RAUT-379-odd-snackbar branch April 4, 2023 19:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants