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

Configure Feed

Select the types of activity you want to include in your feed.

at main 129 lines 4.7 kB view raw
1import { ActorSuggestionList, useActorSuggestions } from "$/components/actors/ActorSearch"; 2import { ActorTypeaheadLoading } from "$/components/actors/ActorTypeaheadLoading"; 3import { useActorTypeaheadCombobox } from "$/components/actors/hooks/useActorTypeaheadCombobox"; 4import type { ActorSuggestion } from "$/lib/types"; 5import { createEffect, Show } from "solid-js"; 6import { Motion } from "solid-motionone"; 7import { Icon } from "./shared/Icon"; 8import { LazuriteLogo } from "./Wordmark"; 9 10function LoginSubmitButton(props: { pending: boolean }) { 11 return ( 12 <button class="pill-action border-0 bg-primary text-on-primary-fixed" type="submit" disabled={props.pending}> 13 <Show 14 when={props.pending} 15 fallback={ 16 <> 17 <Icon kind="ext-link" name="ext-link" aria-hidden class="mr-1" /> 18 <span>Continue</span> 19 </> 20 }> 21 <Icon kind="loader" name="loader" aria-hidden class="mr-1" /> 22 <span>Opening sign-in...</span> 23 </Show> 24 </button> 25 ); 26} 27 28type LoginPanelProps = { 29 value: string; 30 pending: boolean; 31 shakeCount: number; 32 onInput: (value: string) => void; 33 onSubmit: () => void; 34}; 35 36export function LoginPanel(props: LoginPanelProps) { 37 let container: HTMLDivElement | undefined; 38 let input: HTMLInputElement | undefined; 39 const typeahead = useActorSuggestions({ 40 container: () => container, 41 disabled: () => props.pending, 42 input: () => input, 43 value: () => props.value, 44 }); 45 const combobox = useActorTypeaheadCombobox({ 46 ariaControls: "login-suggestions", 47 onSelect: applySuggestion, 48 typeahead, 49 }); 50 51 createEffect(() => { 52 if (props.shakeCount > 0) { 53 input?.focus(); 54 input?.select(); 55 } 56 }); 57 58 function applySuggestion(suggestion: ActorSuggestion) { 59 props.onInput(suggestion.handle); 60 typeahead.close(); 61 input?.focus(); 62 } 63 64 return ( 65 <article 66 class="panel-surface grid gap-5 p-5" 67 ref={(element) => { 68 container = element as HTMLDivElement; 69 }}> 70 <div class="grid place-items-center gap-3 py-2"> 71 <span class="grid place-items-center text-primary"> 72 <LazuriteLogo class="h-14 w-14" /> 73 </span> 74 <div class="grid place-items-center gap-0.5"> 75 <p class="m-0 text-[1.25rem] font-semibold tracking-[-0.02em]">Lazurite</p> 76 <p class="m-0 text-xs text-on-surface-variant">Powered by Bluesky</p> 77 </div> 78 </div> 79 80 <Motion.form 81 class="grid gap-4" 82 initial={{ opacity: 0, y: 18 }} 83 animate={{ opacity: 1, y: 0, x: props.shakeCount > 0 ? [0, -16, 10, -8, 0] : 0 }} 84 transition={{ duration: props.shakeCount > 0 ? 0.42 : 0.24, easing: [0.22, 1, 0.36, 1] }} 85 onSubmit={(event) => { 86 event.preventDefault(); 87 props.onSubmit(); 88 }}> 89 <label class="grid gap-3"> 90 <span class="overline-copy text-xs tracking-[0.08em] text-on-surface-variant"> 91 {/* TODO: use tauri opener */} 92 Sign in with your <a href="https://internethandle.org" class="text-primary underline">Internet Handle</a> 93 {" "} 94 or DID 95 </span> 96 <div class="relative"> 97 <input 98 ref={(element) => { 99 input = element; 100 }} 101 class="min-h-[3.4rem] w-full rounded-xl border-0 bg-white/4 px-[1.15rem] pr-11 text-on-surface shadow-[inset_0_0_0_1px_rgba(125,175,255,0.16)] focus:outline focus:outline-primary/50 focus:shadow-[inset_0_0_0_1px_rgba(125,175,255,0.35),0_0_28px_rgba(125,175,255,0.12)]" 102 type="text" 103 role="combobox" 104 aria-autocomplete="list" 105 aria-controls={combobox.a11y.controls} 106 aria-activedescendant={combobox.a11y.activeDescendant()} 107 aria-expanded={combobox.a11y.expanded()} 108 autocomplete="username" 109 spellcheck={false} 110 value={props.value} 111 placeholder="alice.bsky.social" 112 onFocus={() => typeahead.focus()} 113 onInput={(event) => props.onInput(event.currentTarget.value)} 114 onKeyDown={(event) => combobox.handleKeyDown(event)} /> 115 <ActorTypeaheadLoading visible={typeahead.loading()} class="right-4" /> 116 <ActorSuggestionList 117 activeIndex={typeahead.activeIndex()} 118 id="login-suggestions" 119 open={typeahead.open()} 120 suggestions={typeahead.suggestions()} 121 title="Suggested handles" 122 onSelect={applySuggestion} /> 123 </div> 124 </label> 125 <LoginSubmitButton pending={props.pending} /> 126 </Motion.form> 127 </article> 128 ); 129}