How To Show A Button Inside Of a Parent Button/Link On Hover with React

radzion

Radzion Chachura

Posted on May 28, 2023

How To Show A Button Inside Of a Parent Button/Link On Hover with React

Watch on YouTube | 🐙 GitHub | 🎮 Demo

It's a popular UI pattern to show a button at the corner when hovering another interactive element, but the problem is that HTML doesn't allow putting an anchor or button inside an existing interactive element. That's why I want to share with you an ActionOnHover component. You can see it in action in this focus sounds player at Increaser, where you can add a sound to favorites category by clicking a button that appears on item hover.

The OnHoverAction component receives an action element to display on hover, actionPlacerStyles to adjust the absolute location of the action, and a render function to display the primary interactive element.

import { ReactNode } from "react"
import styled from "styled-components"

import { ElementSizeAware } from "./ElementSizeAware"
import { ElementSize } from "./hooks/useElementSize"

interface OnHoverActionRenderParams<T extends React.CSSProperties> {
  actionSize: ElementSize
  actionPlacerStyles: T
}

interface OnHoverActionProps<T extends React.CSSProperties> {
  render: (params: OnHoverActionRenderParams<T>) => ReactNode
  action: ReactNode
  actionPlacerStyles: T
}

const ActionPlacer = styled.div`
  position: absolute;
  opacity: 0;
`

const Container = styled.div`
  position: relative;
  display: flex;
  flex-direction: row;
  align-items: center;

  :hover ${ActionPlacer} {
    opacity: 1;
  }
`

export function OnHoverAction<T extends React.CSSProperties>({
  render,
  action,
  actionPlacerStyles,
}: OnHoverActionProps<T>) {
  return (
    <Container>
      <ElementSizeAware
        render={({ setElement, size }) => (
          <>
            {size &&
              render({
                actionPlacerStyles,
                actionSize: size,
              })}
            <ActionPlacer ref={setElement} style={actionPlacerStyles}>
              {action}
            </ActionPlacer>
          </>
        )}
      />
    </Container>
  )
}
Enter fullscreen mode Exit fullscreen mode

The Container has a relative position, and it aligns the content horizontally. On hover, we'll set the opacity for the action wrapper to one so that it will appear. To prevent the overflow, the primary element might preserve a space where the action element will appear on hover. To achieve that, we first measure the size of the action and provide its width and height as a parameter to the render function. You can learn about the implementation of the ElementSizeAware component here.

The component is a generic function because it's more convenient to know the exact styles for the ActionPlacer componenent when rendering the primary interactive element. In our player example we set the right property for an absolutely positioned placer and then use it as padding for the primary element to have a consistent UI.

<OnHoverAction
  actionPlacerStyles={{ right: 8 }}
  action={
    <IconButton
      icon={star}
      onClick={...}
    />
  }
  render={({ actionSize, actionPlacerStyles }) => (
    <Container
      style={{ padding: actionPlacerStyles.right }}
      onClick={...}
    >
      {content}
      <VStack style={actionSize} alignItems="center" justifyContent="center">
        {isFavourite && star}
      </VStack>
    </Container>
  )}
/>
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
radzion
Radzion Chachura

Posted on May 28, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

What was your win this week?
weeklyretro What was your win this week?

November 29, 2024

Where GitOps Meets ClickOps
devops Where GitOps Meets ClickOps

November 29, 2024