mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
chore(lint): hoist jsx-as-prop in app components batch 2
Memoize inline JSX passed as props across agent-list, agent-status-bar, file-explorer-pane, git-actions-split-button, and message to satisfy react-perf/jsx-no-jsx-as-prop.
This commit is contained in:
@@ -215,6 +215,11 @@ function SessionRow({
|
||||
[isSelected],
|
||||
);
|
||||
|
||||
const archivedIcon = useMemo(
|
||||
() => <Archive size={theme.fontSize.xs} color={theme.colors.foregroundMuted} />,
|
||||
[theme.fontSize.xs, theme.colors.foregroundMuted],
|
||||
);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={pressableStyle}
|
||||
@@ -230,12 +235,7 @@ function SessionRow({
|
||||
<Text style={sessionTitleStyle} numberOfLines={1}>
|
||||
{agent.title || "New session"}
|
||||
</Text>
|
||||
{agent.archivedAt ? (
|
||||
<SessionBadge
|
||||
label="Archived"
|
||||
icon={<Archive size={theme.fontSize.xs} color={theme.colors.foregroundMuted} />}
|
||||
/>
|
||||
) : null}
|
||||
{agent.archivedAt ? <SessionBadge label="Archived" icon={archivedIcon} /> : null}
|
||||
{(agent.pendingPermissionCount ?? 0) > 0 ? (
|
||||
<SessionBadge label={`${agent.pendingPermissionCount} pending`} tone="warning" />
|
||||
) : null}
|
||||
@@ -428,6 +428,19 @@ export function AgentList({
|
||||
[isActionDaemonUnavailable],
|
||||
);
|
||||
|
||||
const refreshControl = useMemo(
|
||||
() =>
|
||||
onRefresh ? (
|
||||
<RefreshControl
|
||||
refreshing={isRefreshing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={theme.colors.foregroundMuted}
|
||||
colors={refreshColors}
|
||||
/>
|
||||
) : undefined,
|
||||
[onRefresh, isRefreshing, theme.colors.foregroundMuted, refreshColors],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FlatList
|
||||
@@ -439,16 +452,7 @@ export function AgentList({
|
||||
showsVerticalScrollIndicator={false}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
ListFooterComponent={listFooterComponent}
|
||||
refreshControl={
|
||||
onRefresh ? (
|
||||
<RefreshControl
|
||||
refreshing={isRefreshing}
|
||||
onRefresh={onRefresh}
|
||||
tintColor={theme.colors.foregroundMuted}
|
||||
colors={refreshColors}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
refreshControl={refreshControl}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
|
||||
@@ -316,19 +316,17 @@ function ControlledStatusBar({
|
||||
selected: boolean;
|
||||
active: boolean;
|
||||
onPress: () => void;
|
||||
}) => {
|
||||
const visuals = getModeVisuals(provider, option.id, providerDefinitions);
|
||||
const IconComponent = visuals?.icon ? MODE_ICONS[visuals.icon] : ShieldCheck;
|
||||
return (
|
||||
<ComboboxItem
|
||||
label={option.label}
|
||||
selected={selected}
|
||||
active={active}
|
||||
onPress={onPress}
|
||||
leadingSlot={<IconComponent size={16} color={theme.colors.foreground} />}
|
||||
/>
|
||||
);
|
||||
},
|
||||
}) => (
|
||||
<ModeComboboxOption
|
||||
option={option}
|
||||
selected={selected}
|
||||
active={active}
|
||||
onPress={onPress}
|
||||
provider={provider}
|
||||
providerDefinitions={providerDefinitions}
|
||||
iconColor={theme.colors.foreground}
|
||||
/>
|
||||
),
|
||||
[provider, providerDefinitions, theme.colors.foreground],
|
||||
);
|
||||
|
||||
@@ -1021,6 +1019,40 @@ function ThinkingMenuItem({
|
||||
);
|
||||
}
|
||||
|
||||
function ModeComboboxOption({
|
||||
option,
|
||||
selected,
|
||||
active,
|
||||
onPress,
|
||||
provider,
|
||||
providerDefinitions,
|
||||
iconColor,
|
||||
}: {
|
||||
option: ComboboxOption;
|
||||
selected: boolean;
|
||||
active: boolean;
|
||||
onPress: () => void;
|
||||
provider: string;
|
||||
providerDefinitions: AgentProviderDefinition[];
|
||||
iconColor: string;
|
||||
}) {
|
||||
const visuals = getModeVisuals(provider, option.id, providerDefinitions);
|
||||
const IconComponent = visuals?.icon ? MODE_ICONS[visuals.icon] : ShieldCheck;
|
||||
const leadingSlot = useMemo(
|
||||
() => <IconComponent size={16} color={iconColor} />,
|
||||
[IconComponent, iconColor],
|
||||
);
|
||||
return (
|
||||
<ComboboxItem
|
||||
label={option.label}
|
||||
selected={selected}
|
||||
active={active}
|
||||
onPress={onPress}
|
||||
leadingSlot={leadingSlot}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ModeMenuItem({
|
||||
mode,
|
||||
provider,
|
||||
@@ -1042,12 +1074,13 @@ function ModeMenuItem({
|
||||
onSelectMode?.(mode.id);
|
||||
}, [mode.id, onSelectMode]);
|
||||
|
||||
const leadingIcon = useMemo(
|
||||
() => <Icon size={16} color={theme.colors.foreground} />,
|
||||
[Icon, theme.colors.foreground],
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
selected={selected}
|
||||
onSelect={handleSelect}
|
||||
leading={<Icon size={16} color={theme.colors.foreground} />}
|
||||
>
|
||||
<DropdownMenuItem selected={selected} onSelect={handleSelect} leading={leadingIcon}>
|
||||
{mode.label}
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
|
||||
@@ -142,6 +142,15 @@ function TreeRowItem({
|
||||
[isExpanded],
|
||||
);
|
||||
|
||||
const copyLeading = useMemo(
|
||||
() => <Copy size={14} color={theme.colors.foregroundMuted} />,
|
||||
[theme.colors.foregroundMuted],
|
||||
);
|
||||
const downloadLeading = useMemo(
|
||||
() => <Download size={14} color={theme.colors.foregroundMuted} />,
|
||||
[theme.colors.foregroundMuted],
|
||||
);
|
||||
|
||||
return (
|
||||
<Pressable onPress={handlePress} style={pressableStyle}>
|
||||
{depth > 0 && Array.from({ length: depth }, (_, i) => <IndentGuide key={i} index={i} />)}
|
||||
@@ -187,17 +196,11 @@ function TreeRowItem({
|
||||
</View>
|
||||
</View>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
leading={<Copy size={14} color={theme.colors.foregroundMuted} />}
|
||||
onSelect={handleCopy}
|
||||
>
|
||||
<DropdownMenuItem leading={copyLeading} onSelect={handleCopy}>
|
||||
Copy path
|
||||
</DropdownMenuItem>
|
||||
{entry.kind === "file" ? (
|
||||
<DropdownMenuItem
|
||||
leading={<Download size={14} color={theme.colors.foregroundMuted} />}
|
||||
onSelect={handleDownload}
|
||||
>
|
||||
<DropdownMenuItem leading={downloadLeading} onSelect={handleDownload}>
|
||||
Download
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, type ReactElement } from "react";
|
||||
import { useCallback, useMemo } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Shortcut } from "@/components/ui/shortcut";
|
||||
import { useShortcutKeys } from "@/hooks/use-shortcut-keys";
|
||||
import type { ShortcutKey } from "@/utils/format-shortcut";
|
||||
import { useToast } from "@/contexts/toast-context";
|
||||
import type { GitAction, GitActions } from "@/components/git-actions-policy";
|
||||
|
||||
@@ -28,7 +29,7 @@ interface GitActionsSplitButtonProps {
|
||||
interface GitActionMenuItemProps {
|
||||
action: GitAction;
|
||||
onSelect: (action: GitAction) => void;
|
||||
trailing?: ReactElement | null;
|
||||
archiveShortcutKeys?: ShortcutKey[][] | null;
|
||||
needsSeparator?: boolean;
|
||||
showSeparator?: boolean;
|
||||
closeOnSelect?: boolean;
|
||||
@@ -37,12 +38,19 @@ interface GitActionMenuItemProps {
|
||||
function GitActionMenuItem({
|
||||
action,
|
||||
onSelect,
|
||||
trailing,
|
||||
archiveShortcutKeys,
|
||||
needsSeparator,
|
||||
showSeparator,
|
||||
closeOnSelect,
|
||||
}: GitActionMenuItemProps) {
|
||||
const handleSelect = useCallback(() => onSelect(action), [onSelect, action]);
|
||||
const trailing = useMemo(
|
||||
() =>
|
||||
action.id === "archive-worktree" && archiveShortcutKeys ? (
|
||||
<Shortcut chord={archiveShortcutKeys} />
|
||||
) : undefined,
|
||||
[action.id, archiveShortcutKeys],
|
||||
);
|
||||
return (
|
||||
<View>
|
||||
{needsSeparator && showSeparator ? <DropdownMenuSeparator /> : null}
|
||||
@@ -154,11 +162,7 @@ export function GitActionsSplitButton({ gitActions, hideLabels }: GitActionsSpli
|
||||
key={action.id}
|
||||
action={action}
|
||||
onSelect={handleActionSelect}
|
||||
trailing={
|
||||
action.id === "archive-worktree" && archiveShortcutKeys ? (
|
||||
<Shortcut chord={archiveShortcutKeys} />
|
||||
) : undefined
|
||||
}
|
||||
archiveShortcutKeys={archiveShortcutKeys}
|
||||
needsSeparator={
|
||||
action.id === "merge-from-base" || action.id === "archive-worktree"
|
||||
}
|
||||
|
||||
@@ -1129,26 +1129,27 @@ const NativeExpandableBadgeShimmer = memo(function NativeExpandableBadgeShimmer(
|
||||
[nativeShimmerPeakStyle, peakWidth, rowHeight],
|
||||
);
|
||||
|
||||
const maskElement = useMemo(
|
||||
() => (
|
||||
<View pointerEvents="none" style={nativeShimmerMaskStyle}>
|
||||
<Text style={nativeLabelMaskStyle} numberOfLines={1}>
|
||||
{label}
|
||||
</Text>
|
||||
{secondaryLabel ? (
|
||||
<Text style={nativeSecondaryMaskStyle} numberOfLines={1}>
|
||||
{secondaryLabel}
|
||||
</Text>
|
||||
) : (
|
||||
<View style={expandableBadgeStylesheet.spacer} />
|
||||
)}
|
||||
</View>
|
||||
),
|
||||
[nativeShimmerMaskStyle, nativeLabelMaskStyle, nativeSecondaryMaskStyle, label, secondaryLabel],
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={expandableBadgeStylesheet.shimmerOverlay} pointerEvents="none">
|
||||
<MaskedView
|
||||
pointerEvents="none"
|
||||
style={nativeShimmerTrackStyle}
|
||||
maskElement={
|
||||
<View pointerEvents="none" style={nativeShimmerMaskStyle}>
|
||||
<Text style={nativeLabelMaskStyle} numberOfLines={1}>
|
||||
{label}
|
||||
</Text>
|
||||
{secondaryLabel ? (
|
||||
<Text style={nativeSecondaryMaskStyle} numberOfLines={1}>
|
||||
{secondaryLabel}
|
||||
</Text>
|
||||
) : (
|
||||
<View style={expandableBadgeStylesheet.spacer} />
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
>
|
||||
<MaskedView pointerEvents="none" style={nativeShimmerTrackStyle} maskElement={maskElement}>
|
||||
<View pointerEvents="none" style={nativeShimmerTrackStyle}>
|
||||
<Animated.View pointerEvents="none" style={nativeShimmerPeakCombinedStyle}>
|
||||
<Svg width="100%" height="100%" preserveAspectRatio="none">
|
||||
|
||||
Reference in New Issue
Block a user