atlassian / pragmatic-drag-and-drop

Fast drag and drop for any experience on any tech stack
https://atlassian.design/components/pragmatic-drag-and-drop
Other
7.45k stars 133 forks source link

Remove `react-drop-indicator` dependency on `@emotion/react` #8

Open nicksrandall opened 1 month ago

nicksrandall commented 1 month ago

I'm excited to dig more into this library and so far I am loving the approach of a small core and small utility libraries around it. One thing that I think might get in the way for adoption is the dependency on @emotion/react for the drop indicator package.

Emotion is a great library that I have used for many years but it is quite large and it also doesn't work (well) with modern react features like streaming rendering or RSC.

Looking at the usage, it seems possible to provide an additional package that just uses a pre-compiled css file instead of emotion. Would you be open to that?

alexreardon commented 1 month ago

https://atlassian.design/components/pragmatic-drag-and-drop/optional-packages/react-drop-indicator/about

This package depends on:

You are welcome to reimplement this (small) package using your own tech stack.

We plan on soon moving to a no-runtime (or tiny runtime) solution for styling. In the mean time you are welcome to copy / paste and change the component to use whatever styling approach you like. Another option for this one is that we could likely move to just inline styles (given that only one drop indicator is rendered at time 🤔)

hazirmagron commented 1 month ago

Did you manage to create a custom DropIndicator @nicksrandall?

Emotion also doesn't play very well with NextJS not to mention that two additional libraries (Emotion & atlaskit/token) seems a bit excessive to style a single component.

I tried re-writing it in tailwind but gave it up after some frustration.

hazirmagron commented 1 month ago

Below is a DropIndicator component for those using tailwind. Haven't had time to test it myself but is a good starting point for those wishing to avoid unnecessary dependencies.

import { Edge } from "@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge"
import { cn } from "@lib/utils/cn"
import { CSSProperties } from "react"

/**
 * Design decisions for the drop indicator's main line
 */
export const line = {
  borderRadius: 0,
  thickness: 2,
}

const terminalSize = 8
/**
 * By default, the edge of the terminal will be aligned to the edge of the line.
 *
 * Offsetting the terminal by half its size aligns the middle of the terminal
 * with the edge of the line.
 *
 * We must offset by half the line width in the opposite direction so that the
 * middle of the terminal aligns with the middle of the line.
 *
 * That is,
 *
 * offset = - (terminalSize / 2) + (line.thickness / 2)
 *
 * which simplifies to the following value.
 */
const offsetToAlignTerminalWithLine = (line.thickness - terminalSize) / 2

/**
 * We inset the line by half the terminal size,
 * so that the terminal only half sticks out past the item.
 */
const lineOffset = terminalSize / 2

type Orientation = "horizontal" | "vertical"
const edgeToOrientationMap: Record<Edge, Orientation> = {
  top: "horizontal",
  bottom: "horizontal",
  left: "vertical",
  right: "vertical",
}

const Terminal = ({ edge }: { edge: Edge }) => {
  const orientation = edgeToOrientationMap[edge]
  const styleMap = {
    horizontal: {
      // Horizontal indicators have the terminal on the left
      left: -terminalSize,
    },
    vertical: {
      // Vertical indicators have the terminal at the top
      top: -terminalSize,
    },
  }
  let style: CSSProperties = {
    [edge]: offsetToAlignTerminalWithLine,
    width: terminalSize,
    height: terminalSize,
    boxSizing: "border-box",
    position: "absolute",
    border: `${line.thickness}px solid #0070f0`,
    borderRadius: "50%",
  }

  return <div style={{ ...style, ...styleMap[orientation] }} />
}

/**
 * __Drop indicator__
 *
 * A drop indicator is used to communicate the intended resting place of the draggable item. The orientation of the drop indicator should always match the direction of the content flow.
 */
export const DropIndicator = ({ edge, gap }: { edge: Edge; gap: number }) => {
  /**
   * To clearly communicate the resting place of a draggable item during a drag operation,
   * the drop indicator should be positioned half way between draggable items.
   */
  const orientation = edgeToOrientationMap[edge]

  const styleMap = {
    horizontal: {
      height: line.thickness,
      left: lineOffset,
      right: 0,
    },
    vertical: {
      width: line.thickness,
      top: lineOffset,
      bottom: 0,
    },
  }
  const style: CSSProperties = {
    position: "absolute",
    [edge]: -(gap + line.thickness / 2),
    ...styleMap[orientation],
  }

  return (
    <div className={cn("bg-blue-600", {})} style={style}>
      <Terminal edge={edge} />
    </div>
  )
}