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