diff --git a/packages/keystatic/src/app/CollectionPage.tsx b/packages/keystatic/src/app/CollectionPage.tsx
index 67114a3ee..90c602942 100644
--- a/packages/keystatic/src/app/CollectionPage.tsx
+++ b/packages/keystatic/src/app/CollectionPage.tsx
@@ -37,6 +37,7 @@ import {
SortDescriptor,
} from '@keystar/ui/table';
import { Heading, Text } from '@keystar/ui/typography';
+import { NavTree, Item } from '@keystar/ui/nav-tree';
import { Config } from '../config';
import { sortBy } from './collection-sort';
@@ -488,6 +489,63 @@ function CollectionTable(
];
}, [collection, hideStatusColumn]);
+ const entryTree = useMemo(() => {
+ const entries = new Set(entriesWithStatus.map(entry => entry.name));
+ type EntryTreeNode = {
+ path: string;
+ key: string;
+ isEntry: boolean;
+ children: EntryTreeNode[] | undefined;
+ };
+ const root: EntryTreeNode = {
+ path: '',
+ key: '',
+ children: [],
+ isEntry: false,
+ };
+
+ for (const entry of filteredItems.map(x => x.name).sort()) {
+ const split = entry.split('/');
+ let current = root;
+ let currentPath = '';
+ for (const part of split) {
+ if (currentPath) currentPath += '/';
+ currentPath += part;
+ if (!current.children) current.children = [];
+ let existing = current.children?.find(x => x.path === currentPath);
+ if (!existing) {
+ existing = {
+ isEntry: entries.has(currentPath),
+ key: part,
+ path: currentPath,
+ children: undefined,
+ };
+ current.children.push(existing);
+ }
+ current = existing;
+ }
+ }
+ return root.children;
+ }, [entriesWithStatus, filteredItems]);
+
+ return (
+
+ {item => (
+ -
+ {item.isEntry ? (
+
+ {item.key}
+
+ ) : (
+ {item.key}
+ )}
+
+ )}
+
+ );
+
return (