atproto explorer pdsls.dev
atproto tool

improve dropdown flow

juli.ee d813d590 911b5529

verified
+35 -24
src/components/account.tsx
···
import { createSignal, For, onMount, Show } from "solid-js";
import { createStore, produce } from "solid-js/store";
import { resolveDidDoc } from "../utils/api.js";
+
import { ActionMenu, DropdownMenu, MenuProvider, NavMenu } from "./dropdown.jsx";
import { agent, Login, retrieveSession, Sessions, setAgent } from "./login.jsx";
import { Modal } from "./modal.jsx";
export const [sessions, setSessions] = createStore<Sessions>();
+
const AccountDropdown = (props: { did: Did }) => {
+
const removeSession = async (did: Did) => {
+
const currentSession = agent()?.sub;
+
try {
+
const session = await getSession(did, { allowStale: true });
+
const agent = new OAuthUserAgent(session);
+
await agent.signOut();
+
} catch {
+
deleteStoredSession(did);
+
}
+
setSessions(
+
produce((accs) => {
+
delete accs[did];
+
}),
+
);
+
localStorage.setItem("sessions", JSON.stringify(sessions));
+
if (currentSession === did) setAgent(undefined);
+
};
+
+
return (
+
<MenuProvider>
+
<DropdownMenu icon="lucide--ellipsis" buttonClass="rounded-md p-2">
+
<NavMenu href={`/at://${props.did}`} label="Go to repo" icon="lucide--user-round" />
+
<ActionMenu
+
icon="lucide--x"
+
label="Remove account"
+
onClick={() => removeSession(props.did)}
+
/>
+
</DropdownMenu>
+
</MenuProvider>
+
);
+
};
+
export const AccountManager = () => {
const [openManager, setOpenManager] = createSignal(false);
const [avatars, setAvatars] = createStore<Record<Did, string>>();
···
}
};
-
const removeSession = async (did: Did) => {
-
const currentSession = agent()?.sub;
-
try {
-
const session = await getSession(did, { allowStale: true });
-
const agent = new OAuthUserAgent(session);
-
await agent.signOut();
-
} catch {
-
deleteStoredSession(did);
-
}
-
setSessions(
-
produce((accs) => {
-
delete accs[did];
-
}),
-
);
-
localStorage.setItem("sessions", JSON.stringify(sessions));
-
if (currentSession === did) setAgent(undefined);
-
};
-
const getAvatar = async (did: Did) => {
const rpc = new Client({
handler: new CredentialManager({ service: "https://public.api.bsky.app" }),
···
<span class="iconify lucide--circle-alert shrink-0 text-red-500 dark:text-red-400"></span>
</Show>
</button>
-
<button
-
onclick={() => removeSession(did as Did)}
-
class="flex items-center rounded-md p-2 hover:bg-neutral-200 active:bg-neutral-300 dark:hover:bg-neutral-700 dark:active:bg-neutral-600"
-
>
-
<span class="iconify lucide--x"></span>
-
</button>
+
<AccountDropdown did={did as Did} />
</div>
)}
</For>
+38 -12
src/components/dropdown.tsx
···
Show,
useContext,
} from "solid-js";
+
import { Portal } from "solid-js/web";
import { addToClipboard } from "../utils/copy";
const MenuContext = createContext<{
···
const ctx = useContext(MenuContext);
const [menu, setMenu] = createSignal<HTMLDivElement>();
const [menuButton, setMenuButton] = createSignal<HTMLButtonElement>();
+
const [buttonRect, setButtonRect] = createSignal<DOMRect>();
const clickEvent = (event: MouseEvent) => {
const target = event.target as Node;
if (!menuButton()?.contains(target) && !menu()?.contains(target)) ctx?.setShowMenu(false);
};
-
onMount(() => window.addEventListener("click", clickEvent));
-
onCleanup(() => window.removeEventListener("click", clickEvent));
+
const updatePosition = () => {
+
const rect = menuButton()?.getBoundingClientRect();
+
if (rect) setButtonRect(rect);
+
};
+
+
onMount(() => {
+
window.addEventListener("click", clickEvent);
+
window.addEventListener("scroll", updatePosition, true);
+
window.addEventListener("resize", updatePosition);
+
});
+
+
onCleanup(() => {
+
window.removeEventListener("click", clickEvent);
+
window.removeEventListener("scroll", updatePosition, true);
+
window.removeEventListener("resize", updatePosition);
+
});
return (
<div class="relative">
···
props.buttonClass
}
ref={setMenuButton}
-
onClick={() => ctx?.setShowMenu(!ctx?.showMenu())}
+
onClick={() => {
+
updatePosition();
+
ctx?.setShowMenu(!ctx?.showMenu());
+
}}
>
<span class={"iconify " + props.icon}></span>
</button>
<Show when={ctx?.showMenu()}>
-
<div
-
ref={setMenu}
-
class={
-
"dark:bg-dark-300 dark:shadow-dark-700 absolute right-0 z-40 flex min-w-40 flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-2 shadow-md dark:border-neutral-700 " +
-
props.menuClass
-
}
-
>
-
{props.children}
-
</div>
+
<Portal>
+
<div
+
ref={setMenu}
+
style={{
+
position: "fixed",
+
top: `${(buttonRect()?.bottom ?? 0) + 4}px`,
+
left: `${(buttonRect()?.right ?? 0) - 160}px`,
+
}}
+
class={
+
"dark:bg-dark-300 dark:shadow-dark-700 z-50 flex min-w-40 flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-2 text-sm shadow-md dark:border-neutral-700 " +
+
props.menuClass
+
}
+
>
+
{props.children}
+
</div>
+
</Portal>
</Show>
</div>
);
+1 -1
src/components/notification.tsx
···
export const NotificationContainer = () => {
return (
-
<div class="pointer-events-none fixed bottom-4 left-4 z-50 flex flex-col gap-2">
+
<div class="pointer-events-none fixed bottom-4 left-4 z-60 flex flex-col gap-2">
<For each={notifications}>
{(notification) => (
<div
+1 -5
src/layout.tsx
···
</Show>
<AccountManager />
<MenuProvider>
-
<DropdownMenu
-
icon="lucide--menu text-lg"
-
buttonClass="rounded-lg p-1.5"
-
menuClass="top-11 text-sm"
-
>
+
<DropdownMenu icon="lucide--menu text-lg" buttonClass="rounded-lg p-1.5">
<NavMenu href="/jetstream" label="Jetstream" icon="lucide--radio-tower" />
<NavMenu href="/firehose" label="Firehose" icon="lucide--droplet" />
<NavMenu href="/labels" label="Labels" icon="lucide--tags" />
+1 -5
src/views/pds.tsx
···
<Tab tab="info" label="Info" />
</div>
<MenuProvider>
-
<DropdownMenu
-
icon="lucide--ellipsis-vertical"
-
buttonClass="rounded-sm p-1.5"
-
menuClass="top-9 text-sm"
-
>
+
<DropdownMenu icon="lucide--ellipsis-vertical" buttonClass="rounded-sm p-1.5">
<CopyMenu content={params.pds!} label="Copy PDS" icon="lucide--copy" />
<NavMenu
href={`/firehose?instance=wss://${params.pds}`}
+1 -5
src/views/record.tsx
···
</Modal>
</Show>
<MenuProvider>
-
<DropdownMenu
-
icon="lucide--ellipsis-vertical"
-
buttonClass="rounded-sm p-1.5"
-
menuClass="top-9 text-sm"
-
>
+
<DropdownMenu icon="lucide--ellipsis-vertical" buttonClass="rounded-sm p-1.5">
<CopyMenu
content={JSON.stringify(record()?.value, null, 2)}
label="Copy record"
+1 -5
src/views/repo.tsx
···
</Tooltip>
</Show>
<MenuProvider>
-
<DropdownMenu
-
icon="lucide--ellipsis-vertical"
-
buttonClass="rounded-sm p-1.5"
-
menuClass="top-9 text-sm"
-
>
+
<DropdownMenu icon="lucide--ellipsis-vertical" buttonClass="rounded-sm p-1.5">
<CopyMenu content={params.repo!} label="Copy DID" icon="lucide--copy" />
<NavMenu
href={`/jetstream?dids=${params.repo}`}