fix(app): close theme split escape hatches

This commit is contained in:
Mohamed Boudra
2026-04-18 16:25:23 +07:00
parent fa41ba76cd
commit 4de4e90325
10 changed files with 120 additions and 72 deletions
+39 -1
View File
@@ -31,7 +31,7 @@ const styles = StyleSheet.create((theme) => ({
On first mount this can paint with the current adaptive or initial theme. If app settings later load a persisted theme and call [`UnistylesRuntime.setTheme`](https://www.unistyl.es/v3/guides/theming#change-theme), the JS-side style proxy may report the new theme while the native content container keeps the old background. That is how the welcome screen ended up with a light background and dark foreground/buttons. On first mount this can paint with the current adaptive or initial theme. If app settings later load a persisted theme and call [`UnistylesRuntime.setTheme`](https://www.unistyl.es/v3/guides/theming#change-theme), the JS-side style proxy may report the new theme while the native content container keeps the old background. That is how the welcome screen ended up with a light background and dark foreground/buttons.
This applies broadly to non-`style` props that carry theme-dependent values, such as component props named `color`, `trackColor`, `tintColor`, and library-specific style props. The [3rd-party view decision algorithm](https://www.unistyl.es/v3/references/3rd-party-views) recommends explicit handling for these cases, and [issue #1030](https://github.com/jpudysz/react-native-unistyles/issues/1030) shows a related native-prop update edge case around `Image.tintColor`. Treat these values as React props unless wrapped with `withUnistyles`. This applies broadly to non-`style` props that carry theme-dependent values, such as component props named `color`, `trackColor`, `tintColor`, `backgroundStyle`, `handleIndicatorStyle`, and other library-specific style props. The [3rd-party view decision algorithm](https://www.unistyl.es/v3/references/3rd-party-views) recommends explicit handling for these cases, and [issue #1030](https://github.com/jpudysz/react-native-unistyles/issues/1030) shows a related native-prop update edge case around `Image.tintColor`. Treat these values as React props unless wrapped with `withUnistyles`.
## Fix Patterns ## Fix Patterns
@@ -105,6 +105,44 @@ const { theme } = useUnistyles();
Keep layout and typography in `StyleSheet.create`; move only the stale theme-dependent value through React. If a larger subtree shows the same behavior, consider remounting the sheet on theme changes or moving the themed paint onto a wrapper that is mounted with the visible content. Keep layout and typography in `StyleSheet.create`; move only the stale theme-dependent value through React. If a larger subtree shows the same behavior, consider remounting the sheet on theme changes or moving the themed paint onto a wrapper that is mounted with the visible content.
The same rule applies to bottom-sheet component props such as `backgroundStyle` and `handleIndicatorStyle`: they are library props, not the direct React Native `style` prop Unistyles registers. Prefer a custom `backgroundComponent` that calls `useUnistyles()`, or pass a small inline object from the hook theme.
## Memoized Style Objects
When a third-party library receives a plain style object, it is outside Unistyles' native tracking path. Make sure any memo that builds that style object depends on the actual theme values it reads.
Avoid indirect keys like this:
```tsx
const { theme, rt } = useUnistyles();
const markdownStyles = useMemo(() => createMarkdownStyles(theme), [rt.themeName]);
```
On adaptive system-theme changes, the hook can provide a light/dark theme update while an indirect runtime key is not the value that invalidates the memo. That leaves the library rendering stale colors. Assistant markdown hit this exact failure: the workspace shell switched to light, but assistant text and code spans kept the old dark-theme markdown style object.
Prefer the hook theme itself, or explicit theme tokens, as the dependency:
```tsx
const { theme } = useUnistyles();
const markdownStyles = useMemo(() => createMarkdownStyles(theme), [theme]);
```
If a style factory is cheap, skipping `useMemo` entirely is also fine.
## Static Theme Imports
Do not import `theme` from `@/styles/theme` for live UI colors. That export is a dark-theme compatibility default, so using it in render code leaves icons, placeholders, or third-party props pinned to dark colors in light mode.
Use `useUnistyles()` inside the component instead:
```tsx
const { theme } = useUnistyles();
<ChevronDown size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
```
Importing `baseColors`, theme-name constants, or `type Theme` is fine when the value is intentionally static or type-only.
## Adaptive Themes And Persisted Settings ## Adaptive Themes And Persisted Settings
Unistyles [`initialTheme`](https://www.unistyl.es/v3/guides/theming#select-theme) and [`adaptiveThemes`](https://www.unistyl.es/v3/guides/theming#adaptive-themes) are mutually exclusive. `initialTheme` can be a string or a synchronous function, but it cannot wait on async storage. Unistyles [`initialTheme`](https://www.unistyl.es/v3/guides/theming#select-theme) and [`adaptiveThemes`](https://www.unistyl.es/v3/guides/theming#adaptive-themes) are mutually exclusive. `initialTheme` can be a string or a synchronous function, but it cannot wait on async storage.
@@ -109,9 +109,6 @@ const styles = StyleSheet.create((theme) => ({
gap: theme.spacing[4], gap: theme.spacing[4],
flexGrow: 1, flexGrow: 1,
}, },
bottomSheetHandle: {
backgroundColor: theme.colors.surface2,
},
bottomSheetHeader: { bottomSheetHeader: {
paddingHorizontal: theme.spacing[6], paddingHorizontal: theme.spacing[6],
paddingTop: theme.spacing[4], paddingTop: theme.spacing[4],
@@ -243,7 +240,7 @@ export function AdaptiveModalSheet({
enablePanDownToClose enablePanDownToClose
stackBehavior={stackBehavior} stackBehavior={stackBehavior}
backgroundComponent={SheetBackground} backgroundComponent={SheetBackground}
handleIndicatorStyle={styles.bottomSheetHandle} handleIndicatorStyle={{ backgroundColor: theme.colors.surface2 }}
keyboardBehavior="extend" keyboardBehavior="extend"
keyboardBlurBehavior="restore" keyboardBlurBehavior="restore"
accessible={false} accessible={false}
@@ -22,7 +22,6 @@ import {
ShieldAlert, ShieldAlert,
ShieldOff, ShieldOff,
} from "lucide-react-native"; } from "lucide-react-native";
import { theme as defaultTheme } from "@/styles/theme";
import type { import type {
AgentMode, AgentMode,
AgentModelDefinition, AgentModelDefinition,
@@ -86,6 +85,8 @@ export function DropdownField({
renderTrigger, renderTrigger,
testID, testID,
}: DropdownFieldProps): ReactElement { }: DropdownFieldProps): ReactElement {
const { theme } = useUnistyles();
if (renderTrigger) { if (renderTrigger) {
return ( return (
<> <>
@@ -115,7 +116,7 @@ export function DropdownField({
<Text style={value ? styles.dropdownValue : styles.dropdownPlaceholder} numberOfLines={1}> <Text style={value ? styles.dropdownValue : styles.dropdownPlaceholder} numberOfLines={1}>
{value || placeholder} {value || placeholder}
</Text> </Text>
<ChevronDown size={defaultTheme.iconSize.md} color={defaultTheme.colors.foregroundMuted} /> <ChevronDown size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
</Pressable> </Pressable>
{errorMessage ? <Text style={styles.errorText}>{errorMessage}</Text> : null} {errorMessage ? <Text style={styles.errorText}>{errorMessage}</Text> : null}
{warningMessage ? <Text style={styles.warningText}>{warningMessage}</Text> : null} {warningMessage ? <Text style={styles.warningText}>{warningMessage}</Text> : null}
@@ -151,6 +152,8 @@ export function SelectField({
valueEllipsizeMode, valueEllipsizeMode,
testID, testID,
}: SelectFieldProps): ReactElement { }: SelectFieldProps): ReactElement {
const { theme } = useUnistyles();
const getWebKey = useCallback((event: unknown): string | null => { const getWebKey = useCallback((event: unknown): string | null => {
if (!event || typeof event !== "object") return null; if (!event || typeof event !== "object") return null;
const eventWithNative = event as { nativeEvent?: unknown; key?: unknown }; const eventWithNative = event as { nativeEvent?: unknown; key?: unknown };
@@ -210,7 +213,7 @@ export function SelectField({
{value || placeholder || "Select..."} {value || placeholder || "Select..."}
</Text> </Text>
</View> </View>
<ChevronRight size={defaultTheme.iconSize.lg} color={defaultTheme.colors.foregroundMuted} /> <ChevronRight size={theme.iconSize.lg} color={theme.colors.foregroundMuted} />
</Pressable> </Pressable>
{errorMessage ? <Text style={styles.errorText}>{errorMessage}</Text> : null} {errorMessage ? <Text style={styles.errorText}>{errorMessage}</Text> : null}
{warningMessage ? <Text style={styles.warningText}>{warningMessage}</Text> : null} {warningMessage ? <Text style={styles.warningText}>{warningMessage}</Text> : null}
@@ -229,7 +232,21 @@ interface DropdownSheetProps {
} }
function DropdownSheetBackground({ style }: BottomSheetBackgroundProps) { function DropdownSheetBackground({ style }: BottomSheetBackgroundProps) {
return <Animated.View pointerEvents="none" style={[style, styles.bottomSheetBackground]} />; const { theme } = useUnistyles();
return (
<Animated.View
pointerEvents="none"
style={[
style,
{
backgroundColor: theme.colors.surface2,
borderTopLeftRadius: theme.borderRadius["2xl"],
borderTopRightRadius: theme.borderRadius["2xl"],
},
]}
/>
);
} }
export function DropdownSheet({ export function DropdownSheet({
@@ -238,6 +255,7 @@ export function DropdownSheet({
onClose, onClose,
children, children,
}: DropdownSheetProps): ReactElement { }: DropdownSheetProps): ReactElement {
const { theme } = useUnistyles();
const bottomSheetRef = useRef<BottomSheetModal>(null); const bottomSheetRef = useRef<BottomSheetModal>(null);
const snapPoints = useMemo(() => ["60%", "90%"], []); const snapPoints = useMemo(() => ["60%", "90%"], []);
@@ -280,12 +298,12 @@ export function DropdownSheet({
backdropComponent={renderBackdrop} backdropComponent={renderBackdrop}
enablePanDownToClose enablePanDownToClose
backgroundComponent={DropdownSheetBackground} backgroundComponent={DropdownSheetBackground}
handleIndicatorStyle={styles.bottomSheetHandle} handleIndicatorStyle={{ backgroundColor: theme.colors.palette.zinc[600] }}
keyboardBehavior="extend" keyboardBehavior="extend"
keyboardBlurBehavior="restore" keyboardBlurBehavior="restore"
> >
<View style={styles.bottomSheetHeader}> <View style={styles.bottomSheetHeader}>
<Text style={styles.dropdownSheetTitle}>{title}</Text> <Text style={[styles.dropdownSheetTitle, { color: theme.colors.foreground }]}>{title}</Text>
<Pressable <Pressable
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel="Close sheet" accessibilityLabel="Close sheet"
@@ -293,7 +311,7 @@ export function DropdownSheet({
hitSlop={10} hitSlop={10}
testID="dropdown-sheet-close" testID="dropdown-sheet-close"
> >
<X size={defaultTheme.iconSize.md} color={defaultTheme.colors.foregroundMuted} /> <X size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
</Pressable> </Pressable>
</View> </View>
<BottomSheetScrollView <BottomSheetScrollView
@@ -413,6 +431,8 @@ export function FormSelectTrigger({
valueEllipsizeMode, valueEllipsizeMode,
testID, testID,
}: CompactSelectFieldProps): ReactElement { }: CompactSelectFieldProps): ReactElement {
const { theme } = useUnistyles();
const getWebKey = useCallback((event: unknown): string | null => { const getWebKey = useCallback((event: unknown): string | null => {
if (!event || typeof event !== "object") return null; if (!event || typeof event !== "object") return null;
const eventWithNative = event as { nativeEvent?: unknown; key?: unknown }; const eventWithNative = event as { nativeEvent?: unknown; key?: unknown };
@@ -469,7 +489,7 @@ export function FormSelectTrigger({
<View style={styles.compactSelectValueContainer}> <View style={styles.compactSelectValueContainer}>
{showLabel ? <Text style={styles.compactSelectLabel}>{label}</Text> : null} {showLabel ? <Text style={styles.compactSelectLabel}>{label}</Text> : null}
{isLoading ? ( {isLoading ? (
<ActivityIndicator size="small" color={defaultTheme.colors.foregroundMuted} /> <ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
) : ( ) : (
<Text <Text
style={hasConcreteValue ? styles.compactSelectValue : styles.compactSelectPlaceholder} style={hasConcreteValue ? styles.compactSelectValue : styles.compactSelectPlaceholder}
@@ -480,7 +500,7 @@ export function FormSelectTrigger({
</Text> </Text>
)} )}
</View> </View>
<ChevronDown size={defaultTheme.iconSize.md} color={defaultTheme.colors.foregroundMuted} /> <ChevronDown size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
</Pressable> </Pressable>
); );
} }
@@ -518,6 +538,8 @@ export function AgentConfigRow({
onSelectThinkingOption, onSelectThinkingOption,
disabled, disabled,
}: AgentConfigRowProps): ReactElement { }: AgentConfigRowProps): ReactElement {
const { theme } = useUnistyles();
const providerOptions: ComboSelectOption[] = useMemo( const providerOptions: ComboSelectOption[] = useMemo(
() => () =>
providerDefinitions.map((def) => ({ providerDefinitions.map((def) => ({
@@ -576,7 +598,7 @@ export function AgentConfigRow({
placeholder={providerOptions.length > 0 ? "Select..." : "No providers available"} placeholder={providerOptions.length > 0 ? "Select..." : "No providers available"}
disabled={disabled || providerOptions.length === 0} disabled={disabled || providerOptions.length === 0}
onSelect={onSelectProvider} onSelect={onSelectProvider}
icon={<Bot size={defaultTheme.iconSize.md} color={defaultTheme.colors.foregroundMuted} />} icon={<Bot size={theme.iconSize.md} color={theme.colors.foregroundMuted} />}
showLabel={false} showLabel={false}
testID="draft-provider-select" testID="draft-provider-select"
/> />
@@ -591,9 +613,7 @@ export function AgentConfigRow({
disabled={disabled} disabled={disabled}
isLoading={isModelLoading} isLoading={isModelLoading}
onSelect={onSelectModel} onSelect={onSelectModel}
icon={ icon={<Brain size={theme.iconSize.md} color={theme.colors.foregroundMuted} />}
<Brain size={defaultTheme.iconSize.md} color={defaultTheme.colors.foregroundMuted} />
}
showLabel={false} showLabel={false}
testID="draft-model-select" testID="draft-model-select"
/> />
@@ -607,7 +627,7 @@ export function AgentConfigRow({
placeholder="Default" placeholder="Default"
disabled={disabled || modeOptions.length === 0} disabled={disabled || modeOptions.length === 0}
onSelect={onSelectMode} onSelect={onSelectMode}
icon={<ModeIcon size={defaultTheme.iconSize.md} color={modeIconColor} />} icon={<ModeIcon size={theme.iconSize.md} color={modeIconColor} />}
showLabel={false} showLabel={false}
testID="draft-mode-select" testID="draft-mode-select"
/> />
@@ -622,9 +642,7 @@ export function AgentConfigRow({
placeholder="Select..." placeholder="Select..."
disabled={disabled} disabled={disabled}
onSelect={onSelectThinkingOption} onSelect={onSelectThinkingOption}
icon={ icon={<Brain size={theme.iconSize.md} color={theme.colors.foregroundMuted} />}
<Brain size={defaultTheme.iconSize.md} color={defaultTheme.colors.foregroundMuted} />
}
showLabel={false} showLabel={false}
/> />
</View> </View>
@@ -969,6 +987,8 @@ export function GitOptionsSection({
attachWorktreeError, attachWorktreeError,
onSelectWorktreePath, onSelectWorktreePath,
}: GitOptionsSectionProps): ReactElement { }: GitOptionsSectionProps): ReactElement {
const { theme } = useUnistyles();
const isLoading = status === "loading"; const isLoading = status === "loading";
const isCreateMode = worktreeMode === "create"; const isCreateMode = worktreeMode === "create";
const isAttachMode = worktreeMode === "attach"; const isAttachMode = worktreeMode === "attach";
@@ -1103,7 +1123,7 @@ export function GitOptionsSection({
autoCapitalize="none" autoCapitalize="none"
autoCorrect={false} autoCorrect={false}
placeholder="branch name" placeholder="branch name"
placeholderTextColor={defaultTheme.colors.foregroundMuted} placeholderTextColor={theme.colors.foregroundMuted}
onSubmitEditing={handleConfirmEdit} onSubmitEditing={handleConfirmEdit}
/> />
<Pressable <Pressable
@@ -1111,19 +1131,16 @@ export function GitOptionsSection({
hitSlop={8} hitSlop={8}
style={styles.baseBranchIconButton} style={styles.baseBranchIconButton}
> >
<Check <Check size={theme.iconSize.md} color={theme.colors.palette.green[500]} />
size={defaultTheme.iconSize.md}
color={defaultTheme.colors.palette.green[500]}
/>
</Pressable> </Pressable>
<Pressable onPress={handleCancelEdit} hitSlop={8} style={styles.baseBranchIconButton}> <Pressable onPress={handleCancelEdit} hitSlop={8} style={styles.baseBranchIconButton}>
<X size={defaultTheme.iconSize.md} color={defaultTheme.colors.foregroundMuted} /> <X size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
</Pressable> </Pressable>
</View> </View>
) : ( ) : (
<Pressable onPress={handleStartEdit} style={styles.baseBranchValueRow}> <Pressable onPress={handleStartEdit} style={styles.baseBranchValueRow}>
<Text style={styles.baseBranchValue}>{displayBranch}</Text> <Text style={styles.baseBranchValue}>{displayBranch}</Text>
<Pencil size={defaultTheme.iconSize.sm} color={defaultTheme.colors.foregroundMuted} /> <Pencil size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
</Pressable> </Pressable>
)} )}
</View> </View>
@@ -1171,14 +1188,6 @@ const styles = StyleSheet.create((theme) => ({
color: theme.colors.foregroundMuted, color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.base, fontSize: theme.fontSize.base,
}, },
bottomSheetBackground: {
backgroundColor: theme.colors.surface2,
borderTopLeftRadius: theme.borderRadius["2xl"],
borderTopRightRadius: theme.borderRadius["2xl"],
},
bottomSheetHandle: {
backgroundColor: theme.colors.palette.zinc[600],
},
bottomSheetHeader: { bottomSheetHeader: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
+1 -1
View File
@@ -33,7 +33,7 @@ import {
type MessageInputRef, type MessageInputRef,
type AttachmentMenuItem, type AttachmentMenuItem,
} from "./message-input"; } from "./message-input";
import { Theme } from "@/styles/theme"; import type { Theme } from "@/styles/theme";
import type { DraftCommandConfig } from "@/hooks/use-agent-commands-query"; import type { DraftCommandConfig } from "@/hooks/use-agent-commands-query";
import { encodeImages } from "@/utils/encode-images"; import { encodeImages } from "@/utils/encode-images";
import { focusWithRetries } from "@/utils/web-focus"; import { focusWithRetries } from "@/utils/web-focus";
+3 -3
View File
@@ -53,7 +53,6 @@ import Animated, {
withTiming, withTiming,
} from "react-native-reanimated"; } from "react-native-reanimated";
import Svg, { Defs, LinearGradient as SvgLinearGradient, Rect, Stop } from "react-native-svg"; import Svg, { Defs, LinearGradient as SvgLinearGradient, Rect, Stop } from "react-native-svg";
import { theme } from "@/styles/theme";
import { createMarkdownStyles } from "@/styles/markdown-styles"; import { createMarkdownStyles } from "@/styles/markdown-styles";
import { Colors, Fonts } from "@/constants/theme"; import { Colors, Fonts } from "@/constants/theme";
import * as Clipboard from "expo-clipboard"; import * as Clipboard from "expo-clipboard";
@@ -341,6 +340,7 @@ export const UserMessage = memo(function UserMessage({
isLastInGroup = true, isLastInGroup = true,
disableOuterSpacing, disableOuterSpacing,
}: UserMessageProps) { }: UserMessageProps) {
const { theme } = useUnistyles();
const isCompact = useIsCompactFormFactor(); const isCompact = useIsCompactFormFactor();
const [messageHovered, setMessageHovered] = useState(false); const [messageHovered, setMessageHovered] = useState(false);
const [copyButtonHovered, setCopyButtonHovered] = useState(false); const [copyButtonHovered, setCopyButtonHovered] = useState(false);
@@ -962,10 +962,10 @@ export const AssistantMessage = memo(function AssistantMessage({
client, client,
disableOuterSpacing, disableOuterSpacing,
}: AssistantMessageProps) { }: AssistantMessageProps) {
const { theme, rt } = useUnistyles(); const { theme } = useUnistyles();
const resolvedDisableOuterSpacing = useDisableOuterSpacing(disableOuterSpacing); const resolvedDisableOuterSpacing = useDisableOuterSpacing(disableOuterSpacing);
const markdownStyles = useMemo(() => createMarkdownStyles(theme), [rt.themeName]); const markdownStyles = useMemo(() => createMarkdownStyles(theme), [theme]);
const markdownParser = useMemo(() => { const markdownParser = useMemo(() => {
const parser = MarkdownIt({ typographer: true, linkify: true }); const parser = MarkdownIt({ typographer: true, linkify: true });
@@ -86,7 +86,7 @@ export function TerminalPane({ serverId, cwd, terminalId, isPaneFocused }: Termi
const isScreenFocused = useIsFocused(); const isScreenFocused = useIsFocused();
const isAppVisible = useAppVisible(); const isAppVisible = useAppVisible();
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const xtermTheme = useMemo(() => toXtermTheme(theme.colors.terminal), [theme.colors.terminal]); const xtermTheme = useMemo(() => toXtermTheme(theme.colors.terminal), [theme]);
const isMobile = useIsCompactFormFactor(); const isMobile = useIsCompactFormFactor();
const mobileView = usePanelStore((state) => state.mobileView); const mobileView = usePanelStore((state) => state.mobileView);
const openAgentList = usePanelStore((state) => state.openAgentList); const openAgentList = usePanelStore((state) => state.openAgentList);
@@ -59,6 +59,7 @@ interface ToolCallSheetProviderProps {
} }
export function ToolCallSheetProvider({ children }: ToolCallSheetProviderProps) { export function ToolCallSheetProvider({ children }: ToolCallSheetProviderProps) {
const { theme } = useUnistyles();
const bottomSheetRef = useRef<BottomSheetModal>(null); const bottomSheetRef = useRef<BottomSheetModal>(null);
const [sheetData, setSheetData] = React.useState<ToolCallSheetData | null>(null); const [sheetData, setSheetData] = React.useState<ToolCallSheetData | null>(null);
@@ -103,8 +104,8 @@ export function ToolCallSheetProvider({ children }: ToolCallSheetProviderProps)
onChange={handleSheetChange} onChange={handleSheetChange}
backdropComponent={renderBackdrop} backdropComponent={renderBackdrop}
enablePanDownToClose enablePanDownToClose
backgroundStyle={styles.sheetBackground} backgroundComponent={CustomSheetBackground}
handleIndicatorStyle={styles.handleIndicator} handleIndicatorStyle={{ backgroundColor: theme.colors.palette.zinc[600] }}
> >
{sheetData && <ToolCallSheetContent data={sheetData} onClose={closeToolCall} />} {sheetData && <ToolCallSheetContent data={sheetData} onClose={closeToolCall} />}
</BottomSheetModal> </BottomSheetModal>
@@ -120,6 +121,7 @@ interface ToolCallSheetContentProps {
} }
function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) { function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
const { theme } = useUnistyles();
const { toolName, displayName, detail, errorText, showLoadingSkeleton } = data; const { toolName, displayName, detail, errorText, showLoadingSkeleton } = data;
const IconComponent = resolveToolCallIcon(toolName, detail); const IconComponent = resolveToolCallIcon(toolName, detail);
@@ -129,13 +131,13 @@ function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
{/* Header */} {/* Header */}
<View style={styles.header}> <View style={styles.header}>
<View style={styles.headerLeft}> <View style={styles.headerLeft}>
<IconComponent size={20} color={styles.headerIcon.color} /> <IconComponent size={20} color={theme.colors.foreground} />
<Text style={styles.headerTitle} numberOfLines={1}> <Text style={styles.headerTitle} numberOfLines={1}>
{displayName} {displayName}
</Text> </Text>
</View> </View>
<Pressable onPress={onClose} style={styles.closeButton}> <Pressable onPress={onClose} style={styles.closeButton}>
<X size={20} color={styles.closeIcon.color} /> <X size={20} color={theme.colors.foregroundMuted} />
</Pressable> </Pressable>
</View> </View>
@@ -155,12 +157,6 @@ function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
// ----- Styles ----- // ----- Styles -----
const styles = StyleSheet.create((theme) => ({ const styles = StyleSheet.create((theme) => ({
sheetBackground: {
backgroundColor: theme.colors.surface2,
},
handleIndicator: {
backgroundColor: theme.colors.palette.zinc[600],
},
container: { container: {
flex: 1, flex: 1,
backgroundColor: theme.colors.surface2, backgroundColor: theme.colors.surface2,
@@ -180,9 +176,6 @@ const styles = StyleSheet.create((theme) => ({
gap: theme.spacing[2], gap: theme.spacing[2],
flex: 1, flex: 1,
}, },
headerIcon: {
color: theme.colors.foreground,
},
headerTitle: { headerTitle: {
fontSize: theme.fontSize.lg, fontSize: theme.fontSize.lg,
fontWeight: theme.fontWeight.semibold, fontWeight: theme.fontWeight.semibold,
@@ -192,9 +185,6 @@ const styles = StyleSheet.create((theme) => ({
closeButton: { closeButton: {
padding: theme.spacing[2], padding: theme.spacing[2],
}, },
closeIcon: {
color: theme.colors.foregroundMuted,
},
content: { content: {
flex: 1, flex: 1,
minHeight: 0, minHeight: 0,
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef } from "react";
import { ScrollView, Text, View, Pressable, type LayoutChangeEvent } from "react-native"; import { ScrollView, Text, View, Pressable, type LayoutChangeEvent } from "react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { File, Folder } from "lucide-react-native"; import { File, Folder } from "lucide-react-native";
import { Theme } from "@/styles/theme"; import type { Theme } from "@/styles/theme";
import { getAutocompleteScrollOffset } from "./autocomplete-utils"; import { getAutocompleteScrollOffset } from "./autocomplete-utils";
export interface AutocompleteOption { export interface AutocompleteOption {
+18 -11
View File
@@ -101,7 +101,21 @@ function toNumericStyleValue(value: unknown): number | null {
} }
function ComboboxSheetBackground({ style }: BottomSheetBackgroundProps) { function ComboboxSheetBackground({ style }: BottomSheetBackgroundProps) {
return <Animated.View pointerEvents="none" style={[style, styles.bottomSheetBackground]} />; const { theme } = useUnistyles();
return (
<Animated.View
pointerEvents="none"
style={[
style,
{
backgroundColor: theme.colors.surface0,
borderTopLeftRadius: theme.borderRadius["2xl"],
borderTopRightRadius: theme.borderRadius["2xl"],
},
]}
/>
);
} }
export interface SearchInputProps { export interface SearchInputProps {
@@ -268,6 +282,7 @@ export function Combobox({
anchorRef, anchorRef,
children, children,
}: ComboboxProps): ReactElement { }: ComboboxProps): ReactElement {
const { theme } = useUnistyles();
const isMobile = useIsCompactFormFactor(); const isMobile = useIsCompactFormFactor();
const effectiveOptionsPosition = isMobile ? "below-search" : optionsPosition; const effectiveOptionsPosition = isMobile ? "below-search" : optionsPosition;
const isDesktopAboveSearch = !isMobile && isWeb && effectiveOptionsPosition === "above-search"; const isDesktopAboveSearch = !isMobile && isWeb && effectiveOptionsPosition === "above-search";
@@ -714,12 +729,12 @@ export function Combobox({
enableDismissOnClose={enableDismissOnClose} enableDismissOnClose={enableDismissOnClose}
stackBehavior={stackBehavior} stackBehavior={stackBehavior}
backgroundComponent={ComboboxSheetBackground} backgroundComponent={ComboboxSheetBackground}
handleIndicatorStyle={styles.bottomSheetHandle} handleIndicatorStyle={{ backgroundColor: theme.colors.palette.zinc[600] }}
keyboardBehavior="extend" keyboardBehavior="extend"
keyboardBlurBehavior="restore" keyboardBlurBehavior="restore"
> >
<View style={styles.bottomSheetHeader}> <View style={styles.bottomSheetHeader}>
<Text style={styles.comboboxTitle}>{title}</Text> <Text style={[styles.comboboxTitle, { color: theme.colors.foreground }]}>{title}</Text>
</View> </View>
{stickyHeader} {stickyHeader}
{!children && searchable ? searchInput : null} {!children && searchable ? searchInput : null}
@@ -909,14 +924,6 @@ const styles = StyleSheet.create((theme) => ({
color: theme.colors.foregroundMuted, color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.sm, fontSize: theme.fontSize.sm,
}, },
bottomSheetBackground: {
backgroundColor: theme.colors.surface0,
borderTopLeftRadius: theme.borderRadius["2xl"],
borderTopRightRadius: theme.borderRadius["2xl"],
},
bottomSheetHandle: {
backgroundColor: theme.colors.palette.zinc[600],
},
bottomSheetHeader: { bottomSheetHeader: {
paddingHorizontal: theme.spacing[6], paddingHorizontal: theme.spacing[6],
paddingBottom: theme.spacing[2], paddingBottom: theme.spacing[2],
@@ -349,6 +349,7 @@ export function ContextMenuContent({
testID?: string; testID?: string;
}>): ReactElement | null { }>): ReactElement | null {
const context = useContextMenuContext("ContextMenuContent"); const context = useContextMenuContext("ContextMenuContent");
const { theme } = useUnistyles();
const webScrollbarStyle = useWebScrollbarStyle(); const webScrollbarStyle = useWebScrollbarStyle();
const isMobile = useIsCompactFormFactor(); const isMobile = useIsCompactFormFactor();
const useMobileSheet = isMobile && mobileMode === "sheet"; const useMobileSheet = isMobile && mobileMode === "sheet";
@@ -475,8 +476,14 @@ export function ContextMenuContent({
onChange={handleSheetChange} onChange={handleSheetChange}
backdropComponent={renderSheetBackdrop} backdropComponent={renderSheetBackdrop}
enablePanDownToClose enablePanDownToClose
backgroundStyle={styles.sheetBackground} backgroundStyle={[
handleIndicatorStyle={styles.sheetHandle} styles.sheetBackground,
{
backgroundColor: theme.colors.surface0,
borderColor: theme.colors.border,
},
]}
handleIndicatorStyle={[styles.sheetHandle, { backgroundColor: theme.colors.surface2 }]}
keyboardBehavior="extend" keyboardBehavior="extend"
keyboardBlurBehavior="restore" keyboardBlurBehavior="restore"
> >