Skip to content

Commit 97d6c52

Browse files
committed
Added storybook
- fixed invalid type placeholder - added stories-check - added support for Provider type overriding
1 parent 883ed6f commit 97d6c52

File tree

17 files changed

+10842
-565
lines changed

17 files changed

+10842
-565
lines changed

.github/workflows/stories-check.yml

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Stories check
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
8+
jobs:
9+
checkScreenshots:
10+
name: Stories check
11+
12+
runs-on: windows-2022
13+
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v2
17+
18+
- name: Use Node.js 16.x
19+
uses: actions/setup-node@v3
20+
with:
21+
node-version: "16.x"
22+
cache: "yarn"
23+
24+
- name: Yarn install
25+
run: yarn install --network-timeout 1000000
26+
27+
- name: Run stories-check
28+
id: storiesCheck
29+
continue-on-error: true
30+
run: yarn stories-check
31+
32+
- name: Commit story changes
33+
id: csc
34+
if: steps.storiesCheck.outcome == 'failure'
35+
uses: EndBug/add-and-commit@v8
36+
with:
37+
add: ".stories-approved"
38+
message: "[skip ci] [stories-check] Automated story changes"
39+
new_branch: ${{ github.head_ref }}
40+
default_author: github_actions
41+
42+
- name: Run cypress-check
43+
id: cypressCheck
44+
continue-on-error: true
45+
run: yarn cypress-check
46+
47+
- name: Commit cypress changes
48+
id: ccc
49+
if: steps.cypressCheck.outcome == 'failure'
50+
uses: EndBug/add-and-commit@v8
51+
with:
52+
add: ".cypress-approved"
53+
message: "[skip ci] [cypress-check] Automated cypress changes"
54+
new_branch: ${{ github.head_ref }}
55+
default_author: github_actions

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
/cypress/screenshots
1111
/cypress/videos
1212

13+
# storybook
14+
/storybook-static
15+
16+
# storycap
17+
/.stories-pending
18+
1319
# production
1420
/lib
1521

.storybook/main.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const path = require('path');
2+
3+
module.exports = {
4+
stories: [
5+
"../src/**/*.stories.mdx",
6+
"../src/**/*.stories.@(js|jsx|ts|tsx)"
7+
],
8+
addons: [
9+
"@storybook/addon-links",
10+
"@storybook/addon-essentials",
11+
"storycap"
12+
],
13+
framework: "@storybook/react",
14+
core: {
15+
"builder": "webpack5"
16+
}
17+
}

