diff --git a/apps/web/src/components/RightPanelTabs.tsx b/apps/web/src/components/RightPanelTabs.tsx
index 83524009c19..fe652b6fde7 100644
--- a/apps/web/src/components/RightPanelTabs.tsx
+++ b/apps/web/src/components/RightPanelTabs.tsx
@@ -358,6 +358,7 @@ export function RightPanelTabs(props: RightPanelTabsProps) {
void }) {
+ return (
+
+
+ }
+ >
+
+
+ {props.isPending ? "Refreshing…" : "Refresh files"}
+
+ );
+}
+
+function FileSearchField(props: {
+ ariaLabel: string;
+ name: string;
+ onClose: () => void;
+ onValueChange: (value: string) => void;
+ value: string;
+}) {
+ return (
+
+ props.onValueChange(event.target.value)}
+ onKeyDown={(event) => {
+ if (event.key !== "Escape") return;
+ props.onClose();
+ event.currentTarget.blur();
+ }}
+ />
+
+ );
+}
+
export default function FileBrowserPanel({
environmentId,
cwd,
@@ -176,9 +228,17 @@ export default function FileBrowserPanel({
}
},
paths: [],
- search: true,
+ search: false,
unsafeCSS: TREE_UNSAFE_CSS,
});
+ const search = useFileTreeSearch(model);
+ const handleSearchValueChange = (value: string) => {
+ if (value.trim().length === 0) {
+ search.close();
+ return;
+ }
+ search.setValue(value);
+ };
useEffect(() => {
if (previousTreePathsRef.current === treePaths) return;
@@ -187,11 +247,6 @@ export default function FileBrowserPanel({
model.resetPaths(treePaths);
}, [entryKinds, model, treePaths]);
- const fileCount = useMemo(
- () => entries.reduce((count, entry) => count + (entry.kind === "file" ? 1 : 0), 0),
- [entries],
- );
-
// Tag tree drags with the composer mention payload. The row is read from
// the composed event path (the tree's shadow root is open), so this does
// not depend on running after the tree's own dragstart handler; the drag
@@ -223,32 +278,15 @@ export default function FileBrowserPanel({
className="flex min-h-0 flex-1 flex-col bg-background"
data-file-browser-panel={`${environmentId}:${cwd}`}
>
-
-
-
{projectName}
-
- {entriesQuery.isPending && entriesQuery.data === null
- ? "Indexing…"
- : `${fileCount.toLocaleString()} files`}
- {entriesQuery.data?.truncated ? " · partial" : ""}
-
-
-
-
+
+
+
{entriesQuery.error && entriesQuery.data === null ? (
{entriesQuery.error}
diff --git a/apps/web/src/components/preview/PreviewChromeRow.tsx b/apps/web/src/components/preview/PreviewChromeRow.tsx
index 8044b472264..1f4eb95d62b 100644
--- a/apps/web/src/components/preview/PreviewChromeRow.tsx
+++ b/apps/web/src/components/preview/PreviewChromeRow.tsx
@@ -166,7 +166,7 @@ export function PreviewChromeRow({
-
+
) {
+const inputGroupVariants = cva(
+ "relative inline-flex w-full min-w-0 items-center rounded-lg border text-base text-foreground ring-ring/24 transition-shadow has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/64 has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/16 has-[textarea]:h-auto has-data-[align=block-end]:h-auto has-data-[align=block-start]:h-auto has-data-[align=block-end]:flex-col has-data-[align=block-start]:flex-col has-[input:focus-visible,textarea:focus-visible]:border-ring has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/36 has-autofill:bg-foreground/4 has-[input:disabled,textarea:disabled]:opacity-64 has-[input:disabled,textarea:disabled,input:focus-visible,textarea:focus-visible,input[aria-invalid],textarea[aria-invalid]]:shadow-none has-[input:focus-visible,textarea:focus-visible]:ring-[3px] sm:text-sm dark:has-autofill:bg-foreground/8 dark:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/24 has-data-[align=inline-start]:**:[[data-size=sm]_input]:ps-1.5 has-data-[align=inline-end]:**:[[data-size=sm]_input]:pe-1.5 *:[[data-slot=input-control],[data-slot=textarea-control]]:contents *:[[data-slot=input-control],[data-slot=textarea-control]]:before:hidden has-[[data-align=block-start],[data-align=block-end]]:**:[input]:h-auto has-data-[align=inline-start]:**:[input]:ps-2 has-data-[align=inline-end]:**:[input]:pe-2 has-data-[align=block-end]:**:[input]:pt-1.5 has-data-[align=block-start]:**:[input]:pb-1.5 **:[textarea]:min-h-20.5 **:[textarea]:resize-none **:[textarea]:py-[calc(--spacing(3)-1px)] **:[textarea]:max-sm:min-h-23.5 **:[textarea_button]:rounded-[calc(var(--radius-md)-1px)]",
+ {
+ defaultVariants: {
+ variant: "default",
+ },
+ variants: {
+ variant: {
+ default:
+ "border-input bg-background not-dark:bg-clip-padding shadow-xs/5 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_1px_--theme(--color-black/4%)] dark:bg-input/32 dark:not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_-1px_--theme(--color-white/6%)]",
+ ghost:
+ "border-transparent bg-transparent shadow-none hover:bg-muted/40 has-[input:focus-visible,textarea:focus-visible]:bg-background",
+ },
+ },
+ },
+);
+
+function InputGroup({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
return (