BlueSky & more on desktop lazurite.stormlightlabs.org/
tauri rust typescript bluesky appview atproto solid

refactor: remove more hardcoded dark mode css classes & variables

+253 -240
+8 -3
docs/todo.md
··· 5 5 6 6 ## Bugs 7 7 8 - 1. Lists & Labels Not Working 8 + ## Refactor 9 + 10 + Typeahead code is largely repeated in the following places: 11 + 12 + 1. `src/components/LoginPanel.tsx` 13 + 2. `src/components/deck/ColumnPicker/ProfileColumnPicker.tsx` 14 + 3. `src/components/explorer/ExplorerUrlBar.tsx` 15 + 4. `src/components/search/hooks/useSearchController.ts` 9 16 10 17 ## High Priority Updates 11 18 12 - - [ ] Video player 13 - - [ ] Download videos and media attachments 14 19 - [ ] Profile RSS 15 20 - OK. So making an RSS reader with share to BlueSky would be cool... 16 21
+2 -4
src/components/LoginPanel.tsx
··· 16 16 <span>Continue</span> 17 17 </> 18 18 }> 19 - <> 20 - <Icon kind="loader" name="loader" aria-hidden="true" class="mr-1" /> 21 - <span>Opening sign-in...</span> 22 - </> 19 + <Icon kind="loader" name="loader" aria-hidden="true" class="mr-1" /> 20 + <span>Opening sign-in...</span> 23 21 </Show> 24 22 </button> 25 23 );
+5 -5
src/components/deck/AddColumnPanel.tsx
··· 56 56 57 57 function AddColumnPanelHeader(props: { onClose: () => void }) { 58 58 return ( 59 - <div class="flex shrink-0 items-center justify-between gap-3 px-5 py-4 shadow-[inset_0_-1px_0_rgba(255,255,255,0.04)]"> 59 + <div class="flex shrink-0 items-center justify-between gap-3 px-5 py-4 shadow-(--inset-shadow)"> 60 60 <div> 61 61 <p id="add-column-panel-title" class="m-0 text-sm font-semibold text-on-surface">Add column</p> 62 62 <p class="m-0 mt-1 text-xs uppercase tracking-[0.12em] text-on-surface-variant">Choose a view</p> 63 63 </div> 64 64 <button 65 65 type="button" 66 - class="flex h-8 w-8 items-center justify-center rounded-full border-0 bg-transparent text-on-surface-variant transition duration-150 hover:bg-white/6 hover:text-on-surface" 66 + class="flex h-8 w-8 items-center justify-center rounded-full border-0 bg-transparent text-on-surface-variant transition duration-150 hover:bg-surface-bright hover:text-on-surface" 67 67 aria-label="Close panel" 68 68 onClick={() => props.onClose()}> 69 69 <Icon kind="close" /> ··· 88 88 class="flex items-center justify-center gap-1.5 rounded-lg border-0 px-3 py-2 text-xs font-medium transition duration-150" 89 89 classList={{ 90 90 "bg-primary/15 text-primary": props.activeTab === tab.id, 91 - "bg-transparent text-on-surface-variant hover:bg-white/5 hover:text-on-surface": 91 + "bg-transparent text-on-surface-variant hover:bg-surface-bright hover:text-on-surface": 92 92 props.activeTab !== tab.id, 93 93 }} 94 94 onClick={() => props.onTabChange(tab.id)}> ··· 120 120 role="dialog" 121 121 aria-modal="true" 122 122 aria-labelledby="add-column-panel-title" 123 - class="relative z-10 flex h-full w-full max-w-88 flex-col bg-surface-container-highest shadow-[-18px_0_48px_rgba(0,0,0,0.38)] backdrop-blur-[20px]" 123 + class="ui-overlay-card relative z-10 flex h-full w-full max-w-88 flex-col bg-surface-container-highest backdrop-blur-[20px]" 124 124 initial={{ opacity: 0, x: 32 }} 125 125 animate={{ opacity: 1, x: 0 }} 126 126 exit={{ opacity: 0, x: 40 }} ··· 202 202 <Portal> 203 203 <div class="fixed inset-0 z-50 flex justify-end"> 204 204 <Motion.div 205 - class="absolute inset-0 bg-black/45 backdrop-blur-[20px]" 205 + class="ui-scrim absolute inset-0 backdrop-blur-[20px]" 206 206 initial={{ opacity: 0 }} 207 207 animate={{ opacity: 1 }} 208 208 exit={{ opacity: 0 }}
+4 -4
src/components/deck/ColumnPicker.tsx
··· 66 66 {(feed) => ( 67 67 <button 68 68 type="button" 69 - class="flex w-full items-center gap-3 rounded-xl border-0 bg-white/4 px-4 py-3 text-left transition duration-150 hover:-translate-y-px hover:bg-white/8" 69 + class="tone-muted flex w-full items-center gap-3 rounded-xl border-0 px-4 py-3 text-left transition duration-150 hover:-translate-y-px hover:bg-surface-bright" 70 70 onClick={() => props.onSelect({ feed, title: getFeedName(feed, generators()[feed.value]?.displayName) })}> 71 71 <FeedChipAvatar feed={feed} generator={generators()[feed.value]} /> 72 72 <span class="min-w-0 flex-1"> ··· 101 101 </span> 102 102 <input 103 103 type="text" 104 - class="rounded-xl border-0 bg-white/6 px-4 py-2.5 text-sm text-on-surface placeholder:text-on-surface-variant/50 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)] outline-none focus:shadow-[inset_0_0_0_1px_rgba(125,175,255,0.4)]" 104 + class="ui-input ui-input-strong rounded-xl px-4 py-2.5" 105 105 placeholder="at://did:plc:… or handle.bsky.social" 106 106 value={value()} 107 107 onInput={(e) => setValue(e.currentTarget.value)} /> ··· 137 137 <span class="text-xs font-medium uppercase tracking-wide text-on-surface-variant">Handle or DID</span> 138 138 <input 139 139 type="text" 140 - class="rounded-xl border-0 bg-white/6 px-4 py-2.5 text-sm text-on-surface placeholder:text-on-surface-variant/50 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)] outline-none focus:shadow-[inset_0_0_0_1px_rgba(125,175,255,0.4)]" 140 + class="ui-input ui-input-strong rounded-xl px-4 py-2.5" 141 141 placeholder="handle.bsky.social or did:plc:…" 142 142 value={value()} 143 143 onInput={(e) => setValue(e.currentTarget.value)} /> ··· 159 159 export function MessagesPicker(props: { onSubmit: () => void }) { 160 160 return ( 161 161 <div class="grid gap-4"> 162 - <div class="rounded-2xl bg-white/4 p-4 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.04)]"> 162 + <div class="rounded-2xl bg-surface-container-high p-4 shadow-(--inset-shadow)"> 163 163 <div class="flex items-start gap-3"> 164 164 <span class="mt-0.5 flex items-center text-primary"> 165 165 <i class="i-ri-message-3-line" />
+1 -1
src/components/deck/ColumnPicker/ProfileColumnPicker.tsx
··· 96 96 ? `profile-suggestions-option-${typeahead.activeIndex()}` 97 97 : undefined} 98 98 aria-expanded={typeahead.open()} 99 - class="w-full rounded-xl border-0 bg-white/6 px-4 py-2.5 pr-10 text-sm text-on-surface placeholder:text-on-surface-variant/50 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)] outline-none focus:shadow-[inset_0_0_0_1px_rgba(125,175,255,0.4)]" 99 + class="ui-input ui-input-strong w-full rounded-xl px-4 py-2.5 pr-10" 100 100 placeholder="alice.bsky.social" 101 101 spellcheck={false} 102 102 value={value()}
+3 -2
src/components/deck/ColumnPicker/SearchPicker.tsx
··· 10 10 class="inline-flex items-center justify-center gap-2 rounded-xl border-0 px-3 py-2 text-xs font-medium transition duration-150 disabled:cursor-not-allowed disabled:opacity-40" 11 11 classList={{ 12 12 "bg-primary/15 text-primary": props.active, 13 - "bg-white/4 text-on-surface-variant hover:bg-white/8 hover:text-on-surface": !props.active && !props.disabled, 13 + "tone-muted text-on-surface-variant hover:bg-surface-bright hover:text-on-surface": !props.active 14 + && !props.disabled, 14 15 }} 15 16 onClick={() => props.onClick()}> 16 17 <SearchModeIcon mode={props.mode} class="text-sm" /> ··· 39 40 <span class="text-xs font-medium uppercase tracking-wide text-on-surface-variant">Search query</span> 40 41 <input 41 42 type="text" 42 - class="rounded-xl border-0 bg-white/6 px-4 py-2.5 text-sm text-on-surface placeholder:text-on-surface-variant/50 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)] outline-none focus:shadow-[inset_0_0_0_1px_rgba(125,175,255,0.4)]" 43 + class="ui-input ui-input-strong rounded-xl px-4 py-2.5" 43 44 placeholder="from:alice at protocol" 44 45 value={query()} 45 46 onInput={(event) => setQuery(event.currentTarget.value)} />
+8 -8
src/components/deck/DeckColumn.tsx
··· 75 75 <div class="flex shrink-0 items-center gap-1"> 76 76 <button 77 77 type="button" 78 - class="flex h-6 w-6 shrink-0 items-center justify-center rounded border-0 bg-white/5 text-[0.65rem] font-bold text-on-surface-variant transition duration-150 hover:-translate-y-px hover:bg-white/10 hover:text-on-surface" 78 + class="flex h-6 w-6 shrink-0 items-center justify-center rounded border-0 bg-surface-container-high text-[0.65rem] font-bold text-on-surface-variant transition duration-150 hover:-translate-y-px hover:bg-surface-bright hover:text-on-surface" 79 79 aria-label={`Column width: ${props.column.width}. Click to cycle.`} 80 80 title="Cycle column width" 81 81 onClick={() => props.onWidthCycle()}> ··· 83 83 </button> 84 84 <button 85 85 type="button" 86 - class="flex h-6 w-6 shrink-0 items-center justify-center rounded border-0 bg-transparent text-sm text-on-surface-variant transition duration-150 hover:-translate-y-px hover:bg-white/6 hover:text-on-surface" 86 + class="flex h-6 w-6 shrink-0 items-center justify-center rounded border-0 bg-transparent text-sm text-on-surface-variant transition duration-150 hover:-translate-y-px hover:bg-surface-bright hover:text-on-surface" 87 87 aria-label="Move column left" 88 88 title="Move column left" 89 89 onClick={() => props.onMoveLeft()}> ··· 93 93 </button> 94 94 <button 95 95 type="button" 96 - class="flex h-6 w-6 shrink-0 items-center justify-center rounded border-0 bg-transparent text-sm text-on-surface-variant transition duration-150 hover:-translate-y-px hover:bg-white/6 hover:text-on-surface" 96 + class="flex h-6 w-6 shrink-0 items-center justify-center rounded border-0 bg-transparent text-sm text-on-surface-variant transition duration-150 hover:-translate-y-px hover:bg-surface-bright hover:text-on-surface" 97 97 aria-label="Move column right" 98 98 title="Move column right" 99 99 onClick={() => props.onMoveRight()}> ··· 103 103 </button> 104 104 <button 105 105 type="button" 106 - class="flex h-6 w-6 shrink-0 items-center justify-center rounded border-0 bg-transparent text-sm text-on-surface-variant transition duration-150 hover:-translate-y-px hover:bg-white/6 hover:text-error" 106 + class="flex h-6 w-6 shrink-0 items-center justify-center rounded border-0 bg-transparent text-sm text-on-surface-variant transition duration-150 hover:-translate-y-px hover:bg-surface-bright hover:text-error" 107 107 aria-label="Close column" 108 108 title="Close column" 109 109 onClick={() => props.onClose()}> ··· 117 117 118 118 function ColumnHeader(props: ColumnHeaderProps) { 119 119 return ( 120 - <header class="flex shrink-0 items-center gap-2 rounded-t-2xl bg-[rgba(14,14,14,0.94)] px-3 py-2.5 backdrop-blur-[18px] shadow-[inset_0_-1px_0_rgba(255,255,255,0.04)]"> 120 + <header class="flex shrink-0 items-center gap-2 rounded-t-2xl bg-surface-container-highest px-3 py-2.5 backdrop-blur-[18px] shadow-(--inset-shadow)"> 121 121 <span 122 122 class="flex cursor-grab items-center text-on-surface-variant opacity-40 hover:opacity-80 active:cursor-grabbing" 123 123 draggable="true" ··· 244 244 function BlurredMessagesBody() { 245 245 return ( 246 246 <div class="group relative min-h-0 min-w-0 overflow-hidden"> 247 - <div class="pointer-events-none absolute right-3 top-3 z-10 rounded-full bg-black/55 px-2.5 py-1 text-[0.65rem] font-medium uppercase tracking-[0.12em] text-on-surface-variant backdrop-blur-sm transition duration-150 group-hover:opacity-0 group-focus-within:opacity-0"> 247 + <div class="pointer-events-none absolute right-3 top-3 z-10 rounded-full bg-surface-container-highest/88 px-2.5 py-1 text-[0.65rem] font-medium uppercase tracking-[0.12em] text-on-surface-variant backdrop-blur-sm transition duration-150 group-hover:opacity-0 group-focus-within:opacity-0"> 248 248 Hover to reveal 249 249 </div> 250 250 <div class="h-full transition duration-200 ease-out blur-[14px] saturate-50 group-hover:blur-none group-hover:saturate-100 group-focus-within:blur-none group-focus-within:saturate-100"> ··· 325 325 props.onDrop(props.column.id); 326 326 }}> 327 327 <section 328 - class="flex h-full w-full flex-col overflow-hidden rounded-2xl bg-[rgba(8,8,8,0.32)] transition-shadow duration-150" 328 + class="flex h-full w-full flex-col overflow-hidden rounded-2xl bg-surface-container transition-shadow duration-150" 329 329 classList={{ 330 330 "shadow-[inset_0_0_0_2px_rgba(125,175,255,0.45)]": props.isDragOver, 331 - "shadow-[inset_0_0_0_1px_rgba(255,255,255,0.03)]": !props.isDragOver, 331 + "shadow-(--inset-shadow)": !props.isDragOver, 332 332 }}> 333 333 <ColumnHeader 334 334 column={props.column}
+2 -2
src/components/deck/DeckWorkspace.tsx
··· 34 34 </div> 35 35 <button 36 36 type="button" 37 - class="inline-flex h-11 items-center gap-2 rounded-full border-0 bg-white/5 px-4 text-sm text-on-surface transition duration-150 ease-out hover:-translate-y-px hover:bg-white/8" 37 + class="inline-flex h-11 items-center gap-2 rounded-full border-0 bg-surface-container-high px-4 text-sm text-on-surface transition duration-150 ease-out hover:-translate-y-px hover:bg-surface-bright" 38 38 aria-label="Add column (Ctrl+Shift+N)" 39 39 title="Add column (Ctrl+Shift+N)" 40 40 onClick={() => props.onAdd()}> ··· 49 49 50 50 function EmptyDeck(props: { onAdd: () => void }) { 51 51 return ( 52 - <div class="flex h-full min-h-104 flex-col items-center justify-center gap-4 rounded-[1.75rem] bg-white/3 px-6 text-center shadow-[inset_0_0_0_1px_rgba(255,255,255,0.035)]"> 52 + <div class="flex h-full min-h-104 flex-col items-center justify-center gap-4 rounded-[1.75rem] bg-surface-container px-6 text-center shadow-(--inset-shadow)"> 53 53 <span class="flex items-center text-[2.5rem] text-on-surface-variant opacity-30"> 54 54 <i class="i-ri-layout-column-line" /> 55 55 </span>
+22 -16
src/components/deck/DiagnosticsPanel.tsx
··· 345 345 } 346 346 347 347 return ( 348 - <article class="grid min-h-0 grid-rows-[auto_auto_1fr] overflow-hidden rounded-4xl bg-surface-container shadow-[inset_0_0_0_1px_rgba(255,255,255,0.035)]"> 348 + <article class="grid min-h-0 grid-rows-[auto_auto_1fr] overflow-hidden rounded-4xl bg-surface-container shadow-(--inset-shadow)"> 349 349 <DiagnosticsHeader 350 350 did={activeDid()} 351 351 embedded={props.embedded ?? false} ··· 403 403 404 404 return ( 405 405 <nav class="px-3 pb-3" aria-label="Diagnostics tabs"> 406 - <div class="relative flex gap-1 rounded-full bg-black/30 p-1"> 406 + <div class="ui-input-strong relative flex gap-1 rounded-full p-1"> 407 407 <Motion.div 408 - class="absolute inset-y-1 rounded-full bg-white/7 shadow-[inset_0_0_0_1px_rgba(125,175,255,0.16)]" 408 + class="absolute inset-y-1 rounded-full bg-surface-container-high shadow-[inset_0_0_0_1px_rgba(125,175,255,0.16)]" 409 409 animate={{ x: `${activeIndex() * 100}%` }} 410 410 style={{ width: `${100 / DIAGNOSTICS_TABS.length}%` }} 411 411 transition={{ duration: 0.18 }} /> ··· 614 614 <StatCard label={props.isSelf ? "Boundaries around you" : "Blocked by"} value={blockedByCount()} /> 615 615 <StatCard label={props.isSelf ? "Your boundaries" : "Blocking"} value={blockingCount()} /> 616 616 </div> 617 - <div class="rounded-3xl bg-white/3 p-4 text-sm leading-relaxed text-on-surface-variant"> 617 + <div class="rounded-3xl bg-surface-container-high p-4 text-sm leading-relaxed text-on-surface-variant shadow-(--inset-shadow)"> 618 618 Blocks are a normal part of social media. This data is public on the AT Protocol. 619 619 </div> 620 620 <button ··· 719 719 720 720 function DiagnosticsTabIntro(props: { description: string; title: string }) { 721 721 return ( 722 - <div class="grid gap-1 rounded-3xl bg-white/3 p-4"> 722 + <div class="grid gap-1 rounded-3xl bg-surface-container-high p-4 shadow-(--inset-shadow)"> 723 723 <h2 class="m-0 text-base font-semibold text-on-surface">{props.title}</h2> 724 724 <p class="m-0 text-sm leading-relaxed text-on-surface-variant">{props.description}</p> 725 725 </div> ··· 728 728 729 729 function DiagnosticsError(props: { message: string | null; onRetry?: () => void }) { 730 730 return ( 731 - <div class="grid gap-3 rounded-3xl bg-white/3 p-4 text-sm text-on-surface-variant"> 731 + <div class="grid gap-3 rounded-3xl bg-surface-container-high p-4 text-sm text-on-surface-variant shadow-(--inset-shadow)"> 732 732 <p class="m-0">{props.message}</p> 733 733 <Show when={props.onRetry}> 734 734 <button ··· 744 744 } 745 745 746 746 function DiagnosticsEmptyState(props: { copy: string }) { 747 - return <div class="rounded-3xl bg-white/3 p-4 text-sm text-on-surface-variant">{props.copy}</div>; 747 + return ( 748 + <div class="rounded-3xl bg-surface-container-high p-4 text-sm text-on-surface-variant shadow-(--inset-shadow)"> 749 + {props.copy} 750 + </div> 751 + ); 748 752 } 749 753 750 754 function StatCard(props: { label: string; value: number }) { 751 755 return ( 752 - <div class="rounded-3xl bg-white/3 p-4"> 756 + <div class="rounded-3xl bg-surface-container-high p-4 shadow-(--inset-shadow)"> 753 757 <p class="m-0 text-xs uppercase tracking-[0.12em] text-on-surface-variant">{props.label}</p> 754 758 <p class="m-0 mt-2 text-3xl font-semibold text-on-surface">{props.value}</p> 755 759 </div> ··· 768 772 769 773 return ( 770 774 <Motion.span 771 - class="inline-flex items-center gap-2 rounded-full bg-white/5 px-3 py-2 text-sm text-on-secondary-container" 775 + class="inline-flex items-center gap-2 rounded-full bg-surface-bright px-3 py-2 text-sm text-on-secondary-container" 772 776 initial={{ opacity: 0, scale: 0.9 }} 773 777 animate={{ opacity: 1, scale: 1 }} 774 778 title={title()} 775 779 transition={{ delay: Math.min(props.index * 0.02, 0.12), duration: 0.14 }}> 776 - <span class="h-2 w-2 rounded-full bg-white/20" /> 780 + <span class="h-2 w-2 rounded-full bg-primary/35" /> 777 781 <span>{props.label.val ?? "label"}</span> 778 782 <span class="text-xs text-on-surface-variant/90">{props.sourceName}</span> 779 783 </Motion.span> ··· 785 789 const title = () => props.list.title ?? props.list.name ?? "Untitled list"; 786 790 787 791 return ( 788 - <div class="rounded-3xl bg-white/3 p-4 transition duration-150 hover:bg-white/5"> 792 + <div class="rounded-3xl bg-surface-container-high p-4 shadow-(--inset-shadow) transition duration-150 hover:bg-surface-bright"> 789 793 <div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between"> 790 794 <div class="min-w-0"> 791 795 <div class="flex flex-wrap items-center gap-2"> ··· 827 831 const title = () => props.pack.title ?? props.pack.name ?? props.pack.record?.name ?? "Starter pack"; 828 832 829 833 return ( 830 - <div class="rounded-3xl bg-white/3 p-4 transition duration-150 hover:bg-white/5"> 834 + <div class="rounded-3xl bg-surface-container-high p-4 shadow-(--inset-shadow) transition duration-150 hover:bg-surface-bright"> 831 835 <div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"> 832 836 <div class="min-w-0"> 833 837 <p class="m-0 text-base font-semibold text-on-surface">{title()}</p> ··· 840 844 </div> 841 845 842 846 <div class="grid shrink-0 justify-items-start gap-2 sm:justify-items-end"> 843 - <span class="rounded-full bg-white/5 px-3 py-1 text-xs text-on-surface-variant">{count()} members</span> 847 + <span class="rounded-full bg-surface-bright px-3 py-1 text-xs text-on-surface-variant"> 848 + {count()} members 849 + </span> 844 850 <Show when={props.pack.uri}> 845 851 {uri => ( 846 852 <button ··· 875 881 }, 876 882 ) { 877 883 return ( 878 - <div class="grid gap-3 rounded-3xl bg-white/3 p-4"> 884 + <div class="grid gap-3 rounded-3xl bg-surface-container-high p-4 shadow-(--inset-shadow)"> 879 885 <p class="m-0 text-sm font-semibold text-on-surface">{props.title}</p> 880 886 <div class="grid gap-3"> 881 887 <For each={props.items}> ··· 884 890 return ( 885 891 <Motion.div 886 892 class="flex items-start gap-3 rounded-2xl p-3" 887 - classList={{ "bg-black/20": item.available, "bg-white/4 opacity-70": !item.available }} 893 + classList={{ "ui-input-strong": item.available, "tone-muted opacity-70": !item.available }} 888 894 aria-disabled={!item.available} 889 895 initial={{ opacity: 0, y: 8 }} 890 896 animate={{ opacity: 1, y: 0 }} 891 897 transition={{ delay: Math.min(index() * 0.04, 0.16), duration: 0.16 }}> 892 - <div class="flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-full bg-white/8 text-xs font-semibold text-on-surface-variant"> 898 + <div class="flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-full bg-surface-container-high text-xs font-semibold text-on-surface-variant"> 893 899 <Show 894 900 when={item.available && item.avatar} 895 901 fallback={item.available
+10 -10
src/components/deck/DiagnosticsSkeleton.tsx
··· 5 5 <div class="grid gap-4"> 6 6 <For each={Array.from({ length: 3 })}> 7 7 {() => ( 8 - <div class="grid h-32 gap-3 rounded-3xl bg-white/3 p-4"> 9 - <div class="h-4 w-28 rounded-full bg-white/6" /> 10 - <div class="h-4 w-44 rounded-full bg-white/6" /> 11 - <div class="h-4 w-full rounded-full bg-white/6" /> 8 + <div class="grid h-32 gap-3 rounded-3xl bg-surface-container-high p-4 shadow-(--inset-shadow)"> 9 + <div class="skeleton-block h-4 w-28 rounded-full" /> 10 + <div class="skeleton-block h-4 w-44 rounded-full" /> 11 + <div class="skeleton-block h-4 w-full rounded-full" /> 12 12 </div> 13 13 )} 14 14 </For> ··· 19 19 export function DiagnosticsLabelSkeleton() { 20 20 return ( 21 21 <div class="flex flex-wrap gap-2"> 22 - <For each={Array.from({ length: 5 })}>{() => <div class="h-10 w-32 rounded-full bg-white/3" />}</For> 22 + <For each={Array.from({ length: 5 })}>{() => <div class="skeleton-block h-10 w-32 rounded-full" />}</For> 23 23 </div> 24 24 ); 25 25 } ··· 29 29 <div class="grid gap-3"> 30 30 <For each={Array.from({ length: 2 })}> 31 31 {() => ( 32 - <div class="grid h-28 gap-3 rounded-3xl bg-white/3 p-4"> 33 - <div class="h-4 w-40 rounded-full bg-white/6" /> 34 - <div class="h-4 w-28 rounded-full bg-white/6" /> 35 - <div class="h-4 w-full rounded-full bg-white/6" /> 32 + <div class="grid h-28 gap-3 rounded-3xl bg-surface-container-high p-4 shadow-(--inset-shadow)"> 33 + <div class="skeleton-block h-4 w-40 rounded-full" /> 34 + <div class="skeleton-block h-4 w-28 rounded-full" /> 35 + <div class="skeleton-block h-4 w-full rounded-full" /> 36 36 </div> 37 37 )} 38 38 </For> ··· 43 43 export function DiagnosticsBlockSkeleton() { 44 44 return ( 45 45 <div class="grid gap-3"> 46 - <For each={Array.from({ length: 2 })}>{() => <div class="h-24 rounded-3xl bg-white/3" />}</For> 46 + <For each={Array.from({ length: 2 })}>{() => <div class="skeleton-block h-24 rounded-3xl" />}</For> 47 47 </div> 48 48 ); 49 49 }
+10 -10
src/components/explorer/ExplorerPanel.tsx
··· 482 482 {(message) => ( 483 483 <div class="px-6 pt-4"> 484 484 <div 485 - class="rounded-2xl px-4 py-3 text-sm shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)]" 485 + class="rounded-2xl px-4 py-3 text-sm shadow-(--inset-shadow)" 486 486 classList={{ 487 - "bg-[rgba(138,31,31,0.2)] text-error": message().kind === "error", 488 - "bg-[rgba(28,80,49,0.28)] text-on-surface": message().kind === "success", 487 + "bg-error-surface text-error": message().kind === "error", 488 + "bg-surface-container-high text-on-surface": message().kind === "success", 489 489 }}> 490 490 {message().text} 491 491 </div> ··· 505 505 class="h-full overflow-auto p-6"> 506 506 <Switch> 507 507 <Match when={view().error}> 508 - <div class="rounded-3xl bg-[rgba(138,31,31,0.2)] p-4 text-sm text-error shadow-[inset_0_0_0_1px_rgba(255,128,128,0.2)]"> 508 + <div class="rounded-3xl bg-error-surface p-4 text-sm text-error shadow-(--inset-shadow)"> 509 509 {view().error} 510 510 </div> 511 511 </Match> ··· 581 581 582 582 return ( 583 583 <div class="flex h-full items-start overflow-auto p-6"> 584 - <section class="mx-auto grid w-full max-w-4xl gap-6 rounded-[1.75rem] bg-white/3 p-8 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.04)]"> 584 + <section class="mx-auto grid w-full max-w-4xl gap-6 rounded-[1.75rem] bg-surface-container p-8 shadow-(--inset-shadow)"> 585 585 <div class="grid gap-2"> 586 586 <p class="overline-copy text-xs text-primary/80">AT Protocol Explorer</p> 587 587 <h1 class="m-0 text-[2rem] font-medium tracking-[-0.03em] text-on-surface"> ··· 598 598 <button 599 599 type="button" 600 600 onClick={() => void props.onExampleClick(example.value)} 601 - class="rounded-2xl bg-white/4 px-4 py-4 text-left transition duration-150 ease-out hover:bg-white/7 hover:-translate-y-px"> 601 + class="rounded-2xl bg-surface-container-high px-4 py-4 text-left shadow-(--inset-shadow) transition duration-150 ease-out hover:-translate-y-px hover:bg-surface-bright"> 602 602 <p class="m-0 text-xs uppercase tracking-[0.12em] text-on-surface-variant">{example.label}</p> 603 603 <p class="mt-2 truncate text-sm font-mono text-primary">{example.value}</p> 604 604 </button> ··· 627 627 628 628 function ExplorerSkeleton() { 629 629 return ( 630 - <div class="grid gap-4 animate-pulse"> 631 - <div class="h-8 w-1/3 rounded-lg bg-white/5" /> 632 - <div class="h-4 w-1/4 rounded bg-white/5" /> 630 + <div class="grid gap-4" aria-hidden="true"> 631 + <div class="skeleton-block h-8 w-1/3 rounded-lg" /> 632 + <div class="skeleton-block h-4 w-1/4 rounded" /> 633 633 <div class="grid gap-2 mt-4"> 634 - <For each={Array.from({ length: 5 })}>{() => <div class="h-16 rounded-xl bg-white/5" />}</For> 634 + <For each={Array.from({ length: 5 })}>{() => <div class="skeleton-block h-16 rounded-xl" />}</For> 635 635 </div> 636 636 </div> 637 637 );
+12 -7
src/components/explorer/ExplorerUrlBar.tsx
··· 22 22 <button 23 23 onClick={() => props.onClick()} 24 24 disabled={props.disabled} 25 - class="p-2 rounded-lg text-on-surface-variant hover:text-on-surface hover:bg-white/5 transition-all disabled:opacity-30 disabled:cursor-not-allowed" 25 + class="p-2 rounded-lg text-on-surface-variant transition-all hover:bg-surface-bright hover:text-on-surface disabled:cursor-not-allowed disabled:opacity-30" 26 26 aria-label={props.direction === "left" ? "Back" : "Forward"} 27 27 title={props.direction === "left" ? "Back" : "Forward"}> 28 28 <ArrowIcon direction={props.direction} /> ··· 91 91 }} 92 92 onSubmit={handleSubmit} 93 93 class="flex-1 relative"> 94 - <div class="flex items-center gap-3 px-4 py-2 rounded-xl bg-black/40 shadow-[inset_0_0_0_1px_rgba(125,175,255,0.12)]"> 94 + <div 95 + class="ui-input-strong flex items-center gap-3 rounded-xl border px-4 py-2 transition-[border-color,box-shadow,background-color] duration-150" 96 + style={{ 97 + "border-color": focused() ? "var(--focus-ring)" : "var(--outline-subtle)", 98 + "box-shadow": focused() ? "0 0 0 3px var(--focus-ring)" : "var(--inset-shadow)", 99 + }}> 95 100 <Icon kind="explore" class="text-primary/80" /> 96 101 <input 97 102 ref={(element) => { ··· 127 132 </Show> 128 133 <button 129 134 type="submit" 130 - class="p-1.5 rounded-lg text-on-surface-variant hover:text-on-surface hover:bg-white/5 transition-all"> 135 + class="rounded-lg p-1.5 text-on-surface-variant transition-all hover:bg-surface-bright hover:text-on-surface"> 131 136 <Icon kind="search" /> 132 137 </button> 133 138 </div> ··· 144 149 145 150 export function ExplorerUrlBar(props: ExplorerUrlBarProps) { 146 151 return ( 147 - <header class="sticky top-0 z-40 border-b border-white/5 bg-surface-container/80 backdrop-blur-xl"> 152 + <header class="sticky top-0 z-40 border-b ui-outline-subtle bg-surface-container/80 backdrop-blur-xl"> 148 153 <div class="px-6 py-4 flex items-center gap-3"> 149 154 <div class="flex gap-1"> 150 155 <NavButton direction="left" disabled={!props.canGoBack} onClick={props.onBack} /> ··· 155 160 156 161 <button 157 162 onClick={() => props.onSubmit(props.value)} 158 - class="p-2 rounded-lg text-on-surface-variant hover:text-on-surface hover:bg-white/5 transition-all" 163 + class="rounded-lg p-2 text-on-surface-variant transition-all hover:bg-surface-bright hover:text-on-surface" 159 164 aria-label="Reload" 160 165 title="Reload"> 161 166 <Icon kind="refresh" /> ··· 164 169 <button 165 170 onClick={() => props.onClearIconCache()} 166 171 disabled={props.clearingIconCache} 167 - class="p-2 rounded-lg text-on-surface-variant hover:text-on-surface hover:bg-white/5 transition-all disabled:cursor-not-allowed disabled:opacity-30" 172 + class="rounded-lg p-2 text-on-surface-variant transition-all hover:bg-surface-bright hover:text-on-surface disabled:cursor-not-allowed disabled:opacity-30" 168 173 aria-label="Clear icon cache" 169 174 title="Clear icon cache"> 170 175 <Icon iconClass={props.clearingIconCache ? "i-ri-loader-4-line" : "i-ri-delete-bin-6-line"} /> ··· 173 178 <button 174 179 onClick={() => props.onExport()} 175 180 disabled={!props.canExport} 176 - class="p-2 rounded-lg text-on-surface-variant hover:text-on-surface hover:bg-white/5 transition-all disabled:cursor-not-allowed disabled:opacity-30" 181 + class="rounded-lg p-2 text-on-surface-variant transition-all hover:bg-surface-bright hover:text-on-surface disabled:cursor-not-allowed disabled:opacity-30" 177 182 aria-label="Download CAR" 178 183 title="Download CAR"> 179 184 <Icon iconClass="i-ri-download-2-line" />
+154 -156
src/components/posts/ThreadDrawer.tsx
··· 70 70 }; 71 71 } 72 72 73 - export function ThreadDrawer() { 74 - const session = useAppSession(); 75 - const postNavigation = usePostNavigation(); 76 - const threadOverlay = useThreadOverlayNavigation(); 77 - const history = useNavigationHistory(); 78 - const [state, setState] = createStore<ThreadDrawerState>(createThreadDrawerState()); 79 - const activeUri = createMemo(() => (threadOverlay.drawerEnabled() ? threadOverlay.threadUri() : null)); 80 - const rootPost = createMemo(() => findRootPost(state.thread)); 81 - const parentThreadUri = createMemo(() => findParentUri(state.thread, activeUri())); 82 - const parentThreadHref = createMemo(() => 83 - parentThreadUri() ? threadOverlay.buildThreadHref(parentThreadUri()) : null 84 - ); 85 - const interactions = usePostInteractions({ 86 - onError: session.reportError, 87 - patchPost(uri, updater) { 88 - const current = state.thread; 89 - if (!current) { 90 - return; 91 - } 92 - 93 - setState("thread", patchThreadNode(current, uri, updater)); 94 - }, 95 - }); 96 - 97 - createEffect(() => { 98 - const uri = activeUri(); 99 - if (!uri) { 100 - if (state.uri || state.thread || state.error || state.loading) { 101 - setState(createThreadDrawerState()); 102 - } 103 - return; 104 - } 105 - 106 - if (state.uri === uri && (state.loading || state.thread || state.error)) { 107 - return; 108 - } 109 - 110 - void loadThread(uri); 111 - }); 73 + type ThreadDrawerBodyProps = { 74 + activeUri: string | null; 75 + bookmarkPendingByUri: Record<string, boolean>; 76 + error: string | null; 77 + likePendingByUri: Record<string, boolean>; 78 + loading: boolean; 79 + onBookmark: (post: PostView) => void; 80 + onLike: (post: PostView) => void; 81 + onOpenEngagement: (uri: string, tab: "likes" | "reposts" | "quotes") => void; 82 + onOpenThread: (uri: string) => void; 83 + onRepost: (post: PostView) => void; 84 + repostPendingByUri: Record<string, boolean>; 85 + rootPost: PostView | null; 86 + thread: ThreadNode | null; 87 + }; 112 88 113 - createEffect(() => { 114 - if (!activeUri()) { 115 - return; 116 - } 117 - 118 - const handleKeyDown = createEscapeKeyHandler(() => { 119 - void threadOverlay.closeThread(); 120 - }); 121 - 122 - globalThis.addEventListener("keydown", handleKeyDown); 123 - onCleanup(() => globalThis.removeEventListener("keydown", handleKeyDown)); 124 - }); 125 - 126 - async function loadThread(uri: string) { 127 - setState({ error: null, loading: true, thread: null, uri }); 128 - 129 - try { 130 - const payload = await FeedController.getPostThread(uri); 131 - if (activeUri() === uri) { 132 - setState({ error: null, loading: false, thread: payload.thread, uri }); 133 - } 134 - } catch (error) { 135 - if (activeUri() === uri) { 136 - setState({ error: String(error), loading: false, thread: null, uri }); 137 - } 138 - session.reportError(`Failed to open thread: ${String(error)}`); 139 - } 140 - } 141 - 142 - return ( 143 - <Presence> 144 - <Show when={activeUri()}> 145 - <div class="fixed inset-0 z-50"> 146 - <Motion.button 147 - class="ui-scrim absolute inset-0 border-0 backdrop-blur-xl" 148 - type="button" 149 - aria-label="Close thread" 150 - initial={{ opacity: 0 }} 151 - animate={{ opacity: 1 }} 152 - exit={{ opacity: 0 }} 153 - transition={{ duration: 0.2 }} 154 - onClick={() => void threadOverlay.closeThread()} /> 155 - <Motion.aside 156 - class="absolute inset-y-0 right-0 grid w-full max-w-136 grid-rows-[auto_minmax(0,1fr)] overflow-hidden bg-surface-container-highest px-5 pb-6 pt-5 shadow-[-28px_0_50px_rgba(0,0,0,0.24)] backdrop-blur-[22px]" 157 - initial={{ opacity: 0, x: 30 }} 158 - animate={{ opacity: 1, x: 0 }} 159 - exit={{ opacity: 0, x: 36 }} 160 - transition={{ duration: 0.22 }}> 161 - <ThreadDrawerHeader 162 - activeUri={activeUri()} 163 - canGoBack={history.canGoBack()} 164 - canGoForward={history.canGoForward()} 165 - onGoBack={history.goBack} 166 - onGoForward={history.goForward} 167 - onMaximize={(uri) => void postNavigation.openPostScreen(uri)} 168 - parentThreadHref={parentThreadHref()} 169 - onClose={() => void threadOverlay.closeThread()} /> 170 - <ThreadDrawerBody 171 - activeUri={activeUri()} 172 - bookmarkPendingByUri={interactions.bookmarkPendingByUri()} 173 - error={state.error} 174 - likePendingByUri={interactions.likePendingByUri()} 175 - loading={state.loading} 176 - onBookmark={(post) => void interactions.toggleBookmark(post)} 177 - onLike={(post) => void interactions.toggleLike(post)} 178 - onOpenEngagement={(uri, tab) => void postNavigation.openPostEngagement(uri, tab)} 179 - onOpenThread={(uri) => void threadOverlay.openThread(uri)} 180 - onRepost={(post) => void interactions.toggleRepost(post)} 181 - repostPendingByUri={interactions.repostPendingByUri()} 182 - rootPost={rootPost()} 183 - thread={state.thread} /> 184 - </Motion.aside> 185 - </div> 186 - </Show> 187 - </Presence> 188 - ); 189 - } 190 - 191 - function ThreadDrawerBody( 192 - props: { 193 - activeUri: string | null; 194 - bookmarkPendingByUri: Record<string, boolean>; 195 - error: string | null; 196 - likePendingByUri: Record<string, boolean>; 197 - loading: boolean; 198 - onBookmark: (post: PostView) => void; 199 - onLike: (post: PostView) => void; 200 - onOpenEngagement: (uri: string, tab: "likes" | "reposts" | "quotes") => void; 201 - onOpenThread: (uri: string) => void; 202 - onRepost: (post: PostView) => void; 203 - repostPendingByUri: Record<string, boolean>; 204 - rootPost: PostView | null; 205 - thread: ThreadNode | null; 206 - }, 207 - ) { 89 + function ThreadDrawerBody(props: ThreadDrawerBodyProps) { 208 90 return ( 209 91 <div class="min-h-0 overflow-y-auto overscroll-contain pb-1"> 210 92 <ThreadDrawerLoading loading={props.loading} /> ··· 239 121 ); 240 122 } 241 123 242 - function ThreadDrawerHeader( 243 - props: { 244 - activeUri: string | null; 245 - canGoBack: boolean; 246 - canGoForward: boolean; 247 - onClose: () => void; 248 - onGoBack: () => void; 249 - onGoForward: () => void; 250 - onMaximize: (uri: string) => void; 251 - parentThreadHref: string | null; 252 - }, 253 - ) { 124 + type ThreadDrawerHeaderProps = { 125 + activeUri: string | null; 126 + canGoBack: boolean; 127 + canGoForward: boolean; 128 + onClose: () => void; 129 + onGoBack: () => void; 130 + onGoForward: () => void; 131 + onMaximize: (uri: string) => void; 132 + parentThreadHref: string | null; 133 + }; 134 + 135 + function ThreadDrawerHeader(props: ThreadDrawerHeaderProps) { 254 136 const [local, historyControls] = splitProps(props, ["parentThreadHref", "activeUri", "onClose", "onMaximize"]); 255 137 return ( 256 138 <header class="sticky top-0 z-10 mb-4 flex items-center gap-3 rounded-3xl bg-surface-container-high px-4 py-3 shadow-(--inset-shadow)"> 257 139 <div class="min-w-0 flex-1"> 258 - <p class="m-0 text-base font-semibold text-on-surface">Thread</p> 140 + <p class="m-0 text-base font-semibold text-on-surface">Thread!</p> 259 141 <Show when={local.parentThreadHref}> 260 142 {(href) => ( 261 143 <a ··· 266 148 )} 267 149 </Show> 268 150 </div> 269 - <div class="flex items-center gap-1"> 151 + <div class="flex items-center gap-2 flex-1 justify-end"> 270 152 <HistoryControls {...historyControls} /> 271 - </div> 272 - <div class="flex flex-1 items-center justify-end gap-2"> 273 153 <Show when={local.activeUri}> 274 154 {(uri) => ( 275 155 <button ··· 296 176 return ( 297 177 <Show when={props.loading}> 298 178 <div class="grid gap-3"> 299 - <SkeletonThreadCard /> 300 - <SkeletonThreadCard /> 179 + <ThreadSkeletonCard /> 180 + <ThreadSkeletonCard /> 301 181 </div> 302 182 </Show> 303 183 ); ··· 323 203 return ( 324 204 <Switch> 325 205 <Match when={isBlockedNode(props.node)}> 326 - <StateCard label="Blocked post" meta={isBlockedNode(props.node) ? props.node.uri : ""} /> 206 + <ThreadStateCard label="Blocked post" meta={isBlockedNode(props.node) ? props.node.uri : ""} /> 327 207 </Match> 328 208 <Match when={isNotFoundNode(props.node)}> 329 - <StateCard label="Post not found" meta={isNotFoundNode(props.node) ? props.node.uri : ""} /> 209 + <ThreadStateCard label="Post not found" meta={isNotFoundNode(props.node) ? props.node.uri : ""} /> 330 210 </Match> 331 211 <Match when={node()}> 332 212 {(threadNode) => ( ··· 389 269 ); 390 270 } 391 271 392 - function StateCard(props: { label: string; meta: string }) { 272 + function ThreadStateCard(props: { label: string; meta: string }) { 393 273 return ( 394 274 <div class="tone-muted rounded-3xl p-4 shadow-(--inset-shadow)"> 395 275 <p class="m-0 text-sm font-semibold text-on-surface">{props.label}</p> ··· 398 278 ); 399 279 } 400 280 401 - function SkeletonThreadCard() { 281 + function ThreadSkeletonCard() { 402 282 return ( 403 283 <div class="tone-muted rounded-3xl p-5 shadow-(--inset-shadow)"> 404 284 <div class="flex gap-3"> ··· 415 295 </div> 416 296 ); 417 297 } 298 + 299 + export function ThreadDrawer() { 300 + const session = useAppSession(); 301 + const postNavigation = usePostNavigation(); 302 + const threadOverlay = useThreadOverlayNavigation(); 303 + const history = useNavigationHistory(); 304 + const [state, setState] = createStore<ThreadDrawerState>(createThreadDrawerState()); 305 + const activeUri = createMemo(() => (threadOverlay.drawerEnabled() ? threadOverlay.threadUri() : null)); 306 + const rootPost = createMemo(() => findRootPost(state.thread)); 307 + const parentThreadUri = createMemo(() => findParentUri(state.thread, activeUri())); 308 + const parentThreadHref = createMemo(() => 309 + parentThreadUri() ? threadOverlay.buildThreadHref(parentThreadUri()) : null 310 + ); 311 + const interactions = usePostInteractions({ 312 + onError: session.reportError, 313 + patchPost(uri, updater) { 314 + const current = state.thread; 315 + if (!current) { 316 + return; 317 + } 318 + 319 + setState("thread", patchThreadNode(current, uri, updater)); 320 + }, 321 + }); 322 + 323 + createEffect(() => { 324 + const uri = activeUri(); 325 + if (!uri) { 326 + if (state.uri || state.thread || state.error || state.loading) { 327 + setState(createThreadDrawerState()); 328 + } 329 + return; 330 + } 331 + 332 + if (state.uri === uri && (state.loading || state.thread || state.error)) { 333 + return; 334 + } 335 + 336 + void loadThread(uri); 337 + }); 338 + 339 + createEffect(() => { 340 + if (!activeUri()) { 341 + return; 342 + } 343 + 344 + const handleKeyDown = createEscapeKeyHandler(() => { 345 + void threadOverlay.closeThread(); 346 + }); 347 + 348 + globalThis.addEventListener("keydown", handleKeyDown); 349 + onCleanup(() => globalThis.removeEventListener("keydown", handleKeyDown)); 350 + }); 351 + 352 + async function loadThread(uri: string) { 353 + setState({ error: null, loading: true, thread: null, uri }); 354 + 355 + try { 356 + const payload = await FeedController.getPostThread(uri); 357 + if (activeUri() === uri) { 358 + setState({ error: null, loading: false, thread: payload.thread, uri }); 359 + } 360 + } catch (error) { 361 + if (activeUri() === uri) { 362 + setState({ error: String(error), loading: false, thread: null, uri }); 363 + } 364 + session.reportError(`Failed to open thread: ${String(error)}`); 365 + } 366 + } 367 + 368 + return ( 369 + <Presence> 370 + <Show when={activeUri()}> 371 + <div class="fixed inset-0 z-50"> 372 + <Motion.button 373 + class="ui-scrim absolute inset-0 border-0 backdrop-blur-xl" 374 + type="button" 375 + aria-label="Close thread" 376 + initial={{ opacity: 0 }} 377 + animate={{ opacity: 1 }} 378 + exit={{ opacity: 0 }} 379 + transition={{ duration: 0.2 }} 380 + onClick={() => void threadOverlay.closeThread()} /> 381 + <Motion.aside 382 + class="absolute inset-y-0 right-0 grid w-full max-w-136 grid-rows-[auto_minmax(0,1fr)] overflow-hidden bg-surface-container-highest px-5 pb-6 pt-5 shadow-[-28px_0_50px_rgba(0,0,0,0.24)] backdrop-blur-[22px]" 383 + initial={{ opacity: 0, x: 30 }} 384 + animate={{ opacity: 1, x: 0 }} 385 + exit={{ opacity: 0, x: 36 }} 386 + transition={{ duration: 0.22 }}> 387 + <ThreadDrawerHeader 388 + activeUri={activeUri()} 389 + canGoBack={history.canGoBack()} 390 + canGoForward={history.canGoForward()} 391 + onGoBack={history.goBack} 392 + onGoForward={history.goForward} 393 + onMaximize={(uri) => void postNavigation.openPostScreen(uri)} 394 + parentThreadHref={parentThreadHref()} 395 + onClose={() => void threadOverlay.closeThread()} /> 396 + <ThreadDrawerBody 397 + activeUri={activeUri()} 398 + bookmarkPendingByUri={interactions.bookmarkPendingByUri()} 399 + error={state.error} 400 + likePendingByUri={interactions.likePendingByUri()} 401 + loading={state.loading} 402 + onBookmark={(post) => void interactions.toggleBookmark(post)} 403 + onLike={(post) => void interactions.toggleLike(post)} 404 + onOpenEngagement={(uri, tab) => void postNavigation.openPostEngagement(uri, tab)} 405 + onOpenThread={(uri) => void threadOverlay.openThread(uri)} 406 + onRepost={(post) => void interactions.toggleRepost(post)} 407 + repostPendingByUri={interactions.repostPendingByUri()} 408 + rootPost={rootPost()} 409 + thread={state.thread} /> 410 + </Motion.aside> 411 + </div> 412 + </Show> 413 + </Presence> 414 + ); 415 + }
+12 -12
src/components/search/hooks/useSearchController.ts
··· 93 93 return parsed; 94 94 }); 95 95 96 - const actorSuggestions = useActorSuggestions({ 96 + const typeahead = useActorSuggestions({ 97 97 container: actorSearchContainerRef, 98 98 disabled: () => routeState().tab !== "profiles", 99 99 input: searchInputRef, ··· 265 265 } 266 266 267 267 function clearSearch() { 268 - actorSuggestions.close(); 268 + typeahead.close(); 269 269 replaceRoute({ q: "" }); 270 270 clearResults(); 271 271 searchInputRef()?.focus(); ··· 283 283 if (routeState().tab === "profiles") { 284 284 if (event.key === "ArrowDown") { 285 285 event.preventDefault(); 286 - actorSuggestions.moveActiveIndex(1); 286 + typeahead.moveActiveIndex(1); 287 287 return; 288 288 } 289 289 290 290 if (event.key === "ArrowUp") { 291 291 event.preventDefault(); 292 - actorSuggestions.moveActiveIndex(-1); 292 + typeahead.moveActiveIndex(-1); 293 293 return; 294 294 } 295 295 296 - if (event.key === "Enter" && actorSuggestions.open() && actorSuggestions.activeSuggestion()) { 296 + if (event.key === "Enter" && typeahead.open() && typeahead.activeSuggestion()) { 297 297 event.preventDefault(); 298 - openActor(actorSuggestions.activeSuggestion() as ProfileViewBasic); 299 - actorSuggestions.close(); 298 + openActor(typeahead.activeSuggestion() as ProfileViewBasic); 299 + typeahead.close(); 300 300 return; 301 301 } 302 302 } ··· 316 316 } 317 317 318 318 if (event.key === "Escape" && routeState().tab === "profiles") { 319 - actorSuggestions.close(); 319 + typeahead.close(); 320 320 } 321 321 } 322 322 ··· 387 387 setSyncStatus, 388 388 }, 389 389 actorSuggestions: { 390 - activeIndex: actorSuggestions.activeIndex, 391 - focus: actorSuggestions.focus, 392 - open: actorSuggestions.open, 393 - suggestions: actorSuggestions.suggestions, 390 + activeIndex: typeahead.activeIndex, 391 + focus: typeahead.focus, 392 + open: typeahead.open, 393 + suggestions: typeahead.suggestions, 394 394 }, 395 395 derived: { 396 396 hasLocalPosts,