Socket
Socket
Sign inDemoInstall

storybook-multilevel-sort

Package Overview
Dependencies
0
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.1.2 to 1.2.0

15

lib/index.d.ts

@@ -6,6 +6,15 @@ export interface Order {

// eslint-disable-next-line no-explicit-any
type Story = any
export type Story = any
declare function sort(order: Order, story1: Story, story2: Story): 0 | -1 | 1
export type Result = 0 | -1 | 1
export default sort
export interface Context {
path1: string[],
path2: string[]
}
export interface Options {
compareNames?: (name1: string, name2: string, context: Context) => Result
}
export default function sort(order: Order, story1: Story, story2: Story, options?: Options): Result

65

lib/index.js

@@ -7,33 +7,50 @@ // See https://github.com/storybookjs/storybook/issues/548#issuecomment-1099949201

: (obj, key) => Object.prototype.hasOwnProperty.call(obj, key)
const compareAlphabetical = (a, b) => a.localeCompare(b, { numeric: true });
const compareAlphabetical = (name1, name2) => name1.localeCompare(name2, { numeric: true })
const compareStoryPaths = (order, path1, path2) => {
const compareStoryPaths = (order, path1, path2, context) => {
/* c8 ignore next 9 */
if (path1.length === 0 && path2.length === 0) {
return 0;
return 0
} else if (path1.length === 0 && path2.length > 0) {
// Path1 must be an ancestor of path2
return -1;
return -1
} else if (path1.length > 0 && path2.length === 0) {
// Path2 must be an ancestor of path1
return 1;
return 1
}
const [path1Head, ...path1Tail] = path1;
const [path2Head, ...path2Tail] = path2;
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);
const result = context.compareNames(path1Head, path2Head, { path1, path2 })
if (result === 0) {
return compareStoryPaths(null, path1Tail, path2Tail, context)
} else {
return comp;
return result
}
}
let currentOrder
const updatetOrderAndCompare = (newOrder, path1, path2) => {
// Propagate the nested wildcard to the following call to compareStoryPaths
const nestedOrder = order['**']
if (nestedOrder) context.order = nestedOrder
currentOrder = newOrder
// If the same paths are going to be compared, do not use the nested wildcard
// any more; it'd enter this clause once more and end up with a stack overflow
if (!currentOrder && context.path1 !== path1 && context.path2 !== path2) {
currentOrder = context.order
}
// Remember the current paths for future calls to compareStoryPaths
context.path1 = path1
context.path2 = path2
return compareStoryPaths(currentOrder, path1, path2, context)
}
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);
// wildcard keys, otherwise pass `undefined` to sort without an explicit order
return updatetOrderAndCompare(order[path1Head] || order['*'], path1Tail, path2Tail)
}

@@ -43,21 +60,21 @@

// If both heads are in the reference order, use the ordering of the keys in the reference order
const orderKeys = Object.keys(order);
const orderKeys = Object.keys(order)
return orderKeys.indexOf(path1Head) < orderKeys.indexOf(path2Head) ? -1 : 1;
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
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
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);
return updatetOrderAndCompare(order['*'], path1, path2)
}
};
}
export default (order, [, story1], [, story2]) => {
const story1Path = [...story1.kind.split('/'), story1.name].map(key => key.toLowerCase());
const story2Path = [...story2.kind.split('/'), story2.name].map(key => key.toLowerCase());
export default (order, [, story1], [, story2], { compareNames = compareAlphabetical } = {}) => {
const story1Path = [...story1.kind.split('/'), story1.name].map(key => key.toLowerCase())
const story2Path = [...story2.kind.split('/'), story2.name].map(key => key.toLowerCase())
return compareStoryPaths(order, story1Path, story2Path);
};
return compareStoryPaths(order, story1Path, story2Path, { compareNames })
}
{
"name": "storybook-multilevel-sort",
"version": "1.1.2",
"version": "1.2.0",
"description": "Applies specific sort order to more than two levels of chapters and stories in a storybook.",

@@ -40,6 +40,6 @@ "author": "Ferdinand Prantl <prantlf@gmail.com>",

"prepare": "rollup -c",
"lint": "denolint",
"lint": "denolint && tsc --noEmit test/types.ts",
"check": "teru-esm test/index.js && teru-cjs test/index.cjs",
"cover": "c8 tehanu-esm test/index.js",
"test": "denolint && teru-cjs test/index.cjs && c8 teru-esm test/index.js",
"test": "denolint && tsc --noEmit test/types.ts && teru-cjs test/index.cjs && c8 teru-esm test/index.js",
"ci": "teru-cjs test/index.cjs && c8 teru-esm test/index.js"

@@ -75,5 +75,7 @@ },

"rollup": "^3.4.0",
"storybook-multilevel-sort": "link:",
"tehanu": "^1.0.1",
"tehanu-repo-coco": "^1.0.0",
"tehanu-teru": "^1.0.0"
"tehanu-teru": "^1.0.0",
"typescript": "^4.9.3"
},

