refactor: strip useUnistyles in favor of withUnistyles + StyleSheet
Replaces broad useUnistyles() subscriptions in composer, workspace, and related components with withUnistyles wrappers and StyleSheet.create so re-renders stay scoped to the tokens actually consumed. Lifts the design scales (spacing, iconSize, fontSize, etc.) to top-level exports in theme.ts so component literals share a single source of truth with the unistyles theme.
This commit is contained in:
@@ -108,7 +108,7 @@ vi.mock("react-native-unistyles", () => {
|
||||
StyleSheet: {
|
||||
create: createPanelTestStyles,
|
||||
},
|
||||
useUnistyles: () => ({ theme }),
|
||||
withUnistyles: <T,>(component: T) => component,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { ActivityIndicator, Text, View } from "react-native";
|
||||
import ReanimatedAnimated from "react-native-reanimated";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
||||
import invariant from "tiny-invariant";
|
||||
import { shallow, useShallow } from "zustand/shallow";
|
||||
import { useStoreWithEqualityFn } from "zustand/traditional";
|
||||
@@ -44,6 +44,7 @@ import {
|
||||
import { useCreateFlowStore } from "@/stores/create-flow-store";
|
||||
import { buildDraftStoreKey } from "@/stores/draft-keys";
|
||||
import { type Agent, useSessionStore } from "@/stores/session-store";
|
||||
import type { Theme } from "@/styles/theme";
|
||||
import type { PendingPermission } from "@/types/shared";
|
||||
import type { StreamItem } from "@/types/stream";
|
||||
import { getInitDeferred, getInitKey } from "@/utils/agent-initialization";
|
||||
@@ -125,9 +126,8 @@ function buildChatAgentFromState(
|
||||
function renderChatAgentNonReadyView(args: {
|
||||
viewState: AgentScreenViewState;
|
||||
effectiveAgent: AgentScreenAgent | null;
|
||||
theme: ReturnType<typeof useUnistyles>["theme"];
|
||||
}): React.ReactElement | null {
|
||||
const { viewState, effectiveAgent, theme } = args;
|
||||
const { viewState, effectiveAgent } = args;
|
||||
if (viewState.tag === "not_found") {
|
||||
return (
|
||||
<View style={styles.container} testID="agent-not-found">
|
||||
@@ -151,7 +151,7 @@ function renderChatAgentNonReadyView(args: {
|
||||
return (
|
||||
<View style={styles.container} testID="agent-loading">
|
||||
<View style={styles.errorContainer}>
|
||||
<ActivityIndicator size="large" color={theme.colors.foregroundMuted} />
|
||||
<ThemedActivityIndicator size="large" uniProps={foregroundMutedColorMapping} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -419,7 +419,6 @@ function AgentPanelBody({
|
||||
connectionStatus: HostRuntimeConnectionStatus;
|
||||
onOpenWorkspaceFile?: (input: { filePath: string }) => void;
|
||||
}) {
|
||||
const { theme } = useUnistyles();
|
||||
const { isArchivingAgent: _isArchivingAgent } = useArchiveAgent();
|
||||
const hasSession = useSessionStore((state) => Boolean(state.sessions[serverId]));
|
||||
const projectPlacement = useStoreWithEqualityFn(
|
||||
@@ -549,7 +548,7 @@ function AgentPanelBody({
|
||||
return (
|
||||
<View style={styles.container} testID="agent-loading">
|
||||
<View style={styles.errorContainer}>
|
||||
<ActivityIndicator size="large" color={theme.colors.foregroundMuted} />
|
||||
<ThemedActivityIndicator size="large" uniProps={foregroundMutedColorMapping} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -585,7 +584,6 @@ function ChatAgentContent({
|
||||
connectionStatus: HostRuntimeConnectionStatus;
|
||||
onOpenWorkspaceFile?: (input: { filePath: string }) => void;
|
||||
}) {
|
||||
const { theme } = useUnistyles();
|
||||
const panelToast = useToastHost();
|
||||
const { isArchivingAgent } = useArchiveAgent();
|
||||
const streamViewRef = useRef<AgentStreamViewHandle>(null);
|
||||
@@ -945,7 +943,6 @@ function ChatAgentContent({
|
||||
const nonReadyView = renderChatAgentNonReadyView({
|
||||
viewState,
|
||||
effectiveAgent,
|
||||
theme,
|
||||
});
|
||||
if (nonReadyView) return nonReadyView;
|
||||
invariant(effectiveAgent, "effectiveAgent is defined when the non-ready view is absent");
|
||||
@@ -990,7 +987,7 @@ function ChatAgentContent({
|
||||
viewState.sync.status === "catching_up" &&
|
||||
viewState.sync.ui === "overlay" ? (
|
||||
<View style={styles.historySyncOverlay} testID="agent-history-overlay">
|
||||
<ActivityIndicator size="large" color={theme.colors.foregroundMuted} />
|
||||
<ThemedActivityIndicator size="large" uniProps={foregroundMutedColorMapping} />
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
@@ -1004,7 +1001,7 @@ function ChatAgentContent({
|
||||
|
||||
{isArchivingCurrentAgent ? (
|
||||
<View style={styles.archivingOverlay} testID="agent-archiving-overlay">
|
||||
<ActivityIndicator size="large" color={theme.colors.foreground} />
|
||||
<ThemedActivityIndicator size="large" uniProps={foregroundColorMapping} />
|
||||
<Text style={styles.archivingTitle}>Archiving agent...</Text>
|
||||
<Text style={styles.archivingSubtitle}>Please wait while we archive this agent.</Text>
|
||||
</View>
|
||||
@@ -1340,6 +1337,15 @@ function AgentSessionUnavailableState({
|
||||
);
|
||||
}
|
||||
|
||||
const ThemedActivityIndicator = withUnistyles(ActivityIndicator);
|
||||
|
||||
const foregroundMutedColorMapping = (theme: Theme) => ({
|
||||
color: theme.colors.foregroundMuted,
|
||||
});
|
||||
const foregroundColorMapping = (theme: Theme) => ({
|
||||
color: theme.colors.foreground,
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
root: {
|
||||
flex: 1,
|
||||
|
||||
@@ -9,11 +9,12 @@ import {
|
||||
View,
|
||||
} from "react-native";
|
||||
import invariant from "tiny-invariant";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
||||
import { Fonts } from "@/constants/theme";
|
||||
import { usePaneContext } from "@/panels/pane-context";
|
||||
import type { PanelDescriptor, PanelRegistration } from "@/panels/panel-registry";
|
||||
import { buildWorkspaceTabPersistenceKey } from "@/stores/workspace-tabs-store";
|
||||
import type { Theme } from "@/styles/theme";
|
||||
import {
|
||||
useWorkspaceSetupStore,
|
||||
type WorkspaceSetupSnapshot,
|
||||
@@ -62,15 +63,13 @@ function useSetupPanelDescriptor(
|
||||
type CommandStatus = "running" | "completed" | "failed";
|
||||
|
||||
function CommandStatusIcon({ status }: { status: CommandStatus }) {
|
||||
const { theme } = useUnistyles();
|
||||
|
||||
if (status === "running") {
|
||||
return <ActivityIndicator size={14} color={theme.colors.foreground} />;
|
||||
return <ThemedActivityIndicator size={14} uniProps={foregroundColorMapping} />;
|
||||
}
|
||||
if (status === "completed") {
|
||||
return <CheckCircle2 size={14} color={theme.colors.palette.green[500]} />;
|
||||
return <ThemedCheckCircle2 size={14} uniProps={greenColorMapping} />;
|
||||
}
|
||||
return <CircleAlert size={14} color={theme.colors.palette.red[500]} />;
|
||||
return <ThemedCircleAlert size={14} uniProps={redColorMapping} />;
|
||||
}
|
||||
|
||||
function formatDuration(ms: number): string {
|
||||
@@ -151,7 +150,6 @@ function buildCommandRowState(args: BuildCommandRowPropsArgs) {
|
||||
}
|
||||
|
||||
function SetupPanel() {
|
||||
const { theme } = useUnistyles();
|
||||
const { serverId, target } = usePaneContext();
|
||||
invariant(target.kind === "setup", "SetupPanel requires setup target");
|
||||
|
||||
@@ -231,7 +229,7 @@ function SetupPanel() {
|
||||
|
||||
{isWaiting ? (
|
||||
<View style={styles.waitingContainer}>
|
||||
<ActivityIndicator size="large" color={theme.colors.foregroundMuted} />
|
||||
<ThemedActivityIndicator size="large" uniProps={foregroundMutedColorMapping} />
|
||||
<Text style={styles.waitingText}>Setting up workspace...</Text>
|
||||
</View>
|
||||
) : null}
|
||||
@@ -269,7 +267,6 @@ function SetupPanel() {
|
||||
hasError={rowState.hasError}
|
||||
processedLog={rowState.processedLog}
|
||||
errorMessage={snapshot?.error ?? null}
|
||||
foregroundMutedColor={theme.colors.foregroundMuted}
|
||||
onToggle={toggleExpanded}
|
||||
/>
|
||||
);
|
||||
@@ -292,7 +289,6 @@ interface SetupCommandRowProps {
|
||||
hasError: boolean;
|
||||
processedLog: string;
|
||||
errorMessage: string | null;
|
||||
foregroundMutedColor: string;
|
||||
onToggle: (index: number, isAutoExpanded: boolean) => void;
|
||||
}
|
||||
|
||||
@@ -305,7 +301,6 @@ function SetupCommandRow({
|
||||
hasError,
|
||||
processedLog,
|
||||
errorMessage,
|
||||
foregroundMutedColor,
|
||||
onToggle,
|
||||
}: SetupCommandRowProps) {
|
||||
const handlePress = useCallback(() => {
|
||||
@@ -341,7 +336,7 @@ function SetupCommandRow({
|
||||
{command.durationMs != null ? (
|
||||
<Text style={styles.commandDuration}>{formatDuration(command.durationMs)}</Text>
|
||||
) : null}
|
||||
<SetupCommandChevron showDetail={showDetail} color={foregroundMutedColor} />
|
||||
<SetupCommandChevron showDetail={showDetail} />
|
||||
</Pressable>
|
||||
{showDetail ? (
|
||||
<View style={styles.commandDetail}>
|
||||
@@ -388,12 +383,14 @@ export const setupPanelRegistration: PanelRegistration<"setup"> = {
|
||||
useDescriptor: useSetupPanelDescriptor,
|
||||
};
|
||||
|
||||
function SetupCommandChevron({ showDetail, color }: { showDetail: boolean; color: string }) {
|
||||
function SetupCommandChevron({ showDetail }: { showDetail: boolean }) {
|
||||
const chevronStyle = useMemo(
|
||||
() => [styles.chevron, showDetail && styles.chevronExpanded],
|
||||
[showDetail],
|
||||
);
|
||||
return <ChevronRight size={14} color={color} style={chevronStyle} />;
|
||||
return (
|
||||
<ThemedChevronRight size={14} uniProps={foregroundMutedColorMapping} style={chevronStyle} />
|
||||
);
|
||||
}
|
||||
|
||||
function StandaloneLogView({ commands, log }: { commands: SetupCommand[]; log: string }) {
|
||||
@@ -432,6 +429,24 @@ function TopLevelSetupError({
|
||||
);
|
||||
}
|
||||
|
||||
const ThemedActivityIndicator = withUnistyles(ActivityIndicator);
|
||||
const ThemedCheckCircle2 = withUnistyles(CheckCircle2);
|
||||
const ThemedCircleAlert = withUnistyles(CircleAlert);
|
||||
const ThemedChevronRight = withUnistyles(ChevronRight);
|
||||
|
||||
const foregroundColorMapping = (theme: Theme) => ({
|
||||
color: theme.colors.foreground,
|
||||
});
|
||||
const foregroundMutedColorMapping = (theme: Theme) => ({
|
||||
color: theme.colors.foregroundMuted,
|
||||
});
|
||||
const greenColorMapping = (theme: Theme) => ({
|
||||
color: theme.colors.palette.green[500],
|
||||
});
|
||||
const redColorMapping = (theme: Theme) => ({
|
||||
color: theme.colors.palette.red[500],
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
container: {
|
||||
flex: 1,
|
||||
|
||||
Reference in New Issue
Block a user