- from AwesomeComponent
91 | expect(tree.children[0].type).toEqual('div');
92 |
93 | //
- from AwesomeComponent
94 | expect(tree.children[0].children[0]).toEqual('Hello');
95 | });
96 |
97 | it('Should hoc and props features work correctly together', () => {
98 | const feature1 = jest.fn(props => {
99 | return {
100 | ...props,
101 | feature1: 'feature1'
102 | }
103 | });
104 |
105 | const feature2 = {
106 | hoc: jest.fn((Component) => {
107 | return (props) =>
;
108 | }),
109 | };
110 |
111 | const feature3 = {
112 | props: jest.fn(props => {
113 | return {
114 | ...props,
115 | feature3: 'feature3'
116 | }
117 | }),
118 | hoc: jest.fn((Component) => {
119 | return (props) =>
120 | }),
121 | };
122 |
123 | const AwesomeComponent = () =>
Hello
;
124 | const features = forge(feature1, feature2, feature3);
125 | const ForgedComponent = features(AwesomeComponent);
126 |
127 | const initialProps = {
128 | foo: 'foo'
129 | };
130 |
131 | const tree = renderer.create(
).toJSON();
132 |
133 | expect(feature1.mock.calls[0][0]).toEqual(initialProps);
134 |
135 | expect(feature3.props.mock.calls[0][0]).toEqual({
136 | ...initialProps,
137 | feature1: 'feature1'
138 | });
139 |
140 | /**
141 | * Expect tree to be
142 | *
143 | *
144 | * Hello
145 | *
146 | *
147 | */
148 | //
- from HOC
149 | expect(tree.type).toEqual('span');
150 | expect(tree.props).toEqual({
151 | id: 'feature3'
152 | });
153 |
154 | expect(tree.children[0].type).toEqual('span');
155 | expect(tree.children[0].props).toEqual({
156 | id: 'feature2'
157 | });
158 |
159 | expect(tree.children[0].children[0].type).toEqual('div');
160 | expect(tree.children[0].children[0].children[0]).toEqual('Hello');
161 | });
162 |
163 | it ('Should throw exception id bindProps function returns not object', () => {
164 | const feature1 = () => ({});
165 | const AwesomeComponent = () => Hello
;
166 | const features = forge(feature1);
167 | const ForgedComponent = features(AwesomeComponent, 'AwesomeComponent', props => []);
168 |
169 | expect(() => {
170 | renderer.create();
171 | }).toThrow(`Forgekit <${AwesomeComponent.name}/>: "bindProps" as fucntions should return Object`);
172 | });
173 |
174 | it ('Should throw excpetion if there are same props with differnet types', () => {
175 | const feature1 = () => ({});
176 | feature1.propTypes = {
177 | foo: PropTypes.bool
178 | };
179 |
180 | const AwesomeComponent = () => Hello
;
181 | AwesomeComponent.propTypes = {
182 | foo: PropTypes.string
183 | };
184 |
185 | const features = forge(feature1);
186 |
187 | expect(() => {
188 | features(AwesomeComponent);
189 | }).toThrow(`<${AwesomeComponent.name}/>: Prop "foo" was defined at "${feature1.name}" and "${AwesomeComponent.name}" with different propType`);
190 | });
191 |
192 |
193 | it('Should throw excpetion if there are duplicated theme keys', () => {
194 | const featureMock1 = jest.fn(() => {});
195 | featureMock1.propTypes = {
196 | theme: ThemeProp({
197 | foo: PropTypes.string,
198 | }),
199 | };
200 |
201 | const featureMock2 = jest.fn(() => {});
202 | featureMock2.propTypes = {
203 | theme: ThemeProp({
204 | foo: PropTypes.arrayOf(PropTypes.bool),
205 | }),
206 | };
207 |
208 |
209 | const features = forge(featureMock1, featureMock2);
210 | const AwesomeComponent = () => Hello
;
211 |
212 | // beacuse of theme key "foo" exists at both featureMock1 and featureMock2
213 | expect(() => {
214 | features(AwesomeComponent)
215 | }).toThrow(`<${AwesomeComponent.name}/>: Theme key "foo" was defined at "${featureMock1.name}" and "${featureMock2.name}" with different propType`);
216 | });
217 | });
218 |
--------------------------------------------------------------------------------
/__tests__/utils/warn-comopnent-props.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const createWarnComponent = (Component) => {
4 | const availableProps = Object.keys(Component.propTypes);
5 | const hasTheme = Component.propTypes.hasOwnProperty('theme');
6 |
7 | const availableThemeKeys = hasTheme
8 | ? Component.propTypes.theme.themeKeys
9 | : [];
10 |
11 | const WarnComponent = (props) => {
12 | for (const propId of Object.keys(props)) {
13 | if (availableProps.indexOf(propId) === -1) {
14 | console.warn(`"${propId}" is not defiend at "${Component.name}" propsTypes`);
15 | }
16 |
17 | if (hasTheme && propId === 'theme') {
18 | for (const themeKey of Object.keys(props[propId])) {
19 | if (availableThemeKeys.indexOf(themeKey) === -1) {
20 | console.warn(`"${themeKey}" is not defiend at "${Component.displayName}" component's theme`);
21 | }
22 | }
23 | }
24 | }
25 |
26 | return ;
27 | };
28 |
29 | WarnComponent.propTypes = Component.propTypes;
30 | WarnComponent.defaultProps = Component.defaultProps;
31 | WarnComponent.displayName = Component.displayName;
32 |
33 | return WarnComponent;
34 | };
35 |
36 |
37 | export default createWarnComponent;
38 |
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | ## Forgekit api
2 |
3 | In general it looks like props middleware.
4 | But each feature also can implement a higher order component (usually for lifecycle methods).
5 |
6 | **What is Feature**?
7 |
8 | More details about features in features api documentation.
9 |
10 |
11 |
12 | ```js
13 | import forgekit from 'forgekit';
14 |
15 | forge(...features)(Component, displayName, bindProps)
16 | ```
17 |
18 | * **features** *Array[Function]* - Used features
19 | * (required) **Component** *React.Component* - Original component
20 | * **displayName** *String* - New component display name. Works correctly with [React chrome developers tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en)
21 | * **bindProps** *Object | Function* - Props that are merged with the owner props.
22 |
23 | **It also can be read as**
24 |
25 | ```js
26 | const ForgedComponent = forge(feature1, feature2, feature3)(Component);
27 |
28 | //Same as
29 | const ForgedComponent = props => {
30 | const newProps = feature3(featur2(feature1(props)));
31 | return ;
32 | }
33 | ```
34 |
35 | ### bindProps as object
36 |
37 | *bindProps* as *object* - take precedence over props from the owner.
38 |
39 | ```js
40 | const features = forge(...features);
41 | export default features(Component, 'Button');
42 | export const RippleButton = features(Component, 'RippleButton', {
43 | ripple: true
44 | });
45 | ```
46 |
47 | ### bindProps as function
48 |
49 | *bindProps* as *function* is useful when need to define props that depends on another props.
50 |
51 |
52 |
53 | ```js
54 | export default forge(...features)(Component, 'AwesomeComponent', ({
55 | alert,
56 | ...props
57 | }) => ({
58 | ...props,
59 | // Add "error" icon if "alert" prop exists
60 | icon: alert ? 'error' : ''
61 | })
62 | );
63 | ```
64 |
65 | It is easier and much more readable to bind some props in this way than create higher order components for such features or add such simple logic inside component.
66 |
67 |
68 | ## Feedback wanted
69 |
70 | Forgekit is still in the early stages and even still be an experimental project. Your are welcome to submit issue or PR if you have suggestions! Or write me on twitter [@tuchk4](https://twitter.com/tuchk4).
71 |
--------------------------------------------------------------------------------
/docs/feature-examples.md:
--------------------------------------------------------------------------------
1 | # Feature examples
2 |
3 | * [Feature documentation](./feature.md)
4 | * [Forgekit api documentation](./api.md)
5 |
6 | More examples are at Forgekit components library. I will contribute it a lot:
7 |
8 | * Develop all base components and features for them
9 | * Add styles according to Google Material design
10 | * Components should be easily stylized to any other design without extra styles at application build
11 |
12 | ##### Common features that could be added to any component
13 |
14 | * *Ripple* - Component will have a ripple effect on click. [Example implementation](#ripple)
15 | * *HighliteFlags* - Depends on prop *primary* / *alert* / *danger* / *warning* - add styles to the component. [Example implementation](#highliteflags)
16 | * *LoadingOverlay* - If *loading* prop is true - show loader overlay above the component.
17 | * *ClickOutside* - Fires when click outside of the component. [Example implementation](#clickoutside)
18 | * *Sticky* - add fixed position to element. Could be configured (min and max y).
19 | * *Permissions* - Provide permission config. Specific for application.
20 |
21 | ##### ``
22 |
23 | * *ClickValue* - Pass *clickValue* prop that will be passed to *onClick* as first argument. This helps to prevent *fn.bind* usage. [Example implementation](#clickvalue)
24 |
25 | ##### `` or ``
26 |
27 | * *FilterOptions* - add *source* prop that allows to load items only when `` is opened.
28 | * *Overlay* - add body overlay that prevent any click or scroll events if `` is opened. Click outside of `` will only close it and do not trigger any other event.
29 | * *FilterOptions* - Add text input to filter options.
30 |
31 | ##### ``
32 |
33 | * *clickOutsideToClose* - Close dialog if click outside
34 | * *onEscKeyDown* - Fired when dialog is opened and *esc* key is pressed
35 |
36 | ##### ``
37 |
38 | * *validation* - Provide custom validation rules
39 |
40 | #### `
`
41 |
42 | * *seletable* - The elements in the list will display a hover effect and a pointer cursor. Support arrow keys click events.
43 | * *actions* - A list of elements that are placed to the list item.
44 | * *grouped* - Render list items groups.
45 | * *collapsable* - Collapse list items groups. Alos provide *onCollapse* callback prop.
46 |
47 | and so on and so on :tada:
48 |
49 | # Feature example implementation
50 |
51 | #### Ripple
52 |
53 | ```js
54 | const Ripple = ({children}) => {};
55 | const RippleFeature = ({ children, ...props}) => {
56 |
57 | return {
58 | ...props,
59 | children: (
60 | /**
61 | * If such way broke markup - it also could be implemented via hoc and ref.
62 | */
63 |
64 | {children}
65 |
66 | )
67 | }
68 | };
69 |
70 | RippleFeature.propTypes = {
71 | ripple: PropTypes.bool
72 | };
73 | ```
74 |
75 | #### HighliteFlags
76 |
77 | More about theme and *ThemeProp* at [theme documentation](./theme.md);
78 |
79 | ```js
80 | import styles from './highlite-flags.css';
81 | import classnames from 'classnames';
82 |
83 | const HighliteFlags = ({
84 | alert,
85 | warning,
86 | success,
87 | className,
88 | theme,
89 | ...props,
90 | }) => {
91 | return {
92 | ...props,
93 | className: classnames(className, {
94 | [theme.alert]: alert,
95 | [theme.warning]: warning,
96 | [theme.success]: success,
97 | }),
98 | };
99 | };
100 |
101 | HighliteFlags.propTypes = {
102 | alert: PropTypes.bool,
103 | warning: PropTypes.bool,
104 | success: PropTypes.bool,
105 | theme: ThemeProp({
106 | alert: PropTypes.string,
107 | warning: PropTypes.string,
108 | success: PropTypes.string,
109 | }),
110 | };
111 |
112 | HighliteFlags.defaultProps = {
113 | alert: false,
114 | warning: false,
115 | success: false,
116 | theme: {
117 | alert: styles.alert,
118 | warning: styles.warning,
119 | success: styles.success,
120 | },
121 | };
122 | ```
123 |
124 | #### ClickValue
125 |
126 | ```js
127 | const ClickValueFeature = ({
128 | clickValue,
129 | onClick = () => {},
130 | ...props
131 | }) => ({
132 | ...props,
133 | onClick: e => onClick(clickValue, e),
134 | });
135 |
136 | ClickValueFeature.propTypes = {
137 | clickValue: PropTypes.any,
138 | onClick: PropTypes.func,
139 | };
140 | ```
141 |
142 | #### ClickOutside
143 |
144 | ```js
145 | let isDocumentListenerExists = false;
146 | const handlers = {};
147 |
148 | const onDocumentClick = e => {
149 | for (const target of Object.keys(handlers)) {
150 | if (target === e.target && e.target === window ||
151 | (document.documentElement.contains(e.target) && !DOMUtils.isDescendant(target, e.target))) {
152 | onClickOutside();
153 |
154 | handlers[target](e);
155 | }
156 | }
157 | };
158 |
159 | const handleClickOutside = (target, onClickOutside) => {
160 | handlers[target] = onClickOutside;
161 |
162 | if (!isDocumentListenerExists) {
163 | document.addEventListener('click', onDocumentClick, true);
164 | isDocumentListenerExists = true;
165 | }
166 |
167 | return () => {
168 | document.removeEventListener('click', onDocumentClick, true);
169 | if (!handlers.length) {
170 | isDocumentListenerExists = false;
171 | }
172 | };
173 | };
174 |
175 | class ClickOutside extends React.Component {
176 | disableClickOutside = null;
177 |
178 | componentDidMount() {
179 | handleClickOutside(findDOMNode(this), () => {
180 | this.disableClickOutside = this.props.onClickOutside();
181 | });
182 | }
183 |
184 | componentWillUnmount() {
185 | if (this.disableClickOutside) {
186 | this.this.disableClickOutside();
187 | }
188 | }
189 |
190 | render() {
191 | return this.props.children;
192 | }
193 | };
194 |
195 | const ClickOutsideFeature = {
196 | hoc: (Component) => {
197 | return ({
198 | onClickOutsideEnabled,
199 | onClickOutside,
200 | ...props
201 | }) =>
202 | if (!onClickOutside || !onClickOutsideEnabled) {
203 | return ;
204 | }
205 |
206 | return (
207 |
208 |
209 |
210 | );
211 | }
212 | }
213 | };
214 |
215 | ClickOutsideFeature.propTypes = {
216 | onClickOutside: PropTypes.func,
217 | onClickOutsideEnabled: PropTypes.bool,
218 | };
219 |
220 | ClickOutsideFeature.defaultProps = {
221 | onClickOutsideEnabled: true
222 | };
223 | ```
224 |
--------------------------------------------------------------------------------
/docs/feature.md:
--------------------------------------------------------------------------------
1 | # Little theory. What is component feature?
2 |
3 | - [The problem](#the-problem)
4 | - [Component life in the images](#component-life-in-the-images)
5 | - [Solution: develop features separately](#solution-develop-features-separately)
6 | - [High-level and low-level props](#high-level-and-low-level-props)
7 | - [Forgekit feature function](#forgekit-feature-function)
8 | - [Forgekit feature with lifecycle methods](#forgekit-feature-with-lifecycle-methods)
9 |
10 | > Wiki: It is an intentional distinguishing characteristic of a component.
11 |
12 | Component feature - is the new component functionality. And in most cases it depends and provide new props.
13 | For example *HighliteFlags* feature for `` component - add *success*, *alert* and *warning* bool props and depending on them provide specific styles.
14 |
15 | ### The problem
16 |
17 | Usually all such features are developed and stored inside component and this is the reason for next problems:
18 |
19 | * Component code become more and more complex. Even simple component can grow into complex with large collection of available props.
20 | * Hard to develop new features and remove old or not used. To remove feature developer should find and remove large number of lines and then make sure that all other features work correctly.
21 | * Hard to find bugs and fix them. Sometimes there are hotfixes that usually push component to become legacy.
22 | * A lot of *copy-paste* code. Especially a lot of duplication across components *propTypes*.
23 | * Hard to share code. Only whole component could be shared with all implemented features. But sometimes needs to share only specific feature between components or even between applications.
24 |
25 | ### Component life in the images
26 |
27 | * Component is simple and it code is beautiful, simple and readable.
28 |
29 |
30 |
31 | * Developing new component features. Developing at this stage is like breathe the crystal clear air. Component is still virgin.
32 |
33 |
34 |
35 | * Component became complex and it code is much harder and uglier than was before.
36 |
37 |
38 |
39 | * DEATH - bugs, legacy code, spaghetti :( Components look like virus or infection.
40 |
41 |
42 |
43 | ### Solution: develop features separately
44 |
45 | **Very need to rename *"Feature"* term at Forgekit context into more simple name that will not confuse.** If you have some ideas - please describe them at [new issue](https://github.com/tuchk4/forgekit/issues/new).
46 |
47 | Forgekit suggest to develop and store features separately from components. There are a lot of advantages:
48 |
49 | * Responsibility. Each feature stored at separated file (or module if speak in CommonJS context) and there is code that implements only specific functionality and provide its own *propsTypes*.
50 | * Sharable. Features could be shared between components or applications. And don't even depends on components library. It could work above it.
51 | * Tests. Easy to write tests because Feature is a pure function.
52 | * Refactoring. It is more simpler to remove feature import than find and remove large number of related lines of code.
53 | * Refactoring. Old feature could be swapped with new features.
54 | * Optimization. Use only needed features. Not used features will not be in the build.
55 | * Feature customization. Each feature could be customized separately. This is much better than customize whole component.
56 |
57 |
58 | ### High-level and low-level props
59 |
60 | In most cases feature depends and provide new props.
61 | Props could be split into **low-level** and **high-level** props.
62 |
63 | From React [DOM Elements](https://facebook.github.io/react/docs/dom-elements.html) documentation:
64 |
65 | > React implements a browser-independent DOM system for performance and cross-browser compatibility. We took the opportunity to clean up a few rough edges in browser DOM implementations.
66 | In React, all DOM properties and attributes (including event handlers) should be camelCased. For example, the HTML attribute tabindex corresponds to the attribute tabIndex in React. The exception is aria-* and data-* attributes, which should be lowercased.
67 |
68 | So all DOM attributes and *children* prop are **low-level** props.
69 | **high-level** props - are custom props.
70 |
71 | In all cases **high-level** props always affects on **low-level** props.
72 | That is why most features could be implemented by mapping props before *render*. And it is better than create higher order component for each feature or develop all features inside component.
73 |
74 | It is looks like props middlewares (or like props micro services).
75 | It does not generate higher order components so it will not affect on performance.
76 |
77 |
78 |
79 | ### Forgekit feature function
80 |
81 | Forgekit feature is a pure functions that:
82 |
83 | * provide *propTypes* and *defaultProps*
84 | * takes props as argument and returns new props
85 | * provide its own theme structure ([more about themes](./theme.md))
86 |
87 | ```js
88 | Feature = function(props): newProps
89 | Feature.propTypes = {}
90 | Feature.defaultProps = {}
91 | ```
92 |
93 | ### Forgekit feature with lifecycle methods
94 |
95 | Forgekit feature also could be defined as object with two attributes:
96 |
97 | * *props* - pure function to map props.
98 | * *hoc* - higher order component. Takes Component as argument and return higher order component.
99 |
100 | ```js
101 | Feature = {
102 | props: function(props): newProps,
103 | hoc: function(Component: React.Component): function(props): React.Component
104 | }
105 |
106 | Feature.propTypes = {}
107 | Feature.defaultProps = {}
108 | ```
109 |
110 | Example:
111 |
112 | ```js
113 | class HOC extends React.Component {
114 | comopnenDidMount() {}
115 |
116 | componentWillUnmount() {}
117 |
118 | render() {
119 | return this.props.children;
120 | }
121 | }
122 |
123 | const Feature = {
124 | hoc: Component => {
125 | return props => {
126 | return (
127 |
128 |
129 |
130 | );
131 | }
132 | }
133 | }
134 | ```
135 |
--------------------------------------------------------------------------------
/docs/forgekit-and-recompose.md:
--------------------------------------------------------------------------------
1 | # Forgekit and Recompose
2 |
3 | - [Differences](#differences)
4 | - [Forgekit features provide its own propTypes and defaultProps](#forgekit-features-provide-its-own-proptypes-and-defaultprops)
5 | - [Forgekit merge propTypes and defaultProps](#forgekit-merge-proptypes-and-defaultprops)
6 | - [Forgekit provide tools for component and feature theming](#forgekit-provide-tools-for-component-and-feature-theming)
7 | - [Display name](#display-name)
8 | - [Example of same feature with Forgekit and Recompose](#example-of-same-feature-with-forgekit-and-recompose)
9 | - [Example of same lifecycle feature with Forgekit and Recompose](#example-of-same-lifecycle-feature-with-forgekit-and-recompose)
10 | - [Example of same ref feature with Forgekit and Recompose](#example-of-same-ref-feature-with-forgekit-and-recompose)
11 | - [Forgekit and Recompose can work together](#forgekit-and-recompose-can-work-together)
12 | - [Perfomance tests](#perfomance-tests)
13 | - [Feedback wanted](#feedback-wanted)
14 |
15 | As the developer of the Forgekit I am not happy to develop one more library (one more npm package).
16 | At least because most forgekit features could be implemented with:
17 |
18 | * [recompose/mapProps](https://github.com/acdlite/recompose/blob/master/docs/API.md#mapprops)
19 | * [recompose/withProps](https://github.com/acdlite/recompose/blob/master/docs/API.md#withprops)
20 | * [recompose/setDisplayName](https://github.com/acdlite/recompose/blob/master/docs/API.md#setdisplayname)
21 | * [recompose/lifecycle](https://github.com/acdlite/recompose/blob/master/docs/API.md#lifecycle)
22 |
23 | ## Differences
24 |
25 | #### Forgekit features provide its own propTypes and defaultProps
26 |
27 | Forgekit suggest to develop and store features separately from components. Forgekit feature is a pure functions that:
28 |
29 | * provide *propTypes* and *defaultProps*
30 | * takes props as argument and returns new props
31 | * provide its own theme structure ([more about themes](./theme.md))
32 |
33 | There are a lot of advantages:
34 |
35 | * Responsibility. Each feature stored at separated file (or module if speak in CommonJS context) and there is code that implements only specific functionality and provide its own *propsTypes*.
36 | * Sharable. Features could be shared between components or applications. And don't even depends on components library. It could work above it.
37 | * Refactoring. It is more simpler to remove feature import than find and remove large number of related lines of code.
38 | * Refactoring. Old feature can be swapped with new features. So *propTypes* and *defaultProps* also will be swapped.
39 |
40 | More details at [features api documentation](./feature.md);
41 |
42 | #### Forgekit merge propTypes and defaultProps
43 |
44 | This is important. And this is the critical difference for our development flow.
45 | We use[
46 | React storybook
47 | ](https://github.com/storybooks/react-storybook)
48 | with [storybook info addon](https://github.com/storybooks/react-storybook-addon-info) so it is important to collect all available *propTypes* and show them in the docs.
49 |
50 | But anyway propTypes merging can be implemented with higher order function for *recompose*.
51 |
52 | For example (note that this is pseudocode):
53 |
54 | ```js
55 | const Feature = mapProps(() => {});
56 | Feature.propTypes = {...};
57 |
58 | const enchantedCompose = (...features) => {
59 | let propTypes = {};
60 |
61 | features.map(feature => {
62 | propTypes = {
63 | ...propTypes,
64 | ...feature.propTypes
65 | }
66 | });
67 | return Component => {
68 | const EnchantedComponent = compose(...features)(Component);
69 | EnchantedComponent.propTypes = propTypes;
70 | EnchantedComponent.defaultProps = defaultProps;
71 |
72 | return EnchantedComponent;
73 | }
74 | }
75 |
76 | enchantedCompose(Feature)(Component);
77 | ```
78 |
79 | And that is why I am hot happy to develop one more very similar library. Feedback is very wanted. Write me on twitter [@tuchk4](https://twitter.com/tuchk4) or create [Issue](https://github.com/tuchk4/forgekit/issues/new).f there are some ideas.
80 |
81 | #### Forgekit provide tools for component and feature theming
82 |
83 | Allows to define theme structure for feature or component. It is used for *classNames* or *style* calculating.
84 | Support custom themes provided by global CSS or inline styles and other libraries:
85 |
86 | * [CSS Modules](https://github.com/css-modules/css-modules)
87 | * [Aphrodite](https://github.com/Khan/aphrodite)
88 | * [Radium](http://formidable.com/open-source/radium/)
89 | * [React Style](https://github.com/js-next/react-style)
90 | * [JSS](https://github.com/cssinjs/jss)
91 |
92 | More details at theme documentation.
93 |
94 | #### Display name
95 |
96 | From my point of view - Forgekit provides more readable *displayName*.
97 | Display name is useful for generating error messages and for [React chrome developers tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en).
98 |
99 | * Forgekit
100 |
101 | ```html
102 | ...
103 | ```
104 |
105 | * Recompose
106 |
107 | ```html
108 | ...
109 | ```
110 |
111 | ---
112 |
113 | ## Example of same feature with Forgekit and Recompose
114 |
115 | * Forgekit:
116 |
117 | ```js
118 | import forgekit from 'forgekit';
119 |
120 | const AlertIcon = ({
121 | alert,
122 | icon,
123 | ...props
124 | }) => ({
125 | ...props,
126 | alert,
127 | icon: !icon && alert ? 'alert' : ''
128 | });
129 |
130 | const ForgedComponent = forge(AlertIcon)(Component, 'AlertComponent', props => {
131 | return {
132 | onClick: (e) => {
133 | if (props.alert) {
134 | console.warning('alert button clicked');
135 | }
136 |
137 | if (props.onClick) {
138 | onClick(e);
139 | }
140 | }
141 | }
142 | });
143 | ```
144 |
145 | * Recompose:
146 |
147 | ```js
148 | import mapProps from 'recompose/mapProps';
149 | import setDisplayName from 'recompose/setDisplayName';
150 | import withProps from 'recompose/withProps';
151 | import compose from 'recompose/compose'
152 |
153 | const AlertIcon = mapProps({
154 | alert,
155 | icon,
156 | ...props
157 | }) => ({
158 | ...props,
159 | alert,
160 | icon: !icon && alert ? 'alert' : ''
161 | });
162 |
163 | const EnchantedComponent = compose(
164 | AlertIcon,
165 | withProps(props => {
166 | return {
167 | onClick: (e) => {
168 | if (props.alert) {
169 | console.warning('alert button clicked');
170 | }
171 |
172 | if (props.onClick) {
173 | onClick(e);
174 | }
175 | }
176 | }
177 | }),
178 | setDisplayName('AlertComponent')
179 | )(Component);
180 | ```
181 |
182 | ## Example of same lifecycle feature with Forgekit and Recompose
183 |
184 | * Forgekit:
185 |
186 | ```js
187 | import forgekit from 'forgeit';
188 |
189 | class Hoc extends React.Component {
190 | componentDidMount() {
191 | console.log(ReactDOM.findDOMNode(this));
192 | }
193 | }
194 |
195 | const Feature = {
196 | hoc: props => Component => {
197 | return (
198 |
199 |
200 |
201 | );
202 | }
203 | }
204 | export default forgekit(Feature)(Component);
205 | ```
206 |
207 | * Recompose:
208 |
209 | ```js
210 | import lifecycle from 'recompose/lifecycle';
211 | import compose from 'recompose/compose'
212 |
213 | export default compose(
214 | lifecycle({
215 | componentDidMount() {
216 | console.log(ReactDOM.findDOMNode(this));
217 | }
218 | })
219 | )(Component);
220 | ```
221 |
222 |
223 | ## Example of same ref feature with Forgekit and Recompose
224 |
225 | * Forgekit:
226 |
227 | ```js
228 | import forgekit from 'forgeit';
229 |
230 | forgekit(props => {
231 | return {
232 | ...props,
233 | children: console.log(e)}>{props.children}
234 | }
235 | })(Component);
236 | ```
237 |
238 | * Recompose:
239 |
240 | ```js
241 | import mapProps from 'recompose/mapProps';
242 | import compose from 'recompose/compose'
243 |
244 | const EnchantedComponent = compose(
245 | mapProps(props => {
246 | return {
247 | ...props,
248 | children: console.log(e)}>{props.children}
249 | }
250 | })
251 | )(Component);
252 | ```
253 |
254 | ## Forgekit and Recompose can work together
255 |
256 | ```js
257 | import compose from 'recompose/compose';
258 | import pure from 'recompose/pure';
259 | import withContext from 'recompose/withContext';
260 |
261 | import forgekit from 'forgekit';
262 |
263 | const ForgedComponent = (...features)(Component);
264 |
265 | export default compose(
266 | pure(),
267 | withContext({
268 | childContextTypes: {},
269 | getChildContext: prop => {}
270 | })
271 | )(ForgedComponent);
272 | ```
273 |
274 |
275 |
276 | ## Perfomance tests
277 |
278 | Tested components:
279 |
280 | * [Forgekit component gist](https://gist.github.com/tuchk4/53372534b08d778f41223588b9cb3b82)
281 | * [Recompose component gist](https://gist.github.com/tuchk4/d9b2ae886e98006a6ac285e89e8fa4eb)
282 |
283 | Tested 20k renders. Used [React perf addon](https://facebook.github.io/react/docs/perf.html)
284 |
285 | **Forgkit**
286 |
287 | * console.time between *constructor* -> *componentDidMount* time ~ 5.08sec
288 |
289 | | (index) | Owner > Component | Inclusive render time (ms) | Instance count | Render count |
290 | |----------|-----------------------------|----------------------------|----------------|--------------|
291 | | 0 | "App" | 985.01 | 1 | 1 |
292 | | 1 | "App > Perfomance" | 984.87 | 1 | 1 |
293 | | 3 | "Perfomance > Button" | 148.52 | 20000 | 20000 |
294 | | 2 | "Perfomance > ForgedButton" | 725.21 | 20000 | 20000 |
295 |
296 | **Recompose**
297 |
298 | * console.time between *constructor* -> *componentDidMount* time ~ 4.78sec
299 |
300 | | (index) | Owner > Component | Inclusive render time (ms) | Instance count | Render count |
301 | |----------|------------------------------------------------------------------|----------------------------|----------------|--------------|
302 | | 0 | "App" | 620.29 |1 | 1 |
303 | | 1 | "App > Perfomance" | 620.14 |1 | 1 |
304 | | 2 | "Perfomance > defaultProps(mapProps(mapProps(EnchantedButotn)))" | 476.35 |20000 | 20000 |
305 |
306 | So Recompose a bit faster.
307 |
308 | **But Forgekit allows to write more standalone features**. Each feature provide its own *propTypes* and *defaultProps*.
309 |
310 | For example Forgekit *icon.js* provide [propTypes and defaultProps](https://gist.github.com/tuchk4/53372534b08d778f41223588b9cb3b82#file-icon-js-L28);
311 |
312 | With recompose we should define *defaultProps* [while composing](https://gist.github.com/tuchk4/d9b2ae886e98006a6ac285e89e8fa4eb#file-recompose-button-js-L18).
313 | This break modules responsibility.
314 | Also will hard to share features between components or application because default values are defined outside of *mapProps* implementation.
315 |
316 |
317 | ## Feedback wanted
318 |
319 | Forgekit is still in the early stages and even still be an experimental project. Your are welcome to submit issue or PR if you have suggestions! Or write me on twitter [@tuchk4](https://twitter.com/tuchk4).
320 |
--------------------------------------------------------------------------------
/docs/images/component-became-complex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuchk4/forgekit/a2dd0dbeaf5e29554e0ff0923c3e2608f8281805/docs/images/component-became-complex.png
--------------------------------------------------------------------------------
/docs/images/component-with-added-features.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuchk4/forgekit/a2dd0dbeaf5e29554e0ff0923c3e2608f8281805/docs/images/component-with-added-features.png
--------------------------------------------------------------------------------
/docs/images/component-with-features.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuchk4/forgekit/a2dd0dbeaf5e29554e0ff0923c3e2608f8281805/docs/images/component-with-features.png
--------------------------------------------------------------------------------
/docs/images/component.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuchk4/forgekit/a2dd0dbeaf5e29554e0ff0923c3e2608f8281805/docs/images/component.png
--------------------------------------------------------------------------------
/docs/images/props-as-middleware-with-props.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuchk4/forgekit/a2dd0dbeaf5e29554e0ff0923c3e2608f8281805/docs/images/props-as-middleware-with-props.png
--------------------------------------------------------------------------------
/docs/images/props-as-middleware.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuchk4/forgekit/a2dd0dbeaf5e29554e0ff0923c3e2608f8281805/docs/images/props-as-middleware.png
--------------------------------------------------------------------------------
/docs/images/recompose-display-name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tuchk4/forgekit/a2dd0dbeaf5e29554e0ff0923c3e2608f8281805/docs/images/recompose-display-name.png
--------------------------------------------------------------------------------
/docs/perfomance-tests.md:
--------------------------------------------------------------------------------
1 | ## Default approach
2 |
3 | Tested console.time between *constructor* and *componentDidMount* + [React perf addon](https://facebook.github.io/react/docs/perf.html)
4 |
5 | * console.time *constructor* -> *componentDidMount* time ~ 3.222sec
6 |
7 | | (index) | Owner > Component | Inclusive render time (ms) | Instance count | Render count |
8 | |----------|-----------------------|----------------------------|----------------|--------------|
9 | | 0 | "App" | 596.33 | 1 | 1 |
10 | | 1 | "App > Perfomance" | 596.18 | 1 | 1 |
11 | | 2 | "Perfomance > Button" | 332.78 | 20000 | 20000 |
12 |
13 | ## Higher order components
14 |
15 | * console.time between *constructor* -> *componentDidMount* time ~ 5.33sec
16 |
17 | | (index) | Owner > Component | Inclusive render time (ms) | Instance count | Render count |
18 | |----------|-----------------------|----------------------------|----------------|--------------|
19 | | 0 |"App | 874.02 | 1 | 1 |
20 | | 1 |"App > Perfomance | 873.88 | 1 | 1 |
21 | | 2 |"Perfomance > Unknown | 599.1 | 20000 | 20000 |
22 | | 3 |"Unknown > Unknown | 271.7 | 20000 | 20000 |
23 | | 4 |"Unknown > Button | 132.49 | 20000 | 20000 |
24 |
25 |
26 | ## Forged components
27 |
28 | * console.time between *constructor* -> *componentDidMount* time ~ 5.08sec
29 |
30 | | (index) | Owner > Component | Inclusive render time (ms) | Instance count | Render count |
31 | |----------|-----------------------------|----------------------------|----------------|--------------|
32 | | 0 | "App" | 985.01 | 1 | 1 |
33 | | 1 | "App > Perfomance" | 984.87 | 1 | 1 |
34 | | 3 | "Perfomance > Button" | 148.52 | 20000 | 20000 |
35 | | 2 | "Perfomance > ForgedButton" | 725.21 | 20000 | 20000 |
36 |
37 | ## Recompose components
38 |
39 | * console.time between *constructor* -> *componentDidMount* time ~ 4.78sec
40 |
41 | | (index) | Owner > Component | Inclusive render time (ms) | Instance count | Render count |
42 | |----------|------------------------------------------------------------------|----------------------------|----------------|--------------|
43 | | 0 | "App" | 620.29 |1 | 1 |
44 | | 1 | "App > Perfomance" | 620.14 |1 | 1 |
45 | | 2 | "Perfomance > defaultProps(mapProps(mapProps(EnchantedButotn)))" | 476.35 |20000 | 20000 |
46 |
--------------------------------------------------------------------------------
/docs/theme.md:
--------------------------------------------------------------------------------
1 | # Theme
2 |
3 | - [Forgekit theme api](#forgekit-theme-api)
4 | - [What is the ThemeProp?](#what-is-the-themeprop)
5 | - [Theme className example](#theme-classname-example)
6 | - [Theme styles example](#theme-styles-example)
7 |
8 | Allows to define theme structure for feature or component. It is used for *classNames* or *style* calculating.
9 | Support custom themes provided by global CSS or inline styles and other libraries:
10 |
11 | * [CSS Modules](https://github.com/css-modules/css-modules)
12 | * [Aphrodite](https://github.com/Khan/aphrodite)
13 | * [Radium](http://formidable.com/open-source/radium/)
14 | * [React Style](https://github.com/js-next/react-style)
15 | * [JSS](https://github.com/cssinjs/jss)
16 |
17 | **What is theme?**
18 |
19 | Simple styling manager for the React components.
20 |
21 | Theme structure is defined at *propTypes* and default values defined at *defaultProps*.
22 | So *theme* is the component prop.
23 |
24 | ```js
25 |
26 | ```
27 |
28 | **Why theme is important for features?**
29 |
30 | A lot of features usually provides new UI elements for component. So - they should/may be customized.
31 |
32 | ## Forgekit theme api
33 |
34 | Theme structure should be defined at the *propTypes*.
35 |
36 | Default theme values should be defined at *defaultProps*.
37 |
38 | All defined themes are merged in the ForgedComponent.
39 | Each feature or component will receive only defined theme structure.
40 |
41 | ```js
42 | import { ThemeProp } from 'forgekit';
43 |
44 | Feature1.propTypes = {
45 | theme: ThemeProp({
46 | foo: PropTypes.string,
47 | bar: PropTypes.string
48 | })
49 | };
50 |
51 | Feature2.propTypes = {
52 | theme: ThemeProp({
53 | baz: PropTypes.string
54 | })
55 | };
56 |
57 | const ForgedComponent = forge(Feature1, Feature2)(Component);
58 |
59 | expect(ForgedComponent.propTypes.theme).toEqual({
60 | ...Feature1.propTypes.theme,
61 | ...Feature2.propTypes.theme,
62 | });
63 | ```
64 |
65 | ### What is the ThemeProp?
66 |
67 | *ThemeProp* is same as *PropTypes.shape* but with attribute that contains all defined keys. Forgekit needs it for theme merging.
68 |
69 | *PropTypes.shape* cannot be used directly because it is a bound function and there are no access to defined shape attributes.
70 |
71 | ```js
72 | const ThemeProp = theme => {
73 | const themeShape = PropTypes.shape(theme);
74 | themeShape.themeKeys = Object.keys(theme);
75 |
76 | return themeShape;
77 | }
78 | ```
79 |
80 | I have created [React feature request](https://github.com/facebook/react/issues/8310) to fill name attribute for all *propTypes*.
81 | If it is approved - *ThemeProp* will be removed.
82 |
83 | ## Theme className example
84 |
85 | For better experience suggest to use [classnames](https://www.npmjs.com/package/classnames) library.
86 |
87 | * Define component theme structure
88 |
89 | ```js
90 | import { ThemeProp } from 'forgekit';
91 |
92 | import classnames from 'classnames';
93 | import buttonStyles from './style.css';
94 |
95 | const Button = ({theme, children, className, ...props}) => {
96 | const classList = classnames(className, theme.base, theme.design);
97 | return