chore(lint): hoist jsx-as-prop in app components batch 1

Memoize inline JSX icons passed as props across add-host-modal,
branch-switcher, composer, pair-link-modal, pr-pane, and
sidebar-workspace-list to satisfy react-perf/jsx-no-jsx-as-prop.
This commit is contained in:
Mohamed Boudra
2026-04-24 05:33:20 +07:00
parent 12cb5fb6a6
commit 7e84fc0924
6 changed files with 77 additions and 31 deletions

View File

@@ -1,4 +1,4 @@
import { useCallback, useRef, useState } from "react";
import { useCallback, useMemo, useRef, useState } from "react";
import { Alert, Text, TextInput, View } from "react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { useIsCompactFormFactor } from "@/constants/layout";
@@ -160,6 +160,11 @@ export function AddHostModal({ visible, onClose, onCancel, onSaved }: AddHostMod
hostInputRef.current?.clear();
}, []);
const connectIcon = useMemo(
() => <Link2 size={16} color={theme.colors.palette.white} />,
[theme.colors.palette.white],
);
const handleClose = useCallback(() => {
if (isSaving) return;
clearInput();
@@ -289,7 +294,7 @@ export function AddHostModal({ visible, onClose, onCancel, onSaved }: AddHostMod
variant="default"
onPress={handleSavePress}
disabled={isSaving}
leftIcon={<Link2 size={16} color={theme.colors.palette.white} />}
leftIcon={connectIcon}
testID="direct-host-submit"
>
{isSaving ? "Connecting..." : "Connect"}

View File

@@ -1,4 +1,4 @@
import { useCallback, useRef } from "react";
import { useCallback, useMemo, useRef } from "react";
import { Pressable, View, type PressableStateCallbackType } from "react-native";
import { useQueryClient } from "@tanstack/react-query";
import { ChevronDown, GitBranch } from "lucide-react-native";
@@ -62,6 +62,11 @@ export function BranchSwitcher({
[],
);
const branchLeadingSlot = useMemo(
() => <GitBranch size={14} color={theme.colors.foregroundMuted} />,
[theme.colors.foregroundMuted],
);
const renderBranchOption = useCallback<NonNullable<ComboboxProps["renderOption"]>>(
({ option, selected, active, onPress }) => (
<ComboboxItem
@@ -69,10 +74,10 @@ export function BranchSwitcher({
selected={selected}
active={active}
onPress={onPress}
leadingSlot={<GitBranch size={14} color={theme.colors.foregroundMuted} />}
leadingSlot={branchLeadingSlot}
/>
),
[theme.colors.foregroundMuted],
[branchLeadingSlot],
);
if (!currentBranchName) {

View File

@@ -235,6 +235,15 @@ function GithubPickerOption({
const handlePress = useCallback(() => {
onToggle(item);
}, [onToggle, item]);
const leadingSlot = useMemo(
() =>
item.kind === "pr" ? (
<GitPullRequest size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
) : (
<CircleDot size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
),
[item.kind, theme.iconSize.sm, theme.colors.foregroundMuted],
);
return (
<ComboboxItem
testID={testID}
@@ -242,13 +251,7 @@ function GithubPickerOption({
selected={selected}
active={active}
onPress={handlePress}
leadingSlot={
item.kind === "pr" ? (
<GitPullRequest size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
) : (
<CircleDot size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
)
}
leadingSlot={leadingSlot}
/>
);
}

View File

@@ -1,4 +1,4 @@
import { useCallback, useRef, useState } from "react";
import { useCallback, useMemo, useRef, useState } from "react";
import { Alert, Text, TextInput, View } from "react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { useIsCompactFormFactor } from "@/constants/layout";
@@ -74,6 +74,11 @@ export function PairLinkModal({ visible, onClose, onCancel, onSaved }: PairLinkM
inputRef.current?.clear();
}, []);
const pairIcon = useMemo(
() => <Link size={16} color={theme.colors.palette.white} />,
[theme.colors.palette.white],
);
const handleClose = useCallback(() => {
if (isSaving) return;
clearInput();
@@ -209,7 +214,7 @@ export function PairLinkModal({ visible, onClose, onCancel, onSaved }: PairLinkM
testID="pair-link-submit"
accessibilityRole="button"
accessibilityLabel="Pair"
leftIcon={<Link size={16} color={theme.colors.palette.white} />}
leftIcon={pairIcon}
>
{isSaving ? "Pairing..." : "Pair"}
</Button>

View File

@@ -72,6 +72,23 @@ export function PrPane({ data }: { data: PrPaneData }) {
[data.activity],
);
const checkSuccessIcon = useMemo(
() => <CircleCheck size={12} color={theme.colors.statusSuccess} />,
[theme.colors.statusSuccess],
);
const checkDangerIcon = useMemo(
() => <CircleX size={12} color={theme.colors.statusDanger} />,
[theme.colors.statusDanger],
);
const checkWarningIcon = useMemo(
() => <CircleDot size={12} color={theme.colors.statusWarning} />,
[theme.colors.statusWarning],
);
const commentIcon = useMemo(
() => <MessageSquare size={11} color={theme.colors.foregroundMuted} />,
[theme.colors.foregroundMuted],
);
return (
<View style={styles.root}>
<Pressable onPress={handleOpenPrUrl} style={styles.header}>
@@ -105,17 +122,13 @@ export function PrPane({ data }: { data: PrPaneData }) {
<SummaryPill
count={passed}
color={theme.colors.statusSuccess}
icon={<CircleCheck size={12} color={theme.colors.statusSuccess} />}
/>
<SummaryPill
count={failed}
color={theme.colors.statusDanger}
icon={<CircleX size={12} color={theme.colors.statusDanger} />}
icon={checkSuccessIcon}
/>
<SummaryPill count={failed} color={theme.colors.statusDanger} icon={checkDangerIcon} />
<SummaryPill
count={pending}
color={theme.colors.statusWarning}
icon={<CircleDot size={12} color={theme.colors.statusWarning} />}
icon={checkWarningIcon}
/>
</>
}
@@ -136,17 +149,17 @@ export function PrPane({ data }: { data: PrPaneData }) {
<SummaryPill
count={approvals}
color={theme.colors.statusSuccess}
icon={<CircleCheck size={12} color={theme.colors.statusSuccess} />}
icon={checkSuccessIcon}
/>
<SummaryPill
count={changesRequested}
color={theme.colors.statusDanger}
icon={<CircleX size={12} color={theme.colors.statusDanger} />}
icon={checkDangerIcon}
/>
<SummaryPill
count={commentCount}
color={theme.colors.foregroundMuted}
icon={<MessageSquare size={11} color={theme.colors.foregroundMuted} />}
icon={commentIcon}
/>
</>
}

View File

@@ -884,6 +884,11 @@ function ProjectHeaderRow({
[isDragging, selected, isHovered],
);
const removeProjectLeadingIcon = useMemo(
() => <Trash2 size={14} color={theme.colors.foregroundMuted} />,
[theme.colors.foregroundMuted],
);
const rowChildren = (
<>
<View style={styles.projectRowLeft}>
@@ -938,7 +943,7 @@ function ProjectHeaderRow({
<DropdownMenuContent align="end" width={220}>
<DropdownMenuItem
testID={`sidebar-project-menu-remove-${project.projectKey}`}
leading={<Trash2 size={14} color={theme.colors.foregroundMuted} />}
leading={removeProjectLeadingIcon}
status={removeProjectStatus}
pendingLabel="Removing..."
onSelect={onRemoveProject}
@@ -1078,6 +1083,18 @@ function WorkspaceRowInner({
],
[isHovered, isCreating],
);
const copyLeadingIcon = useMemo(
() => <Copy size={14} color={theme.colors.foregroundMuted} />,
[theme.colors.foregroundMuted],
);
const archiveLeadingIcon = useMemo(
() => <Archive size={14} color={theme.colors.foregroundMuted} />,
[theme.colors.foregroundMuted],
);
const archiveTrailing = useMemo(
() => (archiveShortcutKeys ? <Shortcut chord={archiveShortcutKeys} /> : null),
[archiveShortcutKeys],
);
return (
<WorkspaceHoverCard workspace={workspace} prHint={prHint} isDragging={isDragging}>
@@ -1143,7 +1160,7 @@ function WorkspaceRowInner({
{onCopyPath ? (
<DropdownMenuItem
testID={`sidebar-workspace-menu-copy-path-${workspace.workspaceKey}`}
leading={<Copy size={14} color={theme.colors.foregroundMuted} />}
leading={copyLeadingIcon}
onSelect={onCopyPath}
>
Copy path
@@ -1152,7 +1169,7 @@ function WorkspaceRowInner({
{onCopyBranchName ? (
<DropdownMenuItem
testID={`sidebar-workspace-menu-copy-branch-name-${workspace.workspaceKey}`}
leading={<Copy size={14} color={theme.colors.foregroundMuted} />}
leading={copyLeadingIcon}
onSelect={onCopyBranchName}
>
Copy branch name
@@ -1160,10 +1177,8 @@ function WorkspaceRowInner({
) : null}
<DropdownMenuItem
testID={`sidebar-workspace-menu-archive-${workspace.workspaceKey}`}
leading={<Archive size={14} color={theme.colors.foregroundMuted} />}
trailing={
archiveShortcutKeys ? <Shortcut chord={archiveShortcutKeys} /> : null
}
leading={archiveLeadingIcon}
trailing={archiveTrailing}
status={archiveStatus}
pendingLabel={archivePendingLabel}
onSelect={onArchive}