~james/core

core/web/src/lib/components/reaction/ReactionPicker.svelte -rw-r--r-- 2.8 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 16 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
<script lang="ts">
	import { page } from "$app/state";
	import SmilePlus from "$icon/smile-plus";
	import { createAction } from "#lib/action.svelte.ts";
	import type { KnotOrigin } from "#lib/api/syntax.ts";
	import { createBobbinClient } from "#lib/api/client.ts";
	import { getAuth } from "#lib/auth.svelte.ts";
	import Button from "#lib/components/ui/Button.svelte";
	import {
		ORDERED_REACTION_KINDS,
		toggleReaction,
		type ReactionGroup,
		type ReactionKind
	} from "#lib/components/reaction/reactions.ts";
	import Dropdown, { type DropdownVariants } from "#lib/components/ui/Dropdown.svelte";
	import type { PopoverOrigin } from "#lib/components/ui/popover.svelte.ts";
	import Tooltip from "#lib/components/ui/Tooltip.svelte";
	import { resetFailureSoon } from "#lib/optimistic.svelte.ts";

	interface Props {
		subjectUri: string;
		subjectCid?: string;
		knot?: KnotOrigin;
		groups: ReactionGroup[];
		onchange: (next: ReactionGroup[]) => void;
		dropdownAlign?: DropdownVariants["align"];
		dropdownOrigin?: PopoverOrigin;
		triggerClass?: string;
	}

	let {
		subjectUri,
		subjectCid,
		knot,
		groups,
		onchange,
		dropdownAlign,
		dropdownOrigin,
		triggerClass
	}: Props = $props();

	const auth = getAuth();

	let dropdown = $state<ReturnType<typeof Dropdown>>();
	let failed = $state(false);

	resetFailureSoon(
		() => failed,
		() => (failed = false)
	);

	const pick = createAction(async (kind: ReactionKind) => {
		const agent = auth?.agent;
		const viewer = auth?.currentUser;
		if (!agent || !viewer) return;

		const bobbinUrl = page.data.publicConfig?.bobbinUrl;
		await toggleReaction({
			agent,
			knot,
			subject: { uri: subjectUri, cid: subjectCid },
			kind,
			viewer,
			bobbin: bobbinUrl ? createBobbinClient({ serviceUrl: bobbinUrl }) : null,
			groups: () => groups,
			commit: onchange
		});
	});

	const handlePick = async (kind: ReactionKind) => {
		if (pick.loading) return;
		dropdown?.hideMenu();
		failed = false;
		await pick.run(kind);
		failed = pick.error !== undefined;
	};
</script>

<Tooltip content="Something went wrong. Try again." disabled={!failed}>
	<Dropdown
		bind:this={dropdown}
		square
		variant="button"
		buttonVariant={failed ? "danger" : "ghost"}
		size="small"
		label="React"
		title="React"
		menuClass="w-fit"
		align={dropdownAlign}
		origin={dropdownOrigin}
		{triggerClass}
	>
		{#snippet trigger()}
			<SmilePlus class="size-3.5" />
		{/snippet}

		<div class="grid grid-cols-4">
			{#each ORDERED_REACTION_KINDS as kind (kind)}
				<Button
					data-kind={kind}
					variant="ghost"
					class="[--btn-radius:0.25rem]"
					aria-label={`React with ${kind}`}
					aria-pressed={groups.some((g) => g.kind === kind && g.isReacted)}
					disabled={pick.loading}
					onclick={() => handlePick(kind)}
				>
					<span class="typography-paragraph-regular leading-none">{kind}</span>
				</Button>
			{/each}
		</div>
	</Dropdown>
</Tooltip>