adobe / react-spectrum

A collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.
https://react-spectrum.adobe.com
Apache License 2.0
13.07k stars 1.14k forks source link

`react-aria-components/Tooltip`: `state.placement` should receive full `Placement` instead of `PlacementAxis` #6517

Open unional opened 5 months ago

unional commented 5 months ago

Provide a general summary of the feature here

Currently the state.placement in className or style callback is a PlacementAxis, which only contains top | bottom | left | right.

When the position of the tooltip needs to adjust, it may also need to flip.

e.g. a button at the top-left corner with a tooltip placement='top end' should flip on both axis to bottom start.

Currently we only get bottom which does not provide enough information to adjust the css accordingly.

image

๐Ÿค” Expected Behavior?

state.placement contain full Placement information.

๐Ÿ˜ฏ Current Behavior

state.placement only contains PlacementAxis values.

By the way, do you know in what case state.placement will contain the value center?

๐Ÿ’ Possible Solution

No response

๐Ÿ”ฆ Context

The solution is not complete.

๐Ÿ’ป Examples

No response

๐Ÿงข Your Company/Team

Palo Alto Networks

๐Ÿ•ท Tracking Issue

No response

unional commented 5 months ago

Example for all 4 corners: image

Note that related to #6505, I have isOpen but it is not rendered in this case because the positions are off screen.

unional commented 5 months ago

One more thing I notice is state.placement can be undefined. This happens when it is first created and also when isOpen changes.

image

But the type didn't mention that.

export interface TooltipRenderProps {
    /**
     * The placement of the tooltip relative to the trigger.
     * @selector [data-placement="left | right | top | bottom"]
     */
    placement: PlacementAxis;
snowystinger commented 5 months ago

Would you mind providing a reproduction of this to discuss more?

unional commented 5 months ago

While I try to create a repo, I notice that in the other example you share here: https://github.com/adobe/react-spectrum/blob/6e57855257f54738418f98c37efae798bf9fb4ad/packages/react-aria-components/stories/Tooltip.stories.tsx#L116

It is "working" in that example because the offset and the svg are hardcoded:

<Tooltip
  shouldFlip={false}
  offset={7}
  arrowBoundaryOffset={leftTop}
  style={{...}}
  >
  <OverlayArrow>
    <svg style={{...transform: 'rotate(-90deg)'...}} />
  </OverlayArrow>
</Tooltip>

if you try to define a reusable styled component, it wouldn't work:

<Tooltip
  style={(state) => {
    // no `shouldFlip`, and on which axis or both axis
    // `state.placement` no `top start` (as in OP)
    // thus no way to determine `offset` either
  }
  >
  <OverlayArrow>
    <svg style={{ ... no way to determine how to transform ... }}/>
  </OverlayArrow>
</Tooltip>

UPDATE: for children, you can:

<Tooltip>
  {({ placement }) => (
    <OverlayArrow>
      <svg style={{ transform: placement === 'left' ? ... : ... }}/>
    </OverlayArrow>
  )}
</Tooltip>

It works because that transform only needs the 4 directions.

hilja commented 3 months ago

Similar quirk I hit with the OverlayArrow component, it injects the following styles in the style attribute:

position: absolute;
transform: translateX(-50%);
bottom: 100%;
left: 25.875px;

Because of the transform being already there, it's hard to rotate the arrow based on position in a tailwind setup:

function Foo({ children }: { children: React.ReactNode }) {
  return (
    <Tooltip>
      <OverlayArrow
        className={({ placement }) =>
          // Crude override
          placement === 'bottom' ? '!rotate-180 -translate-x-1/2' : ''
        }
      >
        <svg height={8} viewBox='0 0 8 8' width={8}>
          <path d='M0 0 L4 4 L8 0' />
        </svg>
      </OverlayArrow>
      {children}
    </Tooltip>
  )
}

Or actually, style gives you defaultStyle to spread:

<OverlayArrow
  style={({ defaultStyle, placement }) =>
    placement === 'bottom'
      ? { ...defaultStyle, transform: 'rotate(180deg) translateX(50%)' }
      : defaultStyle
  }
>
  <svg height={8} viewBox='0 0 8 8' width={8}>
    <path d='M0 0 L4 4 L8 0' />
  </svg>
</OverlayArrow>

But then you have to jump out of Tailwind land for a moment.