replies timeline only, appview-less bluesky client
1<script lang="ts"> 2 import type { Snippet } from 'svelte'; 3 4 interface Props { 5 isOpen: boolean; 6 onClose?: () => void; 7 title: string; 8 width?: string; 9 height?: string; 10 padding?: string; 11 showHeaderDivider?: boolean; 12 headerActions?: Snippet; 13 children: Snippet; 14 footer?: Snippet; 15 } 16 17 let { 18 isOpen = $bindable(false), 19 onClose = () => (isOpen = false), 20 title, 21 width = 'w-full max-w-md', 22 height = 'auto', 23 padding = 'p-4', 24 showHeaderDivider = false, 25 headerActions, 26 children, 27 footer 28 }: Props = $props(); 29 30 const handleKeydown = (event: KeyboardEvent) => { 31 if (event.key === 'Escape') onClose(); 32 }; 33 34 let popupElement: HTMLDivElement | undefined = $state(); 35 36 // this sucks probably idk 37 $effect(() => { 38 if (!isOpen) return; 39 40 const preventDefault = (e: Event) => { 41 if (popupElement && popupElement.contains(e.target as Node)) return; 42 e.preventDefault(); 43 }; 44 45 document.addEventListener('wheel', preventDefault, { passive: false }); 46 document.addEventListener('touchmove', preventDefault, { passive: false }); 47 48 return () => { 49 document.removeEventListener('wheel', preventDefault); 50 document.removeEventListener('touchmove', preventDefault); 51 }; 52 }); 53</script> 54 55{#if isOpen} 56 <div 57 class="fixed inset-0 z-50 flex items-center justify-center bg-(--nucleus-bg)/80 backdrop-blur-sm" 58 onclick={onClose} 59 onkeydown={handleKeydown} 60 role="button" 61 tabindex="-1" 62 > 63 <!-- svelte-ignore a11y_interactive_supports_focus --> 64 <!-- svelte-ignore a11y_click_events_have_key_events --> 65 <div 66 bind:this={popupElement} 67 class="flex {height === 'auto' 68 ? '' 69 : 'h-[' + 70 height + 71 ']'} {width} shrink animate-fade-in-scale flex-col rounded-sm border-2 border-(--nucleus-accent) bg-(--nucleus-bg) shadow-2xl transition-all" 72 style={height !== 'auto' ? `height: ${height}` : ''} 73 onclick={(e) => e.stopPropagation()} 74 role="dialog" 75 > 76 <!-- Header --> 77 <div 78 class="flex items-center gap-4 {showHeaderDivider 79 ? 'border-b-2 border-(--nucleus-accent)/20' 80 : ''} {padding}" 81 > 82 <div> 83 <h2 class="text-2xl font-bold">{title}</h2> 84 <div class="mt-2 flex gap-2"> 85 <div class="h-1 w-8 rounded-full bg-(--nucleus-accent)"></div> 86 <div class="h-1 w-9.5 rounded-full bg-(--nucleus-accent2)"></div> 87 </div> 88 </div> 89 90 {#if headerActions} 91 {@render headerActions()} 92 {/if} 93 94 <div class="grow"></div> 95 96 <!-- svelte-ignore a11y_consider_explicit_label --> 97 <button 98 onclick={onClose} 99 class="rounded-xl p-2 text-(--nucleus-fg)/40 transition-all hover:scale-110 hover:text-(--nucleus-fg)" 100 > 101 <svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> 102 <path 103 stroke-linecap="round" 104 stroke-linejoin="round" 105 stroke-width="2.5" 106 d="M6 18L18 6M6 6l12 12" 107 /> 108 </svg> 109 </button> 110 </div> 111 112 <!-- Content --> 113 <div class="{height === 'auto' ? '' : 'flex-1 overflow-y-auto'} {padding}"> 114 {@render children()} 115 </div> 116 117 <!-- Footer --> 118 {#if footer} 119 {@render footer()} 120 {/if} 121 </div> 122 </div> 123{/if}