-
Notifications
You must be signed in to change notification settings - Fork 175
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(components): added a component called Tabs #15437
Conversation
…tton/RoundTab components close RSQ-86
components/src/molecules/Tabs.tsx
Outdated
handleClick(index) | ||
button.onClick() | ||
}} | ||
className={index === activeTab ? 'active' : ''} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this className
doing? since we use styled-components instead of raw css we don't need to apply css classes do we?
components/src/molecules/Tabs.tsx
Outdated
handleClick(index) | ||
button.onClick() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think having the button both update its internal state (activeTab
) as well as call the provided onClick
handler is a little confusing because you have two sources of truth for whether or not a button is active (this component's internal react state, as well as the provided isActive
prop). i prefer having the parent component tell us whether or not a button is active, so i think we should get rid of the internal activeTab
react state variable
components/src/molecules/Tabs.tsx
Outdated
const [activeTab, setActiveTab] = React.useState<number | null>(null); | ||
|
||
const handleClick = (index: number) : void => { | ||
setActiveTab(index) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see comment below, i dont think we need this!
components/src/molecules/Tabs.tsx
Outdated
|
||
css={index === activeTab || button.isActive === true ? currentTabStyle : button.disabled === true ? disabledTabStyle : defaultTabStyle} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of conditionally choosing a CSS block for disabled
, you can tell an HTML element that it is disabled by providing the disabled
attribute: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled
this will simplify the ternary 😄
…nClick state close RQS-86
3aeb35b
to
a6e12e9
Compare
import * as React from 'react' | ||
import { Tabs as TabComponent } from './Tabs' | ||
import type { Meta, StoryObj } from '@storybook/react' | ||
import { useArgs } from '@storybook/preview-api'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we are very particular about import order, the pattern goes:
- package imports
- top level directory imports (like
opentrons/shared-data
oropentrons/components
) - imports from the farthest path to the shortest so
../../
should be above./
for example - types (type imports should also follow the above pattern)
import * as React from 'react' | |
import { Tabs as TabComponent } from './Tabs' | |
import type { Meta, StoryObj } from '@storybook/react' | |
import { useArgs } from '@storybook/preview-api'; | |
import * as React from 'react' | |
import { useArgs } from '@storybook/preview-api'; | |
import { Tabs as TabComponent } from './Tabs' | |
import type { Meta, StoryObj } from '@storybook/react' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks jethary, i forgot to explain this to shiyao
components/src/molecules/Tabs.tsx
Outdated
import { TYPOGRAPHY, SPACING } from '../ui-style-constants' | ||
import { COLORS, BORDERS } from '../helix-design-system' | ||
import { POSITION_RELATIVE, DIRECTION_COLUMN, DIRECTION_ROW } from '../styles' | ||
import { Flex } from '../primitives' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import order is good here! 👍
components/src/molecules/Tabs.tsx
Outdated
text: string | ||
isActive?: boolean | ||
disabled?: boolean | ||
onClick: () => void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: optional props should go last
text: string | |
isActive?: boolean | |
disabled?: boolean | |
onClick: () => void | |
text: string | |
onClick: () => void | |
isActive?: boolean | |
disabled?: boolean |
components/src/molecules/Tabs.tsx
Outdated
onClick={() => { | ||
button.onClick() | ||
}} | ||
css={button.isActive === true ? currentTabStyle : defaultTabStyle} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: i think this works as a shorthand
css={button.isActive === true ? currentTabStyle : defaultTabStyle} | |
css={button.isActive ? currentTabStyle : defaultTabStyle} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried deleting the "=== true" statement and it gives me this problem: Unexpected nullable boolean value in conditional. Please handle the nullish case explicitly.eslint@typescript-eslint/strict-boolean-expressions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i see, i guess its because isActive can be undefined. you can keep it as is!
components/src/molecules/Tabs.tsx
Outdated
|
||
export function Tabs(props: TabsProps): JSX.Element { | ||
|
||
const { buttons } = props |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not using interface you defined?
const = {text, onClick, isActive = false, disabled = false } = props
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have the text, onClick, isActive, and disabled wrapped inside an array in the interface because we need to show multiple buttons, so i am not able to call those props directly i think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend you use tabs
instead of buttons
since you button
tag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I checked a couple of ui components.
they use the following structure and I think RoundTab
is also the same.
I guess passing a custom array to ui component is unusual.
<Tabs>
<Tab value="1">tab1<Tab>
<Tab value="2">tab2<Tab>
</Tabs>
Is there any specific reason that you would want to pass an array to this component?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's a really good question! based on what Shlok have told me we want the TabsProps take in a list of tabs, where each tab has text, isActive, isDisabled, and onClick. that way we keep the interface terse and generic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think something we can do to make it cleaner is create a new type that includes
text: string
isActive?: boolean
disabled?: boolean
onClick: () => void
So for example:
interface TabProps {
text: string
isActive?: boolean
disabled?: boolean
onClick: () => void
}
And then
export interface TabsProps {
tabs: TabProps[]
}
Because this is the Tabs component that has Tab[] inside of it, i think instead of calling it buttons, it could be tabs?
components/src/molecules/Tabs.tsx
Outdated
import { POSITION_RELATIVE, DIRECTION_COLUMN, DIRECTION_ROW } from '../styles' | ||
import { Flex } from '../primitives' | ||
|
||
const defaultTabStyle = css` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
basically we use SCREAMING_SNAKE_CASE
for a css var.
const defaultTabStyle = css` | |
const DEFAULT_TAB_STYLE = css` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol i had no idea it was called SCREAMING_SNAKE_CASE
colse RSQ-86
close RSQ-86
components/src/molecules/Tabs.tsx
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unify the TabbedButton/RoundTab
This means that this new component needs to support ODD style like what Jethary does for Tag
.
We use media-query to switch styles. The following for ODD
@media ${RESPONSIVENESS.touchscreenMediaQuerySpecs}
Also, could you write test for the component?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@koji when you have some time would you mind pairing with Shiyao to show her how to write a test using react testing library?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good so far shiyao! it looks like what's remaining is:
- add a test for the component (@koji or @ncdiehl11 can show you how)
- add ODD specific style overrides for the style differences in ODD
- replace all instances of the old chip and tag with your new component (we can do this after Mel gives us the thumbs up that the component is designed to spec)
we use the following structure for a component. molecules
└── Tab
├── Tab.stories.tsx
├── __tests__
│ └── Tab.test.tsx
└── index.tsx https://github.com/Opentrons/opentrons/blob/edge/components/src/atoms/Chip/index.tsx
molecules
└── Tabs
├── Tabs.stories.tsx
├── __tests__
│ └── Tabs.test.tsx
└── index.tsx
Recording.2024-06-18.211405.mp4
If you have any question on this, feel free to reach out to the team/me. |
tabs: [ | ||
{ | ||
text: 'Setup', | ||
isActive: false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when the app displays tabs, setup is selected so settings isActive: true would be designer-friendly.
isActive: false, | |
isActive: true, |
|
||
const DEFAULT_TAB_STYLE = css` | ||
${TYPOGRAPHY.pSemiBold} | ||
color: ${COLORS.black90}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the default text color is black90
so you don't need this.
L28 in app/src/atoms/GlobalStyle/index.ts
color: ${COLORS.black90}; | |
import { SPACING } from '../../../ui-style-constants' | ||
import { POSITION_RELATIVE } from '../../../styles' | ||
import { renderWithProviders } from '../../../testing/utils' | ||
import { Tabs } from '..' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import { Tabs } from '..' | |
import { Tabs } from '../index' |
flexDirection={DIRECTION_COLUMN} | ||
gridGap={SPACING.spacing16} | ||
padding={SPACING.spacing16} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flexDirection
and gridGap
aren't needed since there is one element.
Could you add the Figma link you used to implement this to the pr's overview?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh I was using the css layout in the RoundTab component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the style you took is for storybook's layout.
https://s3-us-west-2.amazonaws.com/opentrons-components/edge/index.html?path=/story/library-molecules-roundtab--round-tab
${TYPOGRAPHY.pSemiBold} | ||
color: ${COLORS.black90}; | ||
background-color: ${COLORS.purple30}; | ||
border: 0px ${BORDERS.styleSolid} ${COLORS.purple30}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need this line?
border uses 0px which isn't visible.
#15437 (comment) |
@koji im not sure what you mean by "existing implementation". the reason i suggested that this component take in a list of tabs is because of the description of the component in Figma: "Tabbed interfaces are a way of navigating between multiple panels". keyword "multiple"! we will never expose a single |
ultimately i don't really think it matters too much though. having a single tab interface is fine too, but i personally don't see anything wrong with the interface this component currently accepts |
…esktop app re RSQ-86
…o components-unify-tab-button
re RSQ-86
My concern is that if there is a request to change the background color of a particular tab or to display an icon, multiples would require additional changes. |
oh if there is ever a request to do that we would say we can't. that would be a big signal that this abstraction is not correct and that this component should not exist. cc @mmencarelli |
parameters: VIEWPORT.touchScreenViewport, | ||
argTypes: { | ||
tabs: { | ||
control: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@syao1226 Have you researched a way to control an element in array?
This isn't must-have but nice-to-have since designers would like to use a select button for disabled instead of changing the boolean directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't and I'll look into it now
expect(tab2).not.toHaveStyle(`background-color: ${COLORS.purple50}`) | ||
expect(tab2).not.toHaveStyle(`color: ${COLORS.white}`) | ||
|
||
expect(tab3).not.toHaveStyle(`background-color: ${COLORS.purple50}`) | ||
expect(tab3).not.toHaveStyle(`color: ${COLORS.white}`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to check these since you're already asserting these same properties on lines 57-58
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
code lgtm
close RSQ-86
<!-- Thanks for taking the time to open a pull request! Please make sure you've read the "Opening Pull Requests" section of our Contributing Guide: https://github.com/Opentrons/opentrons/blob/edge/CONTRIBUTING.md#opening-pull-requests To ensure your code is reviewed quickly and thoroughly, please fill out the sections below to the best of your ability! --> # Overview <!-- Use this section to describe your pull-request at a high level. If the PR addresses any open issues, please tag the issues here. --> RSQ-86: Create a new Tabs component to unify the TabbedButton/RoundTab components in storybook. https://www.figma.com/design/4gRzu3rkNDZnDPkP36YxfF/Chip%2C-Tag-and-Tab?node-id=123-57775&t=zucfumEcRony14Xm-0 # Test Plan <!-- Use this section to describe the steps that you took to test your Pull Request. If you did not perform any testing provide justification why. OT-3 Developers: You should default to testing on actual physical hardware. Once again, if you did not perform testing against hardware, justify why. Note: It can be helpful to write a test plan before doing development Example Test Plan (HTTP API Change) - Verified that new optional argument `dance-party` causes the robot to flash its lights, move the pipettes, then home. - Verified that when you omit the `dance-party` option the robot homes normally - Added protocol that uses `dance-party` argument to G-Code Testing Suite - Ran protocol that did not use `dance-party` argument and everything was successful - Added unit tests to validate that changes to pydantic model are correct --> # Changelog <!-- List out the changes to the code in this PR. Please try your best to categorize your changes and describe what has changed and why. Example changelog: - Fixed app crash when trying to calibrate an illegal pipette - Added state to API to track pipette usage - Updated API docs to mention only two pipettes are supported IMPORTANT: MAKE SURE ANY BREAKING CHANGES ARE PROPERLY COMMUNICATED --> # Review requests <!-- Describe any requests for your reviewers here. --> # Risk assessment <!-- Carefully go over your pull request and look at the other parts of the codebase it may affect. Look for the possibility, even if you think it's small, that your change may affect some other part of the system - for instance, changing return tip behavior in protocol may also change the behavior of labware calibration. Identify the other parts of the system your codebase may affect, so that in addition to your own review and testing, other people who may not have the system internalized as much as you can focus their attention and testing there. --> --------- Co-authored-by: shiyaochen <shiyaochen@admins-MacBook-Pro.local> Co-authored-by: shiyaochen <shiyaochen@admins-mbp.mynetworksettings.com>
Overview
RSQ-86: Create a new Tabs component to unify the TabbedButton/RoundTab components in storybook.
https://www.figma.com/design/4gRzu3rkNDZnDPkP36YxfF/Chip%2C-Tag-and-Tab?node-id=123-57775&t=zucfumEcRony14Xm-0
Test Plan
Changelog
Review requests
Risk assessment