Skip to content

Commit b1eb6a5

Browse files
hipstersmoothiechrisvxd
authored andcommitted
feat: add opt-in fix for pseudo-elements
* add fix for pseudo elements * only replace classname if it exists * use correct variable * run prettier * fix example * hide fix behind a flag * fix typo
1 parent 43556bc commit b1eb6a5

File tree

4 files changed

+100
-20
lines changed

4 files changed

+100
-20
lines changed

README.md

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ module.exports = (storybookBaseConfig, configType) => {
4343
newConfig.output.library = "[name]";
4444

4545
return newConfig;
46-
}
46+
};
4747
```
4848

4949
Manually export the `getStorybook` function in your `./config/storybook/config.js` file:
@@ -67,6 +67,7 @@ story2sketch --url https://localhost:9001/iframe.html --output stories.asketch.j
6767
As stated by [`react-sketchapp`](https://github.com/airbnb/react-sketchapp), it's complicated to manage assets in a design system. Many teams building design systems or component libraries already produce Sketch files for distributing designs and use [Storybook](https://storybook.js.org) to prototype and present the developed components. It can become difficult to keep designs up to date with the latest components, with designers ever playing catchup. `story2sketch` generates a Sketch file from your components via Storybook, so your Sketch designs always stay up to date.
6868

6969
<a name="configuration"><a/>
70+
7071
## Configuration
7172

7273
You can configure `story2sketch` using [the API](#api) via the CLI, configuring your `package.json` or adding a `story2sketch.config.js` file.
@@ -90,7 +91,6 @@ Add the following to your package.json:
9091
"output": "dist/great-ui.asketch.json"
9192
}
9293
}
93-
9494
```
9595

9696
### story2sketch.config.js
@@ -101,24 +101,26 @@ Create a file called `story2sketch.config.js` on the root of your project:
101101
module.exports = {
102102
output: "dist/great-ui.asketch.json",
103103
stories: "all"
104-
}
104+
};
105105
```
106106

107107
<a name="api"><a/>
108+
108109
## API
109110

110-
| Parameter | Explanation | Input Type | Default |
111-
|------------------|-----------------------------------------------------------------------------------------------------------------------------------------|---------------|-------------------------------------------------------------------------------------|
112-
| output | Specifies the filename for the generated asketch.json file. | string | `"dist/stories.asketch.json"` |
113-
| input | The location of Storybook's generated iframe.html. Use this over `url` if possible for performance. | string | `"dist/iframe.html"` |
114-
| url | Storybook iframe URL. Will end in `iframe.html`. Prefer `input` for performance if possible. | string | `"http://localhost:9001/iframe.html"` |
115-
| stories | Stories to extract from Storybook. You should probably override the default. | object/string | `"all"` |
116-
| concurrency | Number of headless Chrome tabs to run in parallel. Drastically impacts performance. | integer | `4` |
117-
| symbolGutter | Gutter to place between symbols in Sketch. | integer | `100` |
118-
| viewports | Viewport configuration. Will be arranged left-to-right by width. Try to avoid changing the key, as this is used to identify the symbol. | object | Mobile viewport (320px wide) and desktop viewport (1920px wide). See example below. |
119-
| querySelector | Query selector to select your node on each page. Uses `document.querySelectorAll`. | string | `"#root"` |
120-
| verbose | Verbose logging output. | boolean | `false` |
121-
| puppeteerOptions | Options to be passed directly to `puppeteer.launch`. See [puppeteer docs](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions) for usage. | object | `{}` |
111+
| Parameter | Explanation | Input Type | Default |
112+
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------- | ----------------------------------------------------------------------------------- |
113+
| output | Specifies the filename for the generated asketch.json file. | string | `"dist/stories.asketch.json"` |
114+
| input | The location of Storybook's generated iframe.html. Use this over `url` if possible for performance. | string | `"dist/iframe.html"` |
115+
| url | Storybook iframe URL. Will end in `iframe.html`. Prefer `input` for performance if possible. | string | `"http://localhost:9001/iframe.html"` |
116+
| stories | Stories to extract from Storybook. You should probably override the default. | object/string | `"all"` |
117+
| concurrency | Number of headless Chrome tabs to run in parallel. Drastically impacts performance. | integer | `4` |
118+
| symbolGutter | Gutter to place between symbols in Sketch. | integer | `100` |
119+
| viewports | Viewport configuration. Will be arranged left-to-right by width. Try to avoid changing the key, as this is used to identify the symbol. | object | Mobile viewport (320px wide) and desktop viewport (1920px wide). See example below. |
120+
| querySelector | Query selector to select your node on each page. Uses `document.querySelectorAll`. | string | `"#root"` |
121+
| verbose | Verbose logging output. | boolean | `false` |
122+
| fixPseudo | Attempt to insert real elements in place of pseudo-elements | boolean | `false` |
123+
| puppeteerOptions | Options to be passed directly to `puppeteer.launch`. See [puppeteer docs](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions) for usage. | object | `{}` |
122124

123125
### Example story2sketch.config.js
124126

@@ -171,11 +173,11 @@ module.exports = {
171173
]
172174
}
173175
]
174-
}
176+
};
175177
```
176178

177-
178179
<a name="questions"><a/>
180+
179181
## Questions
180182

181183
### Why does my stuff look bad?

src/browser/page2layers.js

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,80 @@ import {
77
const getNodeName = node =>
88
node.id || node.className || node.nodeName.toLowerCase();
99

10+
const fixPseudoElements = () => {
11+
// Hide old pseudo-elements during render
12+
const css =
13+
".before-reset::before, .after-reset::after { content: none !important; }";
14+
const head = document.head || document.getElementsByTagName("head")[0];
15+
const style = document.createElement("style");
16+
17+
style.type = "text/css";
18+
style.appendChild(document.createTextNode(css));
19+
head.appendChild(style);
20+
21+
const allElements = document.querySelectorAll("body *");
22+
const oldFakes = document.querySelectorAll("body .fake-pseudo");
23+
24+
// Remove old fake pseudo-elements to handle multiple screen sizes
25+
if (oldFakes) {
26+
Array.from(oldFakes).map(el => el.remove());
27+
}
28+
29+
for (let i = 0; i < allElements.length; i++) {
30+
// Remove reset so we can get the screen sizes pseudo-element styles
31+
if (
32+
allElements[i].className &&
33+
typeof allElements[i].className === "string"
34+
) {
35+
allElements[i].className = allElements[i].className.replace(
36+
"before-reset",
37+
""
38+
);
39+
allElements[i].className = allElements[i].className.replace(
40+
"after-reset",
41+
""
42+
);
43+
}
44+
45+
const elementBeforeStyles = window.getComputedStyle(
46+
allElements[i],
47+
":before"
48+
);
49+
const elementAfterStyles = window.getComputedStyle(
50+
allElements[i],
51+
":after"
52+
);
53+
const elementBeforeContent = elementBeforeStyles.content;
54+
const elementAfterContent = elementAfterStyles.content;
55+
56+
if (elementBeforeContent && elementBeforeContent !== "none") {
57+
const virtualBefore = document.createElement("span");
58+
59+
virtualBefore.className = "fake-pseudo";
60+
virtualBefore.setAttribute("style", elementBeforeStyles.cssText);
61+
virtualBefore.innerHTML = elementBeforeStyles.content.split('"').join("");
62+
allElements[i].className += " before-reset";
63+
allElements[i].prepend(virtualBefore);
64+
}
65+
66+
if (elementAfterContent && elementAfterContent !== "none") {
67+
const virtualAfter = document.createElement("span");
68+
69+
virtualAfter.className = "fake-pseudo";
70+
virtualAfter.setAttribute("style", elementAfterStyles.cssText);
71+
virtualAfter.innerHTML = elementAfterStyles.content.split('"').join("");
72+
allElements[i].className += " after-reset";
73+
allElements[i].appendChild(virtualAfter);
74+
}
75+
}
76+
};
77+
1078
export const getSymbol = ({
1179
name = "symbol",
1280
x = 0,
1381
y = 0,
14-
querySelector = "#root"
82+
querySelector = "#root",
83+
fixPseudo = false
1584
} = {}) => {
1685
let nodes;
1786

@@ -25,6 +94,10 @@ export const getSymbol = ({
2594
return null;
2695
}
2796

97+
if (fixPseudo) {
98+
fixPseudoElements();
99+
}
100+
28101
const layer = nodeTreeToSketchGroup(nodes, {
29102
getGroupName: getNodeName,
30103
getRectangleName: getNodeName

src/server/Story2sketch.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export default class Story2sketch {
3232
symbolGutter = defaultSymbolGutter,
3333
querySelector = "#root",
3434
verbose = false,
35+
fixPseudo = false,
3536
stories,
3637
puppeteerOptions = {}
3738
}) {
@@ -44,6 +45,7 @@ export default class Story2sketch {
4445
this.querySelector = querySelector;
4546
this.stories = stories;
4647
this.verbose = verbose;
48+
this.fixPseudo = fixPseudo;
4749
this.puppeteerOptions = puppeteerOptions;
4850

4951
// Sort viewports by width
@@ -195,7 +197,8 @@ export default class Story2sketch {
195197
// Only prefix if symbolPrefix is defined
196198
const params = JSON.stringify({
197199
name: `${symbolPrefix}${name}`,
198-
querySelector: this.querySelector
200+
querySelector: this.querySelector,
201+
fixPseudo: this.fixPseudo
199202
});
200203

201204
// JSON.parse + JSON.stringify hack was originally used until

src/server/getStorybook.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ export default async (browser, iframeUrl) => {
55
waitUntil: "networkidle2"
66
});
77

8-
return page.evaluate("(typeof preview === 'object' && typeof preview.getStorybook !== 'undefined') ? preview.getStorybook() : __STORYBOOK_CLIENT_API__.getStorybook()");
8+
return page.evaluate(
9+
"(typeof preview === 'object' && typeof preview.getStorybook !== 'undefined') ? preview.getStorybook() : __STORYBOOK_CLIENT_API__.getStorybook()"
10+
);
911
};

0 commit comments

Comments
 (0)