19 | );
20 | };
21 |
22 | export default HintExample;
23 |
--------------------------------------------------------------------------------
/ui-lib/Factory/ui-core/Hint.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | // This file would be an example of an unstyled component which can be used if
4 | // directly provided with a theme as an alternative to compose your custom UI kit
5 | // with react-themeable's createComponent.
6 | export default ({ children, isOpen = false, theme = {} }) => {
7 | return (
8 |
41 | // );
42 | // }
43 | // };
44 |
45 | export default Hint;
46 |
--------------------------------------------------------------------------------
/app/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
4 |
5 | module.exports = {
6 | devtool: 'eval',
7 | entry: [
8 | 'webpack-hot-middleware/client',
9 | './index',
10 | ],
11 | output: {
12 | path: path.join(__dirname, 'dist'),
13 | filename: 'bundle.js',
14 | publicPath: '/static/',
15 | },
16 | plugins: [
17 | new ExtractTextPlugin('style.css', { allChunks: true }),
18 | new webpack.HotModuleReplacementPlugin(),
19 | new webpack.NoErrorsPlugin(),
20 | ],
21 | resolve: {
22 | alias: {
23 | 'ui-lib': path.join(__dirname, '..', 'ui-lib'),
24 | react: path.join(__dirname, 'node_modules', 'react'),
25 | 'react-dom': path.join(__dirname, 'node_modules', 'react-dom'),
26 | },
27 | extensions: ['', '.js'],
28 | },
29 | module: {
30 | loaders: [
31 | {
32 | test: /\.js$/,
33 | loaders: ['babel'],
34 | exclude: /node_modules/,
35 | include: __dirname,
36 | }, {
37 | test: /\.js$/,
38 | loaders: ['babel'],
39 | include: path.join(__dirname, '..', 'ui-lib'),
40 | }, {
41 | test: /\.css/,
42 | loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'),
43 | include: __dirname,
44 | },
45 | ],
46 | },
47 | };
48 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ui-lib-future",
3 | "version": "0.0.0",
4 | "description": "",
5 | "author": {
6 | "name": "Nik Graf",
7 | "email": "nik@nikgraf.com",
8 | "url": "https://github.com/nikgraf"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "TODO"
13 | },
14 | "main": "lib/index.js",
15 | "keywords": [
16 | "react"
17 | ],
18 | "peerDependencies": {
19 | "react": ">=0.14.0",
20 | "react-dom": ">=0.14.0"
21 | },
22 | "scripts": {
23 | "prepublish": "npm run build",
24 | "lint": "npm run lint:eslint && npm run lint:jscs",
25 | "lint:eslint": "eslint ./",
26 | "lint:jscs": "jscs ./"
27 | },
28 | "devDependencies": {
29 | "babel": "^6.3.26",
30 | "babel-cli": "^6.4.5",
31 | "babel-core": "^6.4.5",
32 | "babel-eslint": "^5.0.0-beta6",
33 | "babel-jest": "^6.0.1",
34 | "babel-loader": "^6.2.1",
35 | "babel-plugin-react-transform": "^2.0.0",
36 | "babel-preset-es2015": "^6.3.13",
37 | "babel-preset-react": "^6.3.13",
38 | "babel-preset-react-hmre": "^1.0.1",
39 | "eslint": "^1.10.3",
40 | "eslint-config-airbnb": "^3.1.0",
41 | "eslint-plugin-react": "^3.15.0",
42 | "jest-cli": "^0.8.2",
43 | "jscs": "^2.8.0",
44 | "react-transform-catch-errors": "^1.0.1",
45 | "react-transform-hmr": "^1.0.1",
46 | "redbox-react": "^1.2.0",
47 | "webpack": "^1.12.11",
48 | "webpack-dev-middleware": "^1.5.1",
49 | "webpack-dev-server": "^1.14.1",
50 | "webpack-hot-middleware": "^2.6.0"
51 | },
52 | "license": "MIT",
53 | "dependencies": {
54 | "babel-preset-stage-0": "^6.3.13",
55 | "immutable": "^3.7.6",
56 | "react": "^0.14.6",
57 | "react-dom": "^0.14.6"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/global-theming.md:
--------------------------------------------------------------------------------
1 | #### Module export
2 |
3 | https://github.com/nikgraf/future-react-ui/tree/master/ui-lib
4 |
5 | In this version the defaultTheme is exported through a named module export. On line 9, 10, 11 you can see how the default theme is patched with custom classes. (https://github.com/nikgraf/future-react-ui/blob/master/app/index.js#L8) Since this is not so hand it might be useful to have utility function for each library to apply a theme.
6 |
7 | #### Static property
8 |
9 | https://github.com/nikgraf/future-react-ui/blob/master/ui-lib/StaticProperty/Hint.js
10 |
11 | In this version the defaultTheme is attached to the component itself as static property. Compared to the 'Module export' this is a bit more flexible as you can overwrite the whole `theme` object in one go. See line 13-20 for usage. (https://github.com/nikgraf/future-react-ui/blob/master/app/index.js#L13)
12 |
13 | #### Theme Component leveraging Context
14 |
15 | https://github.com/nikgraf/future-react-ui/blob/master/ui-lib/Context/Hint.js
16 |
17 | In this version we leverage context to build a `` component that takes a theme as property and passes it down to all child components via React's context. A theme is still a simple JS object as can be seen on line 25-30. (https://github.com/nikgraf/future-react-ui/blob/master/app/index.js#L22). On one hand this approach is powerful, because you can apply different themes various nesting levels in the render tree.
18 |
19 | ```
20 |
21 |
22 |
23 |
24 | Basic open hint without styling.
25 |
26 | ```
27 |
28 | There is one obvious concern with this approach. There could be name-clashing between libraries that use the same key in the `theme` object. This could be solved by following a namespace convention like prefixing the keys with the npm package name.
29 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import HintExample from './components/HintExample';
5 | import HintJssExample from './components/HintJssExample';
6 | import HintCssModulesExample from './components/HintCssModulesExample';
7 |
8 | /* In this version each porperty of the importet theme can be overwritten. */
9 | import Hint, { defaultTheme } from 'ui-lib/ModuleExport/Hint';
10 | defaultTheme.questionMark = 'custom-class-for-question-mark-green';
11 | defaultTheme.visibleContent = 'custom-class-visible-content';
12 |
13 | /* In this variant the default theme can be overwritten by assigning a new object. */
14 | import Hint2 from 'ui-lib/StaticProperty/Hint';
15 | Hint2.theme = {
16 | questionMark: 'custom-class-for-question-mark-red',
17 | visibleContent: 'custom-class-visible-content',
18 | };
19 | /* It is also possible to just overwrite a single property. */
20 | Hint2.theme.questionMark = 'custom-class-for-question-mark-red';
21 |
22 | /* Context based theming */
23 | import Hint3 from 'ui-lib/Context/Hint';
24 | import Theme from 'ui-lib/Context/Theme';
25 | const contextTheme = {
26 | hint: {
27 | questionMark: 'custom-class-for-question-mark-blue',
28 | visibleContent: 'custom-class-visible-content',
29 | },
30 | };
31 |
32 | /* Factory based theming
33 | * also see /ui-lib/Factory/belle/Hint or /ui-lib/Factory/elemental/Hint
34 | */
35 | import Hint4Unstyled from '../ui-lib/Factory/ui-core/Hint';
36 | import createComponent from '../ui-lib/Factory/react-themeable/createComponent';
37 | const theme = {
38 | questionMark: 'custom-class-for-question-mark-gold',
39 | visibleContent: 'custom-class-visible-content',
40 | };
41 | const Hint4 = createComponent(Hint, theme);
42 |
43 | window.React = React;
44 |
45 | const App = () => {
46 | return (
47 |
48 |
JSS styled
49 |
50 |
CSS Modules styled
51 |
52 |
53 |
54 |
55 |
Unstyled (globally patched classNames)
56 |
Global theme as module export
57 | Basic closed hint without styling.
58 | Basic open hint without styling.
59 |
60 |
61 |
Global theme as static property
62 | Basic closed hint without styling.
63 | Basic open hint without styling.
64 |
65 |
66 |
Global theme as Theme component leveraging context
67 |
68 | Basic closed hint without styling.
69 | Basic open hint without styling.
70 |
71 |
72 |
73 |
Factory to create Components with a provided Theme
74 | Basic closed hint without styling.
75 | Basic open hint without styling.
76 |
77 | );
78 | };
79 |
80 | ReactDOM.render(, document.getElementById('react'));
81 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A Playground to investigate a good third-party React UI Lib Architecture
2 |
3 | My goal is to find an architecture which could be common ground a base for quality React UI libraries. A consistent theming/styling API would make the lives of many developers way more comfortable.
4 |
5 | HELP/FEEDBACK/YOUR OPINION WANTED! :)
6 | Feel free to join the ongoing discussion [here](https://github.com/nikgraf/future-react-ui/issues/1) or open up a new issue.
7 |
8 | ## How to setup & run
9 |
10 | ```
11 | npm install
12 | cd app
13 | npm install
14 | npm start
15 | ```
16 |
17 | ## Basic Ideas
18 |
19 | #### Self-contained
20 |
21 | Make components self contained (no global UI lib dependencies) so you can import them and avoid importing the whole library e.g.
22 |
23 | ```
24 | // import the whole lib and get Toggle
25 | import { Toggle } from 'ui-lib';
26 |
27 | // just import toggle without importing the whole library
28 | import Toggle from 'ui-lib/Toggle';
29 | ```
30 |
31 | #### react-themeable
32 |
33 | Leverage react-themeable. It's a nice way of providing many styling classes to a single component.
34 | I believe establishing this as a convention would benefit the React community due the consistent API over many libs.
35 |
36 | #### ClassName based
37 |
38 | Component styles should be class based. It's more performant & responsive styling doesn't work with server-side rendering. I myself went down the inline-styles path in the past, but switched back to classes. A component using react-themeable could actually work with both (class-names & inline-styles), but I believe this is an unnecessary overhead. Your thoughts?
39 |
40 | #### Ship without a theme
41 |
42 | Ship without a global theme so people don't have to import the styling code. This might not be relevant to company internal or in general libraries that a opinionated about styling.
43 |
44 | #### Global theming utility
45 |
46 | Provide a simple & handy way to apply a global theme for all the imported components.
47 |
48 | ## Global Theming
49 |
50 | While react-themeable is super useful I believe having a way to set a default styling is a crucial feature for a UI library. Most of the time you will use the default theme specific to your product. This avoids adding a lot of theme props through the whole application.
51 |
52 | ### Factory Pattern
53 |
54 | https://github.com/nikgraf/future-react-ui/tree/master/ui-lib/Factory
55 |
56 | In this version we assume there is a package which includes a completely unstyled version of the UI kits while there is a second one which takes all the needed components and return themed components buy leveraging a factory function.
57 |
58 | ```
59 | /**
60 | * Returns a the provided component as themed component.
61 | *
62 | * Note: defaultProps could be useful for default special behavioural in
63 | * different ui libraries.
64 | */
65 | export default (Component, theme, defaultProps) => (props) => {
66 | return ;
67 | };
68 | ```
69 |
70 | For example these UI
71 |
72 | #### Not themed kits & components
73 |
74 | - material-ui-core (unstyled kit)
75 | - belle-core (unstyled kit)
76 | - elemental-core (unstyled kit)
77 | - react-toolbox-core (unstyled kit)
78 | - react-select (unstyled component)
79 | - react-autocomplete (unstyled component)
80 | - react-modal (unstyled component)
81 |
82 | #### Themed UI kits
83 |
84 | - material-ui (themed with material ui style and based on material-ui-core)
85 | - belle (themed with belle style and based on belle-core)
86 | - belle-flat (themed with a flat theme and based on belle-core)
87 | - elemental (themed with the elemental theme and based on elemental-core)
88 | - react-toolbox (themed with the material theme and based on react-toolbox-core)
89 | - your-product-ui-lib (themed with your company style and based on belle-core[Toggle, Rating] & react-select & react-modal)
90 | - your-friends-product-ui-lib (themed with their company style and based on react-select & react-modal & react-autocomplete)
91 |
92 | Usage:
93 | ```
94 | import Hint from 'elemental/Hint';
95 |
96 | const customTheme = {
97 | questionMark: 'custom-class-for-question-mark-gold',
98 | visibleContent: 'custom-class-visible-content',
99 | };
100 |
101 | export default (props) => {
102 | return (
103 |
104 | {/* Globally theme component */}
105 |
106 | {/* Overwriting the theme locally for this case */}
107 |
108 |
109 | );
110 | };
111 | ```
112 |
113 | ### Other Global Theming Patterns
114 |
115 | other patterns can be found here: https://github.com/nikgraf/future-react-ui/blob/master/global-theming.md
116 |
117 | ## Temporary Conclusion
118 |
119 | While the Theme component based idea is pretty powerful the issues make me not like it as a default. For some time I was convinced that Module export or Static property would be one of the winners. Right now I'm pretty convinced the Factory Pattern is the clear winner. It is super flexible and allows people to create their own company UI kits. They can easily provide their own styling as well as set other props as default suited to their needs. Another benefit is that you can easily get started with prototyping by using an already style version (e.g. belle-flat) and replace it later with your custom product style.
120 |
121 | If you have some ideas/feedback please reach out to me and let's discuss. (Github Issues might be best, but Twitter, Email, Skype, Hangout works as well)
122 |
123 | ## License
124 |
125 | MIT
126 |
--------------------------------------------------------------------------------