Skip to content
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

Sorting stories #548

Closed
tomitrescak opened this issue Oct 16, 2016 · 31 comments
Closed

Sorting stories #548

tomitrescak opened this issue Oct 16, 2016 · 31 comments

Comments

@tomitrescak
Copy link
Contributor

tomitrescak commented Oct 16, 2016

Hi, the stories are listed in rather random order, is it possible to sort them alphabetically? This is how it looks on my end.

screen shot 2016-10-17 at 10 45 11 am

Also, in my project I have quite a few stories by now, would it be possible to add also more hierarchy into stories? Component > View 1 > View 1 Mutation ...

@arunoda
Copy link
Member

arunoda commented Oct 17, 2016

This is based on the way you load stories.
@mnmtanish how about providing a global API to sort stories. (Or sth like that?)

@thani-sh
Copy link
Contributor

I'm sure we'll have to open some way for developers to customize the story list soon (notifications, etc.). I guess we can make sure users can also sort the list when we do that

@tomitrescak
Copy link
Contributor Author

If you do, throw in also support for deeper hierarchies;)
A
-- b
-- c
--- d
--- e
-- f

@mzedeler
Copy link

mzedeler commented Mar 6, 2017

+1

@ndelangen
Copy link
Member

This will be added soon via this PR:
#705

@ndelangen
Copy link
Member

So this is taking quite long, because we're all having some reservations about the apis and keeping our api surface as small as possible.

@tomitrescak That is going to get released with 3.2.0 #1383

@danielduan
Copy link
Member

danielduan commented Aug 26, 2017

Looks like this has been added. Please reopen if there's still issues.

@simon360
Copy link
Contributor

@danielduan I can't find anything in the code or the docs to allow for ordering - should this issue still be open?

@danielduan
Copy link
Member

danielduan commented Nov 10, 2017

It's part of the options addon:
https://github.com/storybooks/storybook/tree/master/addons/options#getting-started
@simon360

@robbertvancaem
Copy link

@simon360 After adding the options addon, use this in your config:

setOptions({
  sortStoriesByKind: true
});

This will sort your stories alphabetically no matter in what order you loaded them.

@ndelangen
Copy link
Member

@simon360 @robbertvancaem Would love a PR on our docs to improve to add this 🙇

gavinorland added a commit to stevesims/govuk-frederic that referenced this issue Jul 19, 2018
This is an attempt to at least be able to have Storybook order its menu alphabetically, so we can then possibly add a top story which serves as the default and could be a readme. So far not working! See storybookjs/storybook#548 (comment)
gavinorland added a commit to govuk-react/govuk-react that referenced this issue Jul 19, 2018
This is an attempt to enable Storybook options so we can at least have it display its stories (and sections) in alphabetical order. Then perhaps we can create a default story which will display first and could act as the readme. So far not working! See storybookjs/storybook#548 (comment)
@pkuczynski
Copy link

This does not work for me. Does anybody else encounter this issue?

@SunHuawei
Copy link

To support alphabetically reordering, in .storybook/config.js, just rewrite this line from

  req.keys().forEach(filename => req(filename));

to

  req.keys().sort().forEach(filename => req(filename));

So, if you want Component > View 1 > View 1 Mutation, you could rename them to 1.Component > 2.View 1 > 3.View 1 Mutation

@ndelangen
Copy link
Member

Thanks @SunHuawei

@timbomckay
Copy link

I followed @danielduan's link but it's pointed to master while mine downloaded v3.4.11. I had to update the tag to view the right instructions and it's working with setOptions as described by @robbertvancaem. There was an update in v4 that changed setOptions to withOptions and required the decorator, thus the master documentation is now misleading for the latest since v4 isn't released yet.

v3.4.11 ReadMe: https://github.com/storybooks/storybook/tree/v3.4.11/addons/options

@yukun-kooboo
Copy link

Hi, I check all solution in this issues comment, but those solutions couldn't sort the secondary menu, so how can I sort the secondary menu?

@timbomckay
Copy link

timbomckay commented Oct 25, 2018

@yukun-kooboo Have you tried the example from master? I haven't tried it, but guessing it might work the same. Looks like you add parameters like so:

storiesOf('Addons|Knobs/Hello', module)
  // If you want to set the option for all stories in of this kind
  .addParameters({ options: { sortStoriesByKind: true } })

Not sure if there's a way to do that globally though instead of manually for each story.

@yukun-kooboo
Copy link

@timbomckay Thanks, but we still use old version storybook, so we need to update it, and then I can add parameters as you say

@piperchester
Copy link
Contributor

