'Is it possible to have README docs appear first in Storybook navigation, then all stories after?

I have all Storybook stories nested under a “Docs” header. Every component gets a README mdx file, then as many stories as necessary. I would like for the README to always appear first in the navigation, then stories can be sorted as they are naturally.

This is my desired sort:

Docs
│
├─ Button
│  │
│  ├─ README
│  ├─ Button Story One
│  ├─ Button Story Two
│  └─ Button Story One
│
└─ Grid
   │
   ├─ README
   │
   ├─ Column
   │  │
   │  ├─ README
   │  ├─ Column Story One
   │  ├─ Column Story Two
   │  └─ Column Story Three
   │
   └─ Row
      │
      ├─ README
      ├─ Row Story One
      ├─ Row Story Two
      └─ Row Story Three

You’ll notice there are 3 levels, but depending on the complexity of the component there may be as many as 4 levels, each with a README.

Is this custom sorting possible? If so, how?

This is the recommended sorting algorithm:

return a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true });

Where a[1] and b[1] are objects with the following structure:

{
  "id": "docs-grid-column-examples--default-story",
  "kind": "Docs/Grid/Column/Examples",
  "name": "default",
  "story": "default"
}


Solution 1:[1]

Using StoryBook v6 in the preview.js you can add a custom sorter, similar to another answer on here using storyBookArray, however the other solution may not be compatible.

There are downsides to this, you will need to do a more advanced comparison if you need to force order.

It's rather easy to debug and look at with other versions of StoryBook as this is also run in the browser, meaning you can debug and use console.log where required.

export const parameters = {
  options: {
    storySort: (a, b) => {
      /*
        Changes order comparison to story name (Storybook v6)
        Removed "readme" from the name and returns the comparison
        this ensures stories with readme comes first
      */
      var aPath = a[0].replace(/readme$/, "");
      var bPath = b[0].replace(/readme$/, "");
      return aPath.localeCompare(bPath);
    },
  },
};

Solution 2:[2]

Give the following a try...

storyBookArray.sort( ( a, b ) => {

  let aPath = a.kind.replace( /\/README$/, '/' );
  let bPath = b.kind.replace( /\/README$/, '/' );
  
  return aPath.localeCompare( bPath );
    
} );

In short, this replaces any '/README' at the end of the path with '/', implying an empty file name, thereby putting the 'README' at the head of the list within that path when the localeCompare is performed on the two paths during the sort.

This assumes that if the storyBookArray tracks directories in addition to files, that the directories are specified as, say, 'Docs/Grid' without the trailing slash...

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Hugh Wood
Solution 2