From 0acb026762087d3e4ff6f05d065cbefc1bd35ad5 Mon Sep 17 00:00:00 2001 From: momcilo2803428 Date: Thu, 12 Feb 2026 21:13:48 +0100 Subject: [PATCH] feat: split preview UX improvements and empty state redesign Split preview: - Add drag-to-resize handles between panes (snaps to 10% increments) - Preserve ratios when splitting/adding panes (no more equal-reset) - Preserve selection during resize (wasResizing guard pattern) - Preview stretches to fill available space (flex layout, no max-height) - Add optional pane name field (model + pane editor) - Redesign pane display: centered hierarchy (name > profile > path > command) - Show full paths with word-break instead of truncation - Show percentage dimension labels on panes (width : height) Empty state: - Redesign landing screen with icon, title, description, and CTA button - Hide tab bar when no workspaces exist - Full-height layout fills entire settings page Other: - Fix dropdown positioning (icon picker, color picker) - Add solid background fallback for dropdown panels - Sync package-lock.json version to 0.2.0 Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 4 +- src/components/paneEditor.component.pug | 8 ++ src/components/splitPreview.component.pug | 36 +++-- src/components/splitPreview.component.scss | 132 ++++++++++++++---- src/components/splitPreview.component.ts | 131 +++++++++++++++-- src/components/workspaceEditor.component.pug | 6 +- src/components/workspaceEditor.component.scss | 30 +++- src/components/workspaceEditor.component.ts | 24 +++- src/components/workspaceList.component.pug | 15 +- src/components/workspaceList.component.scss | 59 ++++++-- src/components/workspaceList.component.ts | 24 +++- src/models/workspace.model.ts | 1 + src/styles/_mixins.scss | 1 + 13 files changed, 386 insertions(+), 85 deletions(-) diff --git a/package-lock.json b/package-lock.json index eaab0ef..cb76d83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "tabby-tabbyspaces", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tabby-tabbyspaces", - "version": "0.1.0", + "version": "0.2.0", "license": "MIT", "devDependencies": { "@angular/common": "^15.0.0", diff --git a/src/components/paneEditor.component.pug b/src/components/paneEditor.component.pug index 13896fe..d289577 100644 --- a/src/components/paneEditor.component.pug +++ b/src/components/paneEditor.component.pug @@ -6,6 +6,14 @@ | Pane Configuration .pane-form + .form-group + label Name + input.form-control( + type='text', + [(ngModel)]='pane.name', + placeholder='Optional pane name' + ) + .form-group label Profile select.form-control([(ngModel)]='pane.profileId') diff --git a/src/components/splitPreview.component.pug b/src/components/splitPreview.component.pug index e8cd0f6..3cd9818 100644 --- a/src/components/splitPreview.component.pug +++ b/src/components/splitPreview.component.pug @@ -1,5 +1,14 @@ .split-preview([class.horizontal]='split.orientation === "horizontal"', [class.vertical]='split.orientation === "vertical"', [class.nested]='depth > 0') ng-container(*ngFor='let child of split.children; let i = index') + //- Resize handle between children + .resize-handle( + *ngIf='i > 0', + [class.horizontal]='split.orientation === "horizontal"', + [class.vertical]='split.orientation === "vertical"', + [class.dragging]='resizing && resizeIndex === i - 1', + (mousedown)='onResizeStart($event, i - 1)' + ) + //- Pane .preview-pane( *ngIf='isPane(child)', @@ -9,15 +18,18 @@ (contextmenu)='onContextMenu($event, asPane(child))' ) .pane-content - .pane-label - | {{ getPaneLabel(asPane(child)) }} - .pane-details - .pane-detail(*ngIf='asPane(child).cwd', [title]='asPane(child).cwd') - i.fas.fa-folder - span {{ truncate(asPane(child).cwd, 20) }} - .pane-detail(*ngIf='asPane(child).startupCommand', [title]='asPane(child).startupCommand') - i.fas.fa-terminal - span {{ truncate(asPane(child).startupCommand, 20) }} + .pane-title + span.pane-name(*ngIf='asPane(child).name') {{ asPane(child).name }} + span.pane-sep(*ngIf='asPane(child).name') - + span.pane-profile {{ getPaneLabel(asPane(child)) }} + .pane-path(*ngIf='asPane(child).cwd') {{ asPane(child).cwd }} + .pane-command(*ngIf='asPane(child).startupCommand') + i.fas.fa-terminal + | {{ asPane(child).startupCommand }} + .pane-dimensions + span.dim-value {{ getPaneGlobalWidth(i) }} + span.dim-sep : + span.dim-value {{ getPaneGlobalHeight(i) }} //- Nested split split-preview( @@ -26,6 +38,8 @@ [depth]='depth + 1', [selectedPaneId]='selectedPaneId', [profiles]='profiles', + [globalWidthRatio]='getChildGlobalWidth(i)', + [globalHeightRatio]='getChildGlobalHeight(i)', [style.flex-basis]='getFlexStyle(i)', (paneEdit)='onNestedPaneEdit($event)', (splitHorizontal)='onNestedSplitH($event)', @@ -34,7 +48,9 @@ (addRight)='onNestedAddRight($event)', (addTop)='onNestedAddTop($event)', (addBottom)='onNestedAddBottom($event)', - (removePane)='onNestedRemove($event)' + (removePane)='onNestedRemove($event)', + (ratioChange)='onNestedRatioChange()', + (resizeEnd)='onNestedResizeEnd()' ) //- Context menu diff --git a/src/components/splitPreview.component.scss b/src/components/splitPreview.component.scss index d521bed..117e10b 100644 --- a/src/components/splitPreview.component.scss +++ b/src/components/splitPreview.component.scss @@ -3,8 +3,7 @@ .split-preview { display: flex; width: 100%; - height: $preview-height; - gap: $spacing-sm; + height: 100%; border-radius: $radius-md; overflow: hidden; background: var(--theme-bg); @@ -20,13 +19,58 @@ &.nested { height: auto; - border: 1px dashed var(--theme-fg-more); + border: 1px dashed rgba(255, 255, 255, 0.08); background: $nested-split-bg; padding: $spacing-sm; border-radius: $radius-sm; } } +.resize-handle { + flex: 0 0 6px; + position: relative; + z-index: 1; + user-select: none; + + &.horizontal { + cursor: col-resize; + } + + &.vertical { + cursor: row-resize; + } + + &::after { + content: ''; + position: absolute; + border-radius: 1px; + background: var(--theme-border, $fallback-border); + transition: background $transition-fast; + } + + &.horizontal::after { + top: 20%; + bottom: 20%; + left: 2px; + width: 2px; + } + + &.vertical::after { + left: 20%; + right: 20%; + top: 2px; + height: 2px; + } + + &:hover::after { + background: var(--theme-fg-more, rgba(255, 255, 255, 0.4)); + } + + &.dragging::after { + background: var(--theme-primary); + } +} + .preview-pane { @include flex-center; background: var(--theme-bg-more); @@ -53,47 +97,73 @@ } .pane-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1px; text-align: center; - padding: $spacing-md; + padding: $spacing-sm $spacing-md; color: var(--theme-fg); max-width: 100%; overflow: hidden; } -.pane-label { - font-size: 0.85rem; - font-weight: 600; - margin-bottom: $spacing-sm; - @include text-ellipsis; +.pane-title { + font-size: 0.75rem; + font-weight: 500; + overflow-wrap: break-word; + + .pane-name { + font-weight: 600; + } + + .pane-sep { + opacity: 0.4; + } + + .pane-profile { + opacity: 0.8; + } } -.pane-title, -.pane-profile { - font-size: 0.8rem; - font-weight: 500; - margin-bottom: $spacing-xs; - @include text-ellipsis; +.pane-path { + font-size: 0.6rem; + opacity: 0.6; + word-break: break-all; } -.pane-details { - font-size: 0.7rem; - opacity: 0.7; - margin-top: $spacing-sm; +.pane-command { + font-size: 0.55rem; + opacity: 0.5; + overflow-wrap: break-word; - .pane-detail { - @include flex-center; - gap: $spacing-sm; + i { + font-size: 0.5rem; + } +} - i { - width: 12px; - text-align: center; - font-size: 0.65rem; - } +.pane-dimensions { + position: absolute; + bottom: $spacing-xs; + left: 50%; + transform: translateX(-50%); + display: flex; + align-items: baseline; + font-size: 0.65rem; + pointer-events: none; + color: var(--theme-primary); + white-space: nowrap; + opacity: 0.7; - span { - @include text-ellipsis; - max-width: 100px; - } + .dim-value { + min-width: 2.5em; + text-align: center; + } + + .dim-sep { + opacity: 0.5; + margin: 0 2px; } } diff --git a/src/components/splitPreview.component.ts b/src/components/splitPreview.component.ts index 8c11bee..be0366b 100644 --- a/src/components/splitPreview.component.ts +++ b/src/components/splitPreview.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges, ChangeDetectorRef } from '@angular/core' +import { Component, Input, Output, EventEmitter, OnChanges, OnDestroy, SimpleChanges, ChangeDetectorRef, ElementRef } from '@angular/core' import { WorkspaceSplit, WorkspacePane, @@ -11,11 +11,13 @@ import { template: require('./splitPreview.component.pug'), styles: [require('./splitPreview.component.scss')], }) -export class SplitPreviewComponent implements OnChanges { +export class SplitPreviewComponent implements OnChanges, OnDestroy { @Input() split!: WorkspaceSplit @Input() depth = 0 @Input() selectedPaneId: string | null = null @Input() profiles: TabbyProfile[] = [] + @Input() globalWidthRatio = 1 + @Input() globalHeightRatio = 1 @Output() paneEdit = new EventEmitter() @Output() splitHorizontal = new EventEmitter() @Output() splitVertical = new EventEmitter() @@ -24,11 +26,21 @@ export class SplitPreviewComponent implements OnChanges { @Output() addRight = new EventEmitter() @Output() addTop = new EventEmitter() @Output() addBottom = new EventEmitter() + @Output() ratioChange = new EventEmitter() + @Output() resizeEnd = new EventEmitter() contextMenuPane: WorkspacePane | null = null contextMenuPosition = { x: 0, y: 0 } - constructor(private cdr: ChangeDetectorRef) {} + // Drag state + resizing = false + wasResizing = false + resizeIndex = -1 + private resizeContainerRect: DOMRect | null = null + private boundOnResizeMove: ((e: MouseEvent) => void) | null = null + private boundOnResizeEnd: (() => void) | null = null + + constructor(private cdr: ChangeDetectorRef, private elementRef: ElementRef) {} ngOnChanges(changes: SimpleChanges): void { // Clear context menu when split input changes to avoid stale state @@ -37,6 +49,10 @@ export class SplitPreviewComponent implements OnChanges { } } + ngOnDestroy(): void { + this.cleanupDragListeners() + } + isPane(child: WorkspacePane | WorkspaceSplit): boolean { return !isWorkspaceSplit(child) } @@ -58,15 +74,10 @@ export class SplitPreviewComponent implements OnChanges { } onPaneClick(pane: WorkspacePane): void { + if (this.wasResizing) return this.paneEdit.emit(pane) } - truncate(text: string, maxLength: number): string { - return text.length > maxLength - ? text.substring(0, maxLength) + '...' - : text - } - onContextMenu(event: MouseEvent, pane: WorkspacePane): void { event.preventDefault() this.contextMenuPane = pane @@ -134,6 +145,26 @@ export class SplitPreviewComponent implements OnChanges { } } + getChildGlobalWidth(index: number): number { + return this.split.orientation === 'horizontal' + ? this.globalWidthRatio * this.split.ratios[index] + : this.globalWidthRatio + } + + getChildGlobalHeight(index: number): number { + return this.split.orientation === 'vertical' + ? this.globalHeightRatio * this.split.ratios[index] + : this.globalHeightRatio + } + + getPaneGlobalWidth(index: number): string { + return Math.round(this.getChildGlobalWidth(index) * 100) + '%' + } + + getPaneGlobalHeight(index: number): string { + return Math.round(this.getChildGlobalHeight(index) * 100) + '%' + } + getPaneLabel(pane: WorkspacePane): string { if (!pane.profileId) return 'Select profile' @@ -141,6 +172,78 @@ export class SplitPreviewComponent implements OnChanges { return profile?.name || 'Select profile' } + // Resize handle drag logic + onResizeStart(event: MouseEvent, handleIndex: number): void { + event.preventDefault() + event.stopPropagation() + + this.resizeIndex = handleIndex + this.resizing = true + + const container = this.elementRef.nativeElement.querySelector('.split-preview') + this.resizeContainerRect = container.getBoundingClientRect() + + this.boundOnResizeMove = this.onResizeMove.bind(this) + this.boundOnResizeEnd = this.onResizeEnd.bind(this) + document.addEventListener('mousemove', this.boundOnResizeMove) + document.addEventListener('mouseup', this.boundOnResizeEnd) + } + + private onResizeMove(event: MouseEvent): void { + if (!this.resizing || !this.resizeContainerRect) return + + const k = this.resizeIndex + const isHorizontal = this.split.orientation === 'horizontal' + const containerStart = isHorizontal ? this.resizeContainerRect.left : this.resizeContainerRect.top + const containerSize = isHorizontal ? this.resizeContainerRect.width : this.resizeContainerRect.height + const mousePos = isHorizontal ? event.clientX : event.clientY + + // Calculate offset: sum of ratios before the left child + let offset = 0 + for (let i = 0; i < k; i++) { + offset += this.split.ratios[i] + } + + const combined = this.split.ratios[k] + this.split.ratios[k + 1] + const mouseRatio = (mousePos - containerStart) / containerSize + + // Snap to nearest 0.1 + let newLeft = Math.round((mouseRatio - offset) / 0.1) * 0.1 + + // Clamp + newLeft = Math.max(0.1, Math.min(combined - 0.1, newLeft)) + + // Only update if changed + if (Math.abs(this.split.ratios[k] - newLeft) > 0.001) { + this.split.ratios[k] = newLeft + this.split.ratios[k + 1] = combined - newLeft + this.ratioChange.emit() + this.cdr.detectChanges() + } + } + + private onResizeEnd(): void { + this.resizing = false + this.wasResizing = true + setTimeout(() => { this.wasResizing = false }, 0) + this.resizeIndex = -1 + this.resizeContainerRect = null + this.cleanupDragListeners() + this.resizeEnd.emit() + this.cdr.detectChanges() + } + + private cleanupDragListeners(): void { + if (this.boundOnResizeMove) { + document.removeEventListener('mousemove', this.boundOnResizeMove) + this.boundOnResizeMove = null + } + if (this.boundOnResizeEnd) { + document.removeEventListener('mouseup', this.boundOnResizeEnd) + this.boundOnResizeEnd = null + } + } + // Pass-through events from nested splits onNestedPaneEdit(pane: WorkspacePane): void { this.paneEdit.emit(pane) @@ -173,4 +276,14 @@ export class SplitPreviewComponent implements OnChanges { onNestedRemove(pane: WorkspacePane): void { this.removePane.emit(pane) } + + onNestedRatioChange(): void { + this.ratioChange.emit() + } + + onNestedResizeEnd(): void { + this.wasResizing = true + setTimeout(() => { this.wasResizing = false }, 0) + this.resizeEnd.emit() + } } diff --git a/src/components/workspaceEditor.component.pug b/src/components/workspaceEditor.component.pug index 925b323..e639f03 100644 --- a/src/components/workspaceEditor.component.pug +++ b/src/components/workspaceEditor.component.pug @@ -69,7 +69,7 @@ ) //- Section 2: Split Layout -.editor-section +.editor-section.layout-section .section-title i.fas.fa-columns | Split Layout @@ -142,7 +142,9 @@ (addRight)='addPaneFromEvent($event, "right")', (addTop)='addPaneFromEvent($event, "top")', (addBottom)='addPaneFromEvent($event, "bottom")', - (removePane)='removePane($event)' + (removePane)='removePane($event)', + (ratioChange)='onRatioChange()', + (resizeEnd)='onResizeEnd()' ) //- Inline pane editor diff --git a/src/components/workspaceEditor.component.scss b/src/components/workspaceEditor.component.scss index 41a73eb..d77f1ae 100644 --- a/src/components/workspaceEditor.component.scss +++ b/src/components/workspaceEditor.component.scss @@ -1,8 +1,22 @@ @use '../styles/index' as *; +:host { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; +} + // Editor section .editor-section { margin-bottom: $spacing-xl; + + &.layout-section { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + } } .section-title { @@ -80,7 +94,7 @@ .icon-dropdown { position: absolute; top: 100%; - left: 0; + right: 0; margin-top: $spacing-sm; padding: $spacing-md; @include dropdown-panel; @@ -189,7 +203,6 @@ .background-dropdown { position: absolute; top: 100%; - left: 0; right: 0; margin-top: $spacing-sm; padding: $spacing-md; @@ -235,6 +248,10 @@ // Split preview container .split-preview-container { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; background: var(--theme-bg-more); border: 1px solid var(--theme-border, $fallback-border); border-radius: $radius-md; @@ -266,10 +283,11 @@ // Layout preview area .layout-preview { + flex: 1; + display: flex; background: var(--theme-bg); border-radius: $radius-sm; min-height: 150px; - max-height: 200px; padding: $spacing-sm; } @@ -278,9 +296,9 @@ display: flex; justify-content: space-between; align-items: center; - padding-top: $spacing-xl; + padding-top: $spacing-lg; border-top: 1px solid var(--theme-border, $fallback-border); - margin-top: $spacing-xl; + margin-top: auto; } .checkbox-group { @@ -304,6 +322,7 @@ .action-buttons-right { display: flex; gap: $spacing-md; + align-items: stretch; .btn-ghost { @include btn-ghost; @@ -312,6 +331,7 @@ .btn-success { @include btn-base; @include btn-success; + border: 1px solid $color-success; } .unsaved-indicator { diff --git a/src/components/workspaceEditor.component.ts b/src/components/workspaceEditor.component.ts index ef1e3a3..e5112aa 100644 --- a/src/components/workspaceEditor.component.ts +++ b/src/components/workspaceEditor.component.ts @@ -34,6 +34,7 @@ export class WorkspaceEditorComponent implements OnInit, OnChanges, AfterViewIni @ViewChild('nameInput') nameInput!: ElementRef selectedPaneId: string | null = null + wasResizing = false editingPane: WorkspacePane | null = null showPaneEditor = false profiles: TabbyProfile[] = [] @@ -202,11 +203,23 @@ export class WorkspaceEditorComponent implements OnInit, OnChanges, AfterViewIni } onPreviewBackgroundClick(): void { + if (this.wasResizing) return this.deselectPane() this.closePaneEditor() } + onResizeEnd(): void { + this.wasResizing = true + setTimeout(() => { this.wasResizing = false }, 0) + } + editPane(pane: WorkspacePane): void { + if (this.wasResizing) return + if (this.selectedPaneId === pane.id) { + this.selectedPaneId = null + this.closePaneEditor() + return + } this.selectedPaneId = pane.id this.editingPane = pane this.showPaneEditor = true @@ -299,7 +312,6 @@ export class WorkspaceEditorComponent implements OnInit, OnChanges, AfterViewIni children: [ctx.child, newPane], } ctx.node.children[ctx.index] = newSplit - this.recalculateRatios(ctx.node) return true } return false @@ -382,6 +394,10 @@ export class WorkspaceEditorComponent implements OnInit, OnChanges, AfterViewIni this.cdr.detectChanges() } + onRatioChange(): void { + this.cdr.detectChanges() + } + // Add pane operations addPane(direction: 'left' | 'right' | 'top' | 'bottom'): void { if (!this.selectedPaneId) return @@ -411,10 +427,12 @@ export class WorkspaceEditorComponent implements OnInit, OnChanges, AfterViewIni newPane.profileId = (ctx.child as WorkspacePane).profileId if (ctx.node.orientation === targetOrientation) { - // Same orientation: add as sibling + // Same orientation: split the target pane's ratio in half const insertIndex = isBefore ? ctx.index : ctx.index + 1 + const originalRatio = ctx.node.ratios[ctx.index] + const halfRatio = originalRatio / 2 ctx.node.children.splice(insertIndex, 0, newPane) - this.recalculateRatios(ctx.node) + ctx.node.ratios.splice(ctx.index, 1, halfRatio, halfRatio) } else { // Different orientation: wrap entire node in new split const nodeCopy: WorkspaceSplit = { diff --git a/src/components/workspaceList.component.pug b/src/components/workspaceList.component.pug index 0cde004..0021451 100644 --- a/src/components/workspaceList.component.pug +++ b/src/components/workspaceList.component.pug @@ -1,6 +1,6 @@ .workspace-list-container //- Tab bar navigation - .tab-bar + .tab-bar(*ngIf='workspaces.length > 0 || editingWorkspace') .tab( *ngFor='let tab of displayTabs; trackBy: trackByTab', [class.active]='isTabSelected(tab)', @@ -30,8 +30,11 @@ ) //- Empty state (when no workspaces) - .tab-content.empty-state(*ngIf='!editingWorkspace && workspaces.length === 0') - p No workspaces configured yet. - p Click - strong + - | to create your first workspace. + .empty-state(*ngIf='!editingWorkspace && workspaces.length === 0') + .empty-state-icon + i.fas.fa-columns + h2.empty-state-title TabbySpaces + p.empty-state-description Create custom split layouts with profiles, working directories, and startup commands. + button.empty-state-cta((click)='createWorkspace()') + i.fas.fa-plus + | Create Workspace diff --git a/src/components/workspaceList.component.scss b/src/components/workspaceList.component.scss index e8e91f4..c62df84 100644 --- a/src/components/workspaceList.component.scss +++ b/src/components/workspaceList.component.scss @@ -1,7 +1,18 @@ @use '../styles/index' as *; +:host { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; +} + .workspace-list-container { padding: $spacing-2xl; + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; } // Tab bar @@ -109,19 +120,43 @@ border: 1px solid var(--theme-border, $fallback-border); border-top: none; padding: $spacing-xl; + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; +} + +// Empty state +.empty-state { + @include flex-center; + flex-direction: column; + padding: 80px $spacing-2xl; + text-align: center; + flex: 1; +} - &.empty-state { - text-align: center; - padding: $spacing-2xl; - color: var(--theme-fg-more); +.empty-state-icon { + font-size: 2.5rem; + color: var(--theme-fg-more, $fallback-fg-more); + margin-bottom: $spacing-xl; + opacity: 0.5; +} - p { - margin: $spacing-md 0; - } +.empty-state-title { + font-size: 1.2rem; + font-weight: 600; + color: var(--theme-fg); + margin: 0 0 $spacing-md; +} - strong { - color: var(--theme-primary); - padding: 0 $spacing-xs; - } - } +.empty-state-description { + font-size: $font-sm; + color: var(--theme-fg-more, $fallback-fg-more); + margin: 0 0 $spacing-2xl; + max-width: 320px; + line-height: 1.5; +} + +.empty-state-cta { + @include btn-primary; } diff --git a/src/components/workspaceList.component.ts b/src/components/workspaceList.component.ts index 072a816..1d4e18d 100644 --- a/src/components/workspaceList.component.ts +++ b/src/components/workspaceList.component.ts @@ -16,7 +16,7 @@ import { isWorkspaceSplit, } from '../models/workspace.model' -const SETTINGS_MAX_WIDTH = '876px' +const SETTINGS_MAX_WIDTH = 'none' @Component({ selector: 'workspace-list', @@ -54,12 +54,26 @@ export class WorkspaceListComponent implements OnInit, OnDestroy, AfterViewInit } ngAfterViewInit(): void { - // Hack: Override Tabby's settings-tab-body max-width restriction + // Hack: Override Tabby's settings layout restrictions to fill entire page setTimeout(() => { - const parent = this.elementRef.nativeElement.closest('settings-tab-body') as HTMLElement - if (parent) { - parent.style.maxWidth = SETTINGS_MAX_WIDTH + const settingsTabBody = this.elementRef.nativeElement.closest('settings-tab-body') as HTMLElement + if (settingsTabBody) { + settingsTabBody.style.maxWidth = SETTINGS_MAX_WIDTH + settingsTabBody.style.height = '100%' + + // .tab-pane needs height instead of just min-height + const tabPane = settingsTabBody.closest('.tab-pane') as HTMLElement + if (tabPane) { + tabPane.style.height = '100%' + tabPane.style.boxSizing = 'border-box' + } } + + // Make our host element fill the container + const host = this.elementRef.nativeElement as HTMLElement + host.style.display = 'flex' + host.style.flexDirection = 'column' + host.style.height = '100%' }, 0) } diff --git a/src/models/workspace.model.ts b/src/models/workspace.model.ts index 770cd03..97beeb0 100644 --- a/src/models/workspace.model.ts +++ b/src/models/workspace.model.ts @@ -58,6 +58,7 @@ export interface TabbySplitLayoutProfile { // Workspace interfaces export interface WorkspacePane { id: string + name?: string profileId: string cwd?: string startupCommand?: string diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss index 284b583..10d7365 100644 --- a/src/styles/_mixins.scss +++ b/src/styles/_mixins.scss @@ -173,6 +173,7 @@ // ====================== @mixin dropdown-panel { + background-color: #1e1e1e; background: var(--theme-bg-more); border: 1px solid var(--theme-border, $fallback-border); border-radius: $radius-md;