chore(lint): memoize inline styles in dropdown-menu

This commit is contained in:
Mohamed Boudra
2026-04-24 02:29:54 +07:00
parent ce51fa79ba
commit 6b97e49c19
2 changed files with 75 additions and 50 deletions

View File

@@ -34,6 +34,8 @@ import { useWebScrollbarStyle } from "@/hooks/use-web-scrollbar-style";
// Action status for menu items with loading/success feedback
export type ActionStatus = "idle" | "pending" | "success";
const DROPDOWN_SCROLL_CONTENT_STYLE = { flexGrow: 1 } as const;
type Placement = "top" | "bottom" | "left" | "right";
type Alignment = "start" | "center" | "end";
@@ -432,6 +434,20 @@ export function DropdownMenuContent({
...(typeof maxWidth === "number" ? { maxWidth } : null),
};
const contentStyle = useMemo(
() => [
styles.content,
resolvedWidthStyle,
{
position: "absolute" as const,
top: position?.y ?? -9999,
left: position?.x ?? -9999,
transformOrigin: getTransformOrigin(actualPlacement, align),
},
],
[resolvedWidthStyle, position?.x, position?.y, actualPlacement, align],
);
return (
<Modal
visible={modalVisible}
@@ -461,22 +477,13 @@ export function DropdownMenuContent({
collapsable={false}
testID={testID}
onLayout={handleContentLayout}
style={[
styles.content,
resolvedWidthStyle,
{
position: "absolute",
top: position?.y ?? -9999,
left: position?.x ?? -9999,
transformOrigin: getTransformOrigin(actualPlacement, align),
},
]}
style={contentStyle}
>
<ScrollView
bounces={false}
showsVerticalScrollIndicator
style={webScrollbarStyle}
contentContainerStyle={{ flexGrow: 1 }}
contentContainerStyle={DROPDOWN_SCROLL_CONTENT_STYLE}
>
{children}
</ScrollView>
@@ -492,8 +499,9 @@ export function DropdownMenuLabel({
style,
testID,
}: PropsWithChildren<{ style?: ViewStyle | ViewStyle[]; testID?: string }>): ReactElement {
const labelContainerStyle = useMemo(() => [styles.labelContainer, style], [style]);
return (
<View style={[styles.labelContainer, style]} testID={testID}>
<View style={labelContainerStyle} testID={testID}>
<Text style={styles.labelText}>{children}</Text>
</View>
);
@@ -506,7 +514,8 @@ export function DropdownMenuSeparator({
style?: ViewStyle;
testID?: string;
}): ReactElement {
return <View style={[styles.separator, style]} testID={testID} />;
const separatorStyle = useMemo(() => [styles.separator, style], [style]);
return <View style={separatorStyle} testID={testID} />;
}
export function DropdownMenuHint({
@@ -618,6 +627,25 @@ export function DropdownMenuItem({
[selected, selectedVariant, isDisabled, muted],
);
const itemTextStyle = useMemo(
() => [
styles.itemText,
destructive && !isSuccess ? styles.itemTextDestructive : null,
isSuccess ? styles.itemTextSuccess : null,
selected && selectedVariant === "accent" ? styles.itemTextSelectedAccent : null,
muted && !isDisabled ? styles.itemTextMuted : null,
],
[destructive, isSuccess, selected, selectedVariant, muted, isDisabled],
);
const itemDescriptionStyle = useMemo(
() => [
styles.itemDescription,
selected && selectedVariant === "accent" ? styles.itemDescriptionSelectedAccent : null,
],
[selected, selectedVariant],
);
const content = (
<Pressable
testID={testID}
@@ -633,28 +661,11 @@ export function DropdownMenuItem({
) : null}
{leadingContent ? <View style={styles.leadingSlot}>{leadingContent}</View> : null}
<View style={styles.itemContent}>
<Text
numberOfLines={1}
style={[
styles.itemText,
destructive && !isSuccess ? styles.itemTextDestructive : null,
isSuccess ? styles.itemTextSuccess : null,
selected && selectedVariant === "accent" ? styles.itemTextSelectedAccent : null,
muted && !isDisabled ? styles.itemTextMuted : null,
]}
>
<Text numberOfLines={1} style={itemTextStyle}>
{label}
</Text>
{description && !isPending && !isSuccess ? (
<Text
numberOfLines={2}
style={[
styles.itemDescription,
selected && selectedVariant === "accent"
? styles.itemDescriptionSelectedAccent
: null,
]}
>
<Text numberOfLines={2} style={itemDescriptionStyle}>
{description}
</Text>
) : null}

View File

@@ -130,10 +130,34 @@ export function WorkspaceTabIcon({
bucket: presentation.statusBucket,
});
const Icon = presentation.icon;
const agentIconWrapperStyle = useMemo(
() => [styles.agentIconWrapper, { width: size, height: size }],
[size],
);
const statusDotStyle = useMemo(
() => [
styles.statusDot,
{
backgroundColor: statusDotColor ?? undefined,
borderColor: statusDotBorderColor ?? theme.colors.surface0,
width: statusDotSize,
height: statusDotSize,
right: statusDotOffset,
bottom: statusDotOffset,
},
],
[
statusDotColor,
statusDotBorderColor,
theme.colors.surface0,
statusDotSize,
statusDotOffset,
],
);
if (shouldShowLoader) {
return (
<View style={[styles.agentIconWrapper, { width: size, height: size }]}>
<View style={agentIconWrapperStyle}>
<SyncedLoader
size={size - 1}
color={
@@ -147,23 +171,9 @@ export function WorkspaceTabIcon({
}
return (
<View style={[styles.agentIconWrapper, { width: size, height: size }]}>
<View style={agentIconWrapperStyle}>
<Icon size={size} color={iconColor} />
{statusDotColor ? (
<View
style={[
styles.statusDot,
{
backgroundColor: statusDotColor,
borderColor: statusDotBorderColor ?? theme.colors.surface0,
width: statusDotSize,
height: statusDotSize,
right: statusDotOffset,
bottom: statusDotOffset,
},
]}
/>
) : null}
{statusDotColor ? <View style={statusDotStyle} /> : null}
</View>
);
}
@@ -191,8 +201,12 @@ export function WorkspaceTabOptionRow({
],
[active],
);
const optionRowStyle = useMemo(
() => [styles.optionRow, active && styles.optionRowActive],
[active],
);
return (
<View style={[styles.optionRow, active && styles.optionRowActive]}>
<View style={optionRowStyle}>
<Pressable onPress={onPress} style={pressableStyle}>
<View style={styles.optionLeadingSlot}>
<WorkspaceTabIcon presentation={presentation} active={selected || active} />