.storybook/preview.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export const parameters = {
2+
actions: { argTypesRegex: "^on[A-Z].*" },
3+
controls: {
4+
matchers: {
5+
color: /(background|color)$/i,
6+
date: /Date$/,
7+
},
8+
},
9+
layout: 'centered'
10+
}

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,18 @@ import { FormBuilderField } from '@enterwell/react-form-builder';
120120
</FormBuilder>
121121
```
122122
123+
#### Override components
124+
125+
You can nest `FormBuilderProvider` inside other provider. The bottom provider will take all components from its parent and override matching with newly provided component types.
126+
127+
```js
128+
<FormBuilderProvider components={formComponentsA}>
129+
<FormBuilderProvider components={formComponentsB}>
130+
<!-- Can use both types from A and B, if B contains same type as A, B type is resolved here -->
131+
</FormBuilderProvider>
132+
</FormBuilderProvider>
133+
```
134+
123135
## Development
124136
125137
For development:

helpers/ci/NodeHelpers.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// General import
2+
const fs = require('fs-extra');
3+
const path = require('path');
4+
const chalk = require('chalk');
5+
const { stdout } = require('process');
6+
7+
/**
8+
* Gets all the file paths from the selected directory recursively.
9+
*
10+
* @param {string} directory Path to the directory from which to read the files.
11+
* @returns Array of file paths in the selected directory
12+
*/
13+
module.exports.getFilesRecursively = (directory) => {
14+
const files = [];
15+
16+
const filesInDirectory = fs.readdirSync(directory);
17+
18+
filesInDirectory.forEach((file) => {
19+
const absolute = path.join(directory, file);
20+
21+
if (fs.lstatSync(absolute).isDirectory()) {
22+
files.push(...this.getFilesRecursively(absolute));
23+
} else {
24+
files.push(absolute);
25+
}
26+
});
27+
28+
return files;
29+
};
30+
31+
/**
32+
* Prints the information about starting the task and its completion to the console.
33+
*
34+
* @param {string} message Message to output before starting the process.
35+
* @param {Function} taskToRun Function to invoke.
36+
* @returns Value that the function returns
37+
*/
38+
module.exports.logProcess = (message, taskToRun) => {
39+
try {
40+
stdout.write(message);
41+
const valueToReturn = taskToRun();
42+
stdout.write(`${chalk.green(' DONE ✔')}\n`);
43+
44+
return valueToReturn;
45+
} catch (error) {
46+
stdout.write(`${chalk.red(' ERROR X')}\n`);
47+
48+
throw error;
49+
}
50+
};

helpers/ci/ScreenshotsCompare.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// General imports
2+
const fs = require('fs-extra');
3+
const chalk = require('chalk');
4+
5+
// Node helpers import
6+
const { getFilesRecursively, logProcess } = require('./NodeHelpers');
7+
8+
/**
9+
* Scripts main function.
10+
*
11+
* @param {string} approvedPath Path to the approved screenshots directory
12+
* @param {string} pendingPath Path to the pending screenshots directory
13+
* @param {string} approvedDirName Name of the approved screenshots directory
14+
* @param {string} pendingDirName Name of the pending screenshots directory
15+
*/
16+
const compare = (approvedPath, pendingPath, approvedDirName, pendingDirName) => {
17+
try {
18+
let numberOfChanges = 0;
19+
20+
// Ensure the approved directory exists.
21+
fs.ensureDirSync(approvedPath);
22+
23+
// Get all the approved files.
24+
const approvedFiles = logProcess('Fetching approved files...', () => getFilesRecursively(approvedPath));
25+
26+
// Get all the pending files.
27+
const pendingFiles = logProcess('Fetching pending files...', () => getFilesRecursively(pendingPath));
28+
29+
// Iterate all the approved files.
30+
approvedFiles.forEach((approvedFile) => {
31+
const approvedFileShort = approvedFile.substring(approvedFile.indexOf(approvedDirName) + approvedDirName.length + 1);
32+
const possiblePending = approvedFile.replace(approvedDirName, pendingDirName);
33+
34+
// If the approved file is an old one (does not exist in the pending folder)
35+
// delete it from the approved folder.
36+
if (!pendingFiles.includes(possiblePending)) {
37+
logProcess(`\t${chalk.red(`[-] ${approvedFileShort}`)}`, () => fs.removeSync(approvedFile));
38+
numberOfChanges++;
39+
}
40+
});
41+
42+
// Iterate all the files that are pending for approval.
43+
pendingFiles.forEach((pendingFile) => {
44+
const possibleNew = pendingFile.replace(pendingDirName, approvedDirName);
45+
const possibleNewShort = possibleNew.substring(possibleNew.indexOf(approvedDirName) + approvedDirName.length + 1);
46+
47+
// If the pending file is a new one (does not exist in the approved folder)
48+
// move it to the approved folder.
49+
if (!approvedFiles.includes(possibleNew)) {
50+
logProcess(`\t${chalk.green(`[+] ${possibleNewShort}`)}`, () => fs.moveSync(pendingFile, possibleNew, { overwrite: true }));
51+
numberOfChanges++;
52+
} else {
53+
const pendingFileBytes = fs.readFileSync(pendingFile);
54+
const approvedFileBytes = fs.readFileSync(possibleNew);
55+
56+
// If the files are not the same, overwrite it.
57+
if (!pendingFileBytes.equals(approvedFileBytes)) {
58+
logProcess(`\t${chalk.yellow(`[≠] ${possibleNewShort}`)}`, () => fs.moveSync(pendingFile, possibleNew, { overwrite: true }));
59+
numberOfChanges++;
60+
} else {
61+
console.log(`\t${chalk.white(`[=] ${possibleNewShort}`)}`);
62+
}
63+
}
64+
});
65+
66+
console.log(`Detected changes: ${numberOfChanges}`);
67+
68+
if (numberOfChanges) process.exit(1);
69+
} catch (error) {
70+
console.error(chalk.red('Error occurred while running the script:'));
71+
console.error(chalk.red(error));
72+
process.exit(2);
73+
}
74+
};
75+
76+
module.exports = { compare };

helpers/ci/StoriesCompare.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// General imports
2+
const path = require('path');
3+
4+
// Compare script import
5+
const { compare } = require('./ScreenshotsCompare');
6+
7+
// Directory paths
8+
const APPROVED_DIR_NAME = '.stories-approved';
9+
const PENDING_DIR_NAME = '.stories-pending';
10+
11+
const APPROVED_STORIES = path.join(__dirname, `../../${APPROVED_DIR_NAME}`);
12+
const PENDING_STORIES = path.join(__dirname, `../../${PENDING_DIR_NAME}`);
13+
14+
/**
15+
* Running the script with the storybook stories parameters.
16+
*
17+
* @param {string} approvedStories Path to the approved stories directory
18+
* @param {string} pendingStories Path to the pending stories directory
19+
* @param {string} approvedDirName Name of the approved stories directory
20+
* @param {string} pendingDirName Name of the pending stories directory
21+
*/
22+
const run = (
23+
approvedStories = APPROVED_STORIES,
24+
pendingStories = PENDING_STORIES,
25+
approvedDirName = APPROVED_DIR_NAME,
26+
pendingDirName = PENDING_DIR_NAME
27+
) => compare(approvedStories, pendingStories, approvedDirName, pendingDirName);
28+
29+
module.exports = { run };

package.json

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
"scripts": {
1717
"clean": "rimraf dist && rimraf lib",
1818
"dev": "npm run clean && rollup -c -w",
19-
"build": "npm run clean && rollup -c"
19+
"build": "npm run clean && rollup -c",
20+
"storybook": "start-storybook -p 6006",
21+
"build-storybook": "build-storybook",
22+
"stories-check": "rimraf ./.stories-pending && storycap --serverCmd \"start-storybook --ci -p 6007\" --serverTimeout 180000 -o .stories-pending http://localhost:6007 && kill-port 6007 && node -e \"require('./helpers/ci/StoriesCompare').run()\""
2023
},
2124
"engines": {
2225
"node": ">=14"
@@ -36,11 +39,21 @@
3639
"react-dom": "^17"
3740
},
3841
"devDependencies": {
42+
"@babel/core": "^7.17.8",
3943
"@enterwell/react-form-validation": "^1.1.9",
4044
"@rollup/plugin-commonjs": "^21.0.2",
4145
"@rollup/plugin-node-resolve": "^13.1.3",
46+
"@storybook/addon-actions": "^6.4.19",
47+
"@storybook/addon-essentials": "^6.4.19",
48+
"@storybook/addon-interactions": "^6.4.19",
49+
"@storybook/addon-links": "^6.4.19",
50+
"@storybook/builder-webpack5": "^6.4.19",
51+
"@storybook/manager-webpack5": "^6.4.19",
52+
"@storybook/react": "^6.4.19",
53+
"@storybook/testing-library": "^0.0.9",
4254
"@types/node": "^17.0.23",
4355
"@types/react": "^17.0.2",
56+
"babel-loader": "^8.2.4",
4457
"cross-env": "^7.0.3",
4558
"postcss": "^8.4.12",
4659
"react": "^17.0.2",
@@ -50,6 +63,9 @@
5063
"rollup-plugin-peer-deps-external": "^2.2.4",
5164
"rollup-plugin-terser": "^7.0.2",
5265
"rollup-plugin-typescript2": "^0.31.2",
53-
"typescript": "^4.6.2"
54-
}
55-
}
66+
"storycap": "^4.0.0-alpha.1",
67+
"typescript": "^4.6.2",
68+
"webpack": "^5.70.0"
69+
},
70+
"dependencies": {}
71+
}

src/FormBuilder/FormBuilder.tsx

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,7 @@ import FormBuilderContext from "./FormBuilderContext";
44
import { submitForm } from '@enterwell/react-form-validation';
55
import { FormBuilderProviderContext } from "../FormBuilderProvider/FormBuilderProviderContext";
66
import { FormBuilderProps } from "./FormBuilder.types";
7-
8-
function UndefinedField({ fieldName }: { fieldName: string }) {
9-
return <div style={{
10-
color: '#FF8080',
11-
padding: '12px',
12-
backgroundColor: '#290000',
13-
borderRadius: 8
14-
}}>{`Undefined form field "${fieldName}"`}</div>
15-
}
7+
import InvalidPlaceholder from "./InvalidPlaceholder";
168

179
export default function FormBuilder(props: FormBuilderProps) {
1810
const {
@@ -42,7 +34,7 @@ export default function FormBuilder(props: FormBuilderProps) {
4234
}
4335
return (
4436
<FieldWrapper key={fieldName}>
45-
<UndefinedField fieldName={fieldName} />
37+
<InvalidPlaceholder fieldName={`empty field ${fieldName}`} />
4638
</FieldWrapper>
4739
);
4840
})}

0 commit comments

Comments
 (0)