1import { A } from "@solidjs/router";
2import {
3 Accessor,
4 createContext,
5 createSignal,
6 JSX,
7 onCleanup,
8 onMount,
9 Setter,
10 Show,
11 useContext,
12} from "solid-js";
13import { addToClipboard } from "../utils/copy";
14
15const MenuContext = createContext<{
16 showMenu: Accessor<boolean>;
17 setShowMenu: Setter<boolean>;
18}>();
19
20export const MenuProvider = (props: { children?: JSX.Element }) => {
21 const [showMenu, setShowMenu] = createSignal(false);
22 const value = { showMenu, setShowMenu };
23
24 return <MenuContext.Provider value={value}>{props.children}</MenuContext.Provider>;
25};
26
27export const CopyMenu = (props: { copyContent: string; label: string; icon?: string }) => {
28 const ctx = useContext(MenuContext);
29
30 return (
31 <button
32 onClick={() => {
33 addToClipboard(props.copyContent);
34 ctx?.setShowMenu(false);
35 }}
36 class="flex items-center gap-1.5 rounded-lg p-1 whitespace-nowrap hover:bg-neutral-200/50 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
37 >
38 <Show when={props.icon}>
39 <span class={"iconify shrink-0 " + props.icon}></span>
40 </Show>
41 <span class="whitespace-nowrap">{props.label}</span>
42 </button>
43 );
44};
45
46export const NavMenu = (props: { href: string; label: string; icon: string; newTab?: boolean }) => {
47 const ctx = useContext(MenuContext);
48
49 return (
50 <A
51 href={props.href}
52 onClick={() => ctx?.setShowMenu(false)}
53 class="flex items-center gap-1.5 rounded-lg p-1 hover:bg-neutral-200/50 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
54 target={props.newTab ? "_blank" : undefined}
55 >
56 <span class={"iconify shrink-0 " + props.icon}></span>
57 <span class="whitespace-nowrap">{props.label}</span>
58 </A>
59 );
60};
61
62export const ActionMenu = (props: {
63 label: string;
64 icon: string;
65 onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>;
66}) => {
67 return (
68 <button
69 onClick={props.onClick}
70 class="flex items-center gap-1.5 rounded-lg p-1 whitespace-nowrap hover:bg-neutral-200/50 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
71 >
72 <Show when={props.icon}>
73 <span class={"iconify shrink-0 " + props.icon}></span>
74 </Show>
75 <span class="whitespace-nowrap">{props.label}</span>
76 </button>
77 );
78};
79
80export const DropdownMenu = (props: {
81 icon: string;
82 buttonClass?: string;
83 menuClass?: string;
84 children?: JSX.Element;
85}) => {
86 const ctx = useContext(MenuContext);
87 const [menu, setMenu] = createSignal<HTMLDivElement>();
88 const [menuButton, setMenuButton] = createSignal<HTMLButtonElement>();
89
90 const clickEvent = (event: MouseEvent) => {
91 const target = event.target as Node;
92 if (!menuButton()?.contains(target) && !menu()?.contains(target)) ctx?.setShowMenu(false);
93 };
94
95 onMount(() => window.addEventListener("click", clickEvent));
96 onCleanup(() => window.removeEventListener("click", clickEvent));
97
98 return (
99 <div class="relative">
100 <button
101 class={
102 "flex items-center hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 " +
103 props.buttonClass
104 }
105 ref={setMenuButton}
106 onClick={() => ctx?.setShowMenu(!ctx?.showMenu())}
107 >
108 <span class={"iconify " + props.icon}></span>
109 </button>
110 <Show when={ctx?.showMenu()}>
111 <div
112 ref={setMenu}
113 class={
114 "dark:bg-dark-300 dark:shadow-dark-800 absolute right-0 z-40 flex flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 shadow-md dark:border-neutral-700 " +
115 props.menuClass
116 }
117 >
118 {props.children}
119 </div>
120 </Show>
121 </div>
122 );
123};