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:
Mohamed Boudra
2026-04-26 16:12:09 +07:00
parent 941b4fb760
commit 01f89c7c36
22 changed files with 1014 additions and 720 deletions
@@ -1,15 +1,16 @@
import { useCallback, useMemo, type ReactElement, type ReactNode } from "react";
import { Pressable, Text, View, type PressableStateCallbackType } from "react-native";
import { Check } from "lucide-react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { StyleSheet, withUnistyles } from "react-native-unistyles";
import invariant from "tiny-invariant";
import { SyncedLoader } from "@/components/synced-loader";
import { ensurePanelsRegistered } from "@/panels/register-panels";
import { getPanelRegistration } from "@/panels/panel-registry";
import type { WorkspaceTabDescriptor } from "@/screens/workspace/workspace-tabs-types";
import type { SidebarStateBucket } from "@/utils/sidebar-agent-state";
import { getStatusDotColor, isEmphasizedStatusDotBucket } from "@/utils/status-dot-color";
import { isEmphasizedStatusDotBucket } from "@/utils/status-dot-color";
import { shouldRenderSyncedStatusLoader } from "@/utils/status-loader";
import type { Theme } from "@/styles/theme";
export interface WorkspaceTabPresentation {
key: string;
@@ -103,22 +104,22 @@ interface WorkspaceTabIconProps {
statusDotBorderColor?: string;
}
const ThemedCheckIcon = withUnistyles(Check);
const mutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
export function WorkspaceTabIcon({
presentation,
active = false,
size = 14,
statusDotBorderColor,
}: WorkspaceTabIconProps): ReactElement {
const { theme } = useUnistyles();
const iconColor = active ? theme.colors.foreground : theme.colors.foregroundMuted;
const statusDotColor =
presentation.statusBucket === null
? null
: getStatusDotColor({
theme,
bucket: presentation.statusBucket,
showDoneAsInactive: false,
});
const iconColor = active ? styles.iconActive.color : styles.iconInactive.color;
const bucket = presentation.statusBucket;
let statusDotColor: string | null = null;
if (bucket === "needs_input") statusDotColor = styles.statusDotNeedsInput.color;
else if (bucket === "failed") statusDotColor = styles.statusDotFailed.color;
else if (bucket === "running") statusDotColor = styles.statusDotRunning.color;
else if (bucket === "attention") statusDotColor = styles.statusDotAttention.color;
const statusDotSize = isEmphasizedStatusDotBucket(presentation.statusBucket)
? EMPHASIZED_STATUS_DOT_SIZE
: DEFAULT_STATUS_DOT_SIZE;
@@ -139,27 +140,20 @@ export function WorkspaceTabIcon({
styles.statusDot,
{
backgroundColor: statusDotColor ?? undefined,
borderColor: statusDotBorderColor ?? theme.colors.surface0,
borderColor: statusDotBorderColor ?? styles.statusDotBorderDefault.borderColor,
width: statusDotSize,
height: statusDotSize,
right: statusDotOffset,
bottom: statusDotOffset,
},
],
[statusDotColor, statusDotBorderColor, theme.colors.surface0, statusDotSize, statusDotOffset],
[statusDotColor, statusDotBorderColor, statusDotSize, statusDotOffset],
);
if (shouldShowLoader) {
return (
<View style={agentIconWrapperStyle}>
<SyncedLoader
size={size - 1}
color={
theme.colorScheme === "light"
? theme.colors.palette.amber[700]
: theme.colors.palette.amber[500]
}
/>
<SyncedLoader size={size - 1} color={styles.syncedLoader.color} />
</View>
);
}
@@ -187,7 +181,6 @@ export function WorkspaceTabOptionRow({
onPress,
trailingAccessory,
}: WorkspaceTabOptionRowProps): ReactElement {
const { theme } = useUnistyles();
const pressableStyle = useCallback(
({ hovered, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [
styles.optionMainPressable,
@@ -213,7 +206,7 @@ export function WorkspaceTabOptionRow({
</Pressable>
{selected ? (
<View style={styles.optionTrailingSlot}>
<Check size={16} color={theme.colors.foregroundMuted} />
<ThemedCheckIcon size={16} uniProps={mutedColorMapping} />
</View>
) : null}
{trailingAccessory ? (
@@ -238,6 +231,33 @@ const styles = StyleSheet.create((theme) => ({
borderRadius: theme.borderRadius.full,
borderWidth: 1,
},
statusDotBorderDefault: {
borderColor: theme.colors.surface0,
},
statusDotNeedsInput: {
color: theme.colors.palette.amber[500],
},
statusDotFailed: {
color: theme.colors.palette.red[500],
},
statusDotRunning: {
color: theme.colors.palette.blue[500],
},
statusDotAttention: {
color: theme.colors.palette.green[500],
},
iconActive: {
color: theme.colors.foreground,
},
iconInactive: {
color: theme.colors.foregroundMuted,
},
syncedLoader: {
color:
theme.colorScheme === "light"
? theme.colors.palette.amber[700]
: theme.colors.palette.amber[500],
},
optionRow: {
flexDirection: "row",
alignItems: "center",