-
Notifications
You must be signed in to change notification settings - Fork 847
feat: toggle hidden files file explorer #7192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
I have read the CLA Document and I hereby sign the CLA |
| const [openFile, setOpenFile] = useState<FileInfo | null>(null); | ||
|
|
||
| const [hideHiddenFilesOrDirectory, setHideHiddenFilesOrDirectory] = useState( | ||
| () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you use atomWithStorage (you can search the repo for examples)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added the requested changes
| <RefreshCcwIcon size={16} /> | ||
| </Button> | ||
| </Tooltip> | ||
| <Tooltip content="Hidden-Files"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe Toggle hidden files for the content
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added the requested changes
4984bd6 to
b8bf7a6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds functionality to toggle the visibility of hidden files and directories in the file explorer by introducing a new button with an eye-off icon in the toolbar.
- Implements persistent storage for the "show hidden files" preference using Jotai atoms
- Adds filtering logic to recursively hide/show files and directories that start with a dot (.)
- Introduces a new toolbar button to toggle the visibility state
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const handleHiddenFilesToggle = useEvent(() => { | ||
| const newValue = !showHiddenFiles; | ||
| setShowHiddenFiles(newValue); | ||
| return; |
Copilot
AI
Nov 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return statement is unnecessary here since the function doesn't need to return anything. Simply remove it or remove the entire line as the function already ends after the assignment.
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added the requested changes
| } | ||
| let next = item; | ||
| if (item.children && item.children.length) { | ||
| const kids = filterHiddenTree(item.children, false); |
Copilot
AI
Nov 18, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recursive call passes false as the showHidden parameter instead of passing through the original parameter value. This means once you enter the recursion, it will always hide hidden files regardless of the parent's showHidden value.
Change line 731 to: const kids = filterHiddenTree(item.children, showHidden);
| const kids = filterHiddenTree(item.children, false); | |
| const kids = filterHiddenTree(item.children, showHidden); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added the requested changes
| const [data, setData] = useState<FileInfo[]>([]); | ||
| const [openFile, setOpenFile] = useState<FileInfo | null>(null); | ||
| const showHiddenFiles = useAtomValue<boolean>(hiddenFilesState); | ||
| const setShowHiddenFiles = useSetAtom(hiddenFilesState); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can be simplified to
const [showHiddenFiles, setShowHiddenFiles] = useAtom(hiddenFilesState);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added the requested changes
| import { useFileExplorerUpload } from "./upload"; | ||
|
|
||
| const hiddenFilesState = atomWithStorage( | ||
| "showHiddenFiles", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| "showHiddenFiles", | |
| "marimo:showHiddenFiles", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added the requested changes
| return true; | ||
| } | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we add some tests for this?
import { describe, it, expect } from 'vitest';
interface FileInfo {
name: string;
children?: FileInfo[];
}
describe('isDirectoryOrFileHidden', () => {
it('should return true for files starting with dot', () => {
expect(isDirectoryOrFileHidden('.git')).toBe(true);
expect(isDirectoryOrFileHidden('.env')).toBe(true);
expect(isDirectoryOrFileHidden('.gitignore')).toBe(true);
});
it('should return false for normal files', () => {
expect(isDirectoryOrFileHidden('README.md')).toBe(false);
expect(isDirectoryOrFileHidden('package.json')).toBe(false);
expect(isDirectoryOrFileHidden('index.ts')).toBe(false);
});
it('should return false for files with dots in the middle', () => {
expect(isDirectoryOrFileHidden('file.test.ts')).toBe(false);
expect(isDirectoryOrFileHidden('my.config.js')).toBe(false);
});
});
describe('filterHiddenTree', () => {
it('should return all items when showHidden is true', () => {
const list: FileInfo[] = [
{ name: '.git' },
{ name: 'README.md' },
{ name: '.env' },
];
const result = filterHiddenTree(list, true);
expect(result).toBe(list); // should be the exact same reference
expect(result).toHaveLength(3);
});
it('should filter out hidden files when showHidden is false', () => {
const list: FileInfo[] = [
{ name: '.git' },
{ name: 'README.md' },
{ name: '.env' },
{ name: 'package.json' },
];
const result = filterHiddenTree(list, false);
expect(result).toHaveLength(2);
expect(result[0].name).toBe('README.md');
expect(result[1].name).toBe('package.json');
});
it('should filter hidden directories recursively', () => {
const list: FileInfo[] = [
{
name: 'src',
children: [
{ name: 'index.ts' },
{ name: '.DS_Store' },
{ name: 'utils.ts' },
],
},
{
name: '.git',
children: [
{ name: 'config' },
],
},
];
const result = filterHiddenTree(list, false);
expect(result).toHaveLength(1);
expect(result[0].name).toBe('src');
expect(result[0].children).toHaveLength(2);
expect(result[0].children?.[0].name).toBe('index.ts');
expect(result[0].children?.[1].name).toBe('utils.ts');
});
it('should handle nested hidden files', () => {
const list: FileInfo[] = [
{
name: 'project',
children: [
{
name: 'src',
children: [
{ name: 'index.ts' },
{ name: '.backup' },
],
},
{ name: '.env' },
],
},
];
const result = filterHiddenTree(list, false);
expect(result).toHaveLength(1);
expect(result[0].children).toHaveLength(1);
expect(result[0].children?.[0].name).toBe('src');
expect(result[0].children?.[0].children).toHaveLength(1);
expect(result[0].children?.[0].children?.[0].name).toBe('index.ts');
});
it('should preserve directory structure when no children are filtered', () => {
const list: FileInfo[] = [
{
name: 'src',
children: [
{ name: 'index.ts' },
{ name: 'utils.ts' },
],
},
];
const result = filterHiddenTree(list, false);
// Should return the same reference since nothing changed
expect(result[0]).toBe(list[0]);
});
it('should create new object only when children are filtered', () => {
const list: FileInfo[] = [
{
name: 'src',
children: [
{ name: 'index.ts' },
{ name: '.hidden' },
],
},
];
const result = filterHiddenTree(list, false);
// Should be a new object since children changed
expect(result[0]).not.toBe(list[0]);
expect(result[0].children).not.toBe(list[0].children);
});
it('should handle empty list', () => {
const result = filterHiddenTree([], false);
expect(result).toEqual([]);
});
it('should handle empty children arrays', () => {
const list: FileInfo[] = [
{
name: 'empty-dir',
children: [],
},
];
const result = filterHiddenTree(list, false);
expect(result).toHaveLength(1);
expect(result[0].children).toEqual([]);
});
it('should handle deeply nested structures', () => {
const list: FileInfo[] = [
{
name: 'level1',
children: [
{
name: 'level2',
children: [
{
name: 'level3',
children: [
{ name: 'file.ts' },
{ name: '.hidden' },
],
},
],
},
{ name: '.ignore' },
],
},
];
const result = filterHiddenTree(list, false);
expect(result[0].children?.[0].children?.[0].children).toHaveLength(1);
expect(result[0].children?.[0].children?.[0].children?.[0].name).toBe('file.ts');
});
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added the requested changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added the requested changes
c0ef425 to
50fe5e6
Compare
|
Deployment failed with the following error: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
|
Hi @mscolnick , I see you have approved the changes but you have not merged. Is there something else you want me to add to this PR? |
📝 Summary
🔍 Description of Changes
Fixes #2873,
Add button to be toggle hidden files/directory in file explorer.
I added a Eye-off Button to toggle the hidden files on/off.
Filters Nodes based on if they are hidden or not, then pass that to the Tree to display them.
📋 Checklist