Skip to content

Commit 8da38fc

Browse files
committed
Create a sample project for testing Storybook+Heft
1 parent f86eb3a commit 8da38fc

17 files changed

+466
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// This is a workaround for https://github.com/eslint/eslint/issues/3458
2+
require('@rushstack/eslint-config/patch/modern-module-resolution');
3+
4+
module.exports = {
5+
extends: ['@rushstack/eslint-config/profile/web-app', '@rushstack/eslint-config/mixins/react'],
6+
parserOptions: { tsconfigRootDir: __dirname }
7+
};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "node",
9+
"request": "launch",
10+
"name": "Debug Jest tests",
11+
"program": "${workspaceFolder}/node_modules/@rushstack/heft/lib/start.js",
12+
"cwd": "${workspaceFolder}",
13+
"args": ["--debug", "test", "--clean"],
14+
"console": "integratedTerminal",
15+
"sourceMaps": true
16+
},
17+
]
18+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# heft-webpack-basic-tutorial
2+
3+
This is a copy of the
4+
[heft-storybook-react-tutorial](https://github.com/microsoft/rushstack-samples/tree/main/heft/heft-storybook-react-tutorial)
5+
tutorial project from the [rushstack-samples](https://github.com/microsoft/rushstack-samples) repo.
6+
7+
The copy here serves as a regression test, by using `"workspace:*"` references to the local projects in this repo.
8+
Please update the copy from time to time to keep it in sync with the official tutorial.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>Example Application</title>
7+
</head>
8+
<body>
9+
<noscript>You need to enable JavaScript to run this app.</noscript>
10+
<div id="root"></div>
11+
</body>
12+
</html>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Defines configuration used by core Heft.
3+
*/
4+
{
5+
"$schema": "https://developer.microsoft.com/json-schemas/heft/heft.schema.json",
6+
7+
"eventActions": [
8+
{
9+
/**
10+
* The kind of built-in operation that should be performed.
11+
* The "deleteGlobs" action deletes files or folders that match the
12+
* specified glob patterns.
13+
*/
14+
"actionKind": "deleteGlobs",
15+
16+
/**
17+
* The stage of the Heft run during which this action should occur. Note that actions specified in heft.json
18+
* occur at the end of the stage of the Heft run.
19+
*/
20+
"heftEvent": "clean",
21+
22+
/**
23+
* A user-defined tag whose purpose is to allow configs to replace/delete handlers that were added by other
24+
* configs.
25+
*/
26+
"actionId": "defaultClean",
27+
28+
/**
29+
* Glob patterns to be deleted. The paths are resolved relative to the project folder.
30+
*/
31+
"globsToDelete": ["dist", "lib", "lib-commonjs", "temp"]
32+
}
33+
],
34+
35+
/**
36+
* The list of Heft plugins to be loaded.
37+
*/
38+
"heftPlugins": [
39+
{
40+
/**
41+
* The path to the plugin package.
42+
*/
43+
"plugin": "@rushstack/heft-webpack4-plugin"
44+
45+
/**
46+
* An optional object that provides additional settings that may be defined by the plugin.
47+
*/
48+
// "options": { }
49+
},
50+
{
51+
"plugin": "@rushstack/heft-jest-plugin"
52+
53+
/**
54+
* An optional object that provides additional settings that may be defined by the plugin.
55+
*/
56+
// "options": { }
57+
}
58+
]
59+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "@rushstack/heft-jest-plugin/includes/jest-shared.config.json"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"projectOutputFolderNames": ["lib", "dist"]
3+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* Configures the TypeScript plugin for Heft. This plugin also manages linting.
3+
*/
4+
{
5+
"$schema": "https://developer.microsoft.com/json-schemas/heft/typescript.schema.json",
6+
7+
/**
8+
* If provided, emit these module kinds in addition to the modules specified in the tsconfig.
9+
* Note that this option only applies to the main tsconfig.json configuration.
10+
*/
11+
"additionalModuleKindsToEmit": [
12+
// {
13+
// /**
14+
// * (Required) Must be one of "commonjs", "amd", "umd", "system", "es2015", "esnext"
15+
// */
16+
// "moduleKind": "amd",
17+
//
18+
// /**
19+
// * (Required) The name of the folder where the output will be written.
20+
// */
21+
// "outFolderName": "lib-amd"
22+
// }
23+
{
24+
"moduleKind": "commonjs",
25+
"outFolderName": "lib-commonjs"
26+
}
27+
],
28+
29+
/**
30+
* Specifies the intermediary folder that tests will use. Because Jest uses the
31+
* Node.js runtime to execute tests, the module format must be CommonJS.
32+
*
33+
* The default value is "lib".
34+
*/
35+
"emitFolderNameForTests": "lib-commonjs",
36+
37+
/**
38+
* If set to "true", the TSlint task will not be invoked.
39+
*/
40+
// "disableTslint": true,
41+
42+
/**
43+
* Set this to change the maximum number of file handles that will be opened concurrently for writing.
44+
* The default is 50.
45+
*/
46+
// "maxWriteParallelism": 50,
47+
48+
/**
49+
* Describes the way files should be statically coped from src to TS output folders
50+
*/
51+
"staticAssetsToCopy": {
52+
/**
53+
* File extensions that should be copied from the src folder to the destination folder(s).
54+
*/
55+
"fileExtensions": [".css"]
56+
57+
/**
58+
* Glob patterns that should be explicitly included.
59+
*/
60+
// "includeGlobs": [
61+
// "some/path/*.js"
62+
// ],
63+
64+
/**
65+
* Glob patterns that should be explicitly excluded. This takes precedence over globs listed
66+
* in "includeGlobs" and files that match the file extensions provided in "fileExtensions".
67+
*/
68+
// "excludeGlobs": [
69+
// "some/path/*.css"
70+
// ]
71+
}
72+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "heft-storybook-react-tutorial",
3+
"description": "(Copy of sample project) Building this project is a regression test for Heft",
4+
"version": "1.0.0",
5+
"private": true,
6+
"scripts": {
7+
"build": "heft test --clean",
8+
"start": "heft start"
9+
},
10+
"devDependencies": {
11+
"@rushstack/eslint-config": "workspace:*",
12+
"@rushstack/heft": "workspace:*",
13+
"@rushstack/heft-jest-plugin": "workspace:*",
14+
"@rushstack/heft-webpack4-plugin": "workspace:*",
15+
"@types/heft-jest": "1.0.1",
16+
"@types/react": "16.9.45",
17+
"@types/react-dom": "16.9.8",
18+
"@types/webpack-env": "1.13.0",
19+
"css-loader": "~4.2.1",
20+
"eslint": "~7.30.0",
21+
"html-webpack-plugin": "~4.5.0",
22+
"react": "~16.13.1",
23+
"react-dom": "~16.13.1",
24+
"style-loader": "~1.2.1",
25+
"typescript": "~3.9.7",
26+
"webpack": "~4.44.2",
27+
"source-map-loader": "~1.1.2"
28+
}
29+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import * as React from 'react';
2+
import { ToggleSwitch, IToggleEventArgs } from './ToggleSwitch';
3+
4+
/**
5+
* This React component renders the application page.
6+
*/
7+
export class ExampleApp extends React.Component {
8+
public render(): React.ReactNode {
9+
const appStyle: React.CSSProperties = {
10+
backgroundColor: '#ffffff',
11+
padding: '20px',
12+
borderRadius: '5px',
13+
width: '400px'
14+
};
15+
16+
return (
17+
<div style={{ padding: '20px' }}>
18+
<div style={appStyle}>
19+
<h2>Hello, world!</h2>
20+
Here is an example control:
21+
<ToggleSwitch leftColor={'#800000'} rightColor={'#008000'} onToggle={this._onToggle} />
22+
</div>
23+
</div>
24+
);
25+
}
26+
27+
// React event handlers should be represented as fields instead of methods to ensure the "this" pointer
28+
// is bound correctly. This form does not work with virtual/override inheritance, so use regular methods
29+
// everywhere else.
30+
private _onToggle = (sender: ToggleSwitch, args: IToggleEventArgs): void => {
31+
console.log('Toggle switch changed: ' + args.sliderPosition);
32+
};
33+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import * as React from 'react';
2+
3+
/**
4+
* Slider positions for `ToggleSwitch`.
5+
*/
6+
export const enum ToggleSwitchPosition {
7+
Left = 'left',
8+
Right = 'right'
9+
}
10+
11+
/**
12+
* Event arguments for `IToggleSwitchProps.onToggle`.
13+
*/
14+
export interface IToggleEventArgs {
15+
sliderPosition: ToggleSwitchPosition;
16+
}
17+
18+
export interface IToggleSwitchProps {
19+
/**
20+
* The CSS color when the `ToggleSwitch` slider is in the left position.
21+
* Example value: `"#800000"`
22+
*/
23+
leftColor: string;
24+
25+
/**
26+
* The CSS color when the `ToggleSwitch` slider is in the right position.
27+
* Example value: `"#008000"`
28+
*/
29+
rightColor: string;
30+
31+
/**
32+
* An event that fires when the `ToggleSwitch` control is clicked.
33+
*/
34+
onToggle?: (sender: ToggleSwitch, args: IToggleEventArgs) => void;
35+
}
36+
37+
/**
38+
* Private state for ToggleSwitch.
39+
*/
40+
interface IToggleSwitchState {
41+
sliderPosition: ToggleSwitchPosition;
42+
}
43+
44+
/**
45+
* An example component that renders a switch whose slider position can be "left" or "right".
46+
*/
47+
export class ToggleSwitch extends React.Component<IToggleSwitchProps, IToggleSwitchState> {
48+
public constructor(props: IToggleSwitchProps) {
49+
super(props);
50+
this.state = {
51+
sliderPosition: ToggleSwitchPosition.Left
52+
};
53+
}
54+
55+
public render(): React.ReactNode {
56+
const frameStyle: React.CSSProperties = {
57+
borderRadius: '10px',
58+
backgroundColor:
59+
this.state.sliderPosition === ToggleSwitchPosition.Left
60+
? this.props.leftColor
61+
: this.props.rightColor,
62+
width: '35px',
63+
height: '20px',
64+
cursor: 'pointer'
65+
};
66+
const sliderStyle: React.CSSProperties = {
67+
borderRadius: '10px',
68+
backgroundColor: '#c0c0c0',
69+
width: '20px',
70+
height: '20px'
71+
};
72+
73+
if (this.state.sliderPosition === ToggleSwitchPosition.Left) {
74+
sliderStyle.marginLeft = '0px';
75+
sliderStyle.marginRight = 'auto';
76+
} else {
77+
sliderStyle.marginLeft = 'auto';
78+
sliderStyle.marginRight = '0px';
79+
}
80+
81+
return (
82+
<div style={frameStyle} onClick={this._onClickSlider}>
83+
<div style={sliderStyle} />
84+
</div>
85+
);
86+
}
87+
88+
// React event handlers should be represented as fields instead of methods to ensure the "this" pointer
89+
// is bound correctly. This form does not work with virtual/override inheritance, so use regular methods
90+
// everywhere else.
91+
private _onClickSlider = (event: React.MouseEvent): void => {
92+
if (this.state.sliderPosition === ToggleSwitchPosition.Left) {
93+
this.setState({ sliderPosition: ToggleSwitchPosition.Right });
94+
} else {
95+
this.setState({ sliderPosition: ToggleSwitchPosition.Left });
96+
}
97+
98+
if (this.props.onToggle) {
99+
this.props.onToggle(this, { sliderPosition: this.state.sliderPosition });
100+
}
101+
};
102+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* This file gets copied to the "lib" folder because its extension is registered in copy-static-assets.json
3+
* Then Webpack uses css-loader to embed it in the application bundle, and then style-loader applies to the DOM.
4+
*/
5+
html,
6+
body {
7+
margin: 0;
8+
height: 100%;
9+
background-color: #c0c0c0;
10+
font-family: Tahoma, sans-serif;
11+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2+
import * as React from 'react';
3+
import * as ReactDOM from 'react-dom';
4+
import { ExampleApp } from './ExampleApp';
5+
6+
import './index.css';
7+
8+
const rootDiv: HTMLElement = document.getElementById('root') as HTMLElement;
9+
ReactDOM.render(<ExampleApp />, rootDiv);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { ToggleSwitch } from '../ToggleSwitch';
2+
3+
describe('ToggleSwitch', () => {
4+
it('can be tested', () => {
5+
expect(ToggleSwitch).toBeDefined();
6+
});
7+
});

0 commit comments

Comments
 (0)