feat(transcribe): add HelpPopover primitive and wire (?) chip into panel header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
84
frontend/src/lib/components/HelpPopover.svelte
Normal file
84
frontend/src/lib/components/HelpPopover.svelte
Normal file
@@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
type Placement = 'bottom' | 'top' | 'left' | 'right';
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
placement?: Placement;
|
||||
children?: Snippet;
|
||||
};
|
||||
|
||||
let { label, placement = 'bottom', children }: Props = $props();
|
||||
|
||||
let open = $state(false);
|
||||
const popoverId = `help-popover-${Math.random().toString(36).slice(2)}`;
|
||||
let triggerEl: HTMLButtonElement | null = $state(null);
|
||||
|
||||
function toggle() {
|
||||
open = !open;
|
||||
}
|
||||
|
||||
function close() {
|
||||
open = false;
|
||||
triggerEl?.focus();
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (!open) return;
|
||||
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
function onPointerDown(e: PointerEvent) {
|
||||
if (triggerEl && (e.target === triggerEl || triggerEl.contains(e.target as Node))) return;
|
||||
const popoverEl = document.getElementById(popoverId);
|
||||
if (popoverEl && popoverEl.contains(e.target as Node)) return;
|
||||
open = false;
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', onKeyDown);
|
||||
document.addEventListener('pointerdown', onPointerDown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', onKeyDown);
|
||||
document.removeEventListener('pointerdown', onPointerDown);
|
||||
};
|
||||
});
|
||||
|
||||
const placementClass: Record<Placement, string> = {
|
||||
bottom: 'top-full mt-1.5 left-1/2 -translate-x-1/2',
|
||||
top: 'bottom-full mb-1.5 left-1/2 -translate-x-1/2',
|
||||
left: 'right-full mr-1.5 top-1/2 -translate-y-1/2',
|
||||
right: 'left-full ml-1.5 top-1/2 -translate-y-1/2'
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="relative inline-block">
|
||||
<button
|
||||
bind:this={triggerEl}
|
||||
type="button"
|
||||
aria-label={label}
|
||||
aria-expanded={open}
|
||||
aria-controls={popoverId}
|
||||
onclick={toggle}
|
||||
class="flex h-5 w-5 items-center justify-center rounded-full border border-line bg-muted font-sans text-[10px] font-bold text-ink-3 transition-colors hover:border-brand-navy hover:text-brand-navy"
|
||||
>
|
||||
?
|
||||
</button>
|
||||
|
||||
{#if open}
|
||||
<div
|
||||
id={popoverId}
|
||||
role="tooltip"
|
||||
class="absolute z-50 w-64 rounded-sm border border-line bg-white p-3 font-sans text-sm text-ink shadow-md {placementClass[placement]}"
|
||||
>
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user