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 144 lines 4.9 kB view raw
1import { MODERATION_REASON_OPTIONS } from "$/lib/api/moderation"; 2import type { ModerationReasonType } from "$/lib/types"; 3import { createEffect, createSignal, For, type ParentProps, Show } from "solid-js"; 4import { Motion, Presence } from "solid-motionone"; 5 6type ReportDialogProps = { 7 open: boolean; 8 subjectLabel: string; 9 onClose: () => void; 10 onSubmit: (input: { reasonType: ModerationReasonType; reason: string }) => Promise<void> | void; 11}; 12 13export function ReportDialog(props: ReportDialogProps) { 14 const [reasonType, setReasonType] = createSignal<ModerationReasonType>(MODERATION_REASON_OPTIONS[0].value); 15 const [reason, setReason] = createSignal(""); 16 const [submitting, setSubmitting] = createSignal(false); 17 18 createEffect(() => { 19 if (!props.open) { 20 return; 21 } 22 23 setReasonType(MODERATION_REASON_OPTIONS[0].value); 24 setReason(""); 25 setSubmitting(false); 26 }); 27 28 async function submit() { 29 if (submitting()) { 30 return; 31 } 32 33 setSubmitting(true); 34 try { 35 await props.onSubmit({ reason: reason().trim(), reasonType: reasonType() }); 36 props.onClose(); 37 } finally { 38 setSubmitting(false); 39 } 40 } 41 42 return ( 43 <Presence> 44 <Show when={props.open}> 45 <DialogBackdrop onClose={props.onClose}> 46 <DialogSurface> 47 <DialogHeader subjectLabel={props.subjectLabel} /> 48 <ReasonTypeField value={reasonType()} onChange={setReasonType} /> 49 <ReasonDetailsField value={reason()} onChange={setReason} /> 50 <DialogActions submitting={submitting()} onCancel={props.onClose} onSubmit={() => void submit()} /> 51 </DialogSurface> 52 </DialogBackdrop> 53 </Show> 54 </Presence> 55 ); 56} 57 58function DialogBackdrop(props: ParentProps<{ onClose: () => void }>) { 59 return ( 60 <Motion.div 61 class="fixed inset-0 z-60 flex items-center justify-center bg-surface-container-highest/70 p-4 backdrop-blur-xl" 62 initial={{ opacity: 0 }} 63 animate={{ opacity: 1 }} 64 exit={{ opacity: 0 }} 65 transition={{ duration: 0.2 }}> 66 <button 67 type="button" 68 aria-label="Close report dialog" 69 class="absolute inset-0 border-0 bg-transparent" 70 onClick={() => props.onClose()} /> 71 {props.children} 72 </Motion.div> 73 ); 74} 75 76function DialogSurface(props: ParentProps) { 77 return ( 78 <Motion.div 79 class="relative z-1 grid w-full max-w-lg gap-4 rounded-2xl bg-surface-container p-5 shadow-2xl" 80 initial={{ scale: 0.96, opacity: 0 }} 81 animate={{ scale: 1, opacity: 1 }} 82 exit={{ scale: 0.96, opacity: 0 }} 83 transition={{ duration: 0.2 }}> 84 {props.children} 85 </Motion.div> 86 ); 87} 88 89function DialogHeader(props: { subjectLabel: string }) { 90 return ( 91 <div class="grid gap-1"> 92 <h3 class="m-0 text-lg font-semibold text-on-surface">Report content</h3> 93 <p class="m-0 text-sm text-on-surface-variant">{props.subjectLabel}</p> 94 </div> 95 ); 96} 97 98function ReasonTypeField(props: { value: ModerationReasonType; onChange: (value: ModerationReasonType) => void }) { 99 return ( 100 <label class="grid gap-1"> 101 <span class="text-sm font-medium text-on-surface">Reason type</span> 102 <select 103 value={props.value} 104 class="rounded-xl border border-white/10 bg-black/40 px-3 py-2 text-sm text-on-surface outline-none transition focus:border-primary/50" 105 onInput={(event) => props.onChange(event.currentTarget.value as ModerationReasonType)}> 106 <For each={MODERATION_REASON_OPTIONS}>{(option) => <option value={option.value}>{option.label}</option>}</For> 107 </select> 108 </label> 109 ); 110} 111 112function ReasonDetailsField(props: { value: string; onChange: (value: string) => void }) { 113 return ( 114 <label class="grid gap-1"> 115 <span class="text-sm font-medium text-on-surface">Details (optional)</span> 116 <textarea 117 rows={4} 118 value={props.value} 119 placeholder="Add context for moderators" 120 class="rounded-xl border border-white/10 bg-black/40 px-3 py-2 text-sm text-on-surface outline-none transition focus:border-primary/50" 121 onInput={(event) => props.onChange(event.currentTarget.value)} /> 122 </label> 123 ); 124} 125 126function DialogActions(props: { submitting: boolean; onCancel: () => void; onSubmit: () => void }) { 127 return ( 128 <div class="flex justify-end gap-2"> 129 <button 130 type="button" 131 class="rounded-lg border border-white/20 px-4 py-2 text-sm font-medium text-on-surface transition hover:bg-white/5" 132 onClick={() => props.onCancel()}> 133 Cancel 134 </button> 135 <button 136 type="button" 137 disabled={props.submitting} 138 class="rounded-lg bg-primary px-4 py-2 text-sm font-medium text-on-primary-fixed transition hover:bg-primary-dim disabled:cursor-wait disabled:opacity-70" 139 onClick={() => props.onSubmit()}> 140 {props.submitting ? "Submitting..." : "Submit report"} 141 </button> 142 </div> 143 ); 144}