Skip to content

Instantly share code, notes, and snippets.

@f1yn
Created May 2, 2020 04:39
Show Gist options
  • Select an option

  • Save f1yn/af7cd96051ed111cf0c2e4c5d7242093 to your computer and use it in GitHub Desktop.

Select an option

Save f1yn/af7cd96051ed111cf0c2e4c5d7242093 to your computer and use it in GitHub Desktop.
Demo Knob injection in storybook (extracted sample)
import React, { useContext } from 'react';
import { DocsContext } from '@storybook/addon-docs/blocks';
import { Source } from '@storybook/components';
/**
* Adapted from https://github.com/storybookjs/storybook/blob/master/addons/docs/src/blocks/Source.tsx
*/
const extractLines = (source, location) => {
const { startBody: start, endBody: end } = location;
const lines = source.split('\n');
if (start.line === end.line) {
return lines[start.line - 1].substring(start.col, end.col);
}
// NOTE: storysource locations are 1-based not 0-based!
const startLine = lines[start.line - 1];
const endLine = lines[end.line - 1];
return [
startLine.substring(start.col),
...lines.slice(start.line, end.line - 1),
endLine.substring(0, end.col),
];
};
/**
* Joins the original source for various
* @param originalLines
* @return {*}
*/
function applyKnobDefaults(originalLines) {
const joinedSource = originalLines.join('\n');
// Special cases for knobs for resolving index. Most knob calls use [1] as the defaultValue index
const specialTypeToParamIndex = {
select: 2,
radios: 2,
files: 2,
};
// safer and slightly eval for converting object into statement
// see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#Do_not_ever_use_eval!
const evaluateObject = (obj) => Function('"use strict";return (' + obj + ')')();
// Special sauce regular expression, will extract the block group representing a common knob helper
// This also works for multiline knobs
return joinedSource
.replace(/(color|boolean|text|array|select|object|radios|files)\(((\s|\S)*?)\)/g, (fullMatch, knobType, rawParams) => {
// Super hack: Evaluate the parameter list as a native object. Note this won't respect the closure of
// the original source. That would require a full VM OR to evaluate the whole script in isolation
try {
const parsedParams = evaluateObject(`[${rawParams}]`);
// Determine which index to use
const indexForExtraction = specialTypeToParamIndex[knobType] || 1;
return parsedParams[indexForExtraction];
} catch (error) {
console.error('Failed to automatically resolve knob value', error);
return fullMatch;
}
})
.split('\n');
}
export function RaiselySource({
id: customId
}) {
const context = useContext(DocsContext);
const data = customId ?
context.storyStore.fromId(customId) :
context;
const { source: rawSource } = data.parameters.storySource;
const location = data.parameters.storySource.locationsMap[data.id];
// Extract the source code for the contextual (or provided) story
let lines = applyKnobDefaults(extractLines(rawSource, location));
return (
<Source
code={lines.join('\n')}
language="jsx"
format={false}
/>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment