feat(tabs): stronger tab icons — blue browser globe, bold terminal tile (#1305)
- BrowserTab: Globe icon uses text-blue-500 on both active and inactive tabs for a distinct, recognizable anchor in the tab strip. - shell-icons: GenericTerminalIcon now renders a pitch-black tile with a thick stroked >_ (chevron at 3.2px, underscore at 2.6px, taller chevron) so mac/linux/default terminal tabs match the colored-badge treatment of the PowerShell/CMD/WSL icons instead of a flat lucide chevron. - SortableTab: all terminal tabs use ShellIcon. On Windows, tabs without a per-tab shellOverride fall back to the user's configured default Windows shell so the tab-strip icon reflects what's actually running. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
ff22944fd7
commit
b8ded7e9fe
|
|
@ -149,9 +149,14 @@ export default function BrowserTab({
|
|||
}}
|
||||
>
|
||||
{isActive && <span className={ACTIVE_TAB_INDICATOR_CLASSES} aria-hidden />}
|
||||
<Globe
|
||||
className={`w-3 h-3 mr-1 shrink-0 ${isActive ? 'text-foreground' : 'text-muted-foreground'}`}
|
||||
/>
|
||||
{/* Why: the browser tab icon is the only non-terminal, non-editor
|
||||
surface in the tab strip. Coloring the Globe blue (matching the
|
||||
in-app browser's identity and the default tab insertion bar)
|
||||
gives it a distinct, recognizable anchor so users can spot
|
||||
browser tabs at a glance even when the strip is saturated. We
|
||||
keep full color on both active and inactive tabs — dimming to
|
||||
muted-foreground made the icon read as "disabled" in practice. */}
|
||||
<Globe className="w-3 h-3 mr-1 shrink-0 text-blue-500" />
|
||||
<span className="truncate max-w-[100px] mr-1">{getBrowserTabLabel(tab)}</span>
|
||||
{tab.loading && !tab.loadError && !isBlankBrowserTab(tab) && (
|
||||
<span className="mr-1 size-1.5 rounded-full bg-sky-500/80 shrink-0" />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
import { X, Terminal as TerminalIcon, Minimize2, Columns2, Rows2 } from 'lucide-react'
|
||||
import { X, Minimize2, Columns2, Rows2 } from 'lucide-react'
|
||||
import { ShellIcon } from './shell-icons'
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
|
@ -81,6 +81,16 @@ export default function SortableTab({
|
|||
// because the slice returns a fresh object reference on each mark/clear.
|
||||
const hasUnreadActivity = useAppStore((s) => s.unreadTerminalTabs[tab.id] === true)
|
||||
|
||||
// Why: on Windows, tabs created before the per-tab shell override landed (or
|
||||
// created via the default Ctrl+T path without picking a specific shell)
|
||||
// don't carry a shellOverride. We still want the tab-strip icon to reflect
|
||||
// the shell actually running, so fall back to the user's configured default
|
||||
// Windows shell. On mac/linux this resolves to undefined and the ShellIcon
|
||||
// generic-terminal fallback renders.
|
||||
const defaultWindowsShell = useAppStore((s) => s.settings?.terminalWindowsShell)
|
||||
const isWindows = navigator.userAgent.includes('Windows')
|
||||
const shellForIcon = tab.shellOverride ?? (isWindows ? defaultWindowsShell : undefined)
|
||||
|
||||
// Why: intentionally no transform/transition/opacity here. The PR's
|
||||
// design is that tabs stay visually anchored during a drag — only the
|
||||
// blue insertion bar moves. Siblings also don't shift (see
|
||||
|
|
@ -255,25 +265,21 @@ export default function SortableTab({
|
|||
<span data-testid="tab-activity-bell" className="inline-flex shrink-0">
|
||||
<FilledBellIcon className="w-3 h-3 mr-1 text-amber-500 drop-shadow-sm" />
|
||||
</span>
|
||||
) : tab.shellOverride ? (
|
||||
// Why: when the tab was explicitly opened with a specific Windows
|
||||
// shell (PowerShell / CMD / WSL via the "+" menu), render the
|
||||
// matching brand-style glyph so the strip shows at a glance which
|
||||
// shell this tab is running. Falls back to the generic lucide
|
||||
// TerminalIcon below for mac/linux shells and for Windows tabs
|
||||
// that were spawned before the per-tab shell override landed
|
||||
// (shellOverride absent → same neutral glyph as before, no visual
|
||||
// regression for existing sessions).
|
||||
) : (
|
||||
// Why: ShellIcon renders a colored brand-style tile for PowerShell,
|
||||
// CMD, and WSL so Windows users can distinguish shells at a glance.
|
||||
// On mac/linux (or Windows tabs without a resolved shell) it falls
|
||||
// back to a matching colored generic-terminal tile — keeping every
|
||||
// tab's leading glyph in the same visual idiom instead of mixing a
|
||||
// flat lucide chevron with the brand tiles. Opacity dims the icon
|
||||
// on inactive tabs to match the existing text treatment without
|
||||
// desaturating the brand colors beyond recognition.
|
||||
<span
|
||||
className={`mr-1 inline-flex shrink-0 ${isActive ? 'text-foreground' : 'text-muted-foreground'}`}
|
||||
className={`mr-1 inline-flex shrink-0 ${isActive ? '' : 'opacity-70'}`}
|
||||
aria-hidden
|
||||
>
|
||||
<ShellIcon shell={tab.shellOverride} size={12} />
|
||||
<ShellIcon shell={shellForIcon} size={12} />
|
||||
</span>
|
||||
) : (
|
||||
<TerminalIcon
|
||||
className={`w-3 h-3 mr-1 shrink-0 ${isActive ? 'text-foreground' : 'text-muted-foreground'}`}
|
||||
/>
|
||||
)}
|
||||
{isEditing ? (
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react'
|
||||
import { Terminal as TerminalIcon } from 'lucide-react'
|
||||
|
||||
export type WindowsShell = 'powershell.exe' | 'cmd.exe' | 'wsl.exe'
|
||||
|
||||
|
|
@ -9,7 +8,10 @@ export type WindowsShell = 'powershell.exe' | 'cmd.exe' | 'wsl.exe'
|
|||
// hand-crafted icons (derived from the official brand marks and redrawn as
|
||||
// small currentColor-aware paths so they inherit the tab's text color) make
|
||||
// each shell identifiable at a glance without shipping a heavier brand-asset
|
||||
// package like simple-icons.
|
||||
// package like simple-icons. The generic (macOS/Linux) terminal fallback uses
|
||||
// the same colored-tile treatment so the tab strip reads as a consistent set
|
||||
// of badges rather than a monochrome lucide glyph next to colorful brand
|
||||
// marks.
|
||||
|
||||
function PowerShellIcon({ size = 14 }: { size?: number }): React.JSX.Element {
|
||||
return (
|
||||
|
|
@ -75,6 +77,40 @@ function WslIcon({ size = 14 }: { size?: number }): React.JSX.Element {
|
|||
)
|
||||
}
|
||||
|
||||
function GenericTerminalIcon({ size = 14 }: { size?: number }): React.JSX.Element {
|
||||
// Why: matches the tile treatment of PowerShell/CMD/WSL so the tab strip
|
||||
// reads as a consistent set of badges instead of a flat monochrome chevron.
|
||||
// Uses black/white (same palette as the CmdIcon) so generic mac/linux
|
||||
// sessions stay visually neutral — the colorful brand tiles are reserved
|
||||
// for shells that actually have a brand identity.
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
>
|
||||
<rect x="1.5" y="3" width="21" height="18" rx="2.5" fill="#000000" />
|
||||
<path
|
||||
d="M6 7.5 L11.5 12 L6 16.5"
|
||||
stroke="#ffffff"
|
||||
strokeWidth="3.2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
d="M12.5 16.5 L18 16.5"
|
||||
stroke="#ffffff"
|
||||
strokeWidth="2.6"
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function ShellIcon({
|
||||
shell,
|
||||
size = 14
|
||||
|
|
@ -92,7 +128,5 @@ export function ShellIcon({
|
|||
if (normalized === 'wsl.exe' || normalized.startsWith('wsl')) {
|
||||
return <WslIcon size={size} />
|
||||
}
|
||||
// Fallback: generic terminal glyph for mac/linux shells and anything
|
||||
// unrecognized. Inherits currentColor so it matches the surrounding tab.
|
||||
return <TerminalIcon width={size} height={size} aria-hidden />
|
||||
return <GenericTerminalIcon size={size} />
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue