learn and share notes on atproto (wip) 馃 malfestio.stormlightlabs.org/
readability solid axum atproto srs
at main 77 lines 2.6 kB view raw
1import { onCleanup, onMount, Show, splitProps } from "solid-js"; 2import type { Component, JSX } from "solid-js"; 3import { Motion, Presence } from "solid-motionone"; 4 5type RightPanelProps = { 6 open: boolean; 7 onClose: () => void; 8 title?: string; 9 width?: string; 10 children: JSX.Element; 11 class?: string; 12}; 13 14export const RightPanel: Component<RightPanelProps> = (props) => { 15 const [local, _others] = splitProps(props, ["open", "onClose", "title", "width", "children", "class"]); 16 const width = () => local.width ?? "400px"; 17 18 const handleKeyDown = (e: KeyboardEvent) => { 19 if (e.key === "Escape" && local.open) { 20 local.onClose(); 21 } 22 }; 23 24 onMount(() => { 25 document.addEventListener("keydown", handleKeyDown); 26 }); 27 28 onCleanup(() => { 29 document.removeEventListener("keydown", handleKeyDown); 30 }); 31 32 return ( 33 <Presence> 34 <Show when={local.open}> 35 {/* Backdrop */} 36 <Motion.div 37 initial={{ opacity: 0 }} 38 animate={{ opacity: 1 }} 39 exit={{ opacity: 0 }} 40 transition={{ duration: 0.15 }} 41 class="fixed inset-0 z-40 bg-black/40" 42 onClick={local.onClose} 43 aria-hidden="true" /> 44 {/* Panel */} 45 <Motion.div 46 initial={{ x: "100%" }} 47 animate={{ x: 0 }} 48 exit={{ x: "100%" }} 49 transition={{ duration: 0.25, easing: [0.22, 1, 0.36, 1] }} 50 class={`fixed top-0 right-0 bottom-0 z-50 bg-gray-900 border-l border-gray-800 shadow-2xl flex flex-col ${ 51 local.class || "" 52 }`} 53 style={{ width: width() }} 54 role="dialog" 55 aria-modal="true" 56 aria-label={local.title || "Panel"}> 57 {/* Header */} 58 <div class="h-16 flex items-center justify-between px-6 border-b border-gray-800"> 59 <Show when={local.title}> 60 <h2 class="text-lg font-semibold text-white">{local.title}</h2> 61 </Show> 62 <button 63 onClick={() => local.onClose()} 64 class="p-2 text-gray-400 hover:text-white hover:bg-gray-800 rounded transition-colors ml-auto" 65 aria-label="Close panel"> 66 <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> 67 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> 68 </svg> 69 </button> 70 </div> 71 {/* Content */} 72 <div class="flex-1 overflow-y-auto p-6 text-gray-300">{local.children}</div> 73 </Motion.div> 74 </Show> 75 </Presence> 76 ); 77};