wrapper component
24 | * @param styles styles to be injected
25 | */
26 | const withStyles = (styles: CSSProperties) => (WrappedComponent: ComponentClass
| FC
) => {
27 | return class extends Component
{
28 | public render() {
29 | return (
30 |
31 |
32 |
33 |
34 |
35 | );
36 | }
37 | };
38 | };
39 |
40 | /**
41 | * Strips away warning comment at the top
42 | * @param styles styles to strip comments from
43 | */
44 | export const stripCommentsAndSelectors = (styles: string): string => {
45 | const placeholderComment = `
46 | /*
47 | * -
48 | */
49 | `;
50 |
51 | const stylesWithoutComments = styles.replace(
52 | /\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/gm,
53 | placeholderComment,
54 | );
55 |
56 | return stylesWithoutComments;
57 | };
58 |
59 | /**
60 | * Adds css variable fallback for legacy browsers
61 | * @param styles styles to add fallback to
62 | */
63 | export const addVariableFallbacks = (styles: string): string => {
64 | const stylesWithoutRootSelector = styles.replace(/:root/g, '');
65 | const variableMap = new Map();
66 | const originalCssLines = stylesWithoutRootSelector.split('\n');
67 | const newCssLines: string[] = [];
68 |
69 | originalCssLines.forEach((cssLine: string) => {
70 | if (cssLine.trim().substring(0, 2) === '--') {
71 | const keyValueSplit = cssLine.trim().split(':');
72 | variableMap.set(keyValueSplit[0], keyValueSplit[1].replace(';', ''));
73 | }
74 | });
75 |
76 | originalCssLines.forEach((cssLine: string) => {
77 | if (cssLine.includes('var')) {
78 | const lineWithoutSemiColon = cssLine.replace(';', '');
79 | const varName = lineWithoutSemiColon.substring(
80 | lineWithoutSemiColon.indexOf('var(') + 4,
81 | lineWithoutSemiColon.length - 1,
82 | );
83 |
84 | const varValue = variableMap.get(varName);
85 | const lineWithValue = `${lineWithoutSemiColon.replace(`var(${varName})`, varValue)};`;
86 |
87 | newCssLines.push(lineWithValue);
88 | }
89 |
90 | newCssLines.push(cssLine);
91 | });
92 |
93 | return newCssLines.join('\n');
94 | };
95 |
96 | export { withStyles, Styled };
97 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { ReactWebComponent } from './reactComponent/ReactWebComponent';
2 | export { EventContext, EventConsumer } from './components/EventContext';
3 | export { Styled, withStyles } from './components/Styled';
--------------------------------------------------------------------------------
/src/reactComponent/CustomComponent.tsx:
--------------------------------------------------------------------------------
1 | import '@webcomponents/webcomponentsjs/webcomponents-bundle.js';
2 | import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js';
3 |
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 | import { createProxy } from 'react-shadow';
7 | import { EventProvider } from '../components/EventContext';
8 |
9 | let componentAttributes: any;
10 | let componentProperties: any;
11 | let rootComponent: React.FC | React.ComponentClass;
12 | let shadow: boolean | undefined;
13 |
14 | export const setComponentAttributes = (attributes: any) => {
15 | componentAttributes = attributes;
16 | };
17 |
18 | export const setComponentProperties = (properties: any) => {
19 | componentProperties = properties;
20 | };
21 |
22 | export const setRootComponent = (component: React.FC | React.ComponentClass) => {
23 | rootComponent = component;
24 | };
25 |
26 | export const setMode = (shadowOption: boolean) => {
27 | shadow = shadowOption;
28 | };
29 |
30 | class CustomComponent extends HTMLElement {
31 | public static get observedAttributes() {
32 | return Object.keys(componentAttributes).map((k) => k.toLowerCase());
33 | }
34 |
35 | private reactProps(): any {
36 | const attributes = {} as any;
37 |
38 | Object.keys(componentAttributes).forEach((key: string) => {
39 | attributes[key] = this.getAttribute(key) || (componentAttributes as any)[key];
40 | });
41 |
42 | return { ...attributes, ...componentProperties };
43 | }
44 |
45 | public connectedCallback() {
46 | this.mountReactApp();
47 | }
48 |
49 | public attributeChangedCallback(name: string, oldValue: string, newValue: string) {
50 | if (oldValue === newValue) {
51 | return;
52 | }
53 |
54 | this.mountReactApp();
55 | }
56 |
57 | public reactPropsChangedCallback(name: string, oldValue: any, newValue: any) {
58 | if (oldValue === newValue) {
59 | return;
60 | }
61 |
62 | componentProperties[name] = newValue;
63 |
64 | this.mountReactApp();
65 | }
66 |
67 | public disconnectedCallback() {
68 | ReactDOM.unmountComponentAtNode(this);
69 | }
70 |
71 | private mountReactApp() {
72 | const application = (
73 |
74 | {React.createElement(rootComponent, this.reactProps())}
75 |
76 | );
77 |
78 | if (shadow !== undefined && !shadow) {
79 | ReactDOM.render(application, this);
80 | } else {
81 | const root = createProxy({ div: undefined });
82 | ReactDOM.render({application} , this);
83 | }
84 | }
85 |
86 | private eventDispatcher = (event: Event) => {
87 | this.dispatchEvent(event);
88 | };
89 | }
90 |
91 | export default CustomComponent;
92 |
--------------------------------------------------------------------------------
/src/reactComponent/ReactWebComponent.tsx:
--------------------------------------------------------------------------------
1 | import CustomComponent, { setComponentAttributes, setComponentProperties, setRootComponent, setMode } from './CustomComponent';
2 |
3 | let componentAttributes: any | null = null;
4 | let componentProperties: any | null = null;
5 | let elementName: string | null = null;
6 | let rootComponent: React.FC | React.ComponentClass | null = null;
7 |
8 | export class ReactWebComponent {
9 | public static setAttributes(attributes: any) {
10 | componentAttributes = attributes;
11 | }
12 |
13 | public static setProperties(properties: any) {
14 | componentProperties = properties;
15 | }
16 |
17 | public static render(App: React.FC | React.ComponentClass, name: string, option?: { shadow: boolean }) {
18 | rootComponent = App;
19 | elementName = name;
20 |
21 | this.validateDependencies();
22 |
23 | setComponentAttributes(componentAttributes);
24 | setComponentProperties(componentProperties);
25 | setRootComponent(rootComponent);
26 |
27 | if (option) {
28 | setMode(option.shadow);
29 | }
30 |
31 | this.setComponentProperties();
32 | customElements.define(elementName, CustomComponent);
33 | }
34 |
35 | private static setComponentProperties() {
36 | if (!rootComponent) {
37 | return;
38 | }
39 |
40 | const properties = { ...componentProperties };
41 | const propertyMap = {} as PropertyDescriptorMap;
42 |
43 | Object.keys(properties).forEach((key: string) => {
44 | const property: PropertyDescriptor = {
45 | configurable: true,
46 | enumerable: true,
47 | get() {
48 | return properties[key];
49 | },
50 | set(newValue) {
51 | const oldValue = properties[key];
52 | properties[key] = newValue;
53 | (this as any).reactPropsChangedCallback(key, oldValue, newValue);
54 | },
55 | };
56 |
57 | propertyMap[key] = property;
58 | });
59 |
60 | Object.defineProperties(CustomComponent.prototype, propertyMap);
61 | }
62 |
63 | private static validateDependencies() {
64 | if (!componentAttributes) {
65 | throw Error('Cannot define custom element: Attributes have not been set.');
66 | }
67 |
68 | if (!componentProperties) {
69 | throw Error('Cannot define custom element: Properties have not been set.');
70 | }
71 |
72 | if (!rootComponent) {
73 | throw Error('Cannot define custom element: Root Component have not been set.');
74 | }
75 |
76 | if (!elementName) {
77 | throw Error('Cannot define custom element: Element name has not been set.');
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/utils/utils.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 |
3 | export interface INames {
4 | title: string;
5 | pascal: string;
6 | snake: string;
7 | }
8 |
9 | export function toTitleFormat(name: string) {
10 | if (name.includes('-')) {
11 | const wordList = name.split('-');
12 | const capitalized = wordList.map((w) => {
13 | return w.charAt(0).toUpperCase() + w.slice(1);
14 | });
15 |
16 | return capitalized.join(' ');
17 | } else {
18 | const capitalized = name.charAt(0).toUpperCase() + name.slice(1);
19 | return capitalized.replace(/([A-Z])/g, ' $1').trim();
20 | }
21 | }
22 |
23 | export function toSnakeCase(name: string) {
24 | const capitalized = name.charAt(0).toUpperCase() + name.slice(1);
25 | const snaked = capitalized.replace(/([A-Z])/g, '-$1').slice(1);
26 | return snaked.toLowerCase();
27 | }
28 |
29 | export function toPascalCase(name: string) {
30 | const wordList = name.split('-');
31 | const capitalized = wordList.map((w) => {
32 | return w.charAt(0).toUpperCase() + w.slice(1);
33 | });
34 |
35 | return capitalized.join('');
36 | }
37 |
38 | export async function changeNameInfile(file: string, changeWhere: RegExp, changeTo: string) {
39 | const changedFile = await new Promise((resolve, reject) => {
40 | fs.readFile(file, 'utf-8', (err, data) => {
41 | if (err) {
42 | reject('Could not read file');
43 | }
44 |
45 | const changed = data.replace(changeWhere, changeTo);
46 |
47 | resolve(changed);
48 | });
49 | });
50 |
51 | await new Promise((resolve, reject) => {
52 | fs.writeFile(file, changedFile, 'utf-8', err => {
53 | if (err) {
54 | reject('Could not write file');
55 | }
56 |
57 | resolve();
58 | });
59 | });
60 | }
61 |
62 | export function createDefaultName(name: string) {
63 | const snakeName = toSnakeCase(name);
64 |
65 | if (!snakeName.includes('-')) {
66 | return `${snakeName}-component`
67 | }
68 |
69 | return snakeName;
70 | }
--------------------------------------------------------------------------------
/templates/js/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React Web Component](https://github.com/Silind-Software/create-react-web-component).
2 |
3 | # %component-name-title%
4 | > %component-description%
5 |
6 | ```html
7 | <%component-name-snake%>%component-name-snake%>
8 | ```
9 |
10 | Use this README to describe your component
--------------------------------------------------------------------------------
/templates/js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "%component-name-snake%",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "react-app-rewired start",
7 | "build": "react-app-rewired build",
8 | "test": "react-app-rewired test --env=jest-environment-jsdom-fourteen"
9 | },
10 | "dependencies": {
11 | "react": "16.10.2",
12 | "react-dom": "16.10.2",
13 | "react-scripts": "3.2.0",
14 | "style-it": "2.1.4",
15 | "create-react-web-component": "2.0.17"
16 | },
17 | "devDependencies": {
18 | "jest-environment-jsdom-fourteen": "0.1.0",
19 | "react-app-rewired": "2.1.3",
20 | "to-string-loader": "1.1.5",
21 | "react-test-renderer": "16.9.0"
22 | },
23 | "eslintConfig": {
24 | "extends": "react-app"
25 | },
26 | "browserslist": {
27 | "production": [
28 | ">0.2%",
29 | "not dead",
30 | "not op_mini all"
31 | ],
32 | "development": [
33 | "last 1 chrome version",
34 | "last 1 firefox version",
35 | "last 1 safari version"
36 | ]
37 | },
38 | "config-overrides-path": "node_modules/create-react-web-component/config/config-overrides.js"
39 | }
--------------------------------------------------------------------------------
/templates/js/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %component-name-title%
8 |
9 |
21 |
22 | <%component-name-snake%>%component-name-snake%>
23 |
24 |
--------------------------------------------------------------------------------
/templates/js/src/App.css:
--------------------------------------------------------------------------------
1 | .app {
2 | width: 750px;
3 | height: 350px;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | border-radius: 5px;
8 | border: 1px solid #E6EEF0;
9 | padding: 20px;
10 | box-sizing: border-box;
11 | background-color: white;
12 | box-shadow: 0 4px 9px 0 #375c821c;
13 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
14 | }
15 |
16 | .header-title {
17 | height: 50px;
18 | line-height: 50px;
19 | font-size: 36px;
20 | color: #444;
21 | text-align: center;
22 | }
23 |
24 | .sub-title {
25 | height: 35px;
26 | line-height: 35px;
27 | font-size: 26px;
28 | margin-top: 35px;
29 | color: #444;
30 | text-align: center;
31 | }
32 |
33 | .todo-list {
34 | display: flex;
35 | justify-content: center;
36 | }
37 |
38 | .todo-title {
39 | height: 25px;
40 | line-height: 25px;
41 | font-size: 18px;
42 | color: #614444;
43 | }
44 |
45 | .button {
46 | width: 150px;
47 | height: 45px;
48 | border-radius: 5px;
49 | background-color: #223a7c;
50 | color: white;
51 | box-shadow: 2px 2px 5px #16314d98;
52 | margin-top: 25px;
53 | outline: none;
54 | border: 0;
55 | cursor: pointer;
56 | transition: 0.3s;
57 | }
58 |
59 | .button:hover {
60 | box-shadow: 4px 4px 8px #16314d63;
61 | background-color: #40558f;
62 | }
63 |
--------------------------------------------------------------------------------
/templates/js/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { EventContext, Styled } from 'create-react-web-component';
3 | import { propTypes } from './componentProperties';
4 | import styles from './App.css';
5 |
6 | const App = (props) => {
7 | const dispatch = useContext(EventContext);
8 |
9 | const handleClick = () => {
10 | const event = new Event('my-event');
11 | dispatch(event);
12 | };
13 |
14 | const renderTodos = props.todos.map((todo) => (
15 |
16 | {todo}
17 |
18 | ));
19 |
20 | return (
21 |
22 |
23 |
{props.componentTitle}
24 |
To get started:
25 |
28 |
29 | Let's go!
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | App.propTypes = propTypes;
37 |
38 | export default App;
39 |
--------------------------------------------------------------------------------
/templates/js/src/componentProperties.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Use the two objects below to register the properties and attributes the
3 | * Web Component is expected to receive.
4 | * These will be passed down as props to the React App underneath.
5 | */
6 | import PropTypes from 'prop-types';
7 |
8 | /**
9 | * Update proptypes to reflect the types of the properties and attributes
10 | * NB: The type of an attribute must be primitive
11 | */
12 | export const propTypes = {
13 | todos: PropTypes.array,
14 | componentTitle: PropTypes.string,
15 | };
16 |
17 | /**
18 | * Update this object with the initial values of the properties
19 | */
20 | export const componentProperties = {
21 | todos: [
22 | 'Go to src/componentProperties.ts...',
23 | 'Register properties and attributes...',
24 | 'Build awesome React Web Component!',
25 | ],
26 | };
27 |
28 | /**
29 | * Update this object with the initial values of the attributes
30 | */
31 | export const componentAttributes = {
32 | componentTitle: '%component-name-title%',
33 | };
34 |
--------------------------------------------------------------------------------
/templates/js/src/index.js:
--------------------------------------------------------------------------------
1 | import { ReactWebComponent } from 'create-react-web-component';
2 | import { componentAttributes, componentProperties } from './componentProperties';
3 | import App from './App';
4 |
5 | ReactWebComponent.setAttributes(componentAttributes);
6 | ReactWebComponent.setProperties(componentProperties);
7 | ReactWebComponent.render(App, '%component-name-snake%');
8 |
--------------------------------------------------------------------------------
/templates/js/src/test/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import renderer from 'react-test-renderer';
4 | import App from '../App';
5 | import {
6 | componentProperties,
7 | componentAttributes,
8 | } from '../componentProperties';
9 |
10 | const reactProps = { ...componentAttributes, ...componentProperties };
11 |
12 | it('renders without crashing', () => {
13 | const div = document.createElement('div');
14 | ReactDOM.render( , div);
15 | ReactDOM.unmountComponentAtNode(div);
16 | });
17 |
18 | it('matches snapshot as expected', () => {
19 | const renderTree = renderer.create( ).toJSON();
20 | expect(renderTree).toMatchSnapshot();
21 | });
22 |
--------------------------------------------------------------------------------
/templates/ts/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React Web Component](https://github.com/Silind-Software/create-react-web-component).
2 |
3 | # %component-name-title%
4 | > %component-description%
5 |
6 | ```html
7 | <%component-name-snake%>%component-name-snake%>
8 | ```
9 |
10 | Use this README to describe your component
--------------------------------------------------------------------------------
/templates/ts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "%component-name-snake%",
3 | "version": "1.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "react-app-rewired start",
7 | "build": "react-app-rewired build",
8 | "test": "react-app-rewired test --env=jest-environment-jsdom-fourteen"
9 | },
10 | "dependencies": {
11 | "@types/node": "12.7.8",
12 | "@types/react": "16.9.3",
13 | "@types/react-dom": "16.9.1",
14 | "create-react-web-component": "2.0.17",
15 | "react": "16.10.1",
16 | "react-dom": "16.10.1",
17 | "react-scripts": "3.1.2",
18 | "style-it": "2.1.4",
19 | "typescript": "3.6.3",
20 | "tslint": "5.20.0"
21 | },
22 | "devDependencies": {
23 | "@types/jest": "24.0.18",
24 | "@types/react-test-renderer": "16.9.0",
25 | "jest-environment-jsdom-fourteen": "0.1.0",
26 | "react-app-rewired": "2.1.3",
27 | "to-string-loader": "1.1.5",
28 | "react-test-renderer": "16.9.0",
29 | "tslint-config-airbnb": "5.11.2",
30 | "tslint-config-silind": "1.0.21",
31 | "tslint-react": "4.1.0"
32 | },
33 | "eslintConfig": {
34 | "extends": "react-app"
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | },
48 | "config-overrides-path": "node_modules/create-react-web-component/config/config-overrides.js"
49 | }
--------------------------------------------------------------------------------
/templates/ts/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %component-name-title%
8 |
9 |
21 |
22 | <%component-name-snake%>%component-name-snake%>
23 |
24 |
--------------------------------------------------------------------------------
/templates/ts/src/App.css:
--------------------------------------------------------------------------------
1 | .app {
2 | width: 750px;
3 | height: 350px;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | border-radius: 5px;
8 | border: 1px solid #E6EEF0;
9 | padding: 20px;
10 | box-sizing: border-box;
11 | background-color: white;
12 | box-shadow: 0 4px 9px 0 #375c821c;
13 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif;
14 | }
15 |
16 | .header-title {
17 | height: 50px;
18 | line-height: 50px;
19 | font-size: 36px;
20 | color: #444;
21 | text-align: center;
22 | }
23 |
24 | .sub-title {
25 | height: 35px;
26 | line-height: 35px;
27 | font-size: 26px;
28 | margin-top: 35px;
29 | color: #444;
30 | text-align: center;
31 | }
32 |
33 | .todo-list {
34 | display: flex;
35 | justify-content: center;
36 | }
37 |
38 | .todo-title {
39 | height: 25px;
40 | line-height: 25px;
41 | font-size: 18px;
42 | color: #614444;
43 | }
44 |
45 | .button {
46 | width: 150px;
47 | height: 45px;
48 | border-radius: 5px;
49 | background-color: #223a7c;
50 | color: white;
51 | box-shadow: 2px 2px 5px #16314d98;
52 | margin-top: 25px;
53 | outline: none;
54 | border: 0;
55 | cursor: pointer;
56 | transition: 0.3s;
57 | }
58 |
59 | .button:hover {
60 | box-shadow: 4px 4px 8px #16314d63;
61 | background-color: #40558f;
62 | }
63 |
--------------------------------------------------------------------------------
/templates/ts/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { FC, useContext } from 'react';
2 | import { EventContext, Styled } from 'create-react-web-component';
3 | import { IComponentProperties, IComponentAttributes } from './componentProperties';
4 | import styles from './App.css';
5 |
6 | interface IProps extends IComponentProperties, IComponentAttributes {}
7 |
8 | const App: FC = (props) => {
9 | const dispatch = useContext(EventContext);
10 |
11 | const handleClick = () => {
12 | const event = new Event('my-event');
13 | dispatch(event);
14 | };
15 |
16 | const renderTodos = props.todos.map((todo: string) => (
17 |
18 | {todo}
19 |
20 | ));
21 |
22 | return (
23 |
24 |
25 |
{props.componentTitle}
26 |
To get started:
27 |
30 |
31 | Let's go!
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default App;
39 |
--------------------------------------------------------------------------------
/templates/ts/src/componentProperties.ts:
--------------------------------------------------------------------------------
1 | /*!
2 | * Use the two interfaces and two objects below to register
3 | * the properties and attributes the Web Component is expected to receive.
4 | * These will be passed down as props to the React App underneath.
5 | */
6 |
7 | /**
8 | * Update this interface to reflect the types of the properties
9 | */
10 | export interface IComponentProperties {
11 | todos: string[];
12 | }
13 |
14 | /**
15 | * Update this interface to reflect the attributes of the Web Component
16 | * NB: The type of an attribute must be primitive
17 | */
18 | export interface IComponentAttributes {
19 | componentTitle: string;
20 | }
21 |
22 | /**
23 | * Update this object with the initial values of the properties
24 | */
25 | export const componentProperties: IComponentProperties = {
26 | todos: [
27 | 'Go to src/componentProperties.ts...',
28 | 'Register properties and attributes...',
29 | 'Build awesome React Web Component!',
30 | ],
31 | };
32 |
33 | /**
34 | * Update this object with the initial values of the attributes
35 | */
36 | export const componentAttributes: IComponentAttributes = {
37 | componentTitle: '%component-name-title%',
38 | };
39 |
--------------------------------------------------------------------------------
/templates/ts/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { ReactWebComponent } from 'create-react-web-component';
2 | import { componentAttributes, componentProperties } from './componentProperties';
3 | import App from './App';
4 |
5 | ReactWebComponent.setAttributes(componentAttributes);
6 | ReactWebComponent.setProperties(componentProperties);
7 | ReactWebComponent.render(App, '%component-name-snake%');
8 |
--------------------------------------------------------------------------------
/templates/ts/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.css' {
4 | const classes: { readonly [key: string]: string };
5 | export default classes;
6 | }
7 |
8 | declare module '*.svg' {
9 | import * as React from 'react';
10 |
11 | export const ReactComponent: React.FunctionComponent>;
12 |
13 | const src: string;
14 | export default src;
15 | }
16 |
17 | declare module '*.svg' {
18 | const content: any;
19 | export default content;
20 | }
--------------------------------------------------------------------------------
/templates/ts/src/test/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import renderer from 'react-test-renderer';
4 | import App from '../App';
5 | import {
6 | componentProperties,
7 | componentAttributes,
8 | } from '../componentProperties';
9 |
10 | const reactProps = { ...componentAttributes, ...componentProperties };
11 |
12 | it('renders without crashing', () => {
13 | const div = document.createElement('div');
14 | ReactDOM.render( , div);
15 | ReactDOM.unmountComponentAtNode(div);
16 | });
17 |
18 | it('matches snapshot as expected', () => {
19 | const renderTree = renderer.create( ).toJSON();
20 | expect(renderTree).toMatchSnapshot();
21 | });
22 |
--------------------------------------------------------------------------------
/templates/ts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "noEmit": true,
19 | "jsx": "react"
20 | },
21 | "include": [
22 | "src"
23 | ],
24 | "exclude": [
25 | "node_modules",
26 | "dist"
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/templates/ts/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "max-line-length": { "options": [120], "severity": "warning" },
4 | "no-lowlevel-commenting": { "severity": "warning" },
5 | "discreet-ternary": { "severity": "warning" },
6 | "curly": { "severity": "warning" },
7 | "jsx-wrap-multiline": false,
8 | "typedef": false
9 | },
10 | "linterOptions": {
11 | "exclude": [
12 | "config/**/*.js",
13 | "node_modules/**/*.ts",
14 | "coverage/lcov-report/*.js",
15 | "webpack.config.js"
16 | ]
17 | },
18 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-airbnb", "tslint-config-silind"]
19 | }
20 |
--------------------------------------------------------------------------------
/test/ReactWebComponent.test.ts:
--------------------------------------------------------------------------------
1 | import { ReactWebComponent } from '../src/index';
2 |
3 | describe('Throw appropriate errors', () => {
4 | it('should throw error on attributes not set', () => {
5 | const render = () => {
6 | ReactWebComponent.render({} as any, '');
7 | }
8 |
9 | expect(render).toThrowError('Cannot define custom element: Attributes have not been set.');
10 | });
11 |
12 | it('should throw error on properties not set', () => {
13 | ReactWebComponent.setAttributes({} as any);
14 |
15 | const render = () => {
16 | ReactWebComponent.render({} as any, '');
17 | }
18 |
19 | expect(render).toThrowError('Cannot define custom element: Properties have not been set.');
20 | });
21 |
22 | it('should throw error on root component not set', () => {
23 | ReactWebComponent.setAttributes({} as any);
24 | ReactWebComponent.setProperties({} as any);
25 |
26 | const render = () => {
27 | ReactWebComponent.render(null as any, '');
28 | }
29 |
30 | expect(render).toThrowError('Cannot define custom element: Root Component have not been set.');
31 | });
32 |
33 | it('should throw error on element name not set', () => {
34 | ReactWebComponent.setAttributes({} as any);
35 | ReactWebComponent.setProperties({} as any);
36 |
37 | const render = () => {
38 | ReactWebComponent.render({} as any, '');
39 | }
40 |
41 | expect(render).toThrowError('Cannot define custom element: Element name has not been set.');
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/test/Styled.test.ts:
--------------------------------------------------------------------------------
1 | import { stripCommentsAndSelectors, addVariableFallbacks } from '../src/components/Styled';
2 |
3 | describe('Strip comments in css', () => {
4 | it('should strip comments in css', () => {
5 | const css =
6 | `
7 | /*
8 | * This is a comment in a css-file.
9 | * This should get stripped.
10 | */
11 |
12 | body {
13 | width: 100%:
14 | }
15 |
16 | .some-element {
17 | background-color: red;
18 | }
19 | `;
20 |
21 | const result =
22 | `
23 | /*
24 | * -
25 | */
26 |
27 | body {
28 | width: 100%:
29 | }
30 |
31 | .some-element {
32 | background-color: red;
33 | }
34 | `;
35 |
36 | const strippedCss = stripCommentsAndSelectors(css);
37 |
38 | expect(strippedCss.replace(/\s/g, '')).toBe(result.replace(/\s/g, ''));
39 | });
40 | });
41 |
42 | describe('Add variable fallback in css', () => {
43 | it('should remove root selector', () => {
44 | const css =
45 | `
46 | :root {
47 | --color-absolute-white: #fff;
48 | --color-absolute-black: #000;
49 | }
50 | `;
51 |
52 | const withoutRoot = addVariableFallbacks(css);
53 |
54 | const result =
55 | `
56 | {
57 | --color-absolute-white: #fff;
58 | --color-absolute-black: #000;
59 | }
60 | `;
61 |
62 | expect(withoutRoot.replace(/\s/g, '')).toBe(result.replace(/\s/g, ''));
63 | });
64 |
65 | it('should replace css variable with final value', () => {
66 | const css =
67 | `
68 | :root {
69 | --color-absolute-white: #fff;
70 | --color-absolute-black: #000;
71 | }
72 |
73 | body {
74 | color: var(--color-absolute-white);
75 | }
76 |
77 | .some-element {
78 | background-color: var(--color-absolute-black);
79 | }
80 | `;
81 |
82 | const withFallbacks = addVariableFallbacks(css);
83 |
84 | const result =
85 | `
86 | {
87 | --color-absolute-white: #fff;
88 | --color-absolute-black: #000;
89 | }
90 |
91 | body {
92 | color: #fff;
93 | color: var(--color-absolute-white);
94 | }
95 |
96 | .some-element {
97 | background-color: #000;
98 | background-color: var(--color-absolute-black);
99 | }
100 | `;
101 |
102 | expect(withFallbacks.replace(/\s/g, '')).toBe(result.replace(/\s/g, ''));
103 | });
104 | });
--------------------------------------------------------------------------------
/test/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { toTitleFormat, toPascalCase, toSnakeCase, createDefaultName } from '../src/utils/utils';
2 |
3 | const nameSnake = 'super-cool-component';
4 | const nameCamelCase = 'superCoolComponent';
5 | const namePascalcase = 'SuperCoolComponent';
6 | const nameTitle = 'Super Cool Component';
7 |
8 | describe('Name Formatters: Title', () => {
9 | it('should return correct title from snake-case', () => {
10 | const title = toTitleFormat(nameSnake);
11 | expect(title).toBe(nameTitle);
12 | });
13 |
14 | it('shoule return correct title from camelCase', () => {
15 | const title = toTitleFormat(nameCamelCase);
16 | expect(title).toBe(nameTitle);
17 | });
18 |
19 | it('shoule return correct title from PascalCase', () => {
20 | const title = toTitleFormat(namePascalcase);
21 | expect(title).toBe(nameTitle);
22 | });
23 | });
24 |
25 | describe('Name Formatters: PascalCase', () => {
26 | it('should return correct PascalCase from snake-case', () => {
27 | const title = toPascalCase(nameSnake);
28 | expect(title).toBe(namePascalcase);
29 | });
30 |
31 | it('should return correct PascalCase from camelCase', () => {
32 | const title = toPascalCase(nameCamelCase);
33 | expect(title).toBe(namePascalcase);
34 | });
35 | })
36 |
37 | describe('Name Formatters: snake-case', () => {
38 | it('should return correct snake-case from camelCase', () => {
39 | const title = toSnakeCase(nameCamelCase);
40 | expect(title).toBe(nameSnake);
41 | });
42 |
43 | it ('should return correct snake-case from PascalCase', () => {
44 | const title = toSnakeCase(namePascalcase);
45 | expect(title).toBe(nameSnake);
46 | });
47 | });
48 |
49 | describe('Default name suggestion', () => {
50 | it('should return snake-case from snake-case', () => {
51 | const title = 'component-name';
52 | const defaultName = createDefaultName(title);
53 | expect(defaultName).toBe('component-name');
54 | });
55 |
56 | it('should return snake-case from camelCase', () => {
57 | const title = 'componentName';
58 | const defaultName = createDefaultName(title);
59 | expect(defaultName).toBe('component-name');
60 | });
61 |
62 | it('should return snake-case from PascalCase', () => {
63 | const title = 'ComponentName';
64 | const defaultName = createDefaultName(title);
65 | expect(defaultName).toBe('component-name');
66 | });
67 |
68 | it('should append "component" to single-word', () => {
69 | const title = 'name';
70 | const defaultName = createDefaultName(title);
71 | expect(defaultName).toBe('name-component');
72 | });
73 | });
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "lib": ["es2017", "es7", "es6", "dom"],
6 | "declaration": true,
7 | "outDir": "dist",
8 | "rootDirs": ["src"],
9 | "strict": true,
10 | "esModuleInterop": true,
11 | "moduleResolution": "node",
12 | "skipLibCheck": true,
13 | "jsx": "react"
14 | },
15 | "exclude": [
16 | "node_modules",
17 | "dist",
18 | "templates",
19 | "test"
20 | ]
21 | }
--------------------------------------------------------------------------------