writeCssScript.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. /* eslint-disable no-underscore-dangle */
  2. /* eslint-disable no-param-reassign */
  3. // This little script converts the overflowscrollbars CSS file into the css-in-js file
  4. // it's normal you have to run prettier over the file after
  5. const fs = require('fs');
  6. const { parse } = require('css');
  7. const { isNaN } = require('@storybook/global');
  8. const INPUT = require.resolve('overlayscrollbars/css/OverlayScrollbars.min.css');
  9. const OUTPUT = `${__dirname}/../src/ScrollArea/ScrollAreaStyles.ts`;
  10. const OPTIONS = { camelCase: true, numbers: true };
  11. const read = (file) => {
  12. return fs
  13. .readFileSync(file)
  14. .toString()
  15. .replace(/(?:\r\n|\r|\n)/g, '');
  16. };
  17. const convert = (css, opts) => {
  18. const ast = parse(css, { source: css });
  19. const obj = cssToObject(opts)(ast.stylesheet.rules);
  20. return obj;
  21. };
  22. const cssToObject =
  23. (opts) =>
  24. (rules, result = {}) => {
  25. rules.forEach((rule) => {
  26. if (rule.type === 'media') {
  27. const key = `@media ${rule.media}`;
  28. const decs = cssToObject(opts)(rule.rules);
  29. result[key] = decs;
  30. return;
  31. }
  32. if (rule.type === 'keyframes') {
  33. result.__keyframes = Object.assign(result.__keyframes || {}, { [camel(rule.name)]: rule });
  34. return;
  35. }
  36. if (rule.type === 'comment') {
  37. return;
  38. }
  39. const key = rule.selectors.filter((s) => !s.includes('.os-theme-none')).join(', ');
  40. if (key.length) {
  41. Object.assign(result, {
  42. [key]: Object.assign(result[key] || {}, getDeclarations(rule.declarations, opts)),
  43. });
  44. }
  45. });
  46. return result;
  47. };
  48. const getDeclarations = (decs, opts = {}) => {
  49. const result = decs
  50. .filter((d) => {
  51. const filtered = d.type === 'comment' || d.property.match(/^(?:-webkit-|-ms-|-moz-)/);
  52. return !filtered;
  53. })
  54. .map((d) => ({
  55. key: opts.camelCase ? camel(d.property) : d.property,
  56. value: opts.numbers ? parsePx(d.value) : d.value,
  57. }))
  58. .reduce((a, b) => {
  59. a[b.key] = b.value;
  60. return a;
  61. }, {});
  62. return result;
  63. };
  64. const camel = (str) => str.replace(/(-[a-z])/g, (x) => x.toUpperCase()).replace(/-/g, '');
  65. const parsePx = (val) => {
  66. return /px$/.test(val) || val === '' || (val.match(/\d$/) && !isNaN(parseInt(val, 10)))
  67. ? parseFloat(val.replace(/px$/, ''))
  68. : val;
  69. };
  70. // eslint-disable-next-line @typescript-eslint/naming-convention
  71. const { __keyframes, ...styles } = convert(read(INPUT), OPTIONS);
  72. const stringifiedKeyFrames = Object.values(__keyframes)
  73. .map((k) => {
  74. return `const ${camel(k.name)} = keyframes\`${k.keyframes.reduce(
  75. (acc, item) =>
  76. `${acc}${k.position.source.substring(
  77. item.position.start.column - 1,
  78. item.position.end.column - 1
  79. )}`,
  80. ''
  81. )}\`;`;
  82. })
  83. .join('\n');
  84. const stringifiedStyles = JSON.stringify(
  85. Object.entries(styles).reduce((acc, [key, item]) => {
  86. if (item.animationName && __keyframes[camel(item.animationName)]) {
  87. item.animationName = camel(item.animationName);
  88. }
  89. if (item.backgroundImage && item.backgroundImage.match(/^url/)) {
  90. item.backgroundImage =
  91. '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%)';
  92. }
  93. acc[key] = item;
  94. return acc;
  95. }, {}),
  96. null,
  97. 2
  98. );
  99. const stringifiedStylesWithReplacedKeyframes = Object.keys(__keyframes)
  100. .reduce((acc, item) => {
  101. // replace keyframes
  102. return acc.replace(`"${item}"`, `\`\${${item}}\``);
  103. }, stringifiedStyles)
  104. .replace(/"([^\s]+)!important"/g, (f, p1) => {
  105. // make "!important" rules work with TS
  106. const v = parsePx(p1);
  107. return `"${p1}!important" as any as ${JSON.stringify(v)}`;
  108. });
  109. const result = `
  110. import { Theme, CSSObject, keyframes } from '@storybook/theming';
  111. ${stringifiedKeyFrames}
  112. export const getScrollAreaStyles: (theme: Theme) => CSSObject = (theme: Theme) => (${stringifiedStylesWithReplacedKeyframes});
  113. `;
  114. fs.writeFileSync(OUTPUT, result);