-
Notifications
You must be signed in to change notification settings - Fork 81
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: Create Tooltip component #871
Conversation
…e tooltipbody in DOM
…h within useEffent
src/components/Tooltip/Tooltip.tsx
Outdated
'is-visible': isVisible, | ||
}) | ||
|
||
useEffect(() => { |
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 placed the position logic within a useEffect hook because certain calculations like tooltipBodyWidth
were being generated when the tooltip was offscreen (!isVisible), causing issues with marginLeft
within the positionLeft
function
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.
This is great work! I have some suggestions/comments:
-
Please remove the
package-lock.json
file - this was likely added as a result of usingnpm install
but we should be consistent in usingyarn
on this project. -
I think the
button
element with thetitle
attribute should be generated by this component, since the USWDS component follows a strict convention (must be a button element and tooltip body is populated with the value of thetitle
attribute) instead of usingprops.children
. This would mean usage would look more like:<Tooltip position="top" title="Top" />
and I think intrinsic props should be for thebutton
element (since the wrapperspan
in USWDS is not customizable other than adding classes, so we could expose awrapperClasses
prop for that) -
I think some additional event handlers are needed, the tooltip should show on focus/mouseenter and hide on mouseleave, blur, keydown according to the source JS
src/components/Tooltip/Tooltip.tsx
Outdated
|
||
const TRIANGLE_SIZE = 5 | ||
const SPACER = 2 | ||
const tooltipID = `tooltip-${Math.floor(Math.random() * 900000) + 100000}` |
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 this should probably be initialized inside the Tooltip
component, probably inside a useEffect
hook so it just happens once on initial mount.
src/components/Tooltip/Tooltip.tsx
Outdated
const adjustToEdgeX = tooltipWidth + TRIANGLE_SIZE + SPACER | ||
const adjustToEdgeY = tooltipHeight + TRIANGLE_SIZE + SPACER | ||
|
||
/** |
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 it would be good to take advantage of React's declarative nature here, and set these attributes (style, classes) using useState
and pass them to the element(s) inside of the markup instead of manipulating the HTML elements 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.
very much agree about avoiding element.style
since we have JSX className
. It is a refactor though and the way the code is written seems fine to me for a first pass (since it just copies uswds). We could backlog an issue about refactoring this. @suzubara thoughts?
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.
yes, I'm okay with that!
@suzubara I advised to follow a flexible approach here. While all the uswds examples are buttons, they make a specific mention of using it on more than just buttons in the docs: I could definitely see people wanting to wrap a custom link in a tooltip, which I thought we might want to allow. |
I don't feel super strongly about the exact name, but my inclination is to use something explicit, like |
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 tested this out on MilMove and the Tooltip works great! I left two minor comments that I think should be fixed before merging. We should also make sure to create an issue to refactor the positioning code so it uses React state to store class/position, instead of JS DOM APIs.
src/components/Tooltip/Tooltip.tsx
Outdated
return 'asCustom' in props | ||
} | ||
|
||
const tooltipID = `tooltip-${Math.floor(Math.random() * 900000) + 100000}` |
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.
Because this tooltipID
is defined outside of the Tooltip
component, it's the same value for all instances of a Tooltip used within a single app. This should be defined inside of the Tooltip
component in a useRef
container:
const tooltipID = useRef(`tooltip-${Math.floor(Math.random() * 900000) + 100000}`)
// to access:
tooltipID.current
src/components/Tooltip/Tooltip.tsx
Outdated
label: string | ||
position?: 'top' | 'bottom' | 'left' | 'right' | undefined | ||
className?: string | ||
dataClasses?: string |
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.
src/components/Tooltip/Tooltip.tsx
Outdated
data-testid="tooltipWrapper" | ||
ref={wrapperRef} | ||
className="usa-tooltip" | ||
{...customProps} |
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.
customProps
are passed into the triggerElement
below, so they should not be repeated on this wrapper span
src/components/Tooltip/Tooltip.tsx
Outdated
data-testid="tooltipWrapper" | ||
ref={wrapperRef} | ||
className="usa-tooltip" | ||
{...spanProps} |
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 remaining props are meant to be passed onto the button element, rather than the wrapper span (this should mirror the custom implementation above)
…e tooltipElementID to tooltip component level
type TooltipProps<T> = { | ||
label: string | ||
position?: 'top' | 'bottom' | 'left' | 'right' | undefined | ||
wrapperclasses?: string |
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 removed className prop, as i didn't see the purpose of having both className and wrapperclasses. may have lost some perspective for why we'd need a separate prop since starting this ticket so your input would be appreciated @suzubara
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 className
prop is meant to allow a user to pass a custom CSS class to the trigger element, so that value should be incorporated in the list of classes passed to the button or custom element used. wrapperClasses
is the same, but for the wrapper element instead (this mirrors the data-classes
attribute exposed by USWDS).
For example if a user of USWDS did this:
<button type="button" class="usa-button usa-tooltip myCustomButtonClass" data-position="top" data-classes="width-full tablet:width-auto" title="Top">Show on top</button>
they should be able to achieve the same result using our Tooltip component with this:
<Tooltip className="myCustomButtonClass" position="top" wrapperClasses="width-full tablet:width-auto" label="Top">Show on top</button>
and that should result in the tooltipWrapper
element having the wrapperClasses value (width-full tablet:width-auto
), and the triggerElement
element having the className value (myCustomButtonClass
) in addition to the other usa tooltip classes
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.
awesome, I think my latest change should fix it @suzubara
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.
awesome, I think this is a great first pass at the Tooltip! I will create a followup issue for refactoring some of the code to use more React and use less of the JS DOM API
gotcha, when you do please add me as a reviewer! |
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.
Small change. Otherwise good to approve! Great work!
* create tooltip using css usilities * additional styles * add useState hooks to tooltip component * add correct styles according to position * additional edits to positioning tooltip body, add element ID to locate tooltipbody in DOM * move all positioning login into a useEffect, register TooltipBodyWidth within useEffent * add tooltip ID and remove comments * remove styles file * add tests to tooltip component * export tooltip component in index.ts * replace reference to tooltipBodyRef.current with variable tooltipBodyWidth * remove package lock file * create a default tooltip trigger button and create tests for tooltip body visibility * begin writing up custom component based off of Link component example * put custom component render in a conditional statement * utilize forward ref in customComponent * abstract useEffect into a fn, useTooltip * move html element role, move event listeners to custom component props * move isElementInViewport to a utils file * correct tests and add test id to tooltip trigger element * add display name * add story displayname * corect var name CustomLinkProps * update tests * add data classes prop for css utility classes * add keyboard events to tests * change dataclasses to wrapperclasses and move to trigger element, move tooltipElementID to tooltip component level * remove className for wrapperclasses from tooltipComponent * apply classname to tooltipTrigger element * reformat for storybook addon Co-authored-by: Andrew Hobson <21983+ahobson@users.noreply.github.com>
Summary
This story creates a react component modeled after the USWDS Tooltip
Related Issues or PRs
#342
How To Test
In storybook see Components > Tooltips
Screenshots (optional)