atproto explorer pdsls.dev
atproto tool
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};