tippyjs-react icon indicating copy to clipboard operation
tippyjs-react copied to clipboard

Accept an array of refs for the `reference` prop

Open kripod opened this issue 5 years ago • 1 comments

Similarly to the triggerTarget prop of Tippy.js, the reference prop should accept React.RefObject[] | Element[] parameters as well, to avoid having to do state-based ref management like below:

import Tippy, { TippyProps } from "@tippyjs/react";
import clsx from "clsx";
import { useMemo, useState } from "react";

export type FileInputProps = Omit<
  React.ComponentPropsWithoutRef<"input">,
  "type" | "children"
> &
  Pick<TippyProps, "children">;

export function FileInput({
  className,
  children,
  ...restProps
}: FileInputProps) {
  const [labelElement, setLabelElement] = useState<HTMLLabelElement | null>(
    null,
  );
  const [inputElement, setinputElement] = useState<HTMLInputElement | null>(
    null,
  );

  return (
    <label
      ref={(instance) => {
        setLabelElement(instance);
      }}
      className={clsx("focus-within:ring", className)}
    >
      <Tippy
        content="Upload file"
        triggerTarget={useMemo(
          () =>
            labelElement && inputElement
              ? [labelElement, inputElement]
              : undefined,
          [inputElement, labelElement],
        )}
      >
        {children}
      </Tippy>

      <input
        ref={(instance) => {
          setinputElement(instance);
        }}
        type="file"
        className="sr-only"
        {...restProps}
      />
    </label>
  );
}

And simplify the code as follows:

import Tippy, { TippyProps } from "@tippyjs/react";
import clsx from "clsx";
import { useMemo, useRef } from "react";

export type FileInputProps = Omit<
  React.ComponentPropsWithoutRef<"input">,
  "type" | "children"
> &
  Pick<TippyProps, "children">;

export function FileInput({
  className,
  children,
  ...restProps
}: FileInputProps) {
  const labelElement = useRef<HTMLLabelElement | null>(null);
  const inputElement = useRef<HTMLInputElement | null>(null);

  return (
    <label ref={labelElement} className={clsx("focus-within:ring", className)}>
      <Tippy
        content="Upload file"
        triggerTarget={useMemo(
          () => [labelElement.current, inputElement.current],
          [],
        )}
      >
        {children}
      </Tippy>

      <input
        ref={inputElement}
        type="file"
        className="sr-only"
        {...restProps}
      />
    </label>
  );
}

kripod avatar Nov 25 '20 10:11 kripod

Also allow null values as triggerTarget list elements implicitly.

const Demo = () => {
    const [checkboxRef, setCheckboxRef] = useState<Element | null>(null);
    const checkboxRefCallback = useCallback((ref) => setCheckboxRef(ref), []);
    const [labelRef, setLabeRef] = useState<Element | null>(null);
    const labelRefCallback = useCallback((ref) => setLabeRef(ref), []);

    return (
        <Tooltip triggerTarget={[checkboxRef, labelRef]}>
            <label>
                <input type="checkbox" ref={checkboxRefCallback} />
                <span ref={labelRefCallback}>labeltext</span>
            </label>
        </Tooltip>
    );
};

ChrisKuBa avatar Feb 08 '21 12:02 ChrisKuBa