collection filter styling #5

closed
opened by futur.blue targeting main
Changed files
+68 -7
src
components
views
+3 -1
src/components/button.tsx
··· 2 3 export interface ButtonProps { 4 class?: string; 5 onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>; 6 children?: JSX.Element; 7 } ··· 12 type="button" 13 class={ 14 props.class ?? 15 - "dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-800 dark:active:bg-dark-100 flex items-center gap-1 rounded-lg bg-white px-2 py-1.5 text-xs font-semibold shadow-sm hover:bg-neutral-50 active:bg-neutral-50" 16 } 17 onClick={props.onClick} 18 > 19 {props.children}
··· 2 3 export interface ButtonProps { 4 class?: string; 5 + classList?: Record<string, boolean | undefined>; 6 onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>; 7 children?: JSX.Element; 8 } ··· 13 type="button" 14 class={ 15 props.class ?? 16 + "dark:hover:bg-dark-100 dark:bg-dark-300 dark:shadow-dark-800 dark:active:bg-dark-100 flex items-center gap-1 rounded-lg border-[0.5px] border-neutral-300 bg-white px-2 py-1.5 text-xs font-semibold shadow-md hover:bg-neutral-50 active:bg-neutral-50 dark:border-neutral-700" 17 } 18 + classList={props.classList} 19 onClick={props.onClick} 20 > 21 {props.children}
+65 -6
src/views/collection.tsx
··· 3 import { $type, ActorIdentifier, InferXRPCBodyOutput } from "@atcute/lexicons"; 4 import * as TID from "@atcute/tid"; 5 import { A, useParams } from "@solidjs/router"; 6 - import { createEffect, createResource, createSignal, For, Show, untrack } from "solid-js"; 7 import { createStore } from "solid-js/store"; 8 - import { Button } from "../components/button.jsx"; 9 import { JSONType, JSONValue } from "../components/json.jsx"; 10 import { agent } from "../components/login.jsx"; 11 import { TextInput } from "../components/text-input.jsx"; ··· 72 const [batchDelete, setBatchDelete] = createSignal(false); 73 const [lastSelected, setLastSelected] = createSignal<number>(); 74 const [reverse, setReverse] = createSignal(false); 75 const did = params.repo; 76 let pds: string; 77 let rpc: Client; 78 79 const fetchRecords = async () => { 80 if (!pds) pds = await resolvePDS(did); ··· 157 true, 158 ); 159 160 return ( 161 <Show when={records.length || response()}> 162 <div class="flex w-full flex-col items-center"> 163 - <div class="dark:bg-dark-500 sticky top-0 z-5 flex w-screen flex-col items-center justify-center gap-2 bg-neutral-100 pt-1 pb-3"> 164 <div class="flex w-[22rem] items-center gap-2 sm:w-[24rem]"> 165 <Show when={agent() && agent()?.sub === did}> 166 <div class="flex items-center gap-x-2"> ··· 221 </div> 222 <Show when={records.length > 1}> 223 <div class="flex w-[22rem] items-center justify-between gap-x-2 sm:w-[24rem]"> 224 - <Button 225 onClick={() => { 226 setReverse(!reverse()); 227 setRecords([]); ··· 233 class={`iconify ${reverse() ? "lucide--rotate-ccw" : "lucide--rotate-cw"} text-sm`} 234 ></span> 235 Reverse 236 - </Button> 237 <div> 238 <Show when={batchDelete()}> 239 <span>{records.filter((rec) => rec.toDelete).length}</span> ··· 244 <div class="flex w-[5rem] items-center justify-end"> 245 <Show when={cursor()}> 246 <Show when={!response.loading}> 247 - <Button onClick={() => refetch()}>Load More</Button> 248 </Show> 249 <Show when={response.loading}> 250 <div class="iconify lucide--loader-circle w-[5rem] animate-spin text-xl" />
··· 3 import { $type, ActorIdentifier, InferXRPCBodyOutput } from "@atcute/lexicons"; 4 import * as TID from "@atcute/tid"; 5 import { A, useParams } from "@solidjs/router"; 6 + import { 7 + createEffect, 8 + createResource, 9 + createSignal, 10 + For, 11 + onCleanup, 12 + onMount, 13 + Show, 14 + untrack, 15 + } from "solid-js"; 16 import { createStore } from "solid-js/store"; 17 + import { Button, type ButtonProps } from "../components/button.jsx"; 18 import { JSONType, JSONValue } from "../components/json.jsx"; 19 import { agent } from "../components/login.jsx"; 20 import { TextInput } from "../components/text-input.jsx"; ··· 81 const [batchDelete, setBatchDelete] = createSignal(false); 82 const [lastSelected, setLastSelected] = createSignal<number>(); 83 const [reverse, setReverse] = createSignal(false); 84 + const [filterStuck, setFilterStuck] = createSignal(false); 85 const did = params.repo; 86 let pds: string; 87 let rpc: Client; 88 + let sticky!: HTMLDivElement; 89 90 const fetchRecords = async () => { 91 if (!pds) pds = await resolvePDS(did); ··· 168 true, 169 ); 170 171 + const FilterButton = (props: ButtonProps) => { 172 + return ( 173 + <Button 174 + class="flex items-center gap-1 rounded-lg border-[0.5px] border-neutral-300 bg-white px-2 py-1.5 text-xs font-semibold shadow-md dark:border-neutral-700" 175 + classList={{ 176 + "dark:bg-dark-300 dark:hover:bg-dark-100 dark:active:bg-dark-100 bg-white hover:bg-neutral-50 active:bg-neutral-50": 177 + !filterStuck(), 178 + "dark:bg-dark-100 dark:hover:bg-dark-50 dark:active:bg-dark-50 bg-neutral-50 hover:bg-neutral-200 active:bg-neutral-200": 179 + filterStuck(), 180 + }} 181 + {...props} 182 + /> 183 + ); 184 + }; 185 + 186 + onMount(() => { 187 + let ticking = false; 188 + const tick = () => { 189 + const topPx = parseFloat(getComputedStyle(sticky).top); 190 + const { top } = sticky.getBoundingClientRect(); 191 + setFilterStuck(top <= topPx + 0.5); 192 + ticking = false; 193 + }; 194 + 195 + const onScroll = () => { 196 + if (!ticking) { 197 + ticking = true; 198 + requestAnimationFrame(tick); 199 + } 200 + }; 201 + 202 + window.addEventListener("scroll", onScroll, { passive: true }); 203 + 204 + tick(); 205 + 206 + onCleanup(() => { 207 + window.removeEventListener("scroll", onScroll); 208 + }); 209 + }); 210 + 211 return ( 212 <Show when={records.length || response()}> 213 <div class="flex w-full flex-col items-center"> 214 + <div 215 + ref={(el) => (sticky = el)} 216 + class="sticky top-2 z-10 flex flex-col items-center justify-center gap-2 rounded-lg p-3 transition-colors" 217 + classList={{ 218 + "bg-neutral-50 dark:bg-dark-300 border-[0.5px] border-neutral-300 dark:border-neutral-700 shadow-md": 219 + filterStuck(), 220 + "bg-transparent border-transparent shadow-none -mt-2": !filterStuck(), 221 + }} 222 + > 223 <div class="flex w-[22rem] items-center gap-2 sm:w-[24rem]"> 224 <Show when={agent() && agent()?.sub === did}> 225 <div class="flex items-center gap-x-2"> ··· 280 </div> 281 <Show when={records.length > 1}> 282 <div class="flex w-[22rem] items-center justify-between gap-x-2 sm:w-[24rem]"> 283 + <FilterButton 284 onClick={() => { 285 setReverse(!reverse()); 286 setRecords([]); ··· 292 class={`iconify ${reverse() ? "lucide--rotate-ccw" : "lucide--rotate-cw"} text-sm`} 293 ></span> 294 Reverse 295 + </FilterButton> 296 <div> 297 <Show when={batchDelete()}> 298 <span>{records.filter((rec) => rec.toDelete).length}</span> ··· 303 <div class="flex w-[5rem] items-center justify-end"> 304 <Show when={cursor()}> 305 <Show when={!response.loading}> 306 + <FilterButton onClick={() => refetch()}>Load More</FilterButton> 307 </Show> 308 <Show when={response.loading}> 309 <div class="iconify lucide--loader-circle w-[5rem] animate-spin text-xl" />