Fix sidebar workspace row alignment

This commit is contained in:
Mohamed Boudra
2026-06-04 13:26:45 +07:00
parent de5fa4dc36
commit 66a5bd8a88
4 changed files with 69 additions and 54 deletions
@@ -28,6 +28,7 @@ const styles = StyleSheet.create((theme) => ({
row: { row: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
height: 20,
gap: 4, gap: 4,
flexShrink: 0, flexShrink: 0,
}, },
@@ -48,9 +48,7 @@ import {
ExternalLink, ExternalLink,
FolderPlus, FolderPlus,
GitPullRequest, GitPullRequest,
Globe,
Settings, Settings,
SquareTerminal,
MoreVertical, MoreVertical,
Pencil, Pencil,
Plus, Plus,
@@ -150,8 +148,6 @@ const ThemedCircleAlert = withUnistyles(CircleAlert);
const ThemedCircleCheck = withUnistyles(CircleCheck); const ThemedCircleCheck = withUnistyles(CircleCheck);
const ThemedSyncedLoader = withUnistyles(SyncedLoader); const ThemedSyncedLoader = withUnistyles(SyncedLoader);
const ThemedFolderPlus = withUnistyles(FolderPlus); const ThemedFolderPlus = withUnistyles(FolderPlus);
const ThemedGlobe = withUnistyles(Globe);
const ThemedSquareTerminal = withUnistyles(SquareTerminal);
const ThemedMoreVertical = withUnistyles(MoreVertical); const ThemedMoreVertical = withUnistyles(MoreVertical);
const ThemedTrash2 = withUnistyles(Trash2); const ThemedTrash2 = withUnistyles(Trash2);
const ThemedSettings = withUnistyles(Settings); const ThemedSettings = withUnistyles(Settings);
@@ -165,7 +161,6 @@ const foregroundMutedColorMapping = (theme: Theme) => ({
}); });
const redColorMapping = (theme: Theme) => ({ color: theme.colors.palette.red[500] }); const redColorMapping = (theme: Theme) => ({ color: theme.colors.palette.red[500] });
const amberColorMapping = (theme: Theme) => ({ color: theme.colors.palette.amber[500] }); const amberColorMapping = (theme: Theme) => ({ color: theme.colors.palette.amber[500] });
const blueColorMapping = (theme: Theme) => ({ color: theme.colors.palette.blue[500] });
const greenColorMapping = (theme: Theme) => ({ color: theme.colors.palette.green[500] }); const greenColorMapping = (theme: Theme) => ({ color: theme.colors.palette.green[500] });
const purpleColorMapping = (theme: Theme) => ({ color: theme.colors.palette.purple[500] }); const purpleColorMapping = (theme: Theme) => ({ color: theme.colors.palette.purple[500] });
const syncedLoaderColorMapping = (theme: Theme) => ({ const syncedLoaderColorMapping = (theme: Theme) => ({
@@ -622,8 +617,6 @@ function WorkspaceRowRightGroup({
workspace, workspace,
isHovered, isHovered,
isTouchPlatform, isTouchPlatform,
showScriptsIcon,
hasRunningService,
isCreating, isCreating,
showShortcutBadge, showShortcutBadge,
shortcutNumber, shortcutNumber,
@@ -640,8 +633,6 @@ function WorkspaceRowRightGroup({
workspace: SidebarWorkspaceEntry; workspace: SidebarWorkspaceEntry;
isHovered: boolean; isHovered: boolean;
isTouchPlatform: boolean; isTouchPlatform: boolean;
showScriptsIcon: boolean;
hasRunningService: boolean;
isCreating: boolean; isCreating: boolean;
showShortcutBadge: boolean; showShortcutBadge: boolean;
shortcutNumber: number | null; shortcutNumber: number | null;
@@ -661,15 +652,6 @@ function WorkspaceRowRightGroup({
const shouldRenderActionSlot = Boolean(onArchive || workspace.diffStat); const shouldRenderActionSlot = Boolean(onArchive || workspace.diffStat);
return ( return (
<> <>
{showScriptsIcon ? (
<View testID="workspace-globe-icon" accessibilityLabel="Scripts available">
{hasRunningService ? (
<ThemedGlobe size={12} uniProps={blueColorMapping} />
) : (
<ThemedSquareTerminal size={12} uniProps={blueColorMapping} />
)}
</View>
) : null}
{isCreating ? <Text style={styles.workspaceCreatingText}>Creating...</Text> : null} {isCreating ? <Text style={styles.workspaceCreatingText}>Creating...</Text> : null}
{shouldRenderActionSlot ? ( {shouldRenderActionSlot ? (
<SidebarWorkspaceTrailingActionSlot> <SidebarWorkspaceTrailingActionSlot>
@@ -1393,6 +1375,10 @@ function WorkspaceRowInner({
const hasRunningService = workspace.scripts.some( const hasRunningService = workspace.scripts.some(
(s) => s.lifecycle === "running" && (s.type ?? "service") === "service", (s) => s.lifecycle === "running" && (s.type ?? "service") === "service",
); );
let scriptIconKind: "service" | "command" | null = null;
if (showScriptsIcon) {
scriptIconKind = hasRunningService ? "service" : "command";
}
const workspaceRowStyle = getProjectWorkspaceRowStyle({ const workspaceRowStyle = getProjectWorkspaceRowStyle({
isDragging, isDragging,
selected, selected,
@@ -1420,6 +1406,7 @@ function WorkspaceRowInner({
> >
<SidebarWorkspaceRowContent <SidebarWorkspaceRowContent
workspace={workspace} workspace={workspace}
scriptIconKind={scriptIconKind}
isHovered={isHovered} isHovered={isHovered}
isLoading={isArchiving || isCreating} isLoading={isArchiving || isCreating}
isCreating={isCreating} isCreating={isCreating}
@@ -1430,8 +1417,6 @@ function WorkspaceRowInner({
workspace={workspace} workspace={workspace}
isHovered={isHovered} isHovered={isHovered}
isTouchPlatform={isTouchPlatform} isTouchPlatform={isTouchPlatform}
showScriptsIcon={showScriptsIcon}
hasRunningService={hasRunningService}
isCreating={isCreating} isCreating={isCreating}
showShortcutBadge={showShortcutBadge} showShortcutBadge={showShortcutBadge}
shortcutNumber={shortcutNumber} shortcutNumber={shortcutNumber}
@@ -21,8 +21,6 @@ import {
CircleDot, CircleDot,
CircleX, CircleX,
MoreVertical, MoreVertical,
SquareTerminal,
Globe,
Copy, Copy,
Archive, Archive,
Pencil, Pencil,
@@ -81,8 +79,6 @@ const ThemedCircleAlert = withUnistyles(CircleAlert);
const ThemedCircleCheck = withUnistyles(CircleCheck); const ThemedCircleCheck = withUnistyles(CircleCheck);
const ThemedCircleDot = withUnistyles(CircleDot); const ThemedCircleDot = withUnistyles(CircleDot);
const ThemedCircleX = withUnistyles(CircleX); const ThemedCircleX = withUnistyles(CircleX);
const ThemedGlobe = withUnistyles(Globe);
const ThemedSquareTerminal = withUnistyles(SquareTerminal);
const ThemedMoreVertical = withUnistyles(MoreVertical); const ThemedMoreVertical = withUnistyles(MoreVertical);
const ThemedCopy = withUnistyles(Copy); const ThemedCopy = withUnistyles(Copy);
const ThemedArchive = withUnistyles(Archive); const ThemedArchive = withUnistyles(Archive);
@@ -607,6 +603,10 @@ function StatusWorkspaceRowInner({
const hasRunningService = workspace.scripts.some( const hasRunningService = workspace.scripts.some(
(s) => s.lifecycle === "running" && (s.type ?? "service") === "service", (s) => s.lifecycle === "running" && (s.type ?? "service") === "service",
); );
let scriptIconKind: "service" | "command" | null = null;
if (showScriptsIcon) {
scriptIconKind = hasRunningService ? "service" : "command";
}
const accessibilityState = useMemo(() => ({ selected }), [selected]); const accessibilityState = useMemo(() => ({ selected }), [selected]);
@@ -631,38 +631,28 @@ function StatusWorkspaceRowInner({
<SidebarWorkspaceRowContent <SidebarWorkspaceRowContent
workspace={workspace} workspace={workspace}
subtitle={projectName} subtitle={projectName}
scriptIconKind={scriptIconKind}
isHovered={isHovered} isHovered={isHovered}
isLoading={isArchiving} isLoading={isArchiving}
shortcutNumber={shortcutNumber} shortcutNumber={shortcutNumber}
showShortcutBadge={showShortcutBadge} showShortcutBadge={showShortcutBadge}
> >
<> {shouldRenderActionSlot ? (
{showScriptsIcon ? ( <StatusWorkspaceActionSlot
<View accessibilityLabel="Scripts available"> workspace={workspace}
{hasRunningService ? ( showBase={Boolean(workspace.diffStat && !showKebabInSlot && !showShortcut)}
<ThemedGlobe size={12} uniProps={blueColorMapping} /> showOverlay={showKebabInSlot}
) : ( onCopyPath={onCopyPath}
<ThemedSquareTerminal size={12} uniProps={blueColorMapping} /> onCopyBranchName={onCopyBranchName}
)} onRename={onRename}
</View> onMarkAsRead={onMarkAsRead}
) : null} onArchive={onArchive}
{shouldRenderActionSlot ? ( archiveLabel={archiveLabel}
<StatusWorkspaceActionSlot archiveStatus={archiveStatus}
workspace={workspace} archivePendingLabel={archivePendingLabel}
showBase={Boolean(workspace.diffStat && !showKebabInSlot && !showShortcut)} archiveShortcutKeys={archiveShortcutKeys}
showOverlay={showKebabInSlot} />
onCopyPath={onCopyPath} ) : null}
onCopyBranchName={onCopyBranchName}
onRename={onRename}
onMarkAsRead={onMarkAsRead}
onArchive={onArchive}
archiveLabel={archiveLabel}
archiveStatus={archiveStatus}
archivePendingLabel={archivePendingLabel}
archiveShortcutKeys={archiveShortcutKeys}
/>
) : null}
</>
</SidebarWorkspaceRowContent> </SidebarWorkspaceRowContent>
</Pressable> </Pressable>
</View> </View>
@@ -14,7 +14,9 @@ import {
Folder, Folder,
FolderGit2, FolderGit2,
GitPullRequest, GitPullRequest,
Globe,
Monitor, Monitor,
SquareTerminal,
} from "lucide-react-native"; } from "lucide-react-native";
import { GitHubIcon } from "@/components/icons/github-icon"; import { GitHubIcon } from "@/components/icons/github-icon";
import { WorkspaceHoverCard } from "@/components/workspace-hover-card"; import { WorkspaceHoverCard } from "@/components/workspace-hover-card";
@@ -42,6 +44,7 @@ const syncedLoaderColorMapping = (theme: Theme) => ({
? theme.colors.palette.amber[700] ? theme.colors.palette.amber[700]
: theme.colors.palette.amber[500], : theme.colors.palette.amber[500],
}); });
const blueColorMapping = (theme: Theme) => ({ color: theme.colors.palette.blue[500] });
const greenColorMapping = (theme: Theme) => ({ color: theme.colors.palette.green[500] }); const greenColorMapping = (theme: Theme) => ({ color: theme.colors.palette.green[500] });
const redColorMapping = (theme: Theme) => ({ color: theme.colors.palette.red[500] }); const redColorMapping = (theme: Theme) => ({ color: theme.colors.palette.red[500] });
const purpleColorMapping = (theme: Theme) => ({ color: theme.colors.palette.purple[500] }); const purpleColorMapping = (theme: Theme) => ({ color: theme.colors.palette.purple[500] });
@@ -55,6 +58,10 @@ const ThemedSyncedLoader = withUnistyles(SyncedLoader);
const ThemedMonitor = withUnistyles(Monitor); const ThemedMonitor = withUnistyles(Monitor);
const ThemedFolder = withUnistyles(Folder); const ThemedFolder = withUnistyles(Folder);
const ThemedFolderGit2 = withUnistyles(FolderGit2); const ThemedFolderGit2 = withUnistyles(FolderGit2);
const ThemedGlobe = withUnistyles(Globe);
const ThemedSquareTerminal = withUnistyles(SquareTerminal);
type SidebarWorkspaceScriptIconKind = "service" | "command";
export function SidebarWorkspaceRowFrame({ export function SidebarWorkspaceRowFrame({
workspace, workspace,
@@ -86,6 +93,7 @@ export function SidebarWorkspaceRowFrame({
export const SidebarWorkspaceRowContent = memo(function SidebarWorkspaceRowContent({ export const SidebarWorkspaceRowContent = memo(function SidebarWorkspaceRowContent({
workspace, workspace,
subtitle, subtitle,
scriptIconKind = null,
isHovered, isHovered,
isLoading, isLoading,
isCreating = false, isCreating = false,
@@ -95,6 +103,7 @@ export const SidebarWorkspaceRowContent = memo(function SidebarWorkspaceRowConte
}: { }: {
workspace: SidebarWorkspaceEntry; workspace: SidebarWorkspaceEntry;
subtitle?: string | null; subtitle?: string | null;
scriptIconKind?: SidebarWorkspaceScriptIconKind | null;
isHovered: boolean; isHovered: boolean;
isLoading: boolean; isLoading: boolean;
isCreating?: boolean; isCreating?: boolean;
@@ -105,10 +114,11 @@ export const SidebarWorkspaceRowContent = memo(function SidebarWorkspaceRowConte
const workspaceBranchTextStyle = useMemo( const workspaceBranchTextStyle = useMemo(
() => [ () => [
styles.workspaceBranchText, styles.workspaceBranchText,
scriptIconKind ? styles.workspaceBranchTextWithAccessory : styles.workspaceBranchTextFlexible,
isHovered && styles.workspaceBranchTextHovered, isHovered && styles.workspaceBranchTextHovered,
isCreating && styles.workspaceBranchTextCreating, isCreating && styles.workspaceBranchTextCreating,
], ],
[isHovered, isCreating], [scriptIconKind, isHovered, isCreating],
); );
return ( return (
@@ -123,6 +133,7 @@ export const SidebarWorkspaceRowContent = memo(function SidebarWorkspaceRowConte
<Text style={workspaceBranchTextStyle} numberOfLines={1}> <Text style={workspaceBranchTextStyle} numberOfLines={1}>
{workspace.name} {workspace.name}
</Text> </Text>
{scriptIconKind ? <WorkspaceScriptIcon kind={scriptIconKind} /> : null}
</View> </View>
<View style={styles.workspaceRowRight}>{children}</View> <View style={styles.workspaceRowRight}>{children}</View>
</View> </View>
@@ -146,6 +157,22 @@ export const SidebarWorkspaceRowContent = memo(function SidebarWorkspaceRowConte
); );
}); });
function WorkspaceScriptIcon({ kind }: { kind: SidebarWorkspaceScriptIconKind }) {
return (
<View
style={styles.workspaceTitleAccessory}
accessibilityLabel="Scripts available"
testID={kind === "service" ? "workspace-globe-icon" : "workspace-terminal-icon"}
>
{kind === "service" ? (
<ThemedGlobe size={12} uniProps={blueColorMapping} />
) : (
<ThemedSquareTerminal size={12} uniProps={blueColorMapping} />
)}
</View>
);
}
function WorkspaceStatusIndicator({ function WorkspaceStatusIndicator({
bucket, bucket,
workspaceKind, workspaceKind,
@@ -454,7 +481,7 @@ const styles = StyleSheet.create((theme) => ({
workspaceStatusDot: { workspaceStatusDot: {
position: "relative", position: "relative",
width: WORKSPACE_STATUS_DOT_WIDTH, width: WORKSPACE_STATUS_DOT_WIDTH,
height: 16, height: 20,
borderRadius: theme.borderRadius.full, borderRadius: theme.borderRadius.full,
flexShrink: 0, flexShrink: 0,
alignItems: "center", alignItems: "center",
@@ -471,9 +498,20 @@ const styles = StyleSheet.create((theme) => ({
fontWeight: "400", fontWeight: "400",
lineHeight: 20, lineHeight: 20,
opacity: 0.76, opacity: 0.76,
flex: 1,
minWidth: 0, minWidth: 0,
}, },
workspaceBranchTextFlexible: {
flex: 1,
},
workspaceBranchTextWithAccessory: {
flexShrink: 1,
},
workspaceTitleAccessory: {
height: 20,
flexShrink: 0,
alignItems: "center",
justifyContent: "center",
},
workspaceBranchTextCreating: { workspaceBranchTextCreating: {
opacity: 0.92, opacity: 0.92,
}, },
@@ -490,6 +528,7 @@ const styles = StyleSheet.create((theme) => ({
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
gap: theme.spacing[2], gap: theme.spacing[2],
marginTop: theme.spacing[1],
paddingLeft: WORKSPACE_STATUS_DOT_WIDTH + theme.spacing[2], paddingLeft: WORKSPACE_STATUS_DOT_WIDTH + theme.spacing[2],
}, },
statusDotNeedsInput: { statusDotNeedsInput: {