From 7b813f7f58354ca3b23536ed65316c3e1769c4a2 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 9 Sep 2026 20:09:55 +0200 Subject: [PATCH] fix(app): float the pane Find widget and tighten its chrome Find pushed the first lines of the file down, ran 480px wide with 32px ghost buttons, and let the replacement input start left of the query input. Float the CodeMirror top panel out of flow so the widget overlays the top right of the pane, move the disclosure toggle into a gutter that spans both rows so the two fields share a leading rail, put the match count inside the query field, and drop the icon controls to the shared pane-toolbar chrome the file bar above already uses. --- packages/app/src/file-pane/find/index.web.tsx | 28 +- packages/app/src/file-pane/find/model.web.ts | 15 +- packages/app/src/pane-find/index.tsx | 331 ++++++++++++------ 3 files changed, 248 insertions(+), 126 deletions(-) diff --git a/packages/app/src/file-pane/find/index.web.tsx b/packages/app/src/file-pane/find/index.web.tsx index 70801aa0ca..6cd27fe9c4 100644 --- a/packages/app/src/file-pane/find/index.web.tsx +++ b/packages/app/src/file-pane/find/index.web.tsx @@ -10,9 +10,11 @@ import { createPortal } from "react-dom"; import { EditorView } from "@codemirror/view"; import { Search } from "lucide-react-native"; import { View } from "react-native"; -import { StyleSheet } from "react-native-unistyles"; +import { StyleSheet, withUnistyles } from "react-native-unistyles"; import { useTranslation } from "react-i18next"; -import { Button } from "@/components/ui/button"; +import { mutedIconColorMapping } from "@/components/ui/icon-button-chrome"; +import { paneContentToolbarIconSize, ToolbarButton } from "@/components/ui/pane-content-toolbar"; +import { useIsCompactFormFactor } from "@/constants/layout"; import { PaneFind, type PaneFindHandle } from "@/pane-find"; import { usePaneFocus } from "@/panels/pane-context"; import { hasActiveWebOverlay } from "@/lib/overlay-root"; @@ -22,6 +24,8 @@ import { FileFindModel } from "./model.web"; export { FileFindModel } from "./model.web"; +const SearchIcon = withUnistyles(Search, mutedIconColorMapping); + export function FileFind({ model, editor, @@ -31,6 +35,7 @@ export function FileFind({ }) { const { t } = useTranslation(); const state = useSyncExternalStore(model.subscribe, model.getSnapshot, model.getSnapshot); + const isCompact = useIsCompactFormFactor(); const widget = useRef(null); const { isInteractive, focusPane } = usePaneFocus(); const active = useRetainedPanelActive(); @@ -75,13 +80,9 @@ export function FileFind({ if (!state.panel) return ( - - - ) : null} + ) : null} + ); }); -const styles = StyleSheet.create((theme) => ({ - widget: { - width: 480, - maxWidth: "100%", - padding: theme.spacing[2], - gap: theme.spacing[1], - backgroundColor: theme.colors.surface1, - borderWidth: theme.borderWidth[1], - borderColor: theme.colors.border, - borderRadius: theme.borderRadius.md, - }, - findRow: { flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: theme.spacing[1] }, - queryRow: { - flex: 1, - minWidth: 140, - flexDirection: "row", - alignItems: "center", - gap: theme.spacing[1], - }, - row: { flexDirection: "row", alignItems: "center", gap: theme.spacing[1] }, - replacement: { gap: theme.spacing[1] }, - input: { - flex: 1, - minWidth: 0, - minHeight: CONTROL_HEIGHTS.compact, - paddingHorizontal: theme.spacing[2], - paddingVertical: theme.spacing[1], - fontSize: theme.fontSize.base, - color: theme.colors.foreground, - backgroundColor: theme.colors.surface0, - borderWidth: theme.borderWidth[1], - borderColor: theme.colors.border, - borderRadius: theme.borderRadius.sm, - }, - status: { - flex: 1, - color: theme.colors.foregroundMuted, - fontSize: theme.fontSize.sm, - paddingHorizontal: theme.spacing[2], - }, -})); +/** Desktop find rows run one step tighter than a form field — this is pane chrome. */ +const COLLAPSED_FIELD_HEIGHT = 28; +const FIND_WIDGET_WIDTH = 340; + +const styles = StyleSheet.create((theme) => { + const geometry = createControlGeometry(theme); + + return { + widget: { + width: FIND_WIDGET_WIDTH, + maxWidth: "100%", + flexDirection: "row", + padding: theme.spacing[1.5], + gap: theme.spacing[1], + backgroundColor: theme.colors.surface1, + borderWidth: theme.borderWidth[1], + borderColor: theme.colors.border, + borderRadius: theme.borderRadius.lg, + ...theme.shadow.md, + }, + // The disclosure column spans both rows so the two fields share a leading rail. + gutter: { alignItems: "center", justifyContent: "center", flexShrink: 0 }, + rows: { flex: 1, minWidth: 0, gap: theme.spacing[1] }, + row: { flexDirection: "row", alignItems: "center", gap: theme.spacing[1] }, + field: { + flex: 1, + minWidth: 0, + flexDirection: "row", + alignItems: "center", + gap: theme.spacing[2], + paddingHorizontal: theme.spacing[2], + backgroundColor: theme.colors.surface2, + borderRadius: theme.borderRadius.md, + }, + controlRest: { ...geometry.controlRest }, + controlHover: { ...geometry.controlHover }, + controlActive: { ...geometry.controlActive }, + input: { + flex: 1, + minWidth: 0, + paddingHorizontal: 0, + paddingVertical: 0, + color: theme.colors.foreground, + outlineWidth: 0, + outlineColor: "transparent", + ...geometry.fieldTextSm, + }, + status: { + flexShrink: 0, + color: theme.colors.foregroundMuted, + fontSize: theme.fontSize.sm, + }, + // Ghost actions sit on the field's rail, so they carry the field's padding. + replaceAction: { paddingHorizontal: theme.spacing[2] }, + }; +});