storybook/lib/components/scripts/writeCssScript.js
Norbert de Langen 341fc2d90d
FIX linting
2020-03-27 20:04:50 +01:00

133 lines
3.8 KiB
JavaScript

/* eslint-disable no-underscore-dangle */
/* eslint-disable no-param-reassign */
// This little script converts the overflowscrollbars CSS file into the css-in-js file
// it's normal you have to run prettier over the file after
const fs = require('fs');
const { parse } = require('css');
const { isNaN } = require('global');
const INPUT = require.resolve('overlayscrollbars/css/OverlayScrollbars.min.css');
const OUTPUT = `${__dirname}/../src/ScrollArea/ScrollAreaStyles.ts`;
const OPTIONS = { camelCase: true, numbers: true };
const read = (file) => {
return fs
.readFileSync(file)
.toString()
.replace(/(?:\r\n|\r|\n)/g, '');
};
const convert = (css, opts) => {
const ast = parse(css, { source: css });
const obj = cssToObject(opts)(ast.stylesheet.rules);
return obj;
};
const cssToObject = (opts) => (rules, result = {}) => {
rules.forEach((rule) => {
if (rule.type === 'media') {
const key = `@media ${rule.media}`;
const decs = cssToObject(opts)(rule.rules);
result[key] = decs;
return;
}
if (rule.type === 'keyframes') {
result.__keyframes = Object.assign(result.__keyframes || {}, { [camel(rule.name)]: rule });
return;
}
if (rule.type === 'comment') {
return;
}
const key = rule.selectors.filter((s) => !s.includes('.os-theme-none')).join(', ');
if (key.length) {
Object.assign(result, {
[key]: Object.assign(result[key] || {}, getDeclarations(rule.declarations, opts)),
});
}
});
return result;
};
const getDeclarations = (decs, opts = {}) => {
const result = decs
.filter((d) => {
const filtered = d.type === 'comment' || d.property.match(/^(?:-webkit-|-ms-|-moz-)/);
return !filtered;
})
.map((d) => ({
key: opts.camelCase ? camel(d.property) : d.property,
value: opts.numbers ? parsePx(d.value) : d.value,
}))
.reduce((a, b) => {
a[b.key] = b.value;
return a;
}, {});
return result;
};
const camel = (str) => str.replace(/(-[a-z])/g, (x) => x.toUpperCase()).replace(/-/g, '');
const parsePx = (val) => {
return /px$/.test(val) || val === '' || (val.match(/\d$/) && !isNaN(parseInt(val, 10)))
? parseFloat(val.replace(/px$/, ''))
: val;
};
const { __keyframes, ...styles } = convert(read(INPUT), OPTIONS);
const stringifiedKeyFrames = Object.values(__keyframes)
.map((k) => {
return `const ${camel(k.name)} = keyframes\`${k.keyframes.reduce(
(acc, item) =>
`${acc}${k.position.source.substring(
item.position.start.column - 1,
item.position.end.column - 1
)}`,
''
)}\`;`;
})
.join('\n');
const stringifiedStyles = JSON.stringify(
Object.entries(styles).reduce((acc, [key, item]) => {
if (item.animationName && __keyframes[camel(item.animationName)]) {
item.animationName = camel(item.animationName);
}
if (item.backgroundImage && item.backgroundImage.match(/^url/)) {
item.backgroundImage =
'linear-gradient(135deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 50%, rgba(0,0,0,0.4) 50%, rgba(0,0,0,0.4) 100%)';
}
acc[key] = item;
return acc;
}, {}),
null,
2
);
const stringifiedStylesWithReplacedKeyframes = Object.keys(__keyframes)
.reduce((acc, item) => {
// replace keyframes
return acc.replace(`"${item}"`, `\`\${${item}}\``);
}, stringifiedStyles)
.replace(/"([^\s]+)!important"/g, (f, p1) => {
// make "!important" rules work with TS
const v = parsePx(p1);
return `"${p1}!important" as any as ${JSON.stringify(v)}`;
});
const result = `
import { Theme, CSSObject, keyframes } from '@storybook/theming';
${stringifiedKeyFrames}
export const getScrollAreaStyles: (theme: Theme) => CSSObject = (theme: Theme) => (${stringifiedStylesWithReplacedKeyframes});
`;
fs.writeFileSync(OUTPUT, result);