aboutsummaryrefslogtreecommitdiff
path: root/source/library/components/TabBar.svelte
diff options
context:
space:
mode:
authornetop://ウィビ <paul@webb.page>2026-04-24 11:33:25 -0700
committernetop://ウィビ <paul@webb.page>2026-04-24 11:33:25 -0700
commit8a59f92d031963e23ecc84b75feecf43eb4dd146 (patch)
tree75de5768885583897061a3b1795e4c987ce90039 /source/library/components/TabBar.svelte
downloadgraphiql-8a59f92d031963e23ecc84b75feecf43eb4dd146.tar.gz
graphiql-8a59f92d031963e23ecc84b75feecf43eb4dd146.zip
Initial commit: @eol/graphiql v0.3
Svelte 5 GraphiQL alternative for JSR. Covers: - HTTP fetcher with injectable fetch; SSE/WS stubs - Session store with tabs, auto-titling, persistence, rename - Operation detection via graphql parse(); Toolbar picker - CodeMirror 6 editor via cm6-graphql with theme prop - Light theme preset (hand-rolled EditorView.theme) - Doc explorer with breadcrumb nav and type guards - History panel with 100-entry cap, favorite pinning - Deno tests for operations, storage, and history eviction
Diffstat (limited to 'source/library/components/TabBar.svelte')
-rw-r--r--source/library/components/TabBar.svelte161
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>