Skip to content

Commit

Permalink
stories: fix sidebar overlap and tree search (#85603)
Browse files Browse the repository at this point in the history
Fixes sidebar tree overlap and a couple other minor details like moving
images and icons into assets folder and fixing the default sort (which
the tree data structure now manages)
  • Loading branch information
JonasBa authored Feb 21, 2025
1 parent 8989ec2 commit b4f8f9c
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 39 deletions.
6 changes: 2 additions & 4 deletions static/app/stories/storyBook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default function storyBook(
name: string;
render: StoryRenderFunction;
}> = [];
const APIDocumentation: TypeLoader.ComponentDocWithFilename[] = [];
const APIDocumentation: Array<TypeLoader.ComponentDocWithFilename | undefined> = [];

const storyFn: StoryContext = (name: string, render: StoryRenderFunction) => {
stories.push({name, render});
Expand All @@ -30,9 +30,7 @@ export default function storyBook(
const apiReferenceFn: (
documentation: TypeLoader.ComponentDocWithFilename | undefined
) => void = (documentation: TypeLoader.ComponentDocWithFilename | undefined) => {
if (documentation) {
APIDocumentation.push(documentation);
}
APIDocumentation.push(documentation);
};

setup(storyFn, apiReferenceFn);
Expand Down
3 changes: 2 additions & 1 deletion static/app/views/stories/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default function Stories() {
const story = useStoriesLoader({files: storyFiles});
const [storyRepresentation, setStoryRepresentation] = useLocalStorageState<
'category' | 'filesystem'
>('story-representation', 'filesystem');
>('story-representation', 'category');

const nodes = useStoryTree(files, {
query: location.query.query ?? '',
Expand Down Expand Up @@ -178,6 +178,7 @@ const SidebarContainer = styled('div')`
gap: ${space(2)};
min-height: 0;
position: relative;
z-index: 10;
`;

const StoryTreeContainer = styled('div')`
Expand Down
102 changes: 68 additions & 34 deletions static/app/views/stories/storyTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,21 @@ class StoryTreeNode {

find(predicate: (node: StoryTreeNode) => boolean): StoryTreeNode | undefined {
for (const {node} of this) {
if (node && predicate(node)) {
if (predicate(node)) {
return node;
}
}
return undefined;
}

sort(predicate: (a: [string, StoryTreeNode], b: [string, StoryTreeNode]) => number) {
this.children = Object.fromEntries(Object.entries(this.children).sort(predicate));

for (const {node} of this) {
node.children = Object.fromEntries(Object.entries(node.children).sort(predicate));
}
}

// Iterator that yields all files in the tree, excluding folders
*[Symbol.iterator]() {
function* recurse(
Expand All @@ -57,24 +65,27 @@ class StoryTreeNode {
}
}

function folderOrSearchScoreFirst(a: StoryTreeNode, b: StoryTreeNode) {
if (a.visible && !b.visible) {
function folderOrSearchScoreFirst(
a: [string, StoryTreeNode],
b: [string, StoryTreeNode]
) {
if (a[1].visible && !b[1].visible) {
return -1;
}

if (!a.visible && b.visible) {
if (!a[1].visible && b[1].visible) {
return 1;
}

if (a.result && b.result) {
if (a.result.score === b.result.score) {
return a.name.localeCompare(b.name);
if (a[1].result && b[1].result) {
if (a[1].result.score === b[1].result.score) {
return a[0].localeCompare(b[0]);
}
return b.result.score - a.result.score;
return b[1].result.score - a[1].result.score;
}

const aIsFolder = Object.keys(a.children).length > 0;
const bIsFolder = Object.keys(b.children).length > 0;
const aIsFolder = Object.keys(a[1].children).length > 0;
const bIsFolder = Object.keys(b[1].children).length > 0;

if (aIsFolder && !bIsFolder) {
return -1;
Expand All @@ -84,7 +95,15 @@ function folderOrSearchScoreFirst(a: StoryTreeNode, b: StoryTreeNode) {
return 1;
}

return a.name.localeCompare(b.name);
return a[0].localeCompare(b[0]);
}

const order: FileCategory[] = ['components', 'hooks', 'views', 'assets', 'styles'];
function rootCategorySort(
a: [FileCategory | string, StoryTreeNode],
b: [FileCategory | string, StoryTreeNode]
) {
return order.indexOf(a[0] as FileCategory) - order.indexOf(b[0] as FileCategory);
}

function normalizeFilename(filename: string) {
Expand All @@ -97,16 +116,19 @@ function normalizeFilename(filename: string) {
return filename.charAt(0).toUpperCase() + filename.slice(1).replace('.stories.tsx', '');
}

function inferFileCategory(
path: string
): 'hooks' | 'components' | 'views' | 'styles' | 'icons' {
const parts = path.split('/');
type FileCategory = 'hooks' | 'components' | 'views' | 'styles' | 'assets';

function inferFileCategory(path: string): FileCategory {
const parts = path.split('/');
const filename = parts.at(-1);
if (filename?.startsWith('use')) {
return 'hooks';
}

if (parts[1]?.startsWith('icons') || path.endsWith('images.stories.tsx')) {
return 'assets';
}

if (parts[1]?.startsWith('views')) {
return 'views';
}
Expand All @@ -115,10 +137,6 @@ function inferFileCategory(
return 'styles';
}

if (parts[1]?.startsWith('icons')) {
return 'icons';
}

return 'components';
}

Expand Down Expand Up @@ -163,6 +181,9 @@ export function useStoryTree(
parent = parent.children[part]!;
}
}

// Sort the root by file/folder name when using the filesystem representation
root.sort(folderOrSearchScoreFirst);
} else if (options.representation === 'category') {
for (const file of files) {
const type = inferFileCategory(file);
Expand All @@ -180,6 +201,11 @@ export function useStoryTree(
);
}
}

// Sort the root by category order when using the category representation
root.children = Object.fromEntries(
Object.entries(root.children).sort(rootCategorySort)
);
}

// If the user navigates to a story, expand to its location in the tree
Expand All @@ -204,7 +230,6 @@ export function useStoryTree(
if (!options.query) {
if (initialName.current) {
initialName.current = null;
return Object.values(root.children);
}

// If there is no initial query and no story is selected, the sidebar
Expand All @@ -214,6 +239,15 @@ export function useStoryTree(
node.expanded = false;
node.result = null;
}

// sort alphabetically or by category
if (options.representation === 'filesystem') {
root.sort(folderOrSearchScoreFirst);
} else {
root.children = Object.fromEntries(
Object.entries(root.children).sort(rootCategorySort)
);
}
return Object.values(root.children);
}

Expand Down Expand Up @@ -260,8 +294,10 @@ export function useStoryTree(
}
}

root.sort(folderOrSearchScoreFirst);

return Object.values(root.children);
}, [tree, options.query]);
}, [tree, options.query, options.representation]);

return nodes;
}
Expand All @@ -276,7 +312,7 @@ export function StoryTree({nodes, ...htmlProps}: Props) {
return (
<nav {...htmlProps}>
<StoryList>
{nodes.sort(folderOrSearchScoreFirst).map(node => {
{nodes.map(node => {
if (!node.visible) {
return null;
}
Expand Down Expand Up @@ -321,18 +357,16 @@ function Folder(props: {node: StoryTreeNode}) {
</FolderName>
{expanded && Object.keys(props.node.children).length > 0 && (
<StoryList>
{Object.values(props.node.children)
.sort(folderOrSearchScoreFirst)
.map(child => {
if (!child.visible) {
return null;
}
return Object.keys(child.children).length === 0 ? (
<File key={child.path} node={child} />
) : (
<Folder key={child.path} node={child} />
);
})}
{Object.values(props.node.children).map(child => {
if (!child.visible) {
return null;
}
return Object.keys(child.children).length === 0 ? (
<File key={child.path} node={child} />
) : (
<Folder key={child.path} node={child} />
);
})}
</StoryList>
)}
</li>
Expand Down

0 comments on commit b4f8f9c

Please sign in to comment.