For the v5 users, see #5827 which discusses fixing sortStoriesByKind in v5.2.

@ghengeveld
Copy link
Member

For reference, here's the recommended setup for v5.2 onwards:

addParameters({
  options: {
    storySort: (a, b) =>
      a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, { numeric: true }),
  },
});

This will sort your story kinds (files) naturally, while stories within one file remain sorted as defined in the source code. Pretty much what you'd expect. Probably this will become the default at some point.

See also https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#storysort-option

@raphaellueckl
Copy link

raphaellueckl commented Oct 16, 2019

@ghengeveld Do you know how we can debug those objects that are being sorted? I tried it like this:

    storySort: (a, b) => {
            console.log(a);
            return a[1].kind === b[1].kind ? 0 : a[1].id.compare(b[1].id)
    },

This does not output anything, even when I run start-storybook -p 6006 -c .storybook -s src/static --debug-webpack.

What I'm trying is to get after which properties I can sort, since this doesn't seem documented.

@ghengeveld
Copy link
Member

ghengeveld commented Oct 17, 2019

You can see the console.log output in the browser console, not the terminal. Took me a while to figure out too. Here's what you'll get (for a and b):

[
  "components-shapes--square",
  {
    "id": "components-shapes--square",
    "kind": "components|Shapes",
    "name": "Square",
    "story": "Square",
    "parameters": {
      "fileName": "./stories/Shapes.stories.js",
      "options": {
        "hierarchyRootSeparator": "|",
        "hierarchySeparator": {}
      },
      "docs": {},
      "framework": "react"
    }
    // plus some react specific stuff you probably shouldn't touch
  }
]

This is for the story right here.

@ftes
Copy link

ftes commented Feb 14, 2020

