chore(lint): hoist jsx-as-prop in desktop components

Memoize inline JSX passed as props across desktop-permissions-section,
desktop-updates-section, integrations-section, and pair-device-section
to satisfy react-perf/jsx-no-jsx-as-prop.
This commit is contained in:
Mohamed Boudra
2026-04-24 05:42:26 +07:00
parent edfdf22ea0
commit 67b29d10f6
4 changed files with 128 additions and 88 deletions

View File

@@ -43,26 +43,34 @@ export function DesktopPermissionsSection() {
void sendTestNotification();
}, [sendTestNotification]);
if (!isDesktopApp) {
return null;
}
const isBusy = isRefreshing || requestingPermission !== null;
const notificationsGranted = snapshot?.notifications.state === "granted";
const refreshButton = (
<Button
variant="ghost"
size="sm"
leftIcon={<RotateCw size={theme.iconSize.md} color={theme.colors.foregroundMuted} />}
onPress={handleRefreshPress}
disabled={isBusy}
accessibilityLabel="Refresh desktop permissions"
>
{isRefreshing ? "Refreshing..." : "Refresh"}
</Button>
const refreshIcon = useMemo(
() => <RotateCw size={theme.iconSize.md} color={theme.colors.foregroundMuted} />,
[theme.iconSize.md, theme.colors.foregroundMuted],
);
const refreshButton = useMemo(
() => (
<Button
variant="ghost"
size="sm"
leftIcon={refreshIcon}
onPress={handleRefreshPress}
disabled={isBusy}
accessibilityLabel="Refresh desktop permissions"
>
{isRefreshing ? "Refreshing..." : "Refresh"}
</Button>
),
[refreshIcon, handleRefreshPress, isBusy, isRefreshing],
);
if (!isDesktopApp) {
return null;
}
return (
<SettingsSection title="Permissions" trailing={refreshButton}>
<View style={settingsStyles.card}>

View File

@@ -1,4 +1,4 @@
import { useCallback, useState } from "react";
import { useCallback, useMemo, useState } from "react";
import { ActivityIndicator, Alert, Text, View } from "react-native";
import * as Clipboard from "expo-clipboard";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
@@ -247,24 +247,56 @@ export function LocalDaemonSection() {
const handleCloseLogsModal = useCallback(() => setIsLogsModalOpen(false), []);
const handleCloseCliStatusModal = useCallback(() => setIsCliStatusModalOpen(false), []);
const advancedSettingsIcon = useMemo(
() => <ArrowUpRight size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />,
[theme.iconSize.sm, theme.colors.foregroundMuted],
);
const playIcon = useMemo(
() => <Play size={theme.iconSize.sm} color={theme.colors.foreground} />,
[theme.iconSize.sm, theme.colors.foreground],
);
const pauseIcon = useMemo(
() => <Pause size={theme.iconSize.sm} color={theme.colors.foreground} />,
[theme.iconSize.sm, theme.colors.foreground],
);
const rotateIcon = useMemo(
() => <RotateCw size={theme.iconSize.sm} color={theme.colors.foreground} />,
[theme.iconSize.sm, theme.colors.foreground],
);
const copyIcon = useMemo(
() => <Copy size={theme.iconSize.sm} color={theme.colors.foreground} />,
[theme.iconSize.sm, theme.colors.foreground],
);
const fileTextIcon = useMemo(
() => <FileText size={theme.iconSize.sm} color={theme.colors.foreground} />,
[theme.iconSize.sm, theme.colors.foreground],
);
const activityIcon = useMemo(
() => <Activity size={theme.iconSize.sm} color={theme.colors.foreground} />,
[theme.iconSize.sm, theme.colors.foreground],
);
const advancedSettingsButton = useMemo(
() => (
<Button
variant="ghost"
size="sm"
leftIcon={advancedSettingsIcon}
textStyle={settingsStyles.sectionHeaderLinkText}
style={settingsStyles.sectionHeaderLink}
onPress={handleOpenAdvancedSettings}
accessibilityLabel="Open advanced daemon settings"
>
Advanced settings
</Button>
),
[advancedSettingsIcon, handleOpenAdvancedSettings],
);
if (!showSection) {
return null;
}
const advancedSettingsButton = (
<Button
variant="ghost"
size="sm"
leftIcon={<ArrowUpRight size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />}
textStyle={settingsStyles.sectionHeaderLinkText}
style={settingsStyles.sectionHeaderLink}
onPress={handleOpenAdvancedSettings}
accessibilityLabel="Open advanced daemon settings"
>
Advanced settings
</Button>
);
return (
<SettingsSection
title="Daemon"
@@ -302,13 +334,7 @@ export function LocalDaemonSection() {
<Button
variant="outline"
size="sm"
leftIcon={
isDaemonManagementPaused ? (
<Play size={theme.iconSize.sm} color={theme.colors.foreground} />
) : (
<Pause size={theme.iconSize.sm} color={theme.colors.foreground} />
)
}
leftIcon={isDaemonManagementPaused ? playIcon : pauseIcon}
onPress={handleToggleDaemonManagement}
disabled={isUpdatingDaemonManagement}
>
@@ -330,7 +356,7 @@ export function LocalDaemonSection() {
<Button
variant="outline"
size="sm"
leftIcon={<RotateCw size={theme.iconSize.sm} color={theme.colors.foreground} />}
leftIcon={rotateIcon}
onPress={handleUpdateLocalDaemon}
disabled={isRestartingDaemon}
>
@@ -353,7 +379,7 @@ export function LocalDaemonSection() {
<Button
variant="outline"
size="sm"
leftIcon={<Copy size={theme.iconSize.sm} color={theme.colors.foreground} />}
leftIcon={copyIcon}
onPress={handleCopyLogPath}
>
Copy path
@@ -362,7 +388,7 @@ export function LocalDaemonSection() {
<Button
variant="outline"
size="sm"
leftIcon={<FileText size={theme.iconSize.sm} color={theme.colors.foreground} />}
leftIcon={fileTextIcon}
onPress={handleOpenLogs}
disabled={!daemonLogs}
>
@@ -380,7 +406,7 @@ export function LocalDaemonSection() {
<Button
variant="outline"
size="sm"
leftIcon={<Activity size={theme.iconSize.sm} color={theme.colors.foreground} />}
leftIcon={activityIcon}
onPress={handleRunCliStatus}
disabled={isLoadingCliStatus}
>

View File

@@ -1,4 +1,4 @@
import { useCallback, useState } from "react";
import { useCallback, useMemo, useState } from "react";
import { Text, View } from "react-native";
import { useFocusEffect } from "@react-navigation/native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
@@ -85,37 +85,45 @@ export function IntegrationsSection() {
void openExternalUrl(SKILLS_DOCS_URL);
}, []);
const arrowIcon = useMemo(
() => <ArrowUpRight size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />,
[theme.iconSize.sm, theme.colors.foregroundMuted],
);
const trailing = useMemo(
() => (
<View style={styles.headerLinks}>
<Button
variant="ghost"
size="sm"
leftIcon={arrowIcon}
textStyle={settingsStyles.sectionHeaderLinkText}
style={settingsStyles.sectionHeaderLink}
onPress={handleOpenCliDocs}
accessibilityLabel="Open CLI documentation"
>
CLI docs
</Button>
<Button
variant="ghost"
size="sm"
leftIcon={arrowIcon}
textStyle={settingsStyles.sectionHeaderLinkText}
style={settingsStyles.sectionHeaderLink}
onPress={handleOpenSkillsDocs}
accessibilityLabel="Open skills documentation"
>
Skills docs
</Button>
</View>
),
[arrowIcon, handleOpenCliDocs, handleOpenSkillsDocs],
);
if (!showSection) {
return null;
}
const trailing = (
<View style={styles.headerLinks}>
<Button
variant="ghost"
size="sm"
leftIcon={<ArrowUpRight size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />}
textStyle={settingsStyles.sectionHeaderLinkText}
style={settingsStyles.sectionHeaderLink}
onPress={handleOpenCliDocs}
accessibilityLabel="Open CLI documentation"
>
CLI docs
</Button>
<Button
variant="ghost"
size="sm"
leftIcon={<ArrowUpRight size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />}
textStyle={settingsStyles.sectionHeaderLinkText}
style={settingsStyles.sectionHeaderLink}
onPress={handleOpenSkillsDocs}
accessibilityLabel="Open skills documentation"
>
Skills docs
</Button>
</View>
);
return (
<SettingsSection title="Integrations" trailing={trailing}>
<View style={settingsStyles.card}>

View File

@@ -55,6 +55,20 @@ export function PairDeviceSection() {
[qrQuery.data],
);
const retryIcon = useMemo(
() => <RotateCw size={theme.iconSize.sm} color={theme.colors.foreground} />,
[theme.iconSize.sm, theme.colors.foreground],
);
const copyButtonIcon = useMemo(
() =>
copied ? (
<Check size={theme.iconSize.sm} color={theme.colors.accent} />
) : (
<Copy size={theme.iconSize.sm} color={theme.colors.foreground} />
),
[copied, theme.iconSize.sm, theme.colors.accent, theme.colors.foreground],
);
if (!showSection) return null;
return (
@@ -72,12 +86,7 @@ export function PairDeviceSection() {
? pairingQuery.error.message
: "Failed to load pairing offer."}
</Text>
<Button
variant="outline"
size="sm"
leftIcon={<RotateCw size={theme.iconSize.sm} color={theme.colors.foreground} />}
onPress={handleRefetch}
>
<Button variant="outline" size="sm" leftIcon={retryIcon} onPress={handleRefetch}>
Retry
</Button>
</View>
@@ -88,12 +97,7 @@ export function PairDeviceSection() {
? "Relay is not enabled. Enable relay to pair a device."
: "Pairing offer unavailable."}
</Text>
<Button
variant="outline"
size="sm"
leftIcon={<RotateCw size={theme.iconSize.sm} color={theme.colors.foreground} />}
onPress={handleRefetch}
>
<Button variant="outline" size="sm" leftIcon={retryIcon} onPress={handleRefetch}>
Retry
</Button>
</View>
@@ -124,13 +128,7 @@ export function PairDeviceSection() {
<Button
variant="outline"
size="sm"
leftIcon={
copied ? (
<Check size={theme.iconSize.sm} color={theme.colors.accent} />
) : (
<Copy size={theme.iconSize.sm} color={theme.colors.foreground} />
)
}
leftIcon={copyButtonIcon}
onPress={handleCopyPress}
>
{copied ? "Copied" : "Copy"}