atproto explorer pdsls.dev
atproto tool

sticky component

Changed files
+51 -49
src
+44
src/components/sticky.tsx
···
···
+
import { createSignal, JSX, onCleanup, onMount } from "solid-js";
+
+
export const StickyOverlay = (props: { children?: JSX.Element }) => {
+
const [filterStuck, setFilterStuck] = createSignal(false);
+
+
return (
+
<div
+
ref={(node) => {
+
onMount(() => {
+
let ticking = false;
+
const tick = () => {
+
const topPx = parseFloat(getComputedStyle(node).top);
+
const { top } = node.getBoundingClientRect();
+
setFilterStuck(top <= topPx + 0.5);
+
ticking = false;
+
};
+
+
const onScroll = () => {
+
if (!ticking) {
+
ticking = true;
+
requestAnimationFrame(tick);
+
}
+
};
+
+
window.addEventListener("scroll", onScroll, { passive: true });
+
+
tick();
+
+
onCleanup(() => {
+
window.removeEventListener("scroll", onScroll);
+
});
+
});
+
}}
+
class="sticky top-2 z-10 flex flex-col items-center justify-center gap-2 rounded-lg p-3 transition-colors"
+
classList={{
+
"bg-neutral-50 dark:bg-dark-300 border-[0.5px] border-neutral-300 dark:border-neutral-700 shadow-md":
+
filterStuck(),
+
"bg-transparent border-transparent shadow-none": !filterStuck(),
+
}}
+
>
+
{props.children}
+
</div>
+
);
+
};
+4 -47
src/views/collection.tsx
···
import { $type, ActorIdentifier, InferXRPCBodyOutput } from "@atcute/lexicons";
import * as TID from "@atcute/tid";
import { A, useParams } from "@solidjs/router";
-
import {
-
createEffect,
-
createResource,
-
createSignal,
-
For,
-
onCleanup,
-
onMount,
-
Show,
-
untrack,
-
} from "solid-js";
import { createStore } from "solid-js/store";
import { Button } from "../components/button.jsx";
import { JSONType, JSONValue } from "../components/json.jsx";
import { agent } from "../components/login.jsx";
import { TextInput } from "../components/text-input.jsx";
import Tooltip from "../components/tooltip.jsx";
import { setNotif } from "../layout.jsx";
···
const [batchDelete, setBatchDelete] = createSignal(false);
const [lastSelected, setLastSelected] = createSignal<number>();
const [reverse, setReverse] = createSignal(false);
-
const [filterStuck, setFilterStuck] = createSignal(false);
const did = params.repo;
let pds: string;
let rpc: Client;
-
let sticky!: HTMLDivElement;
const fetchRecords = async () => {
if (!pds) pds = await resolvePDS(did);
···
true,
);
-
onMount(() => {
-
let ticking = false;
-
const tick = () => {
-
const topPx = parseFloat(getComputedStyle(sticky).top);
-
const { top } = sticky.getBoundingClientRect();
-
setFilterStuck(top <= topPx + 0.5);
-
ticking = false;
-
};
-
-
const onScroll = () => {
-
if (!ticking) {
-
ticking = true;
-
requestAnimationFrame(tick);
-
}
-
};
-
-
window.addEventListener("scroll", onScroll, { passive: true });
-
-
tick();
-
-
onCleanup(() => {
-
window.removeEventListener("scroll", onScroll);
-
});
-
});
-
return (
<Show when={records.length || response()}>
<div class="-mt-2 flex w-full flex-col items-center">
-
<div
-
ref={(el) => (sticky = el)}
-
class="sticky top-2 z-10 flex flex-col items-center justify-center gap-2 rounded-lg p-3 transition-colors"
-
classList={{
-
"bg-neutral-50 dark:bg-dark-300 border-[0.5px] border-neutral-300 dark:border-neutral-700 shadow-md":
-
filterStuck(),
-
"bg-transparent border-transparent shadow-none": !filterStuck(),
-
}}
-
>
<div class="flex w-[22rem] items-center gap-2 sm:w-[24rem]">
<Show when={agent() && agent()?.sub === did}>
<div class="flex items-center gap-x-2">
···
</div>
</div>
</Show>
-
</div>
<div class="flex max-w-full flex-col font-mono">
<For
each={records.filter((rec) =>
···
import { $type, ActorIdentifier, InferXRPCBodyOutput } from "@atcute/lexicons";
import * as TID from "@atcute/tid";
import { A, useParams } from "@solidjs/router";
+
import { createEffect, createResource, createSignal, For, Show, untrack } from "solid-js";
import { createStore } from "solid-js/store";
import { Button } from "../components/button.jsx";
import { JSONType, JSONValue } from "../components/json.jsx";
import { agent } from "../components/login.jsx";
+
import { StickyOverlay } from "../components/sticky.jsx";
import { TextInput } from "../components/text-input.jsx";
import Tooltip from "../components/tooltip.jsx";
import { setNotif } from "../layout.jsx";
···
const [batchDelete, setBatchDelete] = createSignal(false);
const [lastSelected, setLastSelected] = createSignal<number>();
const [reverse, setReverse] = createSignal(false);
const did = params.repo;
let pds: string;
let rpc: Client;
const fetchRecords = async () => {
if (!pds) pds = await resolvePDS(did);
···
true,
);
return (
<Show when={records.length || response()}>
<div class="-mt-2 flex w-full flex-col items-center">
+
<StickyOverlay>
<div class="flex w-[22rem] items-center gap-2 sm:w-[24rem]">
<Show when={agent() && agent()?.sub === did}>
<div class="flex items-center gap-x-2">
···
</div>
</div>
</Show>
+
</StickyOverlay>
<div class="flex max-w-full flex-col font-mono">
<For
each={records.filter((rec) =>
+3 -2
src/views/labels.tsx
···
import { A, useParams, useSearchParams } from "@solidjs/router";
import { createResource, createSignal, For, onMount, Show } from "solid-js";
import { Button } from "../components/button.jsx";
import { TextInput } from "../components/text-input.jsx";
import { labelerCache, resolvePDS } from "../utils/api.js";
import { localDateFromTimestamp } from "../utils/date.js";
···
</div>
</div>
</form>
-
<div class="dark:bg-dark-500 sticky top-0 z-5 flex w-screen flex-col items-center justify-center gap-3 bg-neutral-100 py-3">
<TextInput
placeholder="Filter by label"
onInput={(e) => setFilter(e.currentTarget.value)}
···
</div>
</Show>
</div>
-
</div>
<Show when={labels().length}>
<div class="flex max-w-full min-w-[22rem] flex-col gap-2 divide-y-[0.5px] divide-neutral-400 text-sm wrap-anywhere whitespace-pre-wrap sm:min-w-[24rem] dark:divide-neutral-600">
<For each={filterLabels()}>
···
import { A, useParams, useSearchParams } from "@solidjs/router";
import { createResource, createSignal, For, onMount, Show } from "solid-js";
import { Button } from "../components/button.jsx";
+
import { StickyOverlay } from "../components/sticky.jsx";
import { TextInput } from "../components/text-input.jsx";
import { labelerCache, resolvePDS } from "../utils/api.js";
import { localDateFromTimestamp } from "../utils/date.js";
···
</div>
</div>
</form>
+
<StickyOverlay>
<TextInput
placeholder="Filter by label"
onInput={(e) => setFilter(e.currentTarget.value)}
···
</div>
</Show>
</div>
+
</StickyOverlay>
<Show when={labels().length}>
<div class="flex max-w-full min-w-[22rem] flex-col gap-2 divide-y-[0.5px] divide-neutral-400 text-sm wrap-anywhere whitespace-pre-wrap sm:min-w-[24rem] dark:divide-neutral-600">
<For each={filterLabels()}>