Skip to content

Commit 899f089

Browse files
committed
Resolves #386, resolves #385
1 parent a0b13d3 commit 899f089

3 files changed

Lines changed: 58 additions & 25 deletions

File tree

.changeset/open-crabs-start.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"ai-elements": patch
3+
---
4+
5+
Separate select and toggle actions in FileTreeFolder so clicking the chevron only expands/collapses and clicking the name only selects

packages/elements/__tests__/file-tree.test.tsx

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe("fileTreeFolder", () => {
5555
expect(screen.getByText("index.ts")).toBeInTheDocument();
5656
});
5757

58-
it("toggles expansion on click", async () => {
58+
it("toggles expansion on chevron click", async () => {
5959
const user = userEvent.setup();
6060
render(
6161
<FileTree>
@@ -68,13 +68,32 @@ describe("fileTreeFolder", () => {
6868
// Initially collapsed
6969
expect(screen.queryByText("index.ts")).not.toBeInTheDocument();
7070

71-
// Click to expand
72-
const folderButton = screen.getByRole("button");
73-
await user.click(folderButton);
71+
// Click chevron to expand
72+
const [chevronButton] = screen.getAllByRole("button");
73+
await user.click(chevronButton);
7474

7575
expect(screen.getByText("index.ts")).toBeInTheDocument();
7676
});
7777

78+
it("selects folder without toggling expansion", async () => {
79+
const onSelect = vi.fn();
80+
const user = userEvent.setup();
81+
render(
82+
<FileTree onSelect={onSelect}>
83+
<FileTreeFolder name="src" path="src">
84+
<FileTreeFile name="index.ts" path="src/index.ts" />
85+
</FileTreeFolder>
86+
</FileTree>
87+
);
88+
89+
// Click folder name to select (not chevron)
90+
await user.click(screen.getByText("src"));
91+
92+
expect(onSelect).toHaveBeenCalledWith("src");
93+
// Should NOT expand
94+
expect(screen.queryByText("index.ts")).not.toBeInTheDocument();
95+
});
96+
7897
it("calls onExpandedChange when toggling", async () => {
7998
const onExpandedChange = vi.fn();
8099
const user = userEvent.setup();
@@ -87,8 +106,8 @@ describe("fileTreeFolder", () => {
87106
</FileTree>
88107
);
89108

90-
const folderButton = screen.getByRole("button");
91-
await user.click(folderButton);
109+
const [chevronButton] = screen.getAllByRole("button");
110+
await user.click(chevronButton);
92111

93112
expect(onExpandedChange).toHaveBeenCalledWith(new Set(["src"]));
94113
});
@@ -158,13 +177,13 @@ describe("composability", () => {
158177

159178
expect(screen.getByText("components")).toBeInTheDocument();
160179

161-
// Expand nested folder
162-
const componentsFolderButton = screen
163-
.getByText("components")
164-
.closest("button");
180+
// Click folder name to find its sibling chevron, then expand
181+
const componentsText = screen.getByText("components");
182+
const componentsRow = componentsText.closest("div");
183+
const chevronButton = componentsRow?.querySelector("button:first-child");
165184
// oxlint-disable-next-line eslint-plugin-jest(no-conditional-in-test)
166-
if (componentsFolderButton) {
167-
await user.click(componentsFolderButton);
185+
if (chevronButton) {
186+
await user.click(chevronButton);
168187
}
169188

170189
expect(screen.getByText("Button.tsx")).toBeInTheDocument();

packages/elements/src/file-tree.tsx

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const FileTreeContext = createContext<FileTreeContextType>({
3838
togglePath: noop,
3939
});
4040

41-
export type FileTreeProps = HTMLAttributes<HTMLDivElement> & {
41+
export type FileTreeProps = Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> & {
4242
expanded?: Set<string>;
4343
defaultExpanded?: Set<string>;
4444
selectedPath?: string;
@@ -169,21 +169,30 @@ export const FileTreeFolder = ({
169169
tabIndex={0}
170170
{...props}
171171
>
172-
<CollapsibleTrigger asChild>
172+
<div
173+
className={cn(
174+
"flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50",
175+
isSelected && "bg-muted"
176+
)}
177+
>
178+
<CollapsibleTrigger asChild>
179+
<button
180+
className="flex shrink-0 cursor-pointer items-center border-none bg-transparent p-0"
181+
type="button"
182+
>
183+
<ChevronRightIcon
184+
className={cn(
185+
"size-4 shrink-0 text-muted-foreground transition-transform",
186+
isExpanded && "rotate-90"
187+
)}
188+
/>
189+
</button>
190+
</CollapsibleTrigger>
173191
<button
174-
className={cn(
175-
"flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50",
176-
isSelected && "bg-muted"
177-
)}
192+
className="flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left"
178193
onClick={handleSelect}
179194
type="button"
180195
>
181-
<ChevronRightIcon
182-
className={cn(
183-
"size-4 shrink-0 text-muted-foreground transition-transform",
184-
isExpanded && "rotate-90"
185-
)}
186-
/>
187196
<FileTreeIcon>
188197
{isExpanded ? (
189198
<FolderOpenIcon className="size-4 text-blue-500" />
@@ -193,7 +202,7 @@ export const FileTreeFolder = ({
193202
</FileTreeIcon>
194203
<FileTreeName>{name}</FileTreeName>
195204
</button>
196-
</CollapsibleTrigger>
205+
</div>
197206
<CollapsibleContent>
198207
<div className="ml-4 border-l pl-2">{children}</div>
199208
</CollapsibleContent>

0 commit comments

Comments
 (0)