diff options
Diffstat (limited to 'source/library/components/TabBar.svelte')
| -rw-r--r-- | source/library/components/TabBar.svelte | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/source/library/components/TabBar.svelte b/source/library/components/TabBar.svelte new file mode 100644 index 0000000..d87449d --- /dev/null +++ b/source/library/components/TabBar.svelte @@ -0,0 +1,161 @@ +<script lang="ts"> + import { tick } from "svelte"; + import type { Tab } from "../state/session.svelte.ts"; + + type Props = { + activeId: string; + onAdd: () => void; + onClose: (id: string) => void; + onRename: (id: string, title: string) => void; + onSelect: (id: string) => void; + tabs: Tab[]; + }; + + let { activeId, onAdd, onClose, onRename, onSelect, tabs }: Props = $props(); + + let editingId = $state<string | null>(null); + let draft = $state<string>(""); + let inputEl = $state<HTMLInputElement | null>(null); + + async function startEditing(tab: Tab) { + editingId = tab.id; + draft = tab.title; + await tick(); + inputEl?.select(); + } + + function commit() { + if (editingId === null) return; + onRename(editingId, draft); + editingId = null; + draft = ""; + } + + function cancel() { + editingId = null; + draft = ""; + } + + function onKeydown(event: KeyboardEvent) { + if (event.key === "Enter") { + event.preventDefault(); + commit(); + } else if (event.key === "Escape") { + event.preventDefault(); + cancel(); + } + } + + function handleClose(event: MouseEvent, id: string) { + event.stopPropagation(); + onClose(id); + } +</script> + +<style lang="scss"> + .tabbar { + align-items: stretch; + background: var(--graphiql-panel, #252526); + border-bottom: 1px solid var(--graphiql-border, #333); + display: flex; + font-size: 0.8125rem; + min-height: 2rem; + overflow-x: auto; + } + + .tab { + align-items: center; + background: transparent; + border: none; + border-right: 1px solid var(--graphiql-border, #333); + color: var(--graphiql-muted, #858585); + cursor: pointer; + display: flex; + gap: 0.5rem; + padding: 0 0.75rem; + + &.active { + background: var(--graphiql-bg, #1e1e1e); + color: var(--graphiql-fg, #d4d4d4); + } + + &:hover:not(.active) { + color: var(--graphiql-fg, #d4d4d4); + } + } + + .title { + white-space: nowrap; + } + + .edit { + background: var(--graphiql-bg, #1e1e1e); + border: 1px solid var(--graphiql-accent, #0e639c); + border-radius: 2px; + color: var(--graphiql-fg, #d4d4d4); + font-family: inherit; + font-size: inherit; + min-width: 6rem; + outline: none; + padding: 0.125rem 0.25rem; + } + + .close { + background: none; + border: none; + color: inherit; + cursor: pointer; + font-size: 1rem; + line-height: 1; + opacity: 0.6; + padding: 0; + + &:hover { + opacity: 1; + } + } + + .add { + background: none; + border: none; + color: var(--graphiql-muted, #858585); + cursor: pointer; + font-size: 1rem; + padding: 0 0.75rem; + + &:hover { + color: var(--graphiql-fg, #d4d4d4); + } + } +</style> + +<div class="tabbar"> + {#each tabs as tab (tab.id)} + <button + class="tab" + class:active={tab.id === activeId} + ondblclick={() => startEditing(tab)} + onclick={() => onSelect(tab.id)} + > + {#if editingId === tab.id} + <input + bind:this={inputEl} + bind:value={draft} + class="edit" + onblur={commit} + onclick={(e) => e.stopPropagation()} + onkeydown={onKeydown} + type="text" + /> + {:else} + <span class="title">{tab.title}</span> + {/if} + <button + aria-label="Close tab" + class="close" + onclick={(e) => handleClose(e, tab.id)} + >×</button> + </button> + {/each} + <button aria-label="New tab" class="add" onclick={onAdd}>+</button> +</div> |