File size: 2,645 Bytes
9a66b73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import React, {
  createContext,
  useContext,
  useState,
  useRef,
  useEffect,
} from "react";
import { cn } from "@/lib/utils";

const DropdownContext = createContext(null);

export function DropdownMenu({ children }) {
  const [open, setOpen] = useState(false);
  const triggerRef = useRef(null);

  // Close on outside click
  useEffect(() => {
    if (!open) return;
    function handleClick(e) {
      if (!triggerRef.current) return;
      if (!triggerRef.current.parentElement.contains(e.target)) {
        setOpen(false);
      }
    }
    document.addEventListener("mousedown", handleClick);
    return () => document.removeEventListener("mousedown", handleClick);
  }, [open]);

  return (
    <DropdownContext.Provider value={{ open, setOpen, triggerRef }}>
      <div className="relative inline-block">{children}</div>
    </DropdownContext.Provider>
  );
}

export function DropdownMenuTrigger({ asChild, children }) {
  const { setOpen, triggerRef } = useContext(DropdownContext);

  const handleClick = (e) => {
    e.stopPropagation();
    setOpen((o) => !o);
  };

  if (asChild && React.isValidElement(children)) {
    return React.cloneElement(children, {
      ref: triggerRef,
      onClick: (e) => {
        children.props.onClick?.(e);
        handleClick(e);
      },
    });
  }

  return (
    <button
      ref={triggerRef}
      type="button"
      onClick={handleClick}
      className="inline-flex"
    >
      {children}
    </button>
  );
}

export function DropdownMenuContent({ className, align = "end", ...props }) {
  const { open } = useContext(DropdownContext);
  if (!open) return null;

  const alignment =
    align === "end"
      ? "right-0 origin-top-right"
      : align === "start"
      ? "left-0 origin-top-left"
      : "left-1/2 -translate-x-1/2 origin-top";

  return (
    <div
      className={cn(
        "absolute z-50 mt-2 min-w-[8rem] rounded-md border border-slate-200 bg-white shadow-lg focus:outline-none",
        alignment,
        className
      )}
      {...props}
    />
  );
}

export function DropdownMenuItem({ className, onClick, ...props }) {
  const { setOpen } = useContext(DropdownContext);
  const handleClick = (e) => {
    onClick?.(e);
    setOpen(false);
  };
  return (
    <div
      className={cn(
        "flex cursor-pointer select-none items-center px-2 py-1.5 text-sm text-slate-700 hover:bg-slate-100 rounded-md",
        className
      )}
      onClick={handleClick}
      {...props}
    />
  );
}

export function DropdownMenuSeparator({ className }) {
  return (
    <div
      className={cn("my-1 h-px bg-slate-200 w-full", className)}
    />
  );
}