Don't try to sort by parameters.filename.
After building the storybook the cleartext (./stories/Shapes.stories.js in the above example`) is replaced with a hash of some sort.

@hanilim
Copy link

hanilim commented Apr 29, 2020

Hoping this helps someone. Here is the code I used to set the order of my stories, using v5.3. I wanted to achieve this order:

  • Overview
    • Introduction
    • Any other stories
  • ReactJS
    • Introduction
    • Quickstart
    • Examples
  • Components
    • (component stories can appear in whatever order)

This is the custom sorting function

    storySort: (a, b) => {
      // Sort function to sort stories based on a config file
      // Can handle sorting up to one level deep
      // Any stories that aren't expliciply listed will appear at the end
      // Based off https://github.com/storybookjs/storybook/issues/6327#issuecomment-613122487

      // The order in which we want stories to appear
      const config = [
        {
          category: 'Overview',
          order: ['Introduction'],
        },
        {
          category: 'ReactJS',
          order: [
            'Introduction',
            'Quickstart',
            'Examples',
          ]
        },
        {
          category: 'Components',
        },
      ];

      // assuming storybook uses / to separate everything
      // e.g. Component/Alert
      const story1 = a[1].kind.split('/');
      const story2 = b[1].kind.split('/');

      // util function for getting number
      // given array haystack, returns index for given needle
      // or 9999 so it goes at the end of the list
      function getOrderNumber(needle, haystack) {
        let order = 9999;
        if (Array.isArray(haystack)) {
          order = haystack.findIndex(h => h.toLowerCase() === needle.toLowerCase());
          if (order === -1) order = 9999;
        }
        return order;
      }

      // generate array of top-level categories from config array
      const topLevelOrderArray = config.map(h => h.category);

      const topLevelOrder1 = getOrderNumber(story1[0], topLevelOrderArray);
      const topLevelOrder2 = getOrderNumber(story2[0], topLevelOrderArray);

      // if top-level category is different
      // order based on config array
      // e.g. have Overview|Introduction come before Module|Header
      if (story1[0] !== story2[0]) {
        return topLevelOrder1 - topLevelOrder2;
      }

      // sorting one level deep now
      // if second-level category is different
      // e.g. have ReactJS|Introduction come before ReactJS|Examples
      if (story1[1] !== story2[1]) {
        return getOrderNumber(story1[1], config[topLevelOrder1] && config[topLevelOrder1].order) - getOrderNumber(story2[1], config[topLevelOrder2] && config[topLevelOrder2].order)
      }

      return 0;
    },

@mkrause
Copy link

mkrause commented Jul 17, 2020

I wrote a sorting function similar to what @hanilim wrote above, but with support for any amount of nesting.

const hasKey = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
const compareAlphabetical = (a, b) => a.localeCompare(b, { numeric: true });

export const compareStoryPaths = (order, path1, path2) => {
  if (path1.length === 0 && path2.length === 0) {
    return 0;
  }  else if (path1.length === 0 && path2.length > 0) {
    // Path1 must be an ancestor of path2
    return -1;
  } else if (path1.length > 0 && path2.length === 0) {
    // Path2 must be an ancestor of path1
    return 1;
  }
  
  const [path1Head, ...path1Tail] = path1;
  const [path2Head, ...path2Tail] = path2;
  
  if (!order) {
    // No reference order, so just sort alphabetically
    const comp = compareAlphabetical(path1Head, path2Head);
    if (comp === 0) {
      return compareStoryPaths(null, path1Tail, path2Tail);
    } else {
      return comp;
    }
  }
  
  if (path1Head === path2Head) {
    // The two paths share the same head
    const key = path1Head;
    
    if (hasKey(order, key)) {
      return compareStoryPaths(order[key], path1Tail, path2Tail);
    } else {
      return compareStoryPaths(null, path1Tail, path2Tail);
    }
  }
  
  if (!hasKey(order, path1Head) && !hasKey(order, path2Head)) {
    return compareStoryPaths(null, path1, path2);
  } else if (hasKey(order, path1Head) && !hasKey(order, path2Head)) {
    return -1; // Give preference to path1, since it is included in the reference order
  } else if (!hasKey(order, path1Head) && hasKey(order, path2Head)) {
    return 1; // Give preference to path2, since it is included in the reference order
  } else {
    // If both heads are in the reference order, use the ordering of the keys in the reference order
    const orderKeys = Object.keys(order);
    
    return orderKeys.indexOf(path1Head) < orderKeys.indexOf(path2Head) ? -1 : 1;
  }
};

// Example of a reference order
// Note: keys must be in all lowercase
const storiesOrder = {
  'docs': {
    'design guide': {
      'getting started': null,
      'overview': null,
      'color': null,
      'iconography': null,
      'accessibility': null,
      'faq': null,
    },
  },
  'components': {
    'typography': null,
    'layout': null,
    'buttons': null,
    'forms': null,
    'overlays': null,
    'tables': null,
  },
};

addParameters({
  options: {
    storySort: ([story1Id, story1], [story2Id, story2]) => {
      const story1Path = [...story1.kind.split('/'), story1.name].map(key => key.toLowerCase());
      const story2Path = [...story2.kind.split('/'), story2.name].map(key => key.toLowerCase());
      
      return compareStoryPaths(storiesOrder, story1Path, story2Path);
    },
  },
});

@hanilim
Copy link

hanilim commented Aug 31, 2020

Adding one last update in case people reach this via Google - Storybook 6.0 has a new feature that lets you easily sort stories. https://storybook.js.org/docs/react/writing-stories/naming-components-and-hierarchy#sorting-stories

Copied directly from those docs:

To get alphabetical sorting:

// .storybook/preview.js

export const parameters = {
  options: {
    storySort: {
      method: 'alphabetical',
    },
  },
};

To provide an array of titles:

// .storybook/preview.js

export const parameters = {
  options: {
    storySort: {
      order: ['Intro', 'Pages', ['Home', 'Login', 'Admin'], 'Components'],
    },
  },
};

Full docs: https://storybook.js.org/docs/react/writing-stories/naming-components-and-hierarchy#sorting-stories

@isc30
Copy link

isc30 commented Feb 28, 2022

heads up, it is possible to use both method and order

storySort: {
  order: ['Tutorials', 'Documentation'],
  method: 'alphabetical',
}

@prantlf
Copy link

prantlf commented Apr 15, 2022

Unfortunately, the current sorting configuration works only for two-level storybooks:

The order array can accept a nested array in order to sort 2nd-level story kinds.

If you group your components by one more level, the stories will move to the third level and you won't be able to sort them. For example:

.
├── Articles
│   ├── Getting Started.mdx
│   └── Versioning.mdx
├── Components
│   └── Header
│       ├── Collapsed.mdx
│       ├── Default.mdx
│       └── Expanded.mdx
└── Elements
    ├── Button
    │   ├── Active.mdx
    │   └── Default.mdx
    └── Link
        ├── Active.mdx
        └── Default.mdx

Let's say, that you want:

  1. Put Elements before Components. It's possible. It's the first level.
  2. Leave the articles, components and element in the alphabetical order. It's possible.
  3. Sort the stories alphabetically, but put the Default story, which is present for each element and component, before the other stories. It's impossible using the declarative configuration, because stories are on the third level:
storySort: {
  order: ['Articles', '*', ['*', ['Default', '*']]]
}

I wonder why the sorting wasn't implemented for any grouping depth.

I took @mkrause's sorting function and added support for a wildcard key - *. Thank you!
It allowed me assigning an ordering object to any not explicitly included key - name of any element or component in my case:

const hasKey = (obj, key) => Object.hasOwn(obj, key);
const compareAlphabetical = (a, b) => a.localeCompare(b, { numeric: true });

const compareStoryPaths = (order, path1, path2) => {
  if (path1.length === 0 && path2.length === 0) {
    return 0;
  }  else if (path1.length === 0 && path2.length > 0) {
    // Path1 must be an ancestor of path2
    return -1;
  } else if (path1.length > 0 && path2.length === 0) {
    // Path2 must be an ancestor of path1
    return 1;
  }

  const [path1Head, ...path1Tail] = path1;
  const [path2Head, ...path2Tail] = path2;

  if (!order) {
    // No reference order, so just sort alphabetically
    const comp = compareAlphabetical(path1Head, path2Head);
    if (comp === 0) {
      return compareStoryPaths(null, path1Tail, path2Tail);
    } else {
      return comp;
    }
  }

  if (path1Head === path2Head) {
    // The two paths share the same head; try either the key for the head, or the
    // wildcard key, otherwise pass `undefined` to sort without an explicit order
    return compareStoryPaths(order[path1Head] || order['*'], path1Tail, path2Tail);
  }

  if (hasKey(order, path1Head) && hasKey(order, path2Head)) {
    // If both heads are in the reference order, use the ordering of the keys in the reference order
    const orderKeys = Object.keys(order);

    return orderKeys.indexOf(path1Head) < orderKeys.indexOf(path2Head) ? -1 : 1;
  } else if (hasKey(order, path1Head) && !hasKey(order, path2Head)) {
    return -1; // Give preference to path1, since it is included in the reference order
  } else if (!hasKey(order, path1Head) && hasKey(order, path2Head)) {
    return 1; // Give preference to path2, since it is included in the reference order
  } else {
    // No explicit order for the path heads was found, try the wildcard key,
    // otherwise pass `undefined` to sort without an explicit order
    return compareStoryPaths(order['*'], path1, path2);
  }
};

const storiesOrder = {
  articles: {},
  elements: {
    '*': { default: null, },
  },
  components: {
    '*': { default: null, },
  },
};

export const parameters = {
  options: {
    storySort: ([, story1], [, story2]) => {
      const story1Path = [...story1.kind.split('/'), story1.name].map(key => key.toLowerCase());
      const story2Path = [...story2.kind.split('/'), story2.name].map(key => key.toLowerCase());

      return compareStoryPaths(storiesOrder, story1Path, story2Path);
    },
  },
};

The result of the sorting is:

.
├── Articles
│   ├── Getting Started.mdx
│   └── Versioning.mdx
├── Elements
│   ├── Button
│   │   ├── Default.mdx
│   │   └── Active.mdx
│   └── Link
│       ├── Default.mdx
│       └── Active.mdx
└── Components
    └── Header
        ├── Default.mdx
        ├── Collapsed.mdx
        └── Expanded.mdx

@prantlf
Copy link

prantlf commented Apr 16, 2022

I found it helpful in more than one projects and published the code from the comment above as an NPM package storybook-multilevel-sort:

// .storybook/preview.js
import sort from 'storybook-multilevel-sort'

const order = {
  articles: null,
  elements: {
    '*': { default: null }
  },
  components: {
    '*': { default: null }
  }
}

export const parameters = {
  options: {
    storySort: (story1, story2) => sort(order, story1, story2)
  }
}

@centigrade-julian-lang
Copy link

centigrade-julian-lang commented Jul 29, 2022

All that does not seem to work for me, and to be honest, seem way too complicated for such a simple feature like sorting my sub-stories in a specific way. I remember that in a (much) older version of storybook I had something like an "order" parameter (I think) in the <Meta /> tag of each story.

(I think) the most natural way of describing the hierarchy storybook is featuring, is an object property hierarchy. That's also what many of the others showed, when they implemented such sorting in a custom function.

I would love to see something like that built-in rather than customized by users. This would allow to easily describe patterns of sorted stories, especially when allowing for wildcards. For now, I'm going with renaming the files to create a certain order, but as others mentioned, this can easily break when building it - so, I'm curious what happens ^^

@certainlyakey
Copy link

Clearly as @prantlf mentioned there's a regression - in Storybook 6 it's not possible anymore to sort stories of a specific component in the source code order, while in Storybook 5 it was possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests