This repository has no description
0

Configure Feed

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

core / web / src / lib / components / reaction / ReactionPicker.svelte
2.6 kB 87 lines
1<script lang="ts"> 2 import { now as tidNow } from "@atcute/tid"; 3 import SmilePlus from "$icon/smile-plus"; 4 import { createAction } from "$lib/action.svelte"; 5 import { putReaction, deleteReaction } from "$lib/api/reaction"; 6 import { getAuth } from "$lib/auth.svelte"; 7 import type { ReactionRecord } from "$lib/api/records"; 8 import Button from "$lib/components/ui/Button.svelte"; 9 import { ORDERED_REACTION_KINDS, type ReactionKind } from "./reactions"; 10 11 interface Props { 12 subjectUri: string; 13 // kinds the viewer reacted with, mapped to their record's rkey so a repeat pick deletes it 14 reacted?: Map<ReactionKind, string>; 15 onreacted?: (kind: ReactionKind, rkey: string) => void; 16 onunreacted?: (kind: ReactionKind) => void; 17 } 18 19 let { subjectUri, reacted, onreacted, onunreacted }: Props = $props(); 20 21 const auth = getAuth(); 22 23 const rawId = $props.id(); 24 const popoverId = `reaction-picker-${rawId.replace(/[^a-zA-Z0-9]/g, "")}`; 25 const anchorName = `--${popoverId}`; 26 27 let panel = $state<HTMLElement>(); 28 29 const pick = createAction(async (kind: ReactionKind) => { 30 const agent = auth?.agent; 31 if (!agent) return; 32 33 const existingRkey = reacted?.get(kind); 34 if (existingRkey) { 35 await deleteReaction(agent, existingRkey); 36 onunreacted?.(kind); 37 } else { 38 const rkey = tidNow(); 39 const record: ReactionRecord = { 40 $type: "sh.tangled.feed.reaction", 41 subject: subjectUri as ReactionRecord["subject"], 42 reaction: kind, 43 createdAt: new Date().toISOString() 44 }; 45 await putReaction(agent, rkey, record); 46 onreacted?.(kind, rkey); 47 } 48 panel?.hidePopover(); 49 }); 50 51 const handlePick = (kind: ReactionKind) => { 52 if (!pick.loading) void pick.run(kind); 53 }; 54</script> 55 56<button 57 type="button" 58 popovertarget={popoverId} 59 style={`anchor-name: ${anchorName}`} 60 aria-label="Add reaction" 61 class="flex cursor-pointer items-center justify-center rounded-sm py-2 text-foreground-subtle transition hover:text-foreground-default" 62> 63 <SmilePlus class="size-3" /> 64</button> 65 66<div 67 bind:this={panel} 68 id={popoverId} 69 popover="auto" 70 style={`position-anchor: ${anchorName}`} 71 class="inset-auto mt-1 h-fit w-max rounded-sm border border-border-default bg-background-default text-foreground-default shadow-regular [position-area:bottom_center]" 72> 73 <div class="grid grid-cols-4 p-1"> 74 {#each ORDERED_REACTION_KINDS as kind (kind)} 75 <Button 76 variant="ghost" 77 size="sm" 78 aria-label={`React with ${kind}`} 79 aria-pressed={reacted?.has(kind) ?? false} 80 disabled={pick.loading} 81 onclick={() => handlePick(kind)} 82 > 83 {kind} 84 </Button> 85 {/each} 86 </div> 87</div>