mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
chore(lint): hoist inline arrays in app (jsx-no-new-array-as-prop)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Modal, Pressable, Text, View } from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
@@ -36,6 +36,17 @@ export function AttachmentLightbox({ metadata, onClose }: AttachmentLightboxProp
|
||||
};
|
||||
}, [metadata, onClose]);
|
||||
|
||||
const closeButtonStyle = useMemo(
|
||||
() => [
|
||||
styles.closeButton,
|
||||
{
|
||||
top: insets.top + theme.spacing[3],
|
||||
right: insets.right + theme.spacing[3],
|
||||
},
|
||||
],
|
||||
[insets.top, insets.right, theme.spacing],
|
||||
);
|
||||
|
||||
if (!metadata) {
|
||||
return null;
|
||||
}
|
||||
@@ -74,13 +85,7 @@ export function AttachmentLightbox({ metadata, onClose }: AttachmentLightboxProp
|
||||
accessibilityLabel="Close image"
|
||||
hitSlop={8}
|
||||
onPress={onClose}
|
||||
style={[
|
||||
styles.closeButton,
|
||||
{
|
||||
top: insets.top + theme.spacing[3],
|
||||
right: insets.right + theme.spacing[3],
|
||||
},
|
||||
]}
|
||||
style={closeButtonStyle}
|
||||
>
|
||||
<X size={16} color={theme.colors.foregroundMuted} />
|
||||
</Pressable>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { type ReactNode, useMemo, useState } from "react";
|
||||
import { Pressable, View } from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { X } from "lucide-react-native";
|
||||
@@ -30,6 +30,10 @@ export function AttachmentPill({
|
||||
const [isCloseHovered, setIsCloseHovered] = useState(false);
|
||||
const alwaysShow = isNative || isCompact;
|
||||
const showRemove = alwaysShow || isBodyHovered || isCloseHovered;
|
||||
const closeButtonStyle = useMemo(
|
||||
() => [styles.closeButton, !showRemove && styles.closeButtonHidden],
|
||||
[showRemove],
|
||||
);
|
||||
return (
|
||||
<View style={styles.wrapper}>
|
||||
<Pressable
|
||||
@@ -52,7 +56,7 @@ export function AttachmentPill({
|
||||
hitSlop={8}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={removeAccessibilityLabel}
|
||||
style={[styles.closeButton, !showRemove && styles.closeButtonHidden]}
|
||||
style={closeButtonStyle}
|
||||
>
|
||||
<X size={12} color={theme.colors.foregroundMuted} />
|
||||
</Pressable>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useMemo } from "react";
|
||||
import { ScrollView, type LayoutChangeEvent, type StyleProp, type ViewStyle } from "react-native";
|
||||
import { useWebScrollbarStyle } from "@/hooks/use-web-scrollbar-style";
|
||||
|
||||
@@ -16,13 +17,14 @@ export function DiffScroll({
|
||||
contentContainerStyle,
|
||||
}: DiffScrollProps) {
|
||||
const webScrollbarStyle = useWebScrollbarStyle();
|
||||
const combinedStyle = useMemo(() => [style, webScrollbarStyle], [style, webScrollbarStyle]);
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
showsHorizontalScrollIndicator
|
||||
style={[style, webScrollbarStyle]}
|
||||
style={combinedStyle}
|
||||
contentContainerStyle={contentContainerStyle}
|
||||
onLayout={(e: LayoutChangeEvent) => onScrollViewWidthChange(e.nativeEvent.layout.width)}
|
||||
>
|
||||
|
||||
@@ -90,11 +90,13 @@ afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
const DATA: string[] = ["alpha", "beta"];
|
||||
|
||||
function renderList(): void {
|
||||
act(() => {
|
||||
root?.render(
|
||||
<DraggableList
|
||||
data={["alpha", "beta"]}
|
||||
data={DATA}
|
||||
keyExtractor={(item) => item}
|
||||
onDragEnd={vi.fn()}
|
||||
renderItem={({ item, isActive }) => (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { View, Text } from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import Animated, { useAnimatedStyle, withTiming, useSharedValue } from "react-native-reanimated";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Upload } from "lucide-react-native";
|
||||
import { useFileDropZone } from "@/hooks/use-file-drop-zone";
|
||||
import type { ImageAttachment } from "./message-input";
|
||||
@@ -33,6 +33,11 @@ export function FileDropZone({ children, onFilesDropped, disabled = false }: Fil
|
||||
pointerEvents: overlayOpacity.value > 0 ? "auto" : "none",
|
||||
}));
|
||||
|
||||
const overlayStyle = useMemo(
|
||||
() => [styles.overlay, overlayAnimatedStyle],
|
||||
[overlayAnimatedStyle],
|
||||
);
|
||||
|
||||
// On non-web platforms, just render children
|
||||
if (!IS_WEB) {
|
||||
return <>{children}</>;
|
||||
@@ -47,7 +52,7 @@ export function FileDropZone({ children, onFilesDropped, disabled = false }: Fil
|
||||
{children}
|
||||
|
||||
{/* Drop overlay */}
|
||||
<Animated.View style={[styles.overlay, overlayAnimatedStyle]}>
|
||||
<Animated.View style={overlayStyle}>
|
||||
{/* Backdrop */}
|
||||
<View style={styles.backdrop} />
|
||||
{/* Content */}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
import { useMemo, type ReactElement, type ReactNode } from "react";
|
||||
import { Text, View, type PressableProps, type StyleProp, type ViewStyle } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
@@ -42,6 +42,8 @@ export function HeaderToggleButton({
|
||||
? ({ "aria-expanded": expandedState } as any)
|
||||
: null;
|
||||
|
||||
const combinedStyle = useMemo(() => [headerIconSlotStyle.slot, style], [style]);
|
||||
|
||||
return (
|
||||
<Tooltip delayDuration={tooltipDelayDuration} enabledOnDesktop enabledOnMobile={false}>
|
||||
<TooltipTrigger
|
||||
@@ -51,7 +53,7 @@ export function HeaderToggleButton({
|
||||
onPress={(e) => {
|
||||
onPress(e);
|
||||
}}
|
||||
style={[headerIconSlotStyle.slot, style]}
|
||||
style={combinedStyle}
|
||||
>
|
||||
{typeof children === "function"
|
||||
? (state: { pressed: boolean; hovered?: boolean }) =>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { useMemo, type ReactNode } from "react";
|
||||
import { Text, type StyleProp, type TextStyle } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
|
||||
@@ -15,8 +15,9 @@ interface ScreenTitleProps {
|
||||
* HeaderIconBadge) — never nested inside this component.
|
||||
*/
|
||||
export function ScreenTitle({ children, numberOfLines = 1, testID, style }: ScreenTitleProps) {
|
||||
const combinedStyle = useMemo(() => [styles.text, style], [style]);
|
||||
return (
|
||||
<Text style={[styles.text, style]} numberOfLines={numberOfLines} testID={testID}>
|
||||
<Text style={combinedStyle} numberOfLines={numberOfLines} testID={testID}>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,8 @@ import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
||||
import { getShortcutOs } from "@/utils/shortcut-platform";
|
||||
import { buildKeyboardShortcutHelpSections } from "@/keyboard/keyboard-shortcuts";
|
||||
|
||||
const SNAP_POINTS: string[] = ["70%", "92%"];
|
||||
|
||||
export function KeyboardShortcutsDialog() {
|
||||
const open = useKeyboardShortcutsStore((s) => s.shortcutsDialogOpen);
|
||||
const setOpen = useKeyboardShortcutsStore((s) => s.setShortcutsDialogOpen);
|
||||
@@ -25,7 +27,7 @@ export function KeyboardShortcutsDialog() {
|
||||
visible={open}
|
||||
onClose={() => setOpen(false)}
|
||||
testID="keyboard-shortcuts-dialog"
|
||||
snapPoints={["70%", "92%"]}
|
||||
snapPoints={SNAP_POINTS}
|
||||
>
|
||||
<View testID="keyboard-shortcuts-dialog-content" style={styles.content}>
|
||||
{sections.map((section) => (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useMemo } from "react";
|
||||
import { Pressable, Text, View } from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import type { LucideIcon } from "lucide-react-native";
|
||||
@@ -47,7 +48,7 @@ export function SidebarHeaderRow({
|
||||
return (
|
||||
<>
|
||||
<Icon size={theme.iconSize.md} color={iconColor} />
|
||||
<Text style={[styles.label, isHighlighted && styles.labelHighlighted]}>{label}</Text>
|
||||
<SidebarHeaderRowLabel label={label} isHighlighted={isHighlighted} />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
@@ -56,6 +57,20 @@ export function SidebarHeaderRow({
|
||||
);
|
||||
}
|
||||
|
||||
function SidebarHeaderRowLabel({
|
||||
label,
|
||||
isHighlighted,
|
||||
}: {
|
||||
label: string;
|
||||
isHighlighted: boolean;
|
||||
}) {
|
||||
const labelStyle = useMemo(
|
||||
() => [styles.label, isHighlighted && styles.labelHighlighted],
|
||||
[isHighlighted],
|
||||
);
|
||||
return <Text style={labelStyle}>{label}</Text>;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
container: {
|
||||
height: {
|
||||
|
||||
@@ -73,11 +73,13 @@ afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
const DATA: string[] = ["alpha", "beta"];
|
||||
|
||||
function renderList(): void {
|
||||
act(() => {
|
||||
root?.render(
|
||||
<SortableInlineList
|
||||
data={["alpha", "beta"]}
|
||||
data={DATA}
|
||||
keyExtractor={(item) => item}
|
||||
onDragEnd={vi.fn()}
|
||||
renderItem={({ item, isActive }) => (
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { useState, type ComponentType, type PropsWithChildren, type ReactElement } from "react";
|
||||
import {
|
||||
useMemo,
|
||||
useState,
|
||||
type ComponentType,
|
||||
type PropsWithChildren,
|
||||
type ReactElement,
|
||||
} from "react";
|
||||
import { Pressable, Text, View } from "react-native";
|
||||
import type { PressableProps, StyleProp, TextStyle, ViewStyle } from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
@@ -119,14 +125,17 @@ export function Button({
|
||||
const sizeStyle = size === "sm" ? styles.sm : size === "lg" ? styles.lg : styles.md;
|
||||
const isGhostHovered = hovered && variant === "ghost";
|
||||
|
||||
const resolvedTextStyle = [
|
||||
styles.text,
|
||||
variant === "default" ? styles.textDefault : null,
|
||||
variant === "destructive" ? styles.textDestructive : null,
|
||||
variant === "ghost" ? styles.textGhost : null,
|
||||
textStyle,
|
||||
isGhostHovered ? styles.textGhostHovered : null,
|
||||
];
|
||||
const resolvedTextStyle = useMemo(
|
||||
() => [
|
||||
styles.text,
|
||||
variant === "default" ? styles.textDefault : null,
|
||||
variant === "destructive" ? styles.textDestructive : null,
|
||||
variant === "ghost" ? styles.textGhost : null,
|
||||
textStyle,
|
||||
isGhostHovered ? styles.textGhostHovered : null,
|
||||
],
|
||||
[variant, textStyle, isGhostHovered],
|
||||
);
|
||||
|
||||
function renderIcon() {
|
||||
if (!leftIcon) return null;
|
||||
|
||||
@@ -26,6 +26,8 @@ function toProjectIconDataUri(icon: { mimeType: string; data: string } | null):
|
||||
return `data:${icon.mimeType};base64,${icon.data}`;
|
||||
}
|
||||
|
||||
const SNAP_POINTS: string[] = ["82%", "94%"];
|
||||
|
||||
export function WorkspaceSetupDialog() {
|
||||
const { theme } = useUnistyles();
|
||||
const toast = useToast();
|
||||
@@ -284,7 +286,7 @@ export function WorkspaceSetupDialog() {
|
||||
subtitle={subtitleContent}
|
||||
visible={true}
|
||||
onClose={handleClose}
|
||||
snapPoints={["82%", "94%"]}
|
||||
snapPoints={SNAP_POINTS}
|
||||
testID="workspace-setup-dialog"
|
||||
desktopMaxWidth={640}
|
||||
onFilesDropped={handleFilesDropped}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useMemo } from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { Check } from "lucide-react-native";
|
||||
@@ -38,8 +39,13 @@ export function DesktopPermissionRow({
|
||||
state !== "prompt" &&
|
||||
state !== "not-granted";
|
||||
|
||||
const rowStyle = useMemo(
|
||||
() => [settingsStyles.row, showBorder && settingsStyles.rowBorder],
|
||||
[showBorder],
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={[settingsStyles.row, showBorder && settingsStyles.rowBorder]}>
|
||||
<View style={rowStyle}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>{title}</Text>
|
||||
</View>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useMemo } from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { RotateCw } from "lucide-react-native";
|
||||
@@ -27,6 +28,10 @@ export function DesktopPermissionsSection() {
|
||||
|
||||
const isBusy = isRefreshing || requestingPermission !== null;
|
||||
const notificationsGranted = snapshot?.notifications.state === "granted";
|
||||
const errorTextStyle = useMemo(
|
||||
() => [styles.errorText, { color: theme.colors.destructive }],
|
||||
[theme.colors.destructive],
|
||||
);
|
||||
|
||||
const refreshButton = (
|
||||
<Button
|
||||
@@ -60,11 +65,7 @@ export function DesktopPermissionsSection() {
|
||||
void sendTestNotification();
|
||||
}}
|
||||
/>
|
||||
{testNotificationError ? (
|
||||
<Text style={[styles.errorText, { color: theme.colors.destructive }]}>
|
||||
{testNotificationError}
|
||||
</Text>
|
||||
) : null}
|
||||
{testNotificationError ? <Text style={errorTextStyle}>{testNotificationError}</Text> : null}
|
||||
<DesktopPermissionRow
|
||||
title="Microphone"
|
||||
showBorder
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
|
||||
const CLI_DOCS_URL = "https://paseo.sh/docs/cli";
|
||||
const SKILLS_DOCS_URL = "https://paseo.sh/docs/skills";
|
||||
const ROW_WITH_BORDER_STYLE = [settingsStyles.row, settingsStyles.rowBorder];
|
||||
|
||||
export function IntegrationsSection() {
|
||||
const { theme } = useUnistyles();
|
||||
@@ -136,7 +137,7 @@ export function IntegrationsSection() {
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<View style={styles.rowTitleRow}>
|
||||
<Blocks size={theme.iconSize.md} color={theme.colors.foreground} />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { CheckCircle2, ChevronRight, CircleAlert, SquareTerminal } from "lucide-react-native";
|
||||
import { ActivityIndicator, Pressable, ScrollView, Text, View } from "react-native";
|
||||
import invariant from "tiny-invariant";
|
||||
@@ -243,10 +243,9 @@ function SetupPanel() {
|
||||
{command.durationMs != null ? (
|
||||
<Text style={styles.commandDuration}>{formatDuration(command.durationMs)}</Text>
|
||||
) : null}
|
||||
<ChevronRight
|
||||
size={14}
|
||||
<SetupCommandChevron
|
||||
showDetail={showDetail}
|
||||
color={theme.colors.foregroundMuted}
|
||||
style={[styles.chevron, showDetail && styles.chevronExpanded]}
|
||||
/>
|
||||
</Pressable>
|
||||
{showDetail ? (
|
||||
@@ -324,6 +323,14 @@ export const setupPanelRegistration: PanelRegistration<"setup"> = {
|
||||
useDescriptor: useSetupPanelDescriptor,
|
||||
};
|
||||
|
||||
function SetupCommandChevron({ showDetail, color }: { showDetail: boolean; color: string }) {
|
||||
const chevronStyle = useMemo(
|
||||
() => [styles.chevron, showDetail && styles.chevronExpanded],
|
||||
[showDetail],
|
||||
);
|
||||
return <ChevronRight size={14} color={color} style={chevronStyle} />;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
container: {
|
||||
flex: 1,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { useMemo, type ReactNode } from "react";
|
||||
import { Text, View, type StyleProp, type ViewStyle } from "react-native";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { settingsStyles } from "@/styles/settings";
|
||||
@@ -23,8 +23,9 @@ export function SettingsSection({
|
||||
style,
|
||||
children,
|
||||
}: SettingsSectionProps) {
|
||||
const sectionStyle = useMemo(() => [settingsStyles.section, style], [style]);
|
||||
return (
|
||||
<View style={[settingsStyles.section, style]} testID={testID}>
|
||||
<View style={sectionStyle} testID={testID}>
|
||||
<View style={styles.header}>
|
||||
<Text style={settingsStyles.sectionHeaderTitle}>{title}</Text>
|
||||
{trailing}
|
||||
|
||||
@@ -337,6 +337,11 @@ export function WorkspaceDraftAgentTab({
|
||||
[composerState],
|
||||
);
|
||||
|
||||
const inputAreaWrapperStyle = useMemo(
|
||||
() => [styles.inputAreaWrapper, { paddingBottom: insets.bottom }],
|
||||
[insets.bottom],
|
||||
);
|
||||
|
||||
return (
|
||||
<FileDropZone onFilesDropped={handleFilesDropped}>
|
||||
<View style={styles.container}>
|
||||
@@ -368,7 +373,7 @@ export function WorkspaceDraftAgentTab({
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={[styles.inputAreaWrapper, { paddingBottom: insets.bottom }]}>
|
||||
<View style={inputAreaWrapperStyle}>
|
||||
<Composer
|
||||
agentId={tabId}
|
||||
serverId={serverId}
|
||||
|
||||
@@ -137,6 +137,8 @@ function script(
|
||||
};
|
||||
}
|
||||
|
||||
const LIVE_TERMINAL_IDS: string[] = ["terminal-script-1"];
|
||||
|
||||
function renderScripts(scripts: WorkspaceScriptPayload[]): {
|
||||
rerender: (nextScripts: WorkspaceScriptPayload[]) => Promise<void>;
|
||||
unmount: () => void;
|
||||
@@ -158,7 +160,7 @@ function renderScripts(scripts: WorkspaceScriptPayload[]): {
|
||||
serverId="test-server"
|
||||
workspaceId="workspace-1"
|
||||
scripts={nextScripts}
|
||||
liveTerminalIds={["terminal-script-1"]}
|
||||
liveTerminalIds={LIVE_TERMINAL_IDS}
|
||||
/>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user