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:
@@ -8,11 +8,20 @@ import {
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { Dimensions, Platform, Text, View } from "react-native";
|
||||
import {
|
||||
Dimensions,
|
||||
Platform,
|
||||
Text,
|
||||
View,
|
||||
type StyleProp,
|
||||
type TextStyle,
|
||||
type ViewStyle,
|
||||
} from "react-native";
|
||||
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { StyleSheet, withUnistyles } from "react-native-unistyles";
|
||||
import { ExternalLink } from "lucide-react-native";
|
||||
import { GitHubIcon } from "@/components/icons/github-icon";
|
||||
import type { Theme } from "@/styles/theme";
|
||||
import { DiffStat } from "@/components/diff-stat";
|
||||
import { Pressable } from "react-native";
|
||||
import { Portal } from "@gorhom/portal";
|
||||
@@ -207,7 +216,6 @@ function WorkspaceHoverCardContent({
|
||||
triggerRef: React.RefObject<View | null>;
|
||||
contentRef: React.RefObject<View | null>;
|
||||
}): ReactElement | null {
|
||||
const { theme } = useUnistyles();
|
||||
const bottomSheetInternal = useBottomSheetModalInternal(true);
|
||||
const [triggerRect, setTriggerRect] = useState<Rect | null>(null);
|
||||
const [contentSize, setContentSize] = useState<{ width: number; height: number } | null>(null);
|
||||
@@ -297,7 +305,7 @@ function WorkspaceHoverCardContent({
|
||||
{prHint?.checks && prHint.checks.length > 0 ? (
|
||||
<>
|
||||
<View style={styles.separator} />
|
||||
<ChecksSummaryPressable checks={prHint.checks} url={prHint.url} theme={theme} />
|
||||
<ChecksSummaryPressable checks={prHint.checks} url={prHint.url} />
|
||||
</>
|
||||
) : null}
|
||||
</Animated.View>
|
||||
@@ -306,49 +314,49 @@ function WorkspaceHoverCardContent({
|
||||
);
|
||||
}
|
||||
|
||||
const ThemedExternalLink = withUnistyles(ExternalLink);
|
||||
const ThemedGitHubIcon = withUnistyles(GitHubIcon);
|
||||
|
||||
const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground });
|
||||
const foregroundMutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
|
||||
|
||||
function ChecksSummaryContent({
|
||||
checks,
|
||||
theme,
|
||||
hovered,
|
||||
}: {
|
||||
checks: NonNullable<PrHint["checks"]>;
|
||||
theme: ReturnType<typeof useUnistyles>["theme"];
|
||||
hovered: boolean;
|
||||
}) {
|
||||
const failed = checks.filter((c) => c.status === "failure").length;
|
||||
const pending = checks.filter((c) => c.status === "pending").length;
|
||||
|
||||
let badgeColor: string;
|
||||
let badgeLabel: string;
|
||||
let dotStyle: StyleProp<ViewStyle>;
|
||||
let statusTextStyle: StyleProp<TextStyle>;
|
||||
|
||||
if (failed > 0) {
|
||||
badgeColor = theme.colors.palette.red[500];
|
||||
badgeLabel = `${failed} failed`;
|
||||
dotStyle = styles.checksDotFailed;
|
||||
statusTextStyle = styles.checksStatusTextFailed;
|
||||
} else if (pending > 0) {
|
||||
badgeColor = theme.colors.palette.amber[500];
|
||||
badgeLabel = `${pending} running`;
|
||||
dotStyle = styles.checksDotPending;
|
||||
statusTextStyle = styles.checksStatusTextPending;
|
||||
} else {
|
||||
badgeColor = theme.colors.palette.green[500];
|
||||
badgeLabel = `${checks.length} passed`;
|
||||
dotStyle = styles.checksDotPassed;
|
||||
statusTextStyle = styles.checksStatusTextPassed;
|
||||
}
|
||||
|
||||
const iconColor = hovered ? theme.colors.foreground : theme.colors.foregroundMuted;
|
||||
const labelStyle = useMemo(
|
||||
() => [styles.checksSummaryLabel, hovered && styles.checksSummaryLabelHovered],
|
||||
[hovered],
|
||||
);
|
||||
const dotStyle = useMemo(() => [styles.checksDot, { backgroundColor: badgeColor }], [badgeColor]);
|
||||
const statusTextStyle = useMemo(
|
||||
() => [styles.checksStatusText, { color: badgeColor }],
|
||||
[badgeColor],
|
||||
);
|
||||
const labelStyle = hovered ? checksSummaryLabelHoveredCombined : styles.checksSummaryLabel;
|
||||
const iconUniProps = hovered ? foregroundColorMapping : foregroundMutedColorMapping;
|
||||
|
||||
return (
|
||||
<>
|
||||
{hovered ? (
|
||||
<ExternalLink size={12} color={iconColor} />
|
||||
<ThemedExternalLink size={12} uniProps={iconUniProps} />
|
||||
) : (
|
||||
<GitHubIcon size={12} color={iconColor} />
|
||||
<ThemedGitHubIcon size={12} uniProps={iconUniProps} />
|
||||
)}
|
||||
<Text style={labelStyle}>Checks</Text>
|
||||
<View style={styles.checksSummaryCounts}>
|
||||
@@ -362,38 +370,32 @@ function ChecksSummaryContent({
|
||||
function ChecksSummaryPressable({
|
||||
checks,
|
||||
url,
|
||||
theme,
|
||||
}: {
|
||||
checks: NonNullable<PrHint["checks"]>;
|
||||
url: string;
|
||||
theme: ReturnType<typeof useUnistyles>["theme"];
|
||||
}) {
|
||||
const handlePress = useCallback(() => {
|
||||
void openExternalUrl(`${url}/checks`);
|
||||
}, [url]);
|
||||
|
||||
const pressableStyle = useCallback(
|
||||
({ hovered }: { pressed: boolean; hovered?: boolean }) => [
|
||||
styles.checksSummaryRow,
|
||||
Boolean(hovered) && styles.listRowHovered,
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
const renderChildren = useCallback(
|
||||
({ hovered }: { pressed: boolean; hovered?: boolean }) => (
|
||||
<ChecksSummaryContent checks={checks} theme={theme} hovered={Boolean(hovered)} />
|
||||
<ChecksSummaryContent checks={checks} hovered={Boolean(hovered)} />
|
||||
),
|
||||
[checks, theme],
|
||||
[checks],
|
||||
);
|
||||
|
||||
return (
|
||||
<Pressable style={pressableStyle} onPress={handlePress}>
|
||||
<Pressable style={checksSummaryPressableStyle} onPress={handlePress}>
|
||||
{renderChildren}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
function checksSummaryPressableStyle({ hovered = false }: { pressed: boolean; hovered?: boolean }) {
|
||||
return [styles.checksSummaryRow, hovered && styles.listRowHovered];
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
portalOverlay: {
|
||||
position: "absolute",
|
||||
@@ -467,13 +469,42 @@ const styles = StyleSheet.create((theme) => ({
|
||||
flex: 1,
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
checksDot: {
|
||||
checksDotFailed: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
backgroundColor: theme.colors.palette.red[500],
|
||||
},
|
||||
checksStatusText: {
|
||||
checksDotPending: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
backgroundColor: theme.colors.palette.amber[500],
|
||||
},
|
||||
checksDotPassed: {
|
||||
width: 6,
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
backgroundColor: theme.colors.palette.green[500],
|
||||
},
|
||||
checksStatusTextFailed: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
color: theme.colors.palette.red[500],
|
||||
},
|
||||
checksStatusTextPending: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
color: theme.colors.palette.amber[500],
|
||||
},
|
||||
checksStatusTextPassed: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
color: theme.colors.palette.green[500],
|
||||
},
|
||||
}));
|
||||
|
||||
const checksSummaryLabelHoveredCombined = [
|
||||
styles.checksSummaryLabel,
|
||||
styles.checksSummaryLabelHovered,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user