From f180a1b0858d366d413c1c2a81c48798ef0e25b0 Mon Sep 17 00:00:00 2001 From: Bill Leoutsakos Date: Sat, 19 Sep 2026 14:20:50 -0700 Subject: [PATCH] improvement(ui): centralize tab strip action geometry --- .../hero-resource-panel.tsx | 16 ++--- .../add-resource-dropdown.tsx | 15 ++--- .../resource-content/resource-content.tsx | 63 ++++++------------- .../resource-tabs/resource-tab-controls.ts | 7 --- .../resource-tabs/resource-tabs.tsx | 8 +-- packages/emcn/src/components/index.ts | 1 + .../components/tab-strip/tab-strip-action.tsx | 29 +++++++++ .../tab-strip/tab-strip.dom.test.tsx | 36 ++++++++++- .../src/components/tab-strip/tab-strip.tsx | 12 ++-- 9 files changed, 100 insertions(+), 87 deletions(-) create mode 100644 packages/emcn/src/components/tab-strip/tab-strip-action.tsx diff --git a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-resource-panel.tsx b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-resource-panel.tsx index bb8d64f5d8f..cc03a9ae5b5 100644 --- a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-resource-panel.tsx +++ b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-resource-panel.tsx @@ -9,6 +9,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, TabStrip, + TabStripAction, type TabStripItem, Tooltip, } from '@sim/emcn' @@ -25,7 +26,6 @@ import type { LeadRecord } from '@/app/(landing)/tables/components/tables-record import { TablesRecordsTable } from '@/app/(landing)/tables/components/tables-records-preview/tables-records-table' import { RESOURCE_HEADER_CLASSES, - RESOURCE_TAB_ICON_BUTTON_CLASS, RESOURCE_TAB_ICON_CLASS, resourceTabWidthClass, } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tab-controls' @@ -133,15 +133,14 @@ export function HeroResourcePanel({ activeId === 'workflow' ? ( - + Run workflow @@ -162,14 +161,9 @@ export function HeroResourcePanel({ - + Add resource diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown/add-resource-dropdown.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown/add-resource-dropdown.tsx index 3f6b6dbbe62..e3712297bdc 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown/add-resource-dropdown.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/add-resource-dropdown/add-resource-dropdown.tsx @@ -2,7 +2,6 @@ import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react' import { - Button, cn, DropdownMenu, DropdownMenuContent, @@ -14,6 +13,7 @@ import { DropdownMenuSubTrigger, DropdownMenuTrigger, NATIVE_SURFACE_OCCLUSION_PREPARE_EVENT, + TabStripAction, Tooltip, } from '@sim/emcn' import { Folder, Plus } from '@sim/emcn/icons' @@ -30,10 +30,7 @@ import { byResourceMenuOrder, getResourceConfig, } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry' -import { - RESOURCE_TAB_ICON_BUTTON_CLASS, - RESOURCE_TAB_ICON_CLASS, -} from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tab-controls' +import { RESOURCE_TAB_ICON_CLASS } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tab-controls' import type { MothershipResource, MothershipResourceType, @@ -676,13 +673,9 @@ export function AddResourceDropdown({ - + diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx index a3d8cecd74e..8def5c3398c 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx @@ -1,7 +1,7 @@ 'use client' import { lazy, memo, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { Button, OverflowText, PlayOutline, Skeleton, Tooltip, toast } from '@sim/emcn' +import { OverflowText, PlayOutline, Skeleton, TabStripAction, Tooltip, toast } from '@sim/emcn' import { Download, FileX, @@ -36,10 +36,7 @@ import type { BrowserPanelOverlayController } from '@/app/workspace/[workspaceId import { BrowserSession } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/browser-session/browser-session' import { GenericResourceContent } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/generic-resource-content' import { TerminalSession } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/components/terminal-session/terminal-session' -import { - RESOURCE_TAB_ICON_BUTTON_CLASS, - RESOURCE_TAB_ICON_CLASS, -} from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tab-controls' +import { RESOURCE_TAB_ICON_CLASS } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tab-controls' import { hasRenderableFilePreviewContent } from '@/app/workspace/[workspaceId]/home/hooks/preview' import type { GenericResourceData, @@ -465,14 +462,9 @@ export function EmbeddedWorkflowActions({ workspaceId, workflowId }: EmbeddedWor <> - +

Open workflow

@@ -480,11 +472,10 @@ export function EmbeddedWorkflowActions({ workspaceId, workflowId }: EmbeddedWor
- +

{isExecuting ? 'Stop' : 'Run workflow'}

@@ -520,14 +511,13 @@ export function EmbeddedKnowledgeBaseActions({ return ( - +

Open knowledge base

@@ -562,14 +552,9 @@ function EmbeddedTableActions({ workspaceId, tableId }: EmbeddedTableActionsProp <> - +

Open table

@@ -577,14 +562,13 @@ function EmbeddedTableActions({ workspaceId, tableId }: EmbeddedTableActionsProp
- +

Export CSV

@@ -639,14 +623,9 @@ function EmbeddedFileActions({ <> - +

Open in files

@@ -654,15 +633,14 @@ function EmbeddedFileActions({
- +

Download

@@ -896,14 +874,9 @@ export function EmbeddedLogActions({ workspaceId, logId }: EmbeddedLogActionsPro return ( - +

Open in logs

diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tab-controls.ts b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tab-controls.ts index 43931efbff9..8507e6beafb 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tab-controls.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tab-controls.ts @@ -1,10 +1,3 @@ -/** - * Icon-only controls in the resource header — add, preview mode, the per-resource - * actions — fill the tab strip's control band, so they match the strip's own - * new-tab button and the panel's collapse toggle and the header reads as one row. - */ -export const RESOURCE_TAB_ICON_BUTTON_CLASS = 'size-[var(--tab-strip-band,30px)] shrink-0 p-0' - export const RESOURCE_TAB_ICON_CLASS = 'size-[16px] text-[var(--text-icon)]' /** Shared geometry for the resource header and controls positioned over it. */ diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx index 71f94b3b37b..e3f73406f8c 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx +++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tabs.tsx @@ -10,9 +10,9 @@ import { useState, } from 'react' import { - Button, cn, TabStrip, + TabStripAction, type TabStripDragContext, type TabStripItem, type TabStripSelectionSource, @@ -41,7 +41,6 @@ import { useTerminalCloseConfirmation } from '@/app/workspace/[workspaceId]/home import { getResourceConfig } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry' import { RESOURCE_HEADER_CLASSES, - RESOURCE_TAB_ICON_BUTTON_CLASS, RESOURCE_TAB_ICON_CLASS, resourceTabWidthClass, } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-tabs/resource-tab-controls' @@ -545,14 +544,13 @@ export function ResourceTabs({ previewMode && onCyclePreviewMode ? ( - +

{PREVIEW_MODE_LABELS[previewMode]}

diff --git a/packages/emcn/src/components/index.ts b/packages/emcn/src/components/index.ts index c7c2204968a..bd96f788378 100644 --- a/packages/emcn/src/components/index.ts +++ b/packages/emcn/src/components/index.ts @@ -239,6 +239,7 @@ export { tabStripItemSelector, tabStripWheelPosition, } from './tab-strip/tab-strip' +export { TabStripAction, type TabStripActionProps } from './tab-strip/tab-strip-action' export { Table, TableBody, diff --git a/packages/emcn/src/components/tab-strip/tab-strip-action.tsx b/packages/emcn/src/components/tab-strip/tab-strip-action.tsx new file mode 100644 index 00000000000..5026fe44fcb --- /dev/null +++ b/packages/emcn/src/components/tab-strip/tab-strip-action.tsx @@ -0,0 +1,29 @@ +import { forwardRef } from 'react' +import { cn } from '../../lib/cn' +import { Button, type ButtonProps } from '../button/button' + +export interface TabStripActionProps + extends Omit { + /** Accessible name for the icon action. */ + 'aria-label': string + /** Keeps the Button text scale without applying its separate icon geometry. */ + size?: Exclude +} + +/** + * Icon action sized to its tab strip's control band, defaulting to 30px. + * Callers retain the Button variant, icon, tooltip and action behavior. + * + * @example + */ +export const TabStripAction = forwardRef( + ({ className, ...props }, ref) => ( + + {atLimit ? `Maximum of ${maxTabs} tabs` : newTabLabel}