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: { content: string; label: string; icon?: string }) => {
28 const ctx = useContext(MenuContext);
29
30 return (
31 <button
32 onClick={() => {
33 addToClipboard(props.content);
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: {
47 href: string;
48 label: string;
49 icon?: string;
50 newTab?: boolean;
51}) => {
52 const ctx = useContext(MenuContext);
53
54 return (
55 <A
56 href={props.href}
57 onClick={() => ctx?.setShowMenu(false)}
58 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"
59 target={props.newTab ? "_blank" : undefined}
60 >
61 <Show when={props.icon}>
62 <span class={"iconify shrink-0 " + props.icon}></span>
63 </Show>
64 <span class="whitespace-nowrap">{props.label}</span>
65 </A>
66 );
67};
68
69export const ActionMenu = (props: {
70 label: string;
71 icon: string;
72 onClick: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>;
73}) => {
74 return (
75 <button
76 onClick={props.onClick}
77 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"
78 >
79 <Show when={props.icon}>
80 <span class={"iconify shrink-0 " + props.icon}></span>
81 </Show>
82 <span class="whitespace-nowrap">{props.label}</span>
83 </button>
84 );
85};
86
87export const DropdownMenu = (props: {
88 icon: string;
89 buttonClass?: string;
90 menuClass?: string;
91 children?: JSX.Element;
92}) => {
93 const ctx = useContext(MenuContext);
94 const [menu, setMenu] = createSignal<HTMLDivElement>();
95 const [menuButton, setMenuButton] = createSignal<HTMLButtonElement>();
96
97 const clickEvent = (event: MouseEvent) => {
98 const target = event.target as Node;
99 if (!menuButton()?.contains(target) && !menu()?.contains(target)) ctx?.setShowMenu(false);
100 };
101
102 onMount(() => window.addEventListener("click", clickEvent));
103 onCleanup(() => window.removeEventListener("click", clickEvent));
104
105 return (
106 <div class="relative">
107 <button
108 class={
109 "flex items-center hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600 " +
110 props.buttonClass
111 }
112 ref={setMenuButton}
113 onClick={() => ctx?.setShowMenu(!ctx?.showMenu())}
114 >
115 <span class={"iconify " + props.icon}></span>
116 </button>
117 <Show when={ctx?.showMenu()}>
118 <div
119 ref={setMenu}
120 class={
121 "dark:bg-dark-300 dark:shadow-dark-700 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 " +
122 props.menuClass
123 }
124 >
125 {props.children}
126 </div>
127 </Show>
128 </div>
129 );
130};