@@ -80,0 +82,0 @@ "keywords": [

@@ -20,6 +20,11 @@ # Multi-level Sorting for Storybook

├── Components
│ └── Header
│ ├── Collapsed.mdx Components/Header/Collapsed
│ ├── Default.mdx Components/Header/Default
│ └── Expanded.mdx Components/Header/Expanded
│ ├── Header
│ │ ├── Collapsed.mdx Components/Header/Collapsed
│ │ ├── Default.mdx Components/Header/Default
│ │ ├── Expanded.mdx Components/Header/Expanded
│ │ └── WithSearch.mdx Components/Header/With Search
│ └── List
│ ├── Collapsed.mdx Components/List/Collapsed
│ ├── Default.mdx Components/List/Default
│ └── Expanded.mdx Components/List/Expanded
└── Elements

@@ -56,4 +61,9 @@ ├── Button

Default
With Search
Collapsed
Expanded
List
Default
Collapsed
Expanded
```

@@ -72,2 +82,6 @@

components: {
header: {
default: null,
'with search': null
},
'*': { default: null }

@@ -84,5 +98,21 @@ }

A simplification using nested wildcards:
```js
const order = {
articles: null,
elements: null,
components: {
header: {
default: null,
'with search': null
},
},
'**': { default: null }
}
```
## Installation
This module can be installed in your project using [NPM], [PNPM] or [Yarn]. Make sure, that you use [Node.js] version 14 or newer.
This module can be installed in your project using [NPM], [PNPM] or [Yarn]. Make sure, that you use [Node.js] version 14.8 or newer.

@@ -103,3 +133,3 @@ ```sh

The function expects an object with the sorting configuration and two stories to compare, just like storybook passed them to the `storySort` method:
The function expects an object with the sorting configuration, two stories to compare, just like storybook passed them to the `storySort` method, and optionally sorting options:

@@ -120,2 +150,27 @@ ```js

### Custom Comparisons
Names of groups and stories on one level are compared alphabetically according to the current locale by default. If you need a different comparison, you can specify it using the optional `options` parameter:
```js
const options = {
compareNames: (name1, name2, context) {
// name1 - the string with the name on the left side of the comparison
// name2 - the string with the name on the right side of the comparison
// context - additional information
// context.path1 - an array of strings with the path of groups
// down to the left compared group or story name (name1)
// context.path2 - an array of strings with the path of groups
// down to the right compared group or story name (name2)
return name1.localeCompare(name2, { numeric: true })
}
}
...
storySort: (story1, story2) => sort(order, story1, story2, options)
```
Mind that the strings with names of groups and stories are converted to lower-case, before they are passed to the comparator.
## Configuration

@@ -138,2 +193,34 @@

### Whitespace
Names of groups and stories may include spaces. They are usually declared using pascal-case or camel-case and Storybook will separate the words by spaces:
```js
// The name will be "With Search"
export const WithSearch = Template.bind({})
```
They can be also assigned the displayable name using the `storyName` property:
```js
// The name will be "With Search" too
export const story1 = Template.bind({})
story1.storyName = 'With Search'
```
When you refer to such groups or stories on the ordering configuration, use the displayable name (with spaces) lower-case, for example:
```js
const order = {
'*': {
default: null,
'with search': null
}
}
```
**Generally, names of groups and stories are expected in the ordering configuration as Storybook displays them.** Not as the exported variables are named. You need to be aware of the [algorithm how Storybook generates the names of stories].
### Wildcards
If you want to skip explicit sorting at one level and specify the next level, use `*` instead of names, for which you want to specify the next level. The `*` matches any name, which is not listed explicitly at the same level:

@@ -149,2 +236,42 @@

### Nested Wildcards
If you want to enable implicit sorting at multiple levels, you would have to repeat the `*` selector on each level:
```js
{
elements: {
'*': {
default: null // Link/Default
} // Link/Active
}, // Link/Visited
components: {
'*': {
default: null // Header/Default
} // Header/Collapsed
} // Header/Expanded
}
```
you can use a nested wildcard `**` to specify default for the current and deeper levels. The `**` matches any name, which is not listed explicitly at the same level and if there is no `*` wildcard selector at that level:
```js
{
elements: null,
components: null,
'**': {
default: null // Link/Default
} // Link/Active
} // Link/Visited
// Header/Default
// Header/Collapsed
// Header/Expanded
```
The precedence of the selectors at a particular level:
1. A concrete name of a group or story
2. The wildcard `*` matching any name of a group or story
3. The nested wildcard `**` frm the same or from an outer level matching any name of a group or story
## Motivation

@@ -233,2 +360,3 @@

[sorting configuration supported by Storybook]: https://storybook.js.org/docs/react/writing-stories/naming-components-and-hierarchy#sorting-stories
[algorithm how Storybook generates the names of stories]: https://storybook.js.org/docs/react/api/csf#named-story-exports
[Node.js]: http://nodejs.org/

@@ -235,0 +363,0 @@ [NPM]: https://www.npmjs.com/

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc