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