Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions gno.land/pkg/gnoweb/components/views/playground.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,27 @@ <h1 class="title b-content-h1">
<!-- Editor Area -->
<div class="b-playground-editor-area">
<!-- File tabs -->
<div class="b-playground-tabs" data-playground-target="tabs">
<button class="b-playground-tab b-playground-tab--active">main.gno</button>
<button class="b-playground-tab-add" title="Add file">+</button>
<div class="b-playground-tabs-wrap" data-playground-target="tabs-wrap">
<div class="b-playground-tabs" data-playground-target="tabs">
<button class="b-playground-tab b-playground-tab--active">main.gno</button>
</div>
<div class="b-playground-tabs-actions">
<button type="button" class="b-playground-tabs-nav b-playground-tabs-nav--prev"
data-playground-target="prev-button"
data-action="click->playground#scrollTabsPrev"
title="Scroll tabs left" aria-label="Scroll tabs left" hidden>
<svg class="c-icon"><use href="#ico-arrow"></use></svg>
</button>
<button type="button" class="b-playground-tabs-nav b-playground-tabs-nav--next"
data-playground-target="next-button"
data-action="click->playground#scrollTabsNext"
title="Scroll tabs right" aria-label="Scroll tabs right" hidden>
<svg class="c-icon"><use href="#ico-arrow"></use></svg>
</button>
<button type="button" class="b-playground-tab-add"
data-action="click->playground#addFile"
title="Add file" aria-label="Add file">+</button>
</div>
</div>

<!-- Code editor -->
Expand Down
77 changes: 73 additions & 4 deletions gno.land/pkg/gnoweb/frontend/css/06-blocks.css
Original file line number Diff line number Diff line change
Expand Up @@ -2561,13 +2561,74 @@ main.dev-mode .b-toc a {
margin-bottom: var(--g-space-8);
}

.b-playground-tabs {
.b-playground-tabs-wrap {
display: flex;
align-items: center;
gap: 0;
border-bottom: 1px solid var(--s-color-border-default);
padding-left: var(--g-space-1-5);
}

.b-playground-tabs {
flex: 0 1 auto;
min-width: 0;
display: flex;
align-items: center;
gap: 0;
overflow-x: auto;
padding: 0 var(--g-space-1-5);
scroll-behavior: smooth;
scrollbar-width: none;
-ms-overflow-style: none;

&::-webkit-scrollbar {
display: none;
}
}

.b-playground-tabs-actions {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
gap: 0;
margin-left: var(--g-space-1);
padding-bottom: var(--g-space-2);
}

.b-playground-tabs-nav {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--g-space-6);
height: var(--g-space-6);
padding: 0;
border: none;
background: none;
cursor: pointer;
color: var(--s-color-text-tertiary);
transition: color var(--g-transition-fast);

&:hover:not(:disabled) {
color: var(--s-color-text-secondary);
}

&:disabled {
opacity: 0.3;
cursor: default;
}

& .c-icon {
width: var(--g-space-3);
height: var(--g-space-3);
}
}

.b-playground-tabs-nav--prev .c-icon {
transform: rotate(90deg);
}

.b-playground-tabs-nav--next .c-icon {
transform: rotate(-90deg);
}

.b-playground-tab {
Expand Down Expand Up @@ -2610,12 +2671,20 @@ main.dev-mode .b-toc a {
}

.b-playground-tab-add {
padding: var(--g-space-1) var(--g-space-2);
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--g-space-6);
height: var(--g-space-6);
padding: 0;
border: none;
background: none;
cursor: pointer;
font-size: var(--g-font-size-100);
font-size: var(--g-font-size-200);
line-height: 1;
color: var(--s-color-text-muted);
transition: color var(--g-transition-fast);

&:hover {
color: var(--s-color-text-secondary);
Expand Down
70 changes: 64 additions & 6 deletions gno.land/pkg/gnoweb/frontend/js/controller-playground.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,82 @@ export class PlaygroundController extends BaseController {
private declare codeEl: HTMLTextAreaElement;
private declare outputEl: HTMLElement;
private declare tabsEl: HTMLElement;
private declare tabsWrapEl: HTMLElement;
private declare prevBtnEl: HTMLButtonElement;
private declare nextBtnEl: HTMLButtonElement;

protected connect(): void {
this.files = [];
this.activeFile = 0;
this.codeEl = this.getTarget("code") as HTMLTextAreaElement;
this.outputEl = this.getTarget("output") as HTMLElement;
this.tabsEl = this.getTarget("tabs") as HTMLElement;
this.tabsWrapEl = this.getTarget("tabs-wrap") as HTMLElement;
this.prevBtnEl = this.getTarget("prev-button") as HTMLButtonElement;
this.nextBtnEl = this.getTarget("next-button") as HTMLButtonElement;
if (!this.codeEl || !this.outputEl || !this.tabsEl) return;

this.codeEl.addEventListener("focus", () =>
this._scrollActiveTabIntoView(),
);

this._parseInitialCode();
this._setupKeyboardShortcuts();
this._setupTabsScroll();
this.renderTabs();
}

private _setupTabsScroll(): void {
if (!this.tabsWrapEl || !this.prevBtnEl || !this.nextBtnEl) return;

this.tabsEl.addEventListener("scroll", () => this._updateNavButtons(), {
passive: true,
});

const observer = new ResizeObserver(() => this._updateNavButtons());
observer.observe(this.tabsWrapEl);
observer.observe(this.tabsEl);
}

private _updateNavButtons(): void {
if (!this.tabsWrapEl || !this.prevBtnEl || !this.nextBtnEl) return;

const overflows = this.tabsEl.scrollWidth > this.tabsWrapEl.clientWidth + 1;
this.prevBtnEl.hidden = !overflows;
this.nextBtnEl.hidden = !overflows;
if (!overflows) return;

const { scrollLeft, scrollWidth, clientWidth } = this.tabsEl;
this.prevBtnEl.disabled = scrollLeft <= 0;
this.nextBtnEl.disabled = scrollLeft + clientWidth >= scrollWidth - 1;
}

private _scrollByPage(direction: 1 | -1): void {
// Calculate the scroll distance, keeping 70% of the tab bar visible,
// keeping ~30% overlap, so user keeps visual context across clicks.
// The 80px floor guarantees a meaningful jump when the tab bar is
// very narrow, where 70% could be tiny.
const amount = Math.max(this.tabsEl.clientWidth * 0.7, 80);
this.tabsEl.scrollBy({ left: direction * amount, behavior: "smooth" });
}

private _scrollActiveTabIntoView(): void {
const active = this.tabsEl.querySelector(
".b-playground-tab--active",
) as HTMLElement | null;
if (!active) return;

active.scrollIntoView({ inline: "nearest", block: "nearest" });
}

public scrollTabsPrev(): void {
this._scrollByPage(-1);
}

public scrollTabsNext(): void {
this._scrollByPage(1);
}

private _parseInitialCode(): void {
const initialCode = this.codeEl.value;
if (initialCode.includes("// --- ") && initialCode.includes(" ---")) {
Expand Down Expand Up @@ -95,12 +157,8 @@ export class PlaygroundController extends BaseController {
this.tabsEl.appendChild(btn);
});

const addBtn = document.createElement("button");
addBtn.className = "b-playground-tab-add";
addBtn.textContent = "+";
addBtn.title = "Add file";
addBtn.addEventListener("click", () => this.addFile());
this.tabsEl.appendChild(addBtn);
this._updateNavButtons();
this._scrollActiveTabIntoView();
}

public switchTab(event: Event & { params?: Record<string, unknown> }): void {
Expand Down
22 changes: 11 additions & 11 deletions gno.land/pkg/gnoweb/public/js/controller-playground.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion gno.land/pkg/gnoweb/public/main.css

Large diffs are not rendered by default.

Loading