├── .babelrc
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
├── .DS_Store
├── README.md
├── components
│ ├── About.js
│ ├── Base.js
│ ├── Code.js
│ ├── Column.js
│ ├── Configuration.js
│ ├── FormComponents.js
│ ├── GettingStarted.js
│ ├── Home.js
│ ├── Philosophy.js
│ ├── components
│ │ ├── ButtonDocumentation.js
│ │ ├── CardDocumentation.js
│ │ ├── ChoiceDocumentation.js
│ │ ├── ComboBoxDocumentation.js
│ │ ├── DatePickerDocumentation.js
│ │ ├── OptionDocumentation.js
│ │ ├── PlaceholderDocumentation.js
│ │ ├── RatingDocumentation.js
│ │ ├── SelectDocumentation.js
│ │ ├── SeparatorDocumentation.js
│ │ ├── SpinnerDocumentation.js
│ │ ├── TextInputDocumentation.js
│ │ └── ToggleDocumentation.js
│ ├── guides
│ │ └── IntroducingBelle.js
│ └── routes.js
├── css
│ ├── normalize.css
│ └── style.css
├── icon
│ ├── apple-touch-icon-114x114.png
│ ├── apple-touch-icon-120x120.png
│ ├── apple-touch-icon-144x144.png
│ ├── apple-touch-icon-152x152.png
│ ├── apple-touch-icon-57x57.png
│ ├── apple-touch-icon-60x60.png
│ ├── apple-touch-icon-72x72.png
│ ├── apple-touch-icon-76x76.png
│ ├── favicon-128.png
│ ├── favicon-16x16.png
│ ├── favicon-196x196.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── favicon.ico
│ ├── mstile-144x144.png
│ ├── mstile-150x150.png
│ ├── mstile-310x150.png
│ ├── mstile-310x310.png
│ └── mstile-70x70.png
├── images
│ ├── abyssinian.jpg
│ ├── albatross.jpg
│ ├── angelfish.jpg
│ ├── ant.jpg
│ ├── antelope.jpg
│ ├── asian_elephant.jpg
│ ├── belle_logo.png
│ ├── croatia_100.jpg
│ ├── croatia_original.jpg
│ ├── ngorongoro_caldera_small.jpg
│ ├── overview.png
│ ├── santorini_100.jpg
│ ├── santorini_original.jpg
│ ├── vjeux.jpeg
│ ├── yosemite_100.jpg
│ └── yosemite_original.jpg
├── index.html
├── index.js
├── package.json
├── server.js
├── style.js
├── theme
│ ├── ThemeSwitch.js
│ ├── belle-with-classic-focus.js
│ ├── bootstrap-3.js
│ └── initialize.js
├── webpack.config.js
└── webpack.config.production.js
├── examples
├── README.md
├── components
│ ├── ButtonPlayground.js
│ ├── CardPlayground.js
│ ├── ComboBoxPlayground.js
│ ├── DatePickerPlayground.js
│ ├── RatingPlayground.js
│ ├── SelectPlayground.js
│ ├── SpinnerPlayground.js
│ ├── TextInputPlayground.js
│ └── TogglePlayground.js
├── css
│ ├── normalize.css
│ └── style.css
├── index.html
├── index.js
├── package.json
├── server.js
└── webpack.config.js
├── package.json
├── scripts
└── publish_gh_pages.sh
└── src
├── __tests__
├── Button-test.js
├── Card-test.js
├── ComboBox-test.js
├── DatePicker-test.js
├── Option-test.js
├── Placeholder-test.js
├── Rating-test.js
├── Select-test.js
├── Separator-test.js
├── Spinner-test.js
├── TextInput-test.js
├── date-helpers-test.js
├── helpers-test.js
└── union-class-names-test.js
├── components
├── ActionArea.js
├── Button.js
├── Card.js
├── Choice.js
├── ComboBox.js
├── ComboBoxItem.js
├── DatePicker.js
├── Day.js
├── DisabledDay.js
├── Option.js
├── Placeholder.js
├── Rating.js
├── Select.js
├── SelectItem.js
├── Separator.js
├── Spinner.js
├── TextInput.js
└── Toggle.js
├── config
├── button.js
├── datePicker.js
├── i18n.js
├── rating.js
├── select.js
└── toggle.js
├── index.js
├── style
├── actionArea.js
├── animations.js
├── button.js
├── card.js
├── combo-box.js
├── date-picker.js
├── option.js
├── placeholder.js
├── rating.js
├── select.js
├── separator.js
├── spinner.js
├── text-input.js
└── toggle.js
└── utils
├── animation-frame-management.js
├── calculate-textarea-height.js
├── date-helpers.js
├── helpers.js
├── inject-style.js
├── is-component-of-type.js
└── union-class-names.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react", "es2015", "stage-0"],
3 | "env": {
4 | "development": {
5 | "presets": ["react-hmre"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/node_modules/**
2 | lib/**
3 | scripts/**
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb",
4 | "rules": {
5 | "react/prefer-stateless-function": 0,
6 | "max-len": 0,
7 | "comma-dangle": 0,
8 | "react/jsx-no-bind": 0, // TODO remove this
9 | "react/prefer-es6-class": 0, // TODO remove this
10 | "react/sort-comp": [2, { // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md
11 | "order": [
12 | "constructor",
13 | "displayName",
14 | "propTypes",
15 | "contextTypes",
16 | "childContextTypes",
17 | "mixins",
18 | "statics",
19 | "defaultProps",
20 | "getDefaultProps",
21 | "state",
22 | "getInitialState",
23 | "getChildContext",
24 | "componentWillMount",
25 | "componentDidMount",
26 | "componentWillReceiveProps",
27 | "shouldComponentUpdate",
28 | "componentWillUpdate",
29 | "componentDidUpdate",
30 | "componentWillUnmount",
31 | "/^_on.+$/",
32 | "/^_get.+$/",
33 | "/^_trigger.+$/",
34 | "/^_.+$/",
35 | "/^_render.+$/",
36 | "render"
37 | ]
38 | }]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependency directory
2 | # Commenting this out is preferred by some people, see
3 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
4 | node_modules
5 |
6 | # Browserify cache & build files
7 | bundle.js
8 | .bundle.js
9 |
10 | # Belle build with babel
11 | /lib/
12 |
13 | # Belle docs after npm install
14 | /docs/css/googlecode.css
15 |
16 | # NPM debug
17 | npm-debug.log
18 | npm-debug.log*
19 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Dependency directory
2 | # Commenting this out is preferred by some people, see
3 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
4 | node_modules
5 |
6 | # Browserify cache & build files
7 | bundle.js
8 | .bundle.js
9 |
10 | # Belle sources
11 | /src/
12 | /examples/
13 |
14 | # NPM debug
15 | npm-debug.log
16 |
17 | tests
18 | docs
19 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "5"
4 | sudo: false
5 | script:
6 | - npm test
7 | - npm run lint
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Nikolaus Graf
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Belle
2 |
3 | Configurable React Components with great UX.
4 |
5 | Website & Documentation: [http://nikgraf.github.io/belle/](http://nikgraf.github.io/belle/)
6 |
7 | [ ](http://nikgraf.github.io/belle/)
8 |
9 | [](https://travis-ci.org/nikgraf/belle)
10 | [](https://david-dm.org/nikgraf/belle)
11 | [](https://david-dm.org/nikgraf/belle#info=peerDependencies)
12 |
13 | ## Getting Started
14 |
15 | Belle is available as [npm](http://npmjs.org) package. Once you have npm you can install Belle in your project folder with:
16 |
17 | ```
18 | npm install belle
19 | ```
20 |
21 | ### Import & use Belle Components
22 |
23 | We recommend you to get started with [React](https://facebook.github.io/react/) first. Once you have a simple application setup you can import any Belle component and use it right away. No stylesheets, font or any other prerequisite needed.
24 |
25 | ```html
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
37 |
38 |
39 |
40 | ```
41 |
42 | ```javascript
43 | var React = require('react');
44 | var belle = require('belle');
45 | var TextInput = belle.TextInput;
46 |
47 | var App = React.createClass({
48 |
49 | render: function() {
50 | return (
51 |
52 |
53 |
54 | );
55 | }
56 | });
57 |
58 | React.render( , document.getElementById('react-root'));
59 | ```
60 |
61 | ### Learn more
62 |
63 | In addition you can dig through the [documentation](http://nikgraf.github.io/belle/) to learn about how to modify Belle components.
64 |
65 | ## Browser Support
66 |
67 | - Chrome (mobile and desktop)
68 | - Safari (mobile and desktop)
69 | - Firefox
70 | - Internet Explorer 9, 10, 11
71 |
72 | ## Philosophy
73 |
74 | "Great UX starts with a good UI"
75 |
76 | With Belle I'm aiming to provide a library that follows a couple well established
77 | principles. While some of them might not be consider best practice today
78 | We believe they will become some day in the near future.
79 |
80 | ### Easy to use
81 |
82 | The most important attribute of any great experience is that there was no hassle to achieve
83 | the initial goals.
84 |
85 | ### Consistent Behavior
86 |
87 | Every object someone interacts with has it's own little language. This language
88 | must be learned by everyone getting in touch with a new set of object. In order
89 | to provide a great experience the amount to learn should be reduced to a minimum.
90 |
91 | There are two major personalities to target with Belle. One is the developer.
92 | For him/her APIs should be provided in a consistent way through all components.
93 | The other and more important is the users of the components. Every color,
94 | animation or behavior should be aligned with the other components to provide
95 | a great experience.
96 |
97 | ### Encapsulate Styles
98 |
99 | There is no reason while a style designed one specific element should affect
100 | others. In CSS styles are often defined by overwriting previous ones and introducing
101 | deeper and deeper nesting. Once nesting is introduced resolution of styles for one
102 | specific element is not a trivial task anymore. Due this managing CSS dependencies
103 | is hard. It is hard to predict how an application looks like after updating or
104 | removing dependency.
105 |
106 | That's why with Belle styles should apply to the components themselves in the DOM.
107 | By doing so the visual appearance and business logic are combined in location.
108 |
109 | ### Every Interaction is followed up with Feedback
110 |
111 | Let people know how their behavior affects the system. It assures them that
112 | their input was acknowledged which provides them with a feeling of control over
113 | the system.
114 |
115 | ### High Performance
116 |
117 | The user should see affects of their actions instantly. Any delay can cause confusion
118 | and frustration. While instant certainly is not always possible Belle strives to
119 | provide an experience close to instant.
120 |
121 | ## Development
122 |
123 | You can install the development environment with
124 |
125 | ```
126 | npm install
127 | ```
128 |
129 | `npm run build` will trigger a build into the `lib` folder. To develop a component it's convenient to use the examples or docs application.
130 |
131 | ### Run the examples or docs application
132 |
133 | To run the examples or docs you go into the folder `docs` or `examples` and run `npm install` and `npm start`. The app will run with hot reloading on `http://localhost:3000`.
134 |
135 | ### Tests
136 |
137 | In order to run the tests use
138 |
139 | ```
140 | npm test
141 | ```
142 |
143 | To run the test continuously you can use `npm run test:watch`.
144 |
145 | ## Discussion or need help?
146 |
147 | In addition you can ask the community by [posting your question to StackOverflow with the **belle** tag.](http://stackoverflow.com/questions/ask?tags=belle).
148 |
149 | ## Future Plans
150 |
151 | - Components to add: Dateformatter, Datepicker, Tooltip, Popover, Modal, Navigation Menu, NumberInput, EmailInput, Anchor, DropZone
152 |
153 | ## Special Thanks
154 |
155 | Thanks to [Andrey Popp](https://github.com/andreypopp) & [Eugene](https://github.com/eugene1g) for their inspiring work on [React TextArea Autosize](https://github.com/andreypopp/react-textarea-autosize) which kind of ignited the idea of Belle.
156 |
157 | Thanks to Christian Steiner from [http://www.cropd.at/](http://cropd.at) for creating the logo and helping out with the design.
158 |
159 | Special thanks to [Jyoti Puri](https://github.com/jpuri) for the tremendous amount of work she put into this endeavor.
160 |
161 | ## License
162 |
163 | MIT
164 |
--------------------------------------------------------------------------------
/docs/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/.DS_Store
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | Belle Documentation
2 | ================
3 |
4 | ## Install
5 |
6 | ```
7 | npm install
8 | ```
9 |
10 | Make sure you ran `npm install` in the root folder of Belle.
11 |
12 | ## Run
13 |
14 | ```
15 | npm start
16 | ```
17 |
18 | The app will run with hot reloading on `http://localhost:3000`.
19 |
--------------------------------------------------------------------------------
/docs/components/About.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export default class FuturePlans extends Component {
4 |
5 | render() {
6 | return (
7 |
Discussion or need help?
8 |
9 |
10 | Join us at the #belle channel of the Reactiflux Slack community or our Gitter room .
11 |
12 |
13 |
14 | In addition you can ask the community by posting your question to StackOverflow with the belle tag .
15 |
16 |
17 |
Special Thanks
18 |
19 |
20 | Thanks to Andrey Popp & Eugene for their inspiring work on
21 | React TextArea Autosize which
22 | kind of ignited the idea of Belle.
23 |
24 |
25 | Thanks to Christian Steiner from cropd.at for creating
26 | the logo and helping out with the design.
27 |
28 |
29 |
30 | Special thanks to Jyoti Puri for the tremendous amount of work she put into this endeavor.
31 |
32 |
33 |
Future Plans
34 |
35 |
Components to add: Dateformatter, Datepicker, Tooltip, Popover, Modal, Navigation Menu, NumberInput, EmailInput, Anchor, DropZone
36 |
37 |
);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/docs/components/Code.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import ReactDOM from 'react-dom';
4 | import highlightJs from 'highlight.js';
5 | import { isEqual, omit, extend } from 'underscore';
6 |
7 | const defaultStyle = {
8 | fontFamily: 'Consolas, "Liberation Mono", Menlo, Courier, monospace',
9 | background: '#F5F5F5',
10 | whiteSpace: 'pre-wrap',
11 | padding: 10,
12 | };
13 |
14 | export default class Code extends Component {
15 |
16 | constructor(properties) {
17 | super(properties);
18 | this.childProperties = omit(properties, 'style');
19 | }
20 |
21 | componentDidMount() {
22 | highlightJs.highlightBlock(ReactDOM.findDOMNode(this));
23 | }
24 |
25 | componentWillReceiveProps(properties) {
26 | this.childProperties = omit(properties, 'style');
27 | }
28 |
29 | shouldComponentUpdate(nextProps) {
30 | if (nextProps.value === this.props.value && isEqual(nextProps.style, this.props.style)) {
31 | return false;
32 | }
33 |
34 | return true;
35 | }
36 |
37 | componentDidUpdate() {
38 | highlightJs.highlightBlock(ReactDOM.findDOMNode(this));
39 | }
40 |
41 | render() {
42 | const style = extend({}, defaultStyle, this.props.style);
43 |
44 | return (
45 |
46 | { this.props.value }
47 |
48 | );
49 | }
50 | }
51 |
52 | Code.propTypes = {
53 | value: PropTypes.string.isRequired,
54 | style: PropTypes.object,
55 | };
56 |
--------------------------------------------------------------------------------
/docs/components/Column.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { omit } from 'underscore';
4 |
5 | export default React.createClass({
6 |
7 | propTypes: {
8 | smallScreenStyle: PropTypes.object.isRequired,
9 | mediumScreenStyle: PropTypes.object.isRequired,
10 | children: PropTypes.any,
11 | },
12 |
13 | contextTypes: {
14 | viewport: PropTypes.any,
15 | },
16 |
17 | getInitialState() {
18 | this.childProperties = omit(this.props, [
19 | 'style',
20 | 'smallScreenStyle',
21 | 'mediumScreenStyle',
22 | ]);
23 | return {};
24 | },
25 |
26 | componentWillReceiveProps(properties) {
27 | this.childProperties = omit(properties, [
28 | 'style',
29 | 'smallScreenStyle',
30 | 'mediumScreenStyle',
31 | ]);
32 | },
33 |
34 | render() {
35 | const style = (this.context.viewport.width <= 480) ? this.props.smallScreenStyle : this.props.mediumScreenStyle;
36 |
37 | return (
38 |
39 | { this.props.children }
40 |
41 | );
42 | },
43 | });
44 |
--------------------------------------------------------------------------------
/docs/components/FormComponents.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Code from './Code';
3 |
4 | const controlledComponentCodeOne = `
5 | render: function() {
6 | return ;
7 | }`;
8 |
9 | const controlledComponentCodeTwo = `
10 | getInitialState: function() {
11 | return {value: 'Hello!'};
12 | },
13 | handleChange: function(obj) {
14 | this.setState({value: obj.value});
15 | },
16 | render: function() {
17 | var value = this.state.value;
18 | return ;
19 | }`;
20 |
21 | const uncontrolledComponentCode = `
22 | render: function() {
23 | return ;
24 | }`;
25 |
26 | const reactLinkCode = `var WithLink = React.createClass({
27 | mixins: [LinkedStateMixin],
28 | getInitialState: function() {
29 | return {message: 'Hello!'};
30 | },
31 | render: function() {
32 | return ;
33 | }
34 | });`;
35 |
36 | export default class FormComponents extends Component {
37 |
38 | render() {
39 | return (
40 |
Form Components
41 |
42 |
43 | Belle has many Form Components: ComboBox, Select, Rating, TextInput and Toggle.
44 | They differ from other components because they can be mutated via user interactions.
45 |
46 |
47 |
onUpdate Prop
48 |
49 |
50 | Form components allow listening for changes by setting a callback to the onUpdate property.
51 | The callback receives an object as parameter containing a field 'value' which is the value entered by the user.
52 |
53 |
54 |
Controlled Components
55 |
56 |
57 | An Form Component with value set is a controlled component. In a controlled component,
58 | the value of the rendered element will always reflect the value prop. For example:
59 |
60 |
61 |
62 |
63 |
64 | This will render a TextInput that always has a value of Hello!. Any user input will have no effect on the rendered element
65 | because React has declared the value to be Hello!. If you wanted to update the value in response to user input, you
66 | could use the onUpdate event:
67 |
68 |
69 |
70 |
71 |
This pattern makes it easy to implement interfaces that respond to or validate user interactions.
72 |
73 |
Uncontrolled Components
74 |
75 |
A component that does not supply a value is an uncontrolled component.
76 | In an uncontrolled component, the value of the rendered element will reflect the user's input. For example:
77 |
78 |
79 |
80 |
81 |
82 | This will render an input that starts off with an empty value. Any user input will be immediately reflected by the rendered element.
83 | If you wanted to listen to updates to the value, you could use the onUpdate event just like you can with controlled components.
84 | If you want to initialize the component with a non-empty value, you can supply a defaultValue prop.
85 |
86 |
87 |
Two-Way Binding Helpers
88 |
89 |
Two-way binding is implicitly enforcing that some value in the DOM is always consistent with some React state.
90 | Like React form components Belle form components also provide ReactLink: its syntactic sugar for setting up the common
91 | data flow loop pattern, or "linking" some data source to React state.
92 |
93 |
94 |
95 |
96 |
Note:
97 | ReactLink is just a thin wrapper and convention around the onUpdate/setState() pattern. It doesn't fundamentally change how data
98 | flows in your React application.
99 |
100 |
References
101 |
102 | React Forms
103 |
104 |
105 | Two-Way Binding Helpers
106 |
107 |
108 |
);
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/docs/components/GettingStarted.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Code from './Code';
3 |
4 | const installCommand = 'npm install belle';
5 |
6 | const usageExampleHtml = `
7 |
8 |
9 |
10 |
11 |
12 |
13 |
17 |
18 |
19 |
20 | `;
21 |
22 | const usageExampleJavaScript = `var React = require('react');
23 | var belle = require('belle');
24 | var TextInput = belle.TextInput;
25 |
26 | var App = React.createClass({
27 |
28 | render: function() {
29 | return (
30 |
31 |
32 |
33 | );
34 | }
35 | });
36 |
37 | React.render( , document.getElementById('react-root'));`;
38 |
39 | export default class GettingStarted extends Component {
40 |
41 | render() {
42 | return (
43 |
Getting Started
44 |
45 |
46 | Belle is available as npm package. Once you have npm you can install Belle in your project folder with:
47 |
48 |
49 |
50 |
51 |
Import & use Belle Components
52 |
53 |
54 | We recommend you to get started with React first. Once you have a simple application setup you can import any Belle component and use it right away. No stylesheets, font or any other prerequisite needed.
55 |
56 |
57 |
58 |
59 |
60 |
61 |
Discussion or need help?
62 |
63 |
64 | Join us at the #belle channel of the Reactiflux Slack community or our Gitter room .
65 |
66 |
67 |
);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/docs/components/Home.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ThemeSwitch from '../theme/ThemeSwitch';
3 | import GettingStarted from './GettingStarted';
4 | import { Card } from 'belle';
5 |
6 | export default class Home extends Component {
7 |
8 | render() {
9 | return (
10 |
11 |
12 | Belle provides you with a set of React components like Toggle, ComboBox, Rating, TextInput, Button, Card, Select and soon many more.
13 |
14 |
15 |
16 | All of the components are optimized to work both on mobile & desktop devices . The styles are highly customizable on two levels. You can configure the base styles of all the components as well as modify each one of them individually .
17 |
18 |
19 |
Overview
20 |
21 |
22 |
26 |
27 |
28 |
29 |
33 | “
34 | This is so good. I like the effort you put into tweaking the UX.
35 | ”
42 |
43 |
44 |
50 |
Christopher Chedeau (Vjeux)
53 |
React Core Team
56 |
57 |
58 |
59 |
60 |
Browser Support
61 |
62 | Chrome (mobile and desktop)
63 | Safari (mobile and desktop)
64 | Firefox
65 | Internet Explorer 9, 10, 11
66 |
67 |
68 |
Theme Support
69 |
70 | As mentioned above the styles are highly configurable and for demonstration purposes you can view this website with the Boostrap3 theme.
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/docs/components/Philosophy.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export default class Philosophy extends Component {
4 |
5 | render() {
6 | return (
7 |
Philosophy
8 |
9 |
“Great UX starts with a good UI”
10 |
11 |
12 | With Belle I'm aiming to provide a library that follows a couple well established
13 | principles. While some of them might not be consider best practice today
14 | We believe they will become some day in the near future.
15 |
16 |
17 |
This is an early draft & any improvement you can think of is very welcome.
18 |
19 |
Principles
20 |
21 |
Easy to use
22 |
23 |
24 | The most important attribute of any great experience is that there was no hassle to achieve
25 | the initial goals.
26 |
27 |
28 |
Consistent Behavior
29 |
30 |
31 | Every object someone interacts with has it's own little language. This language
32 | must be learned by everyone getting in touch with a new set of object. In order
33 | to provide a great experience the amount to learn should be reduced to a minimum.
34 |
35 |
36 |
37 | There are two major personalities to target with Belle. One is the developer.
38 | For him/her APIs should be provided in a consistent way through all components.
39 | The other and more important is the users of the components. Every color,
40 | animation or behavior should be aligned with the other components to provide
41 | a great experience.
42 |
43 |
44 |
Encapsulate Styles
45 |
46 |
47 | There is no reason while a style designed one specific element should affect
48 | others. In CSS styles are often defined by overwriting previous ones and introducing
49 | deeper and deeper nesting. Once nesting is introduced resolution of styles for one
50 | specific element is not a trivial task anymore. Due this managing CSS dependencies
51 | is hard. It is hard to predict how an application looks like after updating or
52 | removing dependency.
53 |
54 |
55 |
56 | That's why with Belle styles should apply to the components themselves in the DOM.
57 | By doing so the visual appearance and business logic are combined in location.
58 |
59 |
60 |
Every Interaction is followed up with Feedback
61 |
62 |
63 | Let people know how their behavior affects the system. It assures them that
64 | their input was acknowledged which provides them with a feeling of control over
65 | the system.
66 |
67 |
68 |
High Performance
69 |
70 |
71 | The user should see affects of his actions instantly. Any delay can cause confusion
72 | and frustration. While instant certainly is not always possible Belle strives to
73 | provide an experience close to instant.
74 |
75 |
76 |
);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/docs/components/components/CardDocumentation.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Card } from 'belle';
3 | import Code from '../Code';
4 |
5 | const basicCodeExample = `
6 |
7 | Add any content here like paragraphs, images or other components …
8 | `;
9 |
10 | const imageCodeExample = `
11 |
14 |
16 | `;
17 |
18 | export default class CardDocumentation extends Component {
19 |
20 | render() {
21 | return (
22 |
23 |
Card
24 |
25 |
26 | Add any content here like paragraphs, images or other components …
27 |
28 |
29 |
30 |
31 |
32 | Note : The card is designed to work on non-white areas. To provide a
33 | nice appearance on white areas please change the box-shadow or borders.
34 |
35 |
36 |
Properties
37 |
38 |
39 | Any property valid for a HTML div like
40 | style, id, className, …
41 |
42 |
43 |
More Examples
44 |
45 |
Card with a full-width image
46 |
47 |
52 |
56 |
57 |
58 |
59 |
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/docs/components/components/ChoiceDocumentation.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Choice, Toggle } from 'belle';
3 | import Code from '../Code';
4 | import { propertyNameStyle, propertyDescriptionStyle } from '../../style';
5 |
6 | const choiceCodeExample = `
7 |
8 | On
9 | Off
10 | `;
11 |
12 | export default class ChoiceDocumentation extends Component {
13 |
14 | render() {
15 | return (
16 |
17 |
Choice
18 |
19 |
20 | On
21 | Off
22 |
23 |
24 |
25 |
26 |
Properties
27 |
28 |
29 |
30 |
31 |
32 | value
33 |
34 |
35 |
36 |
37 |
38 | Boolean
39 |
40 | required
41 |
42 | The value to be set in case this Choice is set.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Any property valid for a HTML div like
51 | style, id, className, …
52 |
53 |
54 |
);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/docs/components/components/OptionDocumentation.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Option, Select } from 'belle';
3 | import Code from '../Code';
4 | import { propertyNameStyle, propertyDescriptionStyle } from '../../style';
5 |
6 | const basicCodeExample = `
7 |
8 | Berlin
9 | Tokyo
10 | Vienna
11 | `;
12 |
13 | export default class OptionDocumentation extends Component {
14 |
15 | render() {
16 | return (
17 |
18 |
Option
19 |
20 |
21 | Berlin
22 | Tokyo
23 | Vienna
24 |
25 |
26 |
27 |
28 |
Properties
29 |
30 |
31 |
32 |
33 |
34 | value
35 |
36 |
37 |
38 |
39 |
40 | String, Boolean, Number
41 |
42 | required
43 |
44 | The value to be set in case this Option is selected. The value must be
45 | unique for all Options within one Select. It can be of type Boolean, String or
46 | Number.
47 |
48 |
49 |
50 |
51 |
52 |
53 | hoverStyle
54 |
55 |
56 |
57 |
58 |
59 | Object
60 |
61 | optional
62 |
63 |
64 | Works like React's built-in style property.
65 | Becomes active once the user hovers over the Option with the cursor or focus
66 | on it by leveragin the key board inputs like Arrow-down or Arrow-up.
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | Any property valid for a HTML div like
75 | style, id, className, …
76 |
77 |
78 |
);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/docs/components/components/PlaceholderDocumentation.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Option, Placeholder, Select } from 'belle';
3 | import Code from '../Code';
4 |
5 | const basicCodeExample = `
6 |
7 | Choose a City
8 | Tokyo
9 | Vienna
10 | `;
11 |
12 | export default class PlaceholderDocumentation extends Component {
13 |
14 | render() {
15 | return (
16 |
17 |
Placeholder
18 |
19 |
20 | Choose a City
21 | Tokyo
22 | Vienna
23 |
24 |
25 |
26 |
27 |
Properties
28 |
29 |
30 | Any property valid for a HTML div like
31 | style, id, className, …
32 |
33 |
34 |
);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/docs/components/components/SeparatorDocumentation.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Option, Select, Separator } from 'belle';
3 | import Code from '../Code';
4 |
5 | const basicCodeExample = `
6 |
7 | America
8 | San Francisco
9 | Vancouver
10 | Asia
11 | Hong Kong
12 | Tokyo
13 | Europe
14 | Berlin
15 | Istanbul
16 | Rome
17 | Vienna
18 | `;
19 |
20 | export default class SeparatorDocumentation extends Component {
21 |
22 | render() {
23 | return (
24 |
25 |
Separator
26 |
27 |
28 | America
29 | San Francisco
30 | Vancouver
31 | Asia
32 | Hong Kong
33 | Tokyo
34 | Europe
35 | Berlin
36 | Istanbul
37 | Rome
38 | Vienna
39 |
40 |
41 |
42 |
43 |
Properties
44 |
45 |
46 | Any property valid for a HTML div like
47 | style, id, className, …
48 |
49 |
50 |
);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/docs/components/components/SpinnerDocumentation.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button, Card, Spinner } from 'belle';
3 | import Code from '../Code';
4 | import { propertyNameStyle, propertyDescriptionStyle } from '../../style';
5 |
6 | const basicCodeExample = `
7 | `;
8 |
9 | const buttonCodeExample = `
10 |
11 | Saving
12 |
13 |
14 |
15 | Saving
16 | `;
17 |
18 | const cardCodeExample = `
19 |
24 | Loading
25 | `;
26 |
27 | const SpinnerDocumentation = () => (
28 |
29 |
Spinner
30 |
31 |
32 |
33 |
34 |
35 |
Properties
36 |
37 |
38 |
39 |
40 | characterStyle
41 |
42 |
43 |
44 |
45 |
46 | Object
47 |
48 | optional
49 |
50 |
51 | The property can be used to specify styling for the spans wrapping
52 | the dots. Behaves like Reacts built-in style property.
53 |
54 |
55 |
56 |
57 |
58 |
59 | Any property valid for a HTML div like
60 | style, id, className, …
61 |
62 |
63 |
More Examples
64 |
65 |
Button while loading
66 |
67 |
68 | Saving
69 |
70 |
71 |
72 | Saving
73 |
74 |
75 |
76 |
77 |
Card with a loading indicator
78 |
79 |
85 | Loading
86 |
87 |
88 |
89 |
90 | );
91 |
92 | export default SpinnerDocumentation;
93 |
--------------------------------------------------------------------------------
/docs/components/guides/IntroducingBelle.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Card, Toggle } from 'belle';
3 | import Code from '../Code';
4 |
5 | const basicToggleExample = `
6 | `;
7 |
8 | const selectExample = `
9 |
12 | Berlin
13 | Hong Kong
14 | Istanbul
15 | Rome
16 | San Francisco
17 | Tokyo
18 | Vienna
19 | `;
20 |
21 | export default class Why extends Component {
22 |
23 | render() {
24 | return (
25 |
Nik Graf, 15th July 2015
26 |
27 |
Introducing Belle
28 |
29 |
30 |
34 |
35 |
36 |
37 | Belle is a set of React components including Toggle, ComboBox, Rating, TextInput, Button, Card & Select. Many more like DatePicker, NumberInput, DropZone & Menu will come soon. As of today we hit version 1.0.0 :)
38 |
39 |
40 |
Wait, but why yet another component library?
41 |
42 |
43 | The web platform is a fantastic environment. Still it has certain limitations that are holding you back as a developer. React opened up new opportunities and I finally had tools in my hand to make the kind of UI components I always wanted to.
44 |
45 |
Don't get me wrong. There are a ton of really great UI libraries out there like jQuery UI, Bootstrap, Polymer, you name it. They are super useful and to me often a source for inspiration. Through them I learned a lot about the details to take care of when developing user interface elements. Nevertheless always some feature was missing or the UX not as good as we imagined it. That’s why Jyoti & I started to research and explore building our own components. Quickly it became clear others might benefit from sharing our lessons learned and our endeavour shifted to creating Belle - a set of configurable components with all these features included:
46 |
47 |
48 | Encapsulated components
49 | Mobile support built-in
50 | ARIA Support
51 | Customizable styles / themes (right now there is Belle & Bootstrap 3)
52 | Advanced localized styling for each individual component
53 |
54 |
55 |
56 | With Belle we aim to provide the best possible UX while making the components highly configurable to allow users applying their own theme. For demonstration purposes Belle comes with two themes (Belle, Bootstrap3) and we aim to add more like Elemental UI or Material Design soon.
57 |
58 |
59 |
60 |
65 | “
66 | This is so good. I like the effort you put into tweaking the UX.
67 | ”
74 |
75 |
76 |
82 |
85 | Christopher Chedeau (Vjeux)
86 |
87 |
90 | React Core Team
91 |
92 |
93 |
94 |
95 |
96 |
Let’s have a look at two of the components and walk you through some of the details we took care of.
97 |
98 |
Toggle
99 |
100 |
The handle can be grabbed and dragged on mobile as well as on desktop devices. In addition a simple tap or click also initiates switching the state. As like with the native iOS Toggle it is possible to slightly leave the bounding area while continuing to slide. In order to prevent shaky page behaviour and delayed animations is scrolling is prevented while the Toggle is active.
101 |
102 |
103 |
104 | VIDEO
105 |
106 |
107 |
108 |
109 |
110 |
With custom Styles
111 |
112 |
113 |
114 |
115 |
Bootstrap Styles
116 |
117 |
118 |
119 |
120 | Try it yourself here:
121 |
122 |
123 |
124 |
125 |
126 |
127 | Let’s look at another component.
128 |
129 |
130 |
Select
131 |
132 |
133 | While the HTML native select-tag is a great component it’s styling capabilities are very limited. Adding images is not possible. That’s why we built a Select component allowing you to modify its appearance.
134 |
135 |
136 |
137 |
138 |
Select on Mobile
139 |
140 |
141 | As promised before the Select component even works well on mobile devices. Even if scrolling is involved. You will notice that the menu will always positioned in a way that the focused Option is always visible and right above the selected one.
142 |
143 |
144 |
145 | VIDEO
146 |
147 |
148 |
149 |
150 |
151 | I hope Belle caught your interest. In case you want to give it a try please checkout our example projects like react-starter-with-belle or react-server-example . We look forward to your feedback. Feel free to reach out to us via Twitter @nikgraf &
152 | @jyopur .
153 |
154 |
155 |
159 |
162 |
163 |
164 |
);
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/docs/components/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { IndexRoute, Route } from 'react-router';
3 |
4 | import Base from './Base';
5 | import ComboBoxDocumentation from './components/ComboBoxDocumentation';
6 | import DatePickerDocumentation from './components/DatePickerDocumentation';
7 | import ButtonDocumentation from './components/ButtonDocumentation';
8 | import CardDocumentation from './components/CardDocumentation';
9 | import ChoiceDocumentation from './components/ChoiceDocumentation';
10 | import Configuration from './Configuration';
11 | import GettingStarted from './GettingStarted';
12 | import About from './About';
13 | import Home from './Home';
14 | import OptionDocumentation from './components/OptionDocumentation';
15 | import Philosophy from './Philosophy';
16 | import PlaceholderDocumentation from './components/PlaceholderDocumentation';
17 | import RatingDocumentation from './components/RatingDocumentation';
18 | import SelectDocumentation from './components/SelectDocumentation';
19 | import SeparatorDocumentation from './components/SeparatorDocumentation';
20 | import SpinnerDocumentation from './components/SpinnerDocumentation';
21 | import TextInputDocumentation from './components/TextInputDocumentation';
22 | import ToggleDocumentation from './components/ToggleDocumentation';
23 | import FormComponents from './FormComponents';
24 | import IntroducingBelleGuide from './guides/IntroducingBelle';
25 |
26 | const routes = (
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | );
50 |
51 | module.exports = routes;
52 |
--------------------------------------------------------------------------------
/docs/css/style.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | }
4 |
5 | *, *:before, *:after {
6 | box-sizing: inherit;
7 | }
8 |
9 | body {
10 | font-size: 15px;
11 | line-height: 1.6;
12 | font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
13 | color: #444;
14 | background: #F7F7F7;
15 | }
16 |
17 | h3 {
18 | margin-top: 60px;
19 | }
20 |
21 | a, a:visited {
22 | color: #888;
23 | text-decoration: none;
24 | }
25 |
26 | a:hover {
27 | color: #aaa;
28 | }
29 |
30 | a:active {
31 | color: #444;
32 | }
33 |
34 | .navigation a, .navigation a:visited {
35 | color: #0A202D;
36 | }
37 |
38 | .navigation a:hover {
39 | color: #34A1CA;
40 | }
41 |
42 | .navigation a:active {
43 | color: #444;
44 | }
45 |
--------------------------------------------------------------------------------
/docs/icon/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/docs/icon/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/docs/icon/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/docs/icon/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/docs/icon/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/docs/icon/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/docs/icon/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/docs/icon/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/docs/icon/favicon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/favicon-128.png
--------------------------------------------------------------------------------
/docs/icon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/icon/favicon-196x196.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/favicon-196x196.png
--------------------------------------------------------------------------------
/docs/icon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/favicon-32x32.png
--------------------------------------------------------------------------------
/docs/icon/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/favicon-96x96.png
--------------------------------------------------------------------------------
/docs/icon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/favicon.ico
--------------------------------------------------------------------------------
/docs/icon/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/mstile-144x144.png
--------------------------------------------------------------------------------
/docs/icon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/mstile-150x150.png
--------------------------------------------------------------------------------
/docs/icon/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/mstile-310x150.png
--------------------------------------------------------------------------------
/docs/icon/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/mstile-310x310.png
--------------------------------------------------------------------------------
/docs/icon/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/icon/mstile-70x70.png
--------------------------------------------------------------------------------
/docs/images/abyssinian.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/abyssinian.jpg
--------------------------------------------------------------------------------
/docs/images/albatross.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/albatross.jpg
--------------------------------------------------------------------------------
/docs/images/angelfish.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/angelfish.jpg
--------------------------------------------------------------------------------
/docs/images/ant.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/ant.jpg
--------------------------------------------------------------------------------
/docs/images/antelope.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/antelope.jpg
--------------------------------------------------------------------------------
/docs/images/asian_elephant.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/asian_elephant.jpg
--------------------------------------------------------------------------------
/docs/images/belle_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/belle_logo.png
--------------------------------------------------------------------------------
/docs/images/croatia_100.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/croatia_100.jpg
--------------------------------------------------------------------------------
/docs/images/croatia_original.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/croatia_original.jpg
--------------------------------------------------------------------------------
/docs/images/ngorongoro_caldera_small.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/ngorongoro_caldera_small.jpg
--------------------------------------------------------------------------------
/docs/images/overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/overview.png
--------------------------------------------------------------------------------
/docs/images/santorini_100.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/santorini_100.jpg
--------------------------------------------------------------------------------
/docs/images/santorini_original.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/santorini_original.jpg
--------------------------------------------------------------------------------
/docs/images/vjeux.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/vjeux.jpeg
--------------------------------------------------------------------------------
/docs/images/yosemite_100.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/yosemite_100.jpg
--------------------------------------------------------------------------------
/docs/images/yosemite_original.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgraf/belle/e0f561d6ad704b5e3d66a38752de29f938b065af/docs/images/yosemite_original.jpg
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Belle - Configurable React Components with great UX
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/docs/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import routes from './components/routes';
4 | import { Router, hashHistory } from 'react-router';
5 | import initializeTheme from './theme/initialize';
6 |
7 | // export for http://fb.me/react-devtools
8 | window.React = React;
9 |
10 | initializeTheme();
11 | const rootComponent = ;
12 |
13 | ReactDOM.render(rootComponent, document.getElementById('react'));
14 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "belleDocumentation",
3 | "version": "3.0.2",
4 | "description": "Docuemtation for Belle",
5 | "author": {
6 | "name": "Nik Graf",
7 | "email": "nik@nikgraf.com",
8 | "url": "http://www.nikgraf.com"
9 | },
10 | "main": "js/App.jsx",
11 | "repository": {
12 | "type": "git",
13 | "url": "http://github.com/nikgraf/belle.git"
14 | },
15 | "dependencies": {
16 | "highlight.js": "=9.2.0",
17 | "history": "^2.0.1",
18 | "react": "^=15.4.1",
19 | "react-addons-linked-state-mixin": "^=15.4.1",
20 | "react-dom": "^=15.4.1",
21 | "react-router": "=v2.5.2",
22 | "uglify-js": "=2.6.2",
23 | "underscore": "=1.8.3"
24 | },
25 | "devDependencies": {
26 | "babel-core": "^6.7.4",
27 | "babel-loader": "^6.2.4",
28 | "eslint": "^2.5.3",
29 | "eslint-config-airbnb": "^6.2.0",
30 | "eslint-plugin-react": "^4.2.3",
31 | "express": "^4.13.4",
32 | "node-libs-browser": "^1.0.0",
33 | "webpack": "^1.12.14"
34 | },
35 | "scripts": {
36 | "start": "node server.js",
37 | "build": "NODE_ENV=production webpack -p --config webpack.config.production.js",
38 | "postinstall": "cp node_modules/highlight.js/styles/googlecode.css css/googlecode.css"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/docs/server.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-var, no-console */
2 |
3 | var path = require('path');
4 | var express = require('express');
5 | var webpack = require('webpack');
6 | var config = require('./webpack.config');
7 | var app = express();
8 | var compiler = webpack(config);
9 |
10 | app.use('/css', express.static('css'));
11 | app.use('/icon', express.static('icon'));
12 | app.use('/images', express.static('images'));
13 |
14 | app.use(require('webpack-dev-middleware')(compiler, {
15 | noInfo: true,
16 | publicPath: config.output.publicPath,
17 | }));
18 |
19 | app.use(require('webpack-hot-middleware')(compiler));
20 |
21 | app.get('*', (req, res) => {
22 | res.sendFile(path.join(__dirname, 'index.html'));
23 | });
24 |
25 | app.listen(3000, 'localhost', (err) => {
26 | if (err) {
27 | console.log(err);
28 | return;
29 | }
30 |
31 | console.log('Listening at http://localhost:3000');
32 | });
33 |
--------------------------------------------------------------------------------
/docs/style.js:
--------------------------------------------------------------------------------
1 | const style = {
2 | propertyNameStyle: {
3 | padding: '0 20px 0 0',
4 | textAlign: 'left',
5 | verticalAlign: 'top',
6 | color: 'grey',
7 | },
8 |
9 | propertyDescriptionStyle: {
10 | padding: 0,
11 | textAlign: 'left',
12 | verticalAlign: 'top',
13 | paddingBottom: 30,
14 | },
15 | };
16 |
17 | export default style;
18 |
--------------------------------------------------------------------------------
/docs/theme/ThemeSwitch.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Button } from 'belle';
4 |
5 | export default React.createClass({
6 |
7 | contextTypes: {
8 | location: PropTypes.object,
9 | },
10 |
11 | _onSwitchTheme(name) {
12 | window.location.hash = `${this.context.location.pathname}\?theme=${name}`;
13 | window.location.reload();
14 | },
15 |
16 | render() {
17 | // TODO
18 | const currentTheme = undefined;
19 |
20 | return (
21 |
22 |
23 | You choose between one of the themes below
24 |
25 |
26 |
27 | this._onSwitchTheme('belle') }
29 | primary
30 | disabled={ currentTheme === 'belle' || currentTheme === undefined }
31 | >
32 | Belle
33 |
34 | this._onSwitchTheme('bootstrap') }
36 | style={{ marginLeft: 10 }}
37 | primary
38 | disabled={ currentTheme === 'bootstrap' }
39 | >
40 | Bootstrap
41 |
42 | this._onSwitchTheme('belle-with-default-focus') }
44 | style={{ marginLeft: 10 }}
45 | primary
46 | disabled={ currentTheme === 'belle-with-default-focus' }
47 | >
48 | Belle with default focus behaviour
49 |
50 |
51 |
52 | );
53 | },
54 | });
55 |
--------------------------------------------------------------------------------
/docs/theme/belle-with-classic-focus.js:
--------------------------------------------------------------------------------
1 | const belleWithClassicFocus = {
2 |
3 | config: {
4 | button: {
5 | preventFocusStyleForTouchAndClick: false,
6 | },
7 | rating: {
8 | preventFocusStyleForTouchAndClick: false,
9 | },
10 | toggle: {
11 | preventFocusStyleForTouchAndClick: false,
12 | },
13 | datePicker: {
14 | preventFocusStyleForTouchAndClick: false,
15 | },
16 | },
17 |
18 | style: {
19 | button: {
20 | focusStyle: {
21 | boxShadow: '0 0 0 3px rgba(140, 224, 255, 0.6)',
22 | outline: 0,
23 | },
24 | primaryFocusStyle: {
25 | boxShadow: '0 0 0 3px rgba(140, 224, 255, 0.6)',
26 | outline: 0,
27 | },
28 | },
29 | rating: {
30 | focusStyle: {
31 | boxShadow: '0 0 0 3px rgba(140, 224, 255, 0.6)',
32 | outline: 0,
33 | borderRadius: 3,
34 | },
35 | },
36 | toggle: {
37 | focusStyle: {
38 | boxShadow: '0 0 0 3px rgba(140, 224, 255, 0.6)',
39 | outline: 0,
40 | },
41 | },
42 | datePicker: {
43 | focusStyle: {
44 | boxShadow: '0 0 0 3px rgba(140, 224, 255, 0.6)',
45 | outline: 0,
46 | },
47 | },
48 | },
49 |
50 | };
51 |
52 | export default belleWithClassicFocus;
53 |
--------------------------------------------------------------------------------
/docs/theme/initialize.js:
--------------------------------------------------------------------------------
1 | import belle from 'belle';
2 | import bootstrap3Theme from './bootstrap-3';
3 | import belleWithClassicFocusTheme from './belle-with-classic-focus';
4 |
5 | /**
6 | * Get a URL query parameter from the hash.
7 | */
8 | export function getParameterByName(name) {
9 | const parsedName = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
10 | const regex = new RegExp(`[\\?&]${parsedName}=([^]*)`);
11 | const results = regex.exec(location.hash);
12 | return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
13 | }
14 |
15 | /**
16 | * Updates the deepest structure while keeping the original reference of the outer objects.
17 | */
18 | function updateStructure(targetObject, object) {
19 | for (const componentName in object) {
20 | if (object.hasOwnProperty(componentName)) {
21 | for (const styleName in object[componentName]) {
22 | if (object[componentName].hasOwnProperty(styleName)) {
23 | targetObject[componentName][styleName] = object[componentName][styleName]; // eslint-disable-line no-param-reassign
24 | }
25 | }
26 | }
27 | }
28 | }
29 |
30 | function initializeTheme() {
31 | const theme = getParameterByName('theme');
32 |
33 | if (theme === 'bootstrap') {
34 | updateStructure(belle.style, bootstrap3Theme.style);
35 | updateStructure(belle.config, bootstrap3Theme.config);
36 | } else if (theme === 'belle-with-default-focus') {
37 | updateStructure(belle.style, belleWithClassicFocusTheme.style);
38 | updateStructure(belle.config, belleWithClassicFocusTheme.config);
39 | }
40 | }
41 |
42 | export default initializeTheme;
43 |
--------------------------------------------------------------------------------
/docs/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path'); // eslint-disable-line no-var
2 | var webpack = require('webpack'); // eslint-disable-line no-var
3 |
4 | module.exports = {
5 | devtool: 'eval',
6 | entry: [
7 | 'webpack-hot-middleware/client',
8 | './index',
9 | ],
10 | output: {
11 | path: path.join(__dirname, 'dist'),
12 | filename: 'bundle.js',
13 | publicPath: '/static/',
14 | },
15 | plugins: [
16 | new webpack.HotModuleReplacementPlugin(),
17 | new webpack.NoErrorsPlugin(),
18 | ],
19 | resolve: {
20 | alias: {
21 | belle: path.join(__dirname, '..', 'src'),
22 | react: path.join(__dirname, 'node_modules', 'react'),
23 | },
24 | extensions: ['', '.js'],
25 | },
26 | module: {
27 | loaders: [
28 | {
29 | test: /\.js$/,
30 | loaders: ['babel'],
31 | exclude: /node_modules/,
32 | include: __dirname,
33 | }, {
34 | test: /\.js$/,
35 | loaders: ['babel'],
36 | include: path.join(__dirname, '..', 'src'),
37 | },
38 | ],
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/docs/webpack.config.production.js:
--------------------------------------------------------------------------------
1 | var path = require('path'); // eslint-disable-line no-var
2 | var webpack = require('webpack'); // eslint-disable-line no-var
3 |
4 | module.exports = {
5 | entry: [
6 | './index',
7 | ],
8 | output: {
9 | path: path.join(__dirname, 'static'),
10 | filename: 'bundle.js',
11 | publicPath: '/static/',
12 | },
13 | resolve: {
14 | alias: {
15 | belle: path.join(__dirname, '..', 'src'),
16 | react: path.join(__dirname, 'node_modules', 'react'),
17 | },
18 | extensions: ['', '.js'],
19 | },
20 | plugins: [
21 | new webpack.optimize.UglifyJsPlugin({ minimize: true }),
22 | new webpack.DefinePlugin({
23 | 'process.env': {
24 | NODE_ENV: JSON.stringify('production'),
25 | },
26 | }),
27 | ],
28 | module: {
29 | loaders: [
30 | {
31 | test: /\.js$/,
32 | loaders: ['babel'],
33 | exclude: /node_modules/,
34 | include: __dirname,
35 | }, {
36 | test: /\.js$/,
37 | loaders: ['babel'],
38 | include: path.join(__dirname, '..', 'src'),
39 | },
40 | ],
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | Examples Playground for Development
2 | ================
3 |
4 | ## Install
5 |
6 | ```
7 | npm install
8 | ```
9 |
10 | Make sure you ran `npm install` in the root folder of Belle.
11 |
12 | ## Run
13 |
14 | ```
15 | npm start
16 | ```
17 |
18 | The app will run with hot reloading on `http://localhost:3000`.
19 |
--------------------------------------------------------------------------------
/examples/components/ButtonPlayground.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button, Card } from 'belle';
3 |
4 | export default React.createClass({
5 |
6 | render() {
7 | return (
8 |
9 | Button
10 |
11 | Default Button
12 | Press me …
13 |
14 | Disabled Default Button
15 | Press me …
16 |
17 | Default Button
18 | Press me …
19 |
20 | Disabled Button
21 | Press me …
22 |
23 | Primary Button
24 | Primary Button
25 |
26 | Disabled Primary Button
27 | Press me …
28 |
29 | Colored Buttons
30 | Primary Button
31 |
32 | Primary Button
33 |
34 | Primary Button
35 |
36 | );
37 | },
38 | });
39 |
--------------------------------------------------------------------------------
/examples/components/CardPlayground.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Card } from 'belle';
3 |
4 | export default React.createClass({
5 |
6 | render() {
7 | return (
8 |
9 |
Card
10 |
11 |
Looks nice!
12 |
13 |
What about another font color?
14 |
15 |
16 | Looks nice!
17 | Even with multiple elements passed
18 |
19 |
20 |
21 | );
22 | },
23 | });
24 |
--------------------------------------------------------------------------------
/examples/components/ComboBoxPlayground.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import React from 'react';
4 | import { Card, ComboBox, Option } from 'belle';
5 |
6 | export default React.createClass({
7 |
8 | getInitialState() {
9 | return { comboValue: 'te' };
10 | },
11 |
12 | _handleChange(newValue) {
13 | this.setState({ comboValue: newValue });
14 | },
15 |
16 | render() {
17 | const valueLink = {
18 | value: this.state.comboValue,
19 | requestChange: this._handleChange,
20 | };
21 |
22 | return (
23 |
24 |
25 |
ComboBox
26 |
27 |
28 | Default Value Example
29 |
30 |
31 | Te
32 | Tes
33 | Test
34 | Test1
35 | Test123
36 | Orange
37 |
38 |
39 |
40 | Value Example
41 |
42 | {
45 | console.log(`${event.value} - ${event.identifier} - ${event.isOptionSelection} - ${event.isMatchingOption}`);
46 | }}
47 | >
48 | Te
49 | Tes
50 | Test
51 | Test1
52 | Test123
53 | Orange
54 |
55 |
56 |
57 | Value Link Example
58 |
59 | { console.log(event.value); }}>
60 | Te
61 | Tes
62 | Test
63 | Test1
64 | Test123
65 | Orange
66 |
67 |
68 |
69 |
70 |
71 | Te
72 |
73 |
74 |
75 |
76 |
77 | Te
78 | Tes
79 | Test
80 | Test1
81 | Test123
82 | Orange
83 |
84 |
85 |
86 |
87 |
88 | Te
89 | Tes
90 | Test
91 | Test1
92 | Test123
93 | Orange
94 |
95 |
96 |
97 |
98 |
99 | Zero
100 | One
101 |
102 |
103 |
104 |
105 |
106 |
107 | );
108 | },
109 | });
110 |
--------------------------------------------------------------------------------
/examples/components/DatePickerPlayground.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Card, DatePicker } from 'belle';
3 |
4 | export default React.createClass({
5 |
6 | _renderDay(day) {
7 | const date = day.getDate();
8 | return (
9 |
10 | 🎁{ date }
11 |
12 | );
13 | },
14 |
15 | _onMouseDown(event) {
16 | event.stopPropagation();
17 | },
18 |
19 | render() {
20 | const selectedDate = new Date();
21 | selectedDate.setDate(selectedDate.getDate() + 5);
22 |
23 | const minDate = new Date();
24 | minDate.setDate(10);
25 |
26 | const maxDate = new Date();
27 | maxDate.setDate(20);
28 |
29 | return (
30 |
31 |
32 |
DatePicker
33 |
34 |
35 | Default Calendar Example
36 |
37 |
38 |
39 |
40 | DatePicker with min & max
41 |
42 |
47 |
48 |
49 | Disabled Calendar Example
50 |
51 |
52 |
53 | Calendar without showing other months dates Example
54 |
55 |
56 |
57 | Read-Only active Calendar Example
58 |
59 |
63 |
64 | Special renderDay
65 |
66 |
70 |
71 | Calendar in dutch french !!!
72 |
73 |
74 |
75 | Calendar in dutch arabic !!!
76 |
77 |
78 |
79 | Calendar in dutch hebrew !!!
80 |
81 |
85 |
86 |
87 |
88 |
89 | );
90 | },
91 | });
92 |
--------------------------------------------------------------------------------
/examples/components/RatingPlayground.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import React from 'react';
4 | import LinkedStateMixin from 'react-addons-linked-state-mixin';
5 | import { Card, Rating, Button } from 'belle';
6 |
7 | export default React.createClass({
8 |
9 | mixins: [LinkedStateMixin],
10 |
11 | getInitialState() {
12 | return { ratingValue: 2 };
13 | },
14 |
15 | _handleChange(newValue) {
16 | this.setState({ ratingValue: newValue });
17 | },
18 |
19 | _updateRatingToThree() {
20 | this.setState({ ratingValue: 3 });
21 | },
22 |
23 | render() {
24 | return (
25 |
26 |
Rating
27 |
28 |
29 |
30 | Update Rating to value 3
31 |
32 | //onUpdate should not be called for valueLink
33 | ValueLink
34 | console.log(event.value) }
37 | />
38 |
39 | Value with update function onUpdate
40 | {
43 | console.log(event.value);
44 | this._handleChange(event.value);
45 | }
46 | }
47 | />
48 |
49 | Value
50 | console.log(event.value) }
53 | />
54 |
55 | DefaultValue
56 | console.log(event.value) }
59 | />
60 |
61 | Disabled
62 |
63 |
64 | RatingCharacter
65 |
66 |
67 |
68 |
69 |
70 | );
71 | },
72 | });
73 |
--------------------------------------------------------------------------------
/examples/components/SpinnerPlayground.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button, Card, Spinner } from 'belle';
3 |
4 | export default React.createClass({
5 |
6 | render() {
7 | return (
8 |
9 | Spinner
10 |
11 |
12 |
13 |
14 | Loading
15 |
16 |
17 |
18 | Saving
19 |
20 |
21 |
22 | Saving
23 |
24 |
25 |
26 | );
27 | },
28 |
29 | });
30 |
--------------------------------------------------------------------------------
/examples/components/TextInputPlayground.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import React from 'react';
4 | import { TextInput, Card } from 'belle';
5 |
6 | function conditionalTextInput(showTextInput) {
7 | if (showTextInput) {
8 | return ;
9 | }
10 |
11 | return null;
12 | }
13 |
14 | export default React.createClass({
15 |
16 | getInitialState() {
17 | return {
18 | showTextInput: true,
19 | inputValue: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
20 | };
21 | },
22 |
23 | _handleChange(newValue) {
24 | this.setState({ inputValue: newValue });
25 | },
26 |
27 | _removeTextInput() {
28 | this.setState({
29 | showTextInput: false,
30 | });
31 | },
32 |
33 | render() {
34 | const valueLink = {
35 | value: this.state.inputValue,
36 | requestChange: this._handleChange,
37 | };
38 |
39 | return (
40 |
41 | TextInput
42 |
43 | {/* Common use case */}
44 | console.log(event.value) }
49 | />
50 |
51 |
52 | {/* Common use case disabled */}
53 |
54 |
55 |
56 | {/* Remove TextInput behaviour */}
57 | {conditionalTextInput(this.state.showTextInput)}
58 |
59 | Remove TextInput
60 |
61 | {/* Empty TextInput */}
62 |
63 |
64 |
65 | Not editable value
66 |
67 | {
71 | console.log(event.value);
72 | this._handleChange(event.value);
73 | }
74 | }
75 | />
76 |
77 |
78 |
79 | {/* Full width TextInput */}
80 |
81 |
82 |
83 |
84 |
85 | {/* TextInput with placeholder & a minHeight & custom hoverStyle */}
86 |
87 |
92 |
93 |
94 | Test with 3 min rows & 5 max rows
95 |
96 |
97 |
98 |
99 | Test 4 min rows & minHeight 80
100 |
101 |
102 |
103 |
104 | Test 2 max rows & maxHeight 300
105 |
106 |
107 |
108 |
109 | style minHeight 80
110 |
111 |
112 |
113 |
114 | {/* Value test */}
115 |
116 |
118 |
119 | );
120 | },
121 | });
122 |
--------------------------------------------------------------------------------
/examples/components/TogglePlayground.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import LinkedStateMixin from 'react-addons-linked-state-mixin';
3 | import { Toggle, Choice, Card } from 'belle';
4 |
5 | export default React.createClass({
6 |
7 | mixins: [LinkedStateMixin],
8 |
9 | getInitialState() {
10 | return {
11 | active: true,
12 | };
13 | },
14 |
15 | render() {
16 | return (
17 |
18 | Toggle
19 |
20 | Default Toggle
21 |
22 |
23 | Default value=true Toggle
24 |
25 |
26 | Default value=false Toggle
27 |
28 |
29 | defaultValue=true Toggle (defaultValue)
30 |
31 |
32 | DefaultValue=false Toggle (defaultValue)
33 |
34 |
35 | Default Toggle (valueLink)
36 |
37 | { `active: ${this.state.active}` }
38 |
39 | Custom Toggle
40 |
41 | On
42 | Off
43 |
44 |
45 | Disabled Toggle
46 |
47 |
48 |
49 |
50 | );
51 | },
52 | });
53 |
--------------------------------------------------------------------------------
/examples/css/style.css:
--------------------------------------------------------------------------------
1 | html {
2 | background: #F7F7F7;
3 | }
4 |
5 | body {
6 | font-size: 14px;
7 | line-height: 1.6;
8 | font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
9 | color: #222;
10 | }
11 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Belle - Examples
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { style } from 'belle';
4 | import { config } from 'belle';
5 | import { extend } from 'underscore';
6 | import ButtonPlayground from './components/ButtonPlayground';
7 | import CardPlayground from './components/CardPlayground';
8 | import SelectPlayground from './components/SelectPlayground';
9 | import SpinnerPlayground from './components/SpinnerPlayground';
10 | import TextInputPlayground from './components/TextInputPlayground';
11 | import RatingPlayground from './components/RatingPlayground';
12 | import ComboBoxPlayground from './components/ComboBoxPlayground';
13 | import TogglePlayground from './components/TogglePlayground';
14 | import DatePickerPlayground from './components/DatePickerPlayground';
15 |
16 | // TODO create a button to switch between those stylings for testing purposes
17 | if (true) { // eslint-disable-line no-constant-condition
18 | config.button.preventFocusStyleForTouchAndClick = false;
19 | style.button.focusStyle = {
20 | boxShadow: '0 0 0 3px rgba(140, 224, 255, 0.6)',
21 | outline: 0,
22 | };
23 | style.button.primaryFocusStyle = {
24 | boxShadow: '0 0 0 3px rgba(140, 224, 255, 0.6)',
25 | outline: 0,
26 | };
27 |
28 | config.rating.preventFocusStyleForTouchAndClick = false;
29 | style.rating.focusStyle = {
30 | boxShadow: '0 0 0 3px rgba(140, 224, 255, 0.6)',
31 | outline: 0,
32 | borderRadius: 3,
33 | };
34 |
35 | config.toggle.preventFocusStyleForTouchAndClick = false;
36 | style.toggle.focusStyle = {
37 | boxShadow: '0 0 0 3px rgba(140, 224, 255, 0.6)',
38 | outline: 0,
39 | };
40 |
41 | config.datePicker.preventFocusStyleForTouchAndClick = false;
42 | style.datePicker.focusStyle = {
43 | boxShadow: '0 0 0 3px rgba(140, 224, 255, 0.6)',
44 | outline: 0,
45 | };
46 |
47 | config.select.shouldPositionOptions = false;
48 | } else {
49 | style.button.style = {
50 | boxSizing: 'border-box',
51 | borderRadius: 2,
52 | cursor: 'pointer',
53 | padding: '8px 12px 6px 12px',
54 | textAlign: 'center',
55 | textDecoration: 'none',
56 | display: 'inline-block',
57 | background: 'red',
58 | border: '1px solid #EFEFEF',
59 | borderBottomColor: '#D0D0D0',
60 | color: 'brown',
61 | verticalAlign: 'bottom',
62 | lineHeight: '26px',
63 | };
64 |
65 | style.card.style = extend(style.card.style, {
66 | border: '1px solid black',
67 | });
68 |
69 | style.textInput.style = extend(style.textInput.style, {
70 | color: 'blue',
71 | });
72 | }
73 |
74 | // export for http://fb.me/react-devtools
75 | window.React = React;
76 |
77 | const App = () => (
78 |
79 |
Belle Playground
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | );
100 |
101 | ReactDOM.render( , document.getElementById('react'));
102 |
--------------------------------------------------------------------------------
/examples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "belleExamples",
3 | "version": "3.0.2",
4 | "description": "Playground for using & developing Belle components",
5 | "author": {
6 | "name": "Nik Graf",
7 | "email": "nik@nikgraf.com",
8 | "url": "http://www.nikgraf.com"
9 | },
10 | "main": "js/app.jsx",
11 | "repository": {
12 | "type": "git",
13 | "url": "http://github.com/nikgraf/belle.git"
14 | },
15 | "dependencies": {
16 | "react": "=15.4.1",
17 | "react-addons-linked-state-mixin": "^15.4.1",
18 | "react-dom": "^15.4.1",
19 | "underscore": "=1.8.3"
20 | },
21 | "devDependencies": {
22 | "babel-core": "^6.7.4",
23 | "babel-loader": "^6.2.4",
24 | "eslint": "^2.5.3",
25 | "eslint-config-airbnb": "^6.2.0",
26 | "eslint-plugin-react": "^4.2.3",
27 | "express": "^4.13.4",
28 | "node-libs-browser": "^1.0.0",
29 | "react-transform-catch-errors": "^1.0.2",
30 | "redbox-react": "^1.2.2",
31 | "webpack": "^1.12.14"
32 | },
33 | "scripts": {
34 | "start": "node server.js"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/examples/server.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-var, no-console, prefer-arrow-callback */
2 |
3 | var path = require('path');
4 | var express = require('express');
5 | var webpack = require('webpack');
6 | var config = require('./webpack.config');
7 |
8 | var app = express();
9 | var compiler = webpack(config);
10 |
11 | app.use('/css', express.static('css'));
12 |
13 | app.use(require('webpack-dev-middleware')(compiler, {
14 | noInfo: true,
15 | publicPath: config.output.publicPath,
16 | }));
17 |
18 | app.use(require('webpack-hot-middleware')(compiler));
19 |
20 | app.get('*', function getAll(req, res) {
21 | res.sendFile(path.join(__dirname, 'index.html'));
22 | });
23 |
24 | app.listen(3000, 'localhost', function listen(err) {
25 | if (err) {
26 | console.log(err);
27 | return;
28 | }
29 |
30 | console.log('Listening at http://localhost:3000');
31 | });
32 |
--------------------------------------------------------------------------------
/examples/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-var */
2 |
3 | var path = require('path');
4 | var webpack = require('webpack');
5 |
6 | module.exports = {
7 | devtool: 'eval',
8 | entry: [
9 | 'webpack-hot-middleware/client',
10 | './index',
11 | ],
12 | output: {
13 | path: path.join(__dirname, 'dist'),
14 | filename: 'bundle.js',
15 | publicPath: '/static/',
16 | },
17 | plugins: [
18 | new webpack.HotModuleReplacementPlugin(),
19 | new webpack.NoErrorsPlugin(),
20 | ],
21 | resolve: {
22 | alias: {
23 | belle: path.join(__dirname, '..', 'src'),
24 | react: path.join(__dirname, 'node_modules', 'react'),
25 | },
26 | extensions: ['', '.js'],
27 | },
28 | module: {
29 | loaders: [
30 | {
31 | test: /\.js$/,
32 | loaders: ['babel'],
33 | exclude: /node_modules/,
34 | include: __dirname,
35 | }, {
36 | test: /\.js$/,
37 | loaders: ['babel'],
38 | include: path.join(__dirname, '..', 'src'),
39 | },
40 | ],
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "belle",
3 | "version": "4.0.0",
4 | "description": "Configurable React Components with great UX",
5 | "author": {
6 | "name": "Nik Graf",
7 | "email": "nik@nikgraf.com",
8 | "url": "http://www.nikgraf.com"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git://github.com/nikgraf/belle.git"
13 | },
14 | "main": "lib/index.js",
15 | "keywords": [
16 | "browser",
17 | "react",
18 | "ux",
19 | "components",
20 | "widget",
21 | "javascript",
22 | "react-component"
23 | ],
24 | "peerDependencies": {
25 | "react": ">=15.4.0",
26 | "react-dom": ">=15.4.0",
27 | "prop-types": ">=15.4.0"
28 | },
29 | "scripts": {
30 | "build": "BABEL_ENV=production node_modules/.bin/babel --out-dir='lib' --ignore='__tests__/*' src",
31 | "prepublish": "npm run build",
32 | "postpublish": "./scripts/publish_gh_pages.sh",
33 | "test": "BABEL_ENV=test BABEL_JEST_STAGE=0 jest src/",
34 | "test:watch": "npm run test -- --watch",
35 | "lint": "npm run lint:eslint",
36 | "lint:eslint": "eslint ./",
37 | "lint:eslint:fix": "eslint --fix ./",
38 | "alex": "alex README.md"
39 | },
40 | "devDependencies": {
41 | "alex": "^2.0.1",
42 | "babel": "^6.5.2",
43 | "babel-cli": "^6.6.5",
44 | "babel-core": "^6.7.4",
45 | "babel-eslint": "^6.0.0",
46 | "babel-jest": "^9.0.3",
47 | "babel-loader": "^6.2.4",
48 | "babel-plugin-react-transform": "^2.0.2",
49 | "babel-preset-es2015": "^6.6.0",
50 | "babel-preset-react": "^6.5.0",
51 | "babel-preset-react-hmre": "^1.1.1",
52 | "babel-preset-stage-0": "^6.5.0",
53 | "eslint": "^2.5.3",
54 | "eslint-config-airbnb": "6.2.0",
55 | "eslint-plugin-react": "^4.2.3",
56 | "jest-cli": "^15.1.1",
57 | "prop-types": "^15.5.10",
58 | "react": "^15.4.1",
59 | "react-dom": "^15.4.1",
60 | "react-test-renderer": "^15.6.1",
61 | "react-transform-catch-errors": "^1.0.2",
62 | "react-transform-hmr": "^1.0.4",
63 | "redbox-react": "^1.2.2",
64 | "webpack": "^1.12.14",
65 | "webpack-dev-middleware": "^1.6.1",
66 | "webpack-hot-middleware": "^2.10.0"
67 | },
68 | "jest": {
69 | "testRunner": "jasmine2",
70 | "automock": true,
71 | "unmockedModulePathPatterns": [
72 | "/node_modules/react",
73 | "/node_modules/fbjs",
74 | "/node_modules/react-dom",
75 | "/node_modules/prop-types",
76 | "../utils/helpers",
77 | "../utils/union-class-names",
78 | "../utils/is-component-of-type"
79 | ],
80 | "scriptPreprocessor": "/node_modules/babel-jest"
81 | },
82 | "license": "MIT",
83 | "dependencies": {
84 | "exenv": "^1.2.0"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/scripts/publish_gh_pages.sh:
--------------------------------------------------------------------------------
1 | git checkout master
2 | git checkout -b tmp-gh-pages
3 | rm .gitignore
4 |
5 | cd docs
6 | npm prune
7 | npm install
8 | npm run build
9 |
10 | git add static/bundle.js
11 | git add css/googlecode.css
12 | git commit -am 'add files'
13 | cd ..
14 | git subtree split --prefix docs -b gh-pages
15 | git push -f origin gh-pages:gh-pages
16 | git checkout master
17 | git branch -D tmp-gh-pages
18 | git branch -D gh-pages
19 |
--------------------------------------------------------------------------------
/src/__tests__/Button-test.js:
--------------------------------------------------------------------------------
1 | /* global jest describe beforeEach it expect */
2 |
3 | jest.dontMock('../components/Button');
4 | jest.dontMock('../utils/inject-style');
5 | jest.dontMock('../utils/union-class-names');
6 | jest.dontMock('../style/button');
7 |
8 | import React from 'react';
9 | import TestUtils from 'react-dom/test-utils';
10 |
11 | // Babel would move an import in front of the jest.dontMock. That's why require
12 | // is used instead of import.
13 | const Button = require('../components/Button').default;
14 | const injectStyle = require('../utils/inject-style');
15 |
16 | describe('Button', () => {
17 | describe('without any properties', () => {
18 | let button;
19 | let buttonNode;
20 |
21 | beforeEach(() => {
22 | injectStyle.injectStyles = jest.genMockFunction();
23 | button = TestUtils.renderIntoDocument(
24 | Follow
25 | );
26 | buttonNode = TestUtils.findRenderedDOMComponentWithTag(button, 'button');
27 | });
28 |
29 | it('should come with default styles', () => {
30 | expect(buttonNode.hasAttribute('style')).toBeTruthy();
31 | });
32 |
33 | it('should set the type to button by default', () => {
34 | expect(buttonNode.type).toBe('button');
35 | });
36 |
37 | it('should inject styles for active & foucs', () => {
38 | expect(injectStyle.injectStyles.mock.calls.length).toBe(1);
39 |
40 | const styles = injectStyle.injectStyles.mock.calls[0][0];
41 | expect(styles[0].pseudoClass).toBe('active');
42 | expect(styles[2].pseudoClass).toBe('focus');
43 | });
44 | });
45 |
46 | it('should be able to bind onClick', () => {
47 | let wasClicked = false;
48 |
49 | // Render a button with an onClick handler
50 | const button = TestUtils.renderIntoDocument(
51 | { wasClicked = true; }}>Follow
52 | );
53 |
54 | // Simulate a click
55 | TestUtils.Simulate.click(TestUtils.findRenderedDOMComponentWithTag(button, 'button'));
56 |
57 | expect(wasClicked).toBeTruthy();
58 | });
59 |
60 | it('should be able to provide a className', () => {
61 | const button = TestUtils.renderIntoDocument(
62 | Follow
63 | );
64 |
65 | const buttonNode = TestUtils.findRenderedDOMComponentWithTag(button, 'button');
66 | expect(buttonNode.className).toContain('test-me');
67 | });
68 |
69 | it('should be able to adopt the style of the button', () => {
70 | const button = TestUtils.renderIntoDocument(
71 | Follow
72 | );
73 |
74 | const buttonNode = TestUtils.findRenderedDOMComponentWithTag(button, 'button');
75 | expect(buttonNode.getAttribute('style').indexOf('color: rgb(255, 0, 0)') > -1).toBeTruthy();
76 | });
77 |
78 | it('should be able to use a primary button', () => {
79 | const defaultButton = TestUtils.renderIntoDocument(
80 | Follow
81 | );
82 |
83 | const primaryButton = TestUtils.renderIntoDocument(
84 | Follow
85 | );
86 |
87 | const defaultButtonNode = TestUtils.findRenderedDOMComponentWithTag(defaultButton, 'button');
88 | const prmaryButtonNode = TestUtils.findRenderedDOMComponentWithTag(primaryButton, 'button');
89 |
90 | expect(prmaryButtonNode.getAttribute('style')).not.toEqual(defaultButtonNode.getAttribute('style'));
91 | });
92 |
93 | it('should be able to change the type to submit or reset', () => {
94 | const submitButton = TestUtils.renderIntoDocument(
95 | Submit
96 | );
97 | const submitButtonNode = TestUtils.findRenderedDOMComponentWithTag(submitButton, 'button');
98 | expect(submitButtonNode.type).toBe('submit');
99 |
100 | const resetButton = TestUtils.renderIntoDocument(
101 | Submit
102 | );
103 | const resetButtonNode = TestUtils.findRenderedDOMComponentWithTag(resetButton, 'button');
104 | expect(resetButtonNode.type).toBe('reset');
105 | });
106 |
107 | it('should be able to adopt the pseudoClass styles of the button', () => {
108 | injectStyle.injectStyles = jest.genMockFunction();
109 |
110 | const bodyWithButton = TestUtils.renderIntoDocument(
111 |
117 | Follow
118 |
119 | );
120 |
121 | TestUtils.findRenderedDOMComponentWithTag(bodyWithButton, 'button');
122 |
123 | expect(injectStyle.injectStyles.mock.calls.length).toBe(1);
124 |
125 | const styles = injectStyle.injectStyles.mock.calls[0][0];
126 |
127 | expect(styles[0].pseudoClass).toBe('active');
128 | expect(styles[0].style.color).toBe('green');
129 | expect(styles[2].pseudoClass).toBe('focus');
130 | expect(styles[2].style.color).toBe('brown');
131 | });
132 |
133 | it('should remove the custom styles from the dom when the button unmounts', () => {
134 | injectStyle.removeStyle = jest.genMockFunction();
135 | expect(injectStyle.removeStyle.mock.calls.length).toBe(0);
136 |
137 | const button = TestUtils.renderIntoDocument(
138 | Follow
139 | );
140 |
141 | button.componentWillUnmount();
142 |
143 | expect(injectStyle.removeStyle.mock.calls.length).toBe(1);
144 | });
145 |
146 | it('should set isHovered state to true on mouseEnter and false on mouseLeave', () => {
147 | const button = TestUtils.renderIntoDocument(
148 | Follow
149 | );
150 |
151 | expect(button.state.isHovered).toBeFalsy();
152 | TestUtils.Simulate.mouseEnter(TestUtils.findRenderedDOMComponentWithTag(button, 'button'));
153 | expect(button.state.isHovered).toBeTruthy();
154 | TestUtils.Simulate.mouseLeave(TestUtils.findRenderedDOMComponentWithTag(button, 'button'));
155 | expect(button.state.isHovered).toBeFalsy();
156 | });
157 | });
158 |
--------------------------------------------------------------------------------
/src/__tests__/Card-test.js:
--------------------------------------------------------------------------------
1 | /* global jest describe beforeEach it expect */
2 |
3 | jest.dontMock('../components/Card');
4 | jest.dontMock('../style/card');
5 | jest.dontMock('../utils/inject-style');
6 |
7 | import React from 'react';
8 | import TestUtils from 'react-dom/test-utils';
9 |
10 | // Babel would move an import in front of the jest.dontMock. That's why require
11 | // is used instead of import.
12 | const Card = require('../components/Card').default;
13 |
14 | describe('Card', () => {
15 | it('should come with default styles', () => {
16 | const card = TestUtils.renderIntoDocument(
17 |
18 | );
19 | const divNode = TestUtils.findRenderedDOMComponentWithTag(card, 'div');
20 | expect(divNode.hasAttribute('style')).toBeTruthy();
21 |
22 | expect(divNode.getAttribute('style').indexOf('background: rgb(255, 255, 255)') > -1).toBeTruthy();
23 | });
24 |
25 | it('should be able to adopt the style of the card', () => {
26 | const card = TestUtils.renderIntoDocument(
27 |
28 | );
29 | const divNode = TestUtils.findRenderedDOMComponentWithTag(card, 'div');
30 | expect(divNode.getAttribute('style').indexOf('background: rgb(255, 0, 0)') > -1).toBeTruthy();
31 | });
32 |
33 | it('should render its children', () => {
34 | const card = TestUtils.renderIntoDocument(
35 | Hello there
36 | );
37 | const divNode = TestUtils.findRenderedDOMComponentWithTag(card, 'div');
38 | const spanNode = TestUtils.findRenderedDOMComponentWithTag(card, 'span');
39 |
40 | expect(divNode.tagName).toEqual('DIV');
41 | expect(divNode.childNodes[0].tagName).toEqual('SPAN');
42 | expect(spanNode.textContent).toEqual('Hello there');
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/src/__tests__/Option-test.js:
--------------------------------------------------------------------------------
1 | /* global jest describe beforeEach it expect */
2 |
3 | jest.dontMock('../components/Option');
4 | jest.dontMock('../style/option');
5 |
6 | import React from 'react';
7 | import Shallow from 'react-test-renderer/shallow';
8 |
9 | // Babel would move an import in front of the jest.dontMock. That's why require
10 | // is used instead of import.
11 | const Option = require('../components/Option').default;
12 |
13 | describe('Option', () => {
14 | let shallowRenderer;
15 |
16 | beforeEach(() => {
17 | shallowRenderer = new Shallow();
18 | });
19 |
20 | it('should initialise _isDisplayedAsSelected during construction', () => {
21 | shallowRenderer.render(
22 | Rome ,
23 | { isDisabled: false, }
24 | );
25 | const option = shallowRenderer.getRenderOutput();
26 |
27 | expect(option._isDisplayedAsSelected).toBeFalsy();
28 | });
29 |
30 | it('should show the select style in case _isDisplayedAsSelected is true', () => {
31 | shallowRenderer.render(
32 | Rome ,
33 | { isDisabled: false, }
34 | );
35 | const option = shallowRenderer.getRenderOutput();
36 |
37 | expect(option.props.style.padding).toBe(0);
38 | });
39 |
40 | it('should show the hover style in case _isHovered is true', () => {
41 | shallowRenderer.render(
42 | Rome ,
43 | {
44 | isDisabled: false,
45 | isHoveredValue: 'rome',
46 | }
47 | );
48 | const option = shallowRenderer.getRenderOutput();
49 |
50 | expect(option.props.style.background).toBe('#F5F5F5');
51 | });
52 |
53 | it('should be able to provide custom properties', () => {
54 | shallowRenderer.render(
55 | Rome ,
56 | { isDisabled: false, }
57 | );
58 | const option = shallowRenderer.getRenderOutput();
59 |
60 | expect(option.props['data-custom']).toBe('example');
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/src/__tests__/Placeholder-test.js:
--------------------------------------------------------------------------------
1 | /* global jest describe beforeEach it expect */
2 |
3 | jest.dontMock('../components/Placeholder');
4 | jest.dontMock('../style/placeholder');
5 |
6 | import React from 'react';
7 | import TestUtils from 'react-dom/test-utils';
8 |
9 | // Babel would move an import in front of the jest.dontMock. That's why require
10 | // is used instead of import.
11 | const Placeholder = require('../components/Placeholder').default;
12 |
13 | describe('Placeholder', () => {
14 | it('should come with a set of default styles', () => {
15 | const placeholder = TestUtils.renderIntoDocument(
16 | Please select a city
17 | );
18 |
19 | const div = TestUtils.findRenderedDOMComponentWithTag(placeholder, 'div');
20 | expect(div.hasAttribute('style')).toBeTruthy();
21 | expect(div.getAttribute('style').indexOf('color: rgb(102, 102, 102)') > -1).toBeTruthy();
22 | });
23 |
24 | it('should be able to overwrite the default styles', () => {
25 | const placeholder = TestUtils.renderIntoDocument(
26 | Please select a city
27 | );
28 |
29 | const div = TestUtils.findRenderedDOMComponentWithTag(placeholder, 'div');
30 | expect(div.hasAttribute('style')).toBeTruthy();
31 | expect(div.getAttribute('style').indexOf('color: rgb(255, 0, 0)') > -1).toBeTruthy();
32 | });
33 |
34 | it('should be able to provide custom properties', () => {
35 | const placeholder = TestUtils.renderIntoDocument(
36 | Please select a city
37 | );
38 |
39 | const div = TestUtils.findRenderedDOMComponentWithTag(placeholder, 'div');
40 | expect(div.getAttribute('data-custom')).toBe('example');
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/src/__tests__/Rating-test.js:
--------------------------------------------------------------------------------
1 | /* global jest describe beforeEach it expect */
2 |
3 | jest.dontMock('../components/Rating');
4 | jest.dontMock('../utils/inject-style');
5 | jest.dontMock('../utils/union-class-names');
6 |
7 | import React from 'react';
8 | import TestUtils from 'react-dom/test-utils';
9 |
10 | // Babel would move an import in front of the jest.dontMock. That's why require
11 | // is used instead of import.
12 | const Rating = require('../components/Rating').default;
13 |
14 | describe('Rating', () => {
15 | it('should be able to provide a valueLink', () => {
16 | const valueLink = {
17 | requestChange: () => undefined,
18 | value: 1,
19 | };
20 | const rating = TestUtils.renderIntoDocument( );
21 |
22 | expect(rating.state.value).toBe(1);
23 | });
24 |
25 | it('should be able to provide a value', () => {
26 | const rating = TestUtils.renderIntoDocument( );
27 | expect(rating.state.value).toBe(4);
28 | });
29 |
30 | it('should be able to provide a defaultValue', () => {
31 | const rating = TestUtils.renderIntoDocument( );
32 | expect(rating.state.value).toBe(2);
33 | });
34 |
35 | it('should to not provide any kind of value', () => {
36 | const rating = TestUtils.renderIntoDocument( );
37 | expect(rating.state.value).toBeUndefined();
38 | });
39 |
40 | it('should not be able to change value via the user interface if a value property is defined', () => {
41 | const rating = TestUtils.renderIntoDocument( );
42 | rating.setState({ focusedValue: 3 });
43 | rating._triggerComponentUpdate();
44 | expect(rating.state.value).toBe(4);
45 | });
46 |
47 | describe('update the internal value', () => {
48 | let rating;
49 |
50 | beforeEach(() => {
51 | rating = TestUtils.renderIntoDocument(
52 |
53 | );
54 | });
55 |
56 | it('should be possible by updating the value property', () => {
57 | rating.componentWillReceiveProps({ value: 2 });
58 | expect(rating.state.value).toBe(2);
59 | });
60 |
61 | it('should be possible by updating the valueLink property', () => {
62 | const valueLink = {
63 | requestChange: () => undefined,
64 | value: 1,
65 | };
66 |
67 | rating.componentWillReceiveProps({ valueLink });
68 | expect(rating.state.value).toBe(1);
69 | });
70 |
71 | it('should not be possible by updating the defaultValue property', () => {
72 | rating.componentWillReceiveProps({ defaultValue: 3 });
73 | expect(rating.state.value).toBeUndefined();
74 | });
75 | });
76 | });
77 |
--------------------------------------------------------------------------------
/src/__tests__/Separator-test.js:
--------------------------------------------------------------------------------
1 | /* global jest describe beforeEach it expect */
2 |
3 | jest.dontMock('../components/Separator');
4 | jest.dontMock('../style/separator');
5 |
6 | import React from 'react';
7 | import TestUtils from 'react-dom/test-utils';
8 |
9 | // Babel would move an import in front of the jest.dontMock. That's why require
10 | // is used instead of import.
11 | const Separator = require('../components/Separator').default;
12 |
13 | describe('Separator', () => {
14 | it('should come with a set of default styles', () => {
15 | const separator = TestUtils.renderIntoDocument(
16 | Europe
17 | );
18 |
19 | const div = TestUtils.findRenderedDOMComponentWithTag(separator, 'div');
20 | expect(div.hasAttribute('style')).toBeTruthy();
21 | expect(div.getAttribute('style').indexOf('color: rgb(102, 102, 102)') > -1).toBeTruthy();
22 | });
23 |
24 | it('should be able to overwrite the default styles', () => {
25 | const separator = TestUtils.renderIntoDocument(
26 | Please select a city
27 | );
28 |
29 | const div = TestUtils.findRenderedDOMComponentWithTag(separator, 'div');
30 | expect(div.hasAttribute('style')).toBeTruthy();
31 | expect(div.getAttribute('style').indexOf('color: rgb(255, 0, 0)') > -1).toBeTruthy();
32 | });
33 |
34 | it('should be able to provide custom properties', () => {
35 | const separator = TestUtils.renderIntoDocument(
36 | Please select a city
37 | );
38 |
39 | const div = TestUtils.findRenderedDOMComponentWithTag(separator, 'div');
40 | expect(div.getAttribute('data-custom')).toBe('example');
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/src/__tests__/Spinner-test.js:
--------------------------------------------------------------------------------
1 | /* global jest describe beforeEach it expect */
2 |
3 | jest.dontMock('../components/Spinner');
4 | jest.dontMock('../style/spinner');
5 |
6 | import React from 'react';
7 | import TestUtils from 'react-dom/test-utils';
8 |
9 | // Babel would move an import in front of the jest.dontMock. That's why require
10 | // is used instead of import.
11 | const Spinner = require('../components/Spinner').default;
12 |
13 | describe('Spinner', () => {
14 | it('should come with default styles', () => {
15 | const spinner = TestUtils.renderIntoDocument(
16 |
17 | );
18 | const spanNode = TestUtils.scryRenderedDOMComponentsWithTag(spinner, 'span')[0];
19 | expect(spanNode.hasAttribute('style')).toBeTruthy();
20 | expect(spanNode.getAttribute('style')).toContain('font-size: 15px');
21 | });
22 |
23 | it('should be able to adopt the style of the spinner wrapper', () => {
24 | const spinner = TestUtils.renderIntoDocument(
25 |
26 | );
27 | const spanNode = TestUtils.scryRenderedDOMComponentsWithTag(spinner, 'span')[0];
28 | expect(spanNode.getAttribute('style')).toContain('width: 200px');
29 | });
30 |
31 | it('should be able to adopt the character style of the spinner', () => {
32 | const spinner = TestUtils.renderIntoDocument(
33 |
34 | );
35 | const spanNode = TestUtils.scryRenderedDOMComponentsWithTag(spinner, 'span')[1];
36 | expect(spanNode.getAttribute('style')).toContain('color: red');
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/src/__tests__/TextInput-test.js:
--------------------------------------------------------------------------------
1 | /* global jest, describe, it, expect*/
2 |
3 | jest.dontMock('../components/TextInput');
4 | jest.dontMock('../utils/inject-style');
5 | jest.dontMock('../utils/calculate-textarea-height');
6 | jest.dontMock('../style/text-input');
7 |
8 | import React from 'react';
9 | import TestUtils from 'react-dom/test-utils';
10 |
11 | // Babel would move an import in front of the jest.dontMock. That's why require
12 | // is used instead of import.
13 | const injectStyle = require('../utils/inject-style');
14 | const TextInput = require('../components/TextInput').default;
15 |
16 | describe('TextInput', () => {
17 | it('should come with default styles', () => {
18 | const textInput = TestUtils.renderIntoDocument(
19 |
20 | );
21 | const textareaNode = TestUtils.findRenderedDOMComponentWithTag(textInput, 'textarea');
22 |
23 | expect(textareaNode.hasAttribute('style')).toBeTruthy();
24 | expect(textareaNode.getAttribute('style').indexOf('overflow: hidden') > -1).toBeTruthy();
25 | });
26 |
27 | it('should be able to adopt the style of the textInput', () => {
28 | const textInput = TestUtils.renderIntoDocument(
29 |
30 | );
31 | const textareaNode = TestUtils.findRenderedDOMComponentWithTag(textInput, 'textarea');
32 |
33 | expect(textareaNode.hasAttribute('style')).toBeTruthy();
34 | expect(textareaNode.getAttribute('style').indexOf('color: red') > -1).toBeTruthy();
35 | });
36 |
37 | it('should calculate its height after initializing', () => {
38 | const textInput = TestUtils.renderIntoDocument(
39 |
40 | );
41 |
42 | textInput._triggerResize = jest.genMockFunction();
43 | textInput.componentDidMount();
44 |
45 | expect(textInput._triggerResize.mock.calls.length).toBe(1);
46 | });
47 |
48 | it('should re-calculate its height after changing (default)', () => {
49 | const textInput = TestUtils.renderIntoDocument(
50 |
51 | );
52 |
53 | textInput._triggerResize = jest.genMockFunction();
54 |
55 | const textareaNode = TestUtils.findRenderedDOMComponentWithTag(textInput, 'textarea');
56 |
57 | TestUtils.Simulate.change(textareaNode, 'some input text');
58 |
59 | expect(textInput._triggerResize.mock.calls.length).toBe(1);
60 | });
61 |
62 | it('should re-calculate its height after changing (with value & new lines not allowed)', () => {
63 | const textInput = TestUtils.renderIntoDocument(
64 |
65 | );
66 |
67 | textInput._triggerResize = jest.genMockFunction();
68 |
69 | const textareaNode = TestUtils.findRenderedDOMComponentWithTag(textInput, 'textarea');
70 |
71 | TestUtils.Simulate.change(textareaNode, 'some other text');
72 |
73 | expect(textInput._triggerResize.mock.calls.length).toBe(1);
74 | });
75 |
76 | it('should re-calculate its height after changing (new lines allowed)', () => {
77 | const textInput = TestUtils.renderIntoDocument(
78 |
79 | );
80 |
81 | textInput._triggerResize = jest.genMockFunction();
82 |
83 | const textareaNode = TestUtils.findRenderedDOMComponentWithTag(textInput, 'textarea');
84 |
85 | TestUtils.Simulate.change(textareaNode, 'some other text');
86 |
87 | expect(textInput._triggerResize.mock.calls.length).toBe(1);
88 | });
89 |
90 | it('should be able to bind onKeyDown', () => {
91 | let wasPressed = false;
92 |
93 | const textInput = TestUtils.renderIntoDocument(
94 | { wasPressed = true; } } />
95 | );
96 |
97 | const textareaNode = TestUtils.findRenderedDOMComponentWithTag(textInput, 'textarea');
98 |
99 | TestUtils.Simulate.keyDown(textareaNode, { key: '1' });
100 |
101 | expect(wasPressed).toEqual(true);
102 | });
103 |
104 | it('should be able to bind onUpdate', () => {
105 | let wasChanged = false;
106 |
107 | const textInput = TestUtils.renderIntoDocument(
108 | { wasChanged = true; } } />
109 | );
110 |
111 | const textareaNode = TestUtils.findRenderedDOMComponentWithTag(textInput, 'textarea');
112 |
113 | TestUtils.Simulate.change(textareaNode, 'some text');
114 |
115 | expect(wasChanged).toEqual(true);
116 | });
117 |
118 | it('should be able to provide a valueLink', () => {
119 | let wasCalled = false;
120 |
121 | const valueLink = {
122 | requestChange: () => {
123 | wasCalled = true;
124 | },
125 |
126 | value: 'some text',
127 | };
128 |
129 | const textInput = TestUtils.renderIntoDocument(
130 |
131 | );
132 |
133 | const textareaNode = TestUtils.findRenderedDOMComponentWithTag(textInput, 'textarea');
134 |
135 | TestUtils.Simulate.change(textareaNode, 'some other text');
136 |
137 | expect(wasCalled).toEqual(true);
138 | });
139 |
140 | it('should be able to provide a className', () => {
141 | const textInput = TestUtils.renderIntoDocument(
142 |
143 | );
144 |
145 | const textareaNode = TestUtils.findRenderedDOMComponentWithTag(textInput, 'textarea');
146 | expect(textareaNode.className.indexOf('test-me')).toBeGreaterThan(-1);
147 | });
148 |
149 | it('should remove the custom styles from the dom when the textInput unmounts', () => {
150 | injectStyle.removeStyle = jest.genMockFunction();
151 |
152 | const textInput = TestUtils.renderIntoDocument(
153 |
154 | );
155 |
156 | textInput.componentWillUnmount();
157 |
158 | expect(injectStyle.removeStyle.mock.calls.length).toBe(1);
159 | });
160 | });
161 |
--------------------------------------------------------------------------------
/src/__tests__/date-helpers-test.js:
--------------------------------------------------------------------------------
1 | /* global jest, describe, it, expect*/
2 |
3 | jest.dontMock('../utils/date-helpers');
4 |
5 | const dateHelpers = require('../utils/date-helpers');
6 |
7 | describe('helpers getDateKey method', () => {
8 | it('should return an iso string', () => {
9 | expect(dateHelpers.getDateKey(2012, 10, 1)).toBe('2012-10-1');
10 | expect(dateHelpers.getDateKey(2200, 1, 31)).toBe('2200-1-31');
11 | expect(dateHelpers.getDateKey(2016, 12, 12)).toBe('2016-12-12');
12 | });
13 |
14 | it('should parse a datekey (iso string) properly to a date', () => {
15 | expect(dateHelpers.getDateForDateKey('1912-10-1').getTime()).toBe(new Date(1912, 9, 1).getTime());
16 | expect(dateHelpers.getDateForDateKey('2018-1-12').getTime()).toBe(new Date(2018, 0, 12).getTime());
17 | expect(dateHelpers.getDateForDateKey('2112-12-12').getTime()).toBe(new Date(2112, 11, 12).getTime());
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/src/__tests__/union-class-names-test.js:
--------------------------------------------------------------------------------
1 | /* global jest, describe, it, expect*/
2 |
3 | import unionClassNames from '../utils/union-class-names';
4 |
5 | describe('unionClassNames', () => {
6 | it('should add a class to existing ones', () => {
7 | expect(unionClassNames('first button', 'last')).toBe('first button last');
8 | });
9 |
10 | it('should not add a class in case it is a duplicate', () => {
11 | expect(unionClassNames('first button', 'button')).toBe('first button');
12 | });
13 |
14 | it('should ignore undefined values', () => {
15 | expect(unionClassNames(undefined, undefined)).toBe('');
16 | expect(unionClassNames(undefined, 'button')).toBe('button');
17 | expect(unionClassNames('first', undefined)).toBe('first');
18 | });
19 |
20 | it('should work with names which contain the new class', () => {
21 | expect(unionClassNames('first button-first', 'button')).toBe('first button-first button');
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/src/components/ActionArea.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import actionAreaStyle from '../style/actionArea';
4 | import { omit } from '../utils/helpers';
5 |
6 | const actionAreaPropTypes = {
7 | activeStyle: PropTypes.object,
8 | children: PropTypes.oneOfType([
9 | PropTypes.arrayOf(PropTypes.node),
10 | PropTypes.node,
11 | ]),
12 | hoverStyle: PropTypes.object,
13 | onTouchStart: PropTypes.func,
14 | onTouchEnd: PropTypes.func,
15 | onTouchCancel: PropTypes.func,
16 | onMouseDown: PropTypes.func,
17 | onMouseEnter: PropTypes.func,
18 | onMouseLeave: PropTypes.func,
19 |
20 | // TODO investigate how we solve mouseUp in other compents (like the right click edgecase)
21 | onMouseUp: PropTypes.func,
22 | onUpdate: PropTypes.func,
23 | onClick: PropTypes.func,
24 | style: PropTypes.object,
25 | };
26 |
27 | function sanitizeChildProps(properties) {
28 | return omit(properties, Object.keys(actionAreaPropTypes));
29 | }
30 |
31 | /**
32 | * ActionArea
33 | *
34 | * The purpose of this component is to provide a button like behaviour for a
35 | * click like interaction within other components. Button can't be used in such
36 | * cases as it always will have it's own focus which is not desired in
37 | * components like DatePicker e.g. next month button.
38 | *
39 | * Note: Use the ActionArea's onUpdate instead of onClick as otherwise on iOS9
40 | * the ActionArea will trigger onFocus for it's parent with a set tabindex.
41 | */
42 | export default class ActionArea extends Component {
43 |
44 | constructor(properties) {
45 | super(properties);
46 | this.childProps = sanitizeChildProps(properties);
47 | }
48 |
49 | static displayName = 'ActionArea';
50 | static propTypes = actionAreaPropTypes;
51 |
52 | state = {
53 | // Note: On touch devices mouseEnter is fired while mouseLeave is not.
54 | // This would result in a hover effect that keeps active until another
55 | // element is focused on. This would result in the same behaviour as using
56 | // the :hover pseudo class. To prevent it from happening activating the
57 | // hover state is prevented when a touch event has been triggered before.
58 | // source: http://stackoverflow.com/a/22444532/837709
59 | isIgnoringHover: false,
60 | isActive: false,
61 | isHovered: false,
62 | };
63 |
64 | /**
65 | * Update the childProps based on the updated properties passed to the card.
66 | */
67 | componentWillReceiveProps(properties) {
68 | this.childProps = sanitizeChildProps(properties);
69 | }
70 |
71 | /**
72 | * As soon as the mouse enters the component the isHovered state is activated.
73 | *
74 | * The state isHovered is not set to true in case onMouseEnter was triggered
75 | * by a touch event.
76 | */
77 | _onMouseEnter = (event) => {
78 | if (!this.state.isIgnoringHover) {
79 | this.setState({
80 | isHovered: true,
81 | isIgnoringHover: false,
82 | });
83 | }
84 |
85 | if (this.props.onMouseEnter) {
86 | this.props.onMouseEnter(event);
87 | }
88 | };
89 |
90 | /**
91 | * Deactivate the isHovered state.
92 | */
93 | _onMouseLeave = (event) => {
94 | this.setState({
95 | isHovered: false,
96 | });
97 |
98 | if (this.props.onMouseLeave) {
99 | this.props.onMouseLeave(event);
100 | }
101 | };
102 |
103 | /**
104 | * Activates the active state in case the main mouse button was pressed.
105 | */
106 | _onMouseDown = (event) => {
107 | if (event.button === 0) {
108 | this.setState({
109 | isActive: true,
110 | });
111 | }
112 |
113 | if (this.props.onMouseDown) {
114 | this.props.onMouseDown(event);
115 | }
116 | };
117 |
118 | /**
119 | * Triggers onUpdate in case the mouse button was pressed on this element.
120 | *
121 | * In addition the active state is deactivated.
122 | */
123 | _onMouseUp = (event) => {
124 | if (event.button === 0) {
125 | this.setState({
126 | isActive: false,
127 | });
128 | }
129 |
130 | if (this.props.onMouseUp) {
131 | this.props.onMouseUp(event);
132 | }
133 | };
134 |
135 | /**
136 | * Updates the button to be active and makes sure the next onMouseEnter is
137 | * ignored.
138 | */
139 | _onTouchStart = (event) => {
140 | if (event.touches.length === 1) {
141 | this.setState({
142 | isActive: true,
143 | isIgnoringHover: true,
144 | });
145 | }
146 |
147 | if (this.props.onTouchStart) {
148 | this.props.onTouchStart(event);
149 | }
150 | };
151 |
152 | /**
153 | * Triggers onUpdate in case the touch event started on this element and makes
154 | * sure the next onMouseEnter is ignored.
155 | */
156 | _onTouchEnd = () => {
157 | this.setState({
158 | isActive: false,
159 | isIgnoringHover: true,
160 | });
161 |
162 | if (this.props.onTouchEnd) {
163 | this.props.onTouchEnd(event);
164 | }
165 | };
166 |
167 | /**
168 | * Updates the button to be release and makes sure the next onMouseEnter is
169 | * ignored.
170 | */
171 | _onTouchCancel = () => {
172 | this.setState({
173 | isActive: false,
174 | isIgnoringHover: true,
175 | });
176 |
177 | if (this.props.onTouchCancel) {
178 | this.props.onTouchCancel(event);
179 | }
180 | };
181 |
182 | _onClick = (event) => {
183 | if (this.props.onClick) {
184 | this.props.onClick(event);
185 | }
186 |
187 | if (this.props.onUpdate) {
188 | this.props.onUpdate({});
189 | }
190 | };
191 |
192 | render() {
193 | let style = { ...actionAreaStyle.style, ...this.props.style };
194 | if (this.state.isHovered) {
195 | style = {
196 | ...style,
197 | ...actionAreaStyle.hoverStyle,
198 | ...this.props.hoverStyle,
199 | };
200 | }
201 |
202 | if (this.state.isActive) {
203 | style = {
204 | ...style,
205 | ...actionAreaStyle.activeStyle,
206 | ...this.props.activeStyle,
207 | };
208 | }
209 |
210 | return (
211 |
224 | { this.props.children }
225 |
226 | );
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/src/components/Card.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types'; // eslint-disable-line no-unused-vars
3 | import cardStyle from '../style/card';
4 |
5 | /**
6 | * Card component with a light shadow.
7 | *
8 | * This component will apply any attribute to the div that has been provided as
9 | * property & is valid for a div.
10 | */
11 | export default class Card extends Component {
12 |
13 | constructor(properties) {
14 | super(properties);
15 | const { style, ...childProps } = properties; // eslint-disable-line no-unused-vars
16 | this.childProps = childProps;
17 | }
18 |
19 | static displayName = 'Card';
20 |
21 | static propTypes = {
22 | children: PropTypes.oneOfType([
23 | PropTypes.arrayOf(PropTypes.node),
24 | PropTypes.node,
25 | ]),
26 | style: PropTypes.object,
27 | };
28 |
29 | /**
30 | * Update the childProps based on the updated properties passed to the card.
31 | */
32 | componentWillReceiveProps(properties) {
33 | const { style, ...childProps } = properties; // eslint-disable-line no-unused-vars
34 | this.childProps = childProps;
35 | }
36 |
37 | render() {
38 | const divStyle = { ...cardStyle.style, ...this.props.style };
39 |
40 | return (
41 |
42 | { this.props.children }
43 |
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/Choice.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | /**
5 | * Choice component
6 | */
7 | export default class Choice extends Component {
8 |
9 | static displayName = 'Choice';
10 |
11 | static propTypes = {
12 | children: PropTypes.oneOfType([
13 | PropTypes.arrayOf(PropTypes.node),
14 | PropTypes.node,
15 | ]),
16 | value: PropTypes.bool.isRequired,
17 | };
18 |
19 | render() {
20 | return {this.props.children}
;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/ComboBoxItem.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | /**
5 | * Belle internal component to wrap an Option in a ComboBox.
6 | *
7 | * This component exists to avoid binding functions in JSX.
8 | */
9 | export default class ComboBoxItem extends Component {
10 |
11 | static displayName = 'ComboBoxItem';
12 |
13 | static propTypes = {
14 | children: PropTypes.node.isRequired,
15 | index: PropTypes.number.isRequired,
16 | onItemClick: PropTypes.func.isRequired,
17 | onItemTouchStart: PropTypes.func.isRequired,
18 | onItemTouchEnd: PropTypes.func.isRequired,
19 | onItemTouchCancel: PropTypes.func.isRequired,
20 | onItemMouseEnter: PropTypes.func.isRequired,
21 | onItemMouseLeave: PropTypes.func.isRequired,
22 | };
23 |
24 | _onClick = () => {
25 | this.props.onItemClick(this.props.index);
26 | };
27 |
28 | _onTouchStart = (event) => {
29 | this.props.onItemTouchStart(event, this.props.index);
30 | };
31 |
32 | _onTouchEnd = (event) => {
33 | this.props.onItemTouchEnd(event, this.props.index);
34 | };
35 |
36 | _onTouchCancel = () => {
37 | this.props.onItemTouchCancel();
38 | };
39 |
40 | _onMouseEnter = () => {
41 | this.props.onItemMouseEnter(this.props.index);
42 | };
43 |
44 | _onMouseLeave = () => {
45 | this.props.onItemMouseLeave();
46 | };
47 |
48 | _onMouseDown = (event) => {
49 | event.preventDefault();
50 | };
51 |
52 | render() {
53 | return (
54 |
64 | { this.props.children }
65 |
66 | );
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/components/Day.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | /**
5 | * Belle internal component to wrap a Day in a DatePicker.
6 | *
7 | * This component exists to avoid binding functions in JSX.
8 | */
9 | export default class Day extends Component {
10 |
11 | static displayName = 'Day';
12 |
13 | static propTypes = {
14 | children: PropTypes.node.isRequired,
15 | dateKey: PropTypes.string.isRequired,
16 | onDayMouseEnter: PropTypes.func.isRequired,
17 | onDayMouseLeave: PropTypes.func.isRequired,
18 | onDayMouseDown: PropTypes.func.isRequired,
19 | onDayMouseUp: PropTypes.func.isRequired,
20 | onDayTouchStart: PropTypes.func.isRequired,
21 | onDayTouchEnd: PropTypes.func.isRequired,
22 | onDayTouchCancel: PropTypes.func.isRequired,
23 | style: PropTypes.object.isRequired,
24 | dayProps: PropTypes.any,
25 | selected: PropTypes.bool.isRequired,
26 | };
27 |
28 | _onMouseEnter = (event) => {
29 | this.props.onDayMouseEnter(this.props.dateKey, event);
30 | };
31 |
32 | _onMouseLeave = (event) => {
33 | this.props.onDayMouseLeave(this.props.dateKey, event);
34 | };
35 |
36 | _onMouseDown = (event) => {
37 | this.props.onDayMouseDown(this.props.dateKey, event);
38 | };
39 |
40 | _onMouseUp = (event) => {
41 | this.props.onDayMouseUp(this.props.dateKey, event);
42 | };
43 |
44 | _onTouchStart = (event) => {
45 | this.props.onDayTouchStart(this.props.dateKey, event);
46 | };
47 |
48 | _onTouchEnd = (event) => {
49 | this.props.onDayTouchEnd(this.props.dateKey, event);
50 | };
51 |
52 | _onTouchCancel = (event) => {
53 | this.props.onDayTouchCancel(this.props.dateKey, event);
54 | };
55 |
56 | render() {
57 | return (
58 |
71 | { this.props.children }
72 |
73 | );
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/components/DisabledDay.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | /**
5 | * Belle internal component to wrap a DisabledDay in a DatePicker.
6 | *
7 | * This component exists to avoid binding functions in JSX.
8 | */
9 | export default class DisabledDay extends Component {
10 |
11 | static displayName = 'DisabledDay';
12 |
13 | static propTypes = {
14 | children: PropTypes.node.isRequired,
15 | dateKey: PropTypes.string.isRequired,
16 | onDayMouseEnter: PropTypes.func.isRequired,
17 | onDayMouseLeave: PropTypes.func.isRequired,
18 | style: PropTypes.object.isRequired,
19 | disabledDayProps: PropTypes.any,
20 | };
21 |
22 | _onMouseEnter = (event) => {
23 | this.props.onDayMouseEnter(this.props.dateKey, event);
24 | };
25 |
26 | _onMouseLeave = (event) => {
27 | this.props.onDayMouseLeave(this.props.dateKey, event);
28 | };
29 |
30 | render() {
31 | return (
32 |
38 | { this.props.children }
39 |
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/Option.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { omit } from '../utils/helpers';
4 | import style from '../style/option';
5 |
6 | const optionPropTypes = {
7 | children: PropTypes.oneOfType([
8 | PropTypes.arrayOf(PropTypes.node),
9 | PropTypes.node,
10 | ]),
11 | style: PropTypes.object,
12 | hoverStyle: PropTypes.object,
13 | selectStyle: PropTypes.object,
14 | disabledSelectStyle: PropTypes.object,
15 | _isDisplayedAsSelected: PropTypes.bool,
16 | value: PropTypes.oneOfType([
17 | PropTypes.bool,
18 | PropTypes.string,
19 | PropTypes.number,
20 | ]).isRequired,
21 | identifier: PropTypes.oneOfType([
22 | PropTypes.bool,
23 | PropTypes.string,
24 | PropTypes.number,
25 | ]),
26 | };
27 |
28 | /**
29 | * Returns an object with properties that are relevant for the wrapping div.
30 | */
31 | function sanitizeChildProps(properties) {
32 | return omit(properties, Object.keys(optionPropTypes));
33 | }
34 |
35 | /**
36 | * Option component.
37 | *
38 | * This component should be used together with Belle's Select.
39 | */
40 | export default class Option extends Component {
41 |
42 | constructor(properties) {
43 | super(properties);
44 | this.state = {
45 | childProps: sanitizeChildProps(properties),
46 | };
47 | }
48 |
49 | static displayName = 'Option';
50 |
51 | static propTypes = optionPropTypes;
52 |
53 | static contextTypes = {
54 | isDisabled: PropTypes.bool.isRequired,
55 | isHoveredValue: PropTypes.oneOfType([
56 | PropTypes.bool,
57 | PropTypes.string,
58 | PropTypes.number,
59 | ]),
60 | };
61 |
62 | static defaultProps = {
63 | _isDisplayedAsSelected: false,
64 | };
65 |
66 | /**
67 | * Update the childProps based on the updated properties passed to the
68 | * Option.
69 | */
70 | componentWillReceiveProps(properties) {
71 | this.setState({ childProps: sanitizeChildProps(properties) });
72 | }
73 |
74 | render() {
75 | let optionStyle;
76 |
77 | if (this.props._isDisplayedAsSelected) {
78 | optionStyle = {
79 | ...style.selectStyle,
80 | ...this.props.selectStyle,
81 | };
82 | if (this.context.isDisabled) {
83 | optionStyle = {
84 | ...optionStyle,
85 | ...style.disabledSelectStyle,
86 | ...this.props.disabledSelectStyle,
87 | };
88 | }
89 | } else {
90 | optionStyle = {
91 | ...style.style,
92 | ...this.props.style,
93 | };
94 | if (this.context.isHoveredValue === this.props.value) {
95 | optionStyle = {
96 | ...optionStyle,
97 | ...style.hoverStyle,
98 | ...this.props.hoverStyle,
99 | };
100 | }
101 | }
102 |
103 | return (
104 |
108 | { this.props.children }
109 |
110 | );
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/components/Placeholder.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { omit } from '../utils/helpers';
4 | import style from '../style/placeholder';
5 |
6 | const placeholderPropTypes = {
7 | children: PropTypes.oneOfType([
8 | PropTypes.arrayOf(PropTypes.node),
9 | PropTypes.node,
10 | ]),
11 | style: PropTypes.object,
12 | disabledStyle: PropTypes.object,
13 | _isDisabled: PropTypes.bool,
14 | };
15 |
16 | /**
17 | * Returns an object with properties that are relevant for the wrapping div.
18 | */
19 | function sanitizeChildProps(properties) {
20 | return omit(properties, Object.keys(placeholderPropTypes));
21 | }
22 |
23 | /**
24 | * Placeholder component.
25 | *
26 | * This component should be used together with Belle's Select.
27 | */
28 | export default class Placeholder extends Component {
29 |
30 | constructor(properties) {
31 | super(properties);
32 | this.state = {
33 | childProps: sanitizeChildProps(properties),
34 | };
35 | }
36 |
37 | static displayName = 'Placeholder';
38 |
39 | static propTypes = placeholderPropTypes;
40 |
41 | static defaultProps = {
42 | _isDisabled: false,
43 | };
44 |
45 | /**
46 | * Update the childProps based on the updated properties passed to the
47 | * Placeholder.
48 | */
49 | componentWillReceiveProps(properties) {
50 | this.setState({ childProps: sanitizeChildProps(properties) });
51 | }
52 |
53 | render() {
54 | let computedStyle = {
55 | ...style.style,
56 | ...this.props.style,
57 | };
58 | if (this.props._isDisabled) {
59 | computedStyle = {
60 | ...computedStyle,
61 | ...style.disabledStyle,
62 | ...this.props.disabledStyle,
63 | };
64 | }
65 |
66 | return (
67 |
68 | { this.props.children }
69 |
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/components/SelectItem.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | /**
5 | * Belle internal component to wrap an Option in a Select.
6 | *
7 | * This component exists to avoid binding functions in JSX.
8 | */
9 | export default class SelectItem extends Component {
10 |
11 | static displayName = 'SelectItem';
12 |
13 | static propTypes = {
14 | children: PropTypes.node.isRequired,
15 | isHovered: PropTypes.bool.isRequired,
16 | index: PropTypes.number.isRequired,
17 | onItemClick: PropTypes.func.isRequired,
18 | onItemTouchStart: PropTypes.func.isRequired,
19 | onItemTouchMove: PropTypes.func.isRequired,
20 | onItemTouchEnd: PropTypes.func.isRequired,
21 | onItemTouchCancel: PropTypes.func.isRequired,
22 | onItemMouseEnter: PropTypes.func.isRequired,
23 | };
24 |
25 | _onClick = () => {
26 | this.props.onItemClick(this.props.index);
27 | };
28 |
29 | _onTouchStart = (event) => {
30 | this.props.onItemTouchStart(event, this.props.index);
31 | };
32 |
33 | _onTouchMove = () => {
34 | this.props.onItemTouchMove();
35 | };
36 |
37 | _onTouchEnd = (event) => {
38 | this.props.onItemTouchEnd(event, this.props.index);
39 | };
40 |
41 | _onTouchCancel = () => {
42 | this.props.onItemTouchCancel();
43 | };
44 |
45 | _onMouseEnter = () => {
46 | this.props.onItemMouseEnter(this.props.index);
47 | };
48 |
49 | render() {
50 | return (
51 |
61 | { this.props.children }
62 |
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/components/Separator.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { omit } from '../utils/helpers';
4 | import style from '../style/separator';
5 |
6 | const separatorPropTypes = {
7 | children: PropTypes.oneOfType([
8 | PropTypes.arrayOf(PropTypes.node),
9 | PropTypes.node,
10 | ]),
11 | style: PropTypes.object,
12 | };
13 |
14 | /**
15 | * Returns an object with properties that are relevant for the wrapping div.
16 | */
17 | function sanitizeChildProps(properties) {
18 | return omit(properties, Object.keys(separatorPropTypes));
19 | }
20 |
21 | /**
22 | * Separator component.
23 | *
24 | * This component should be used together with Belle's Select.
25 | */
26 | export default class Separator extends Component {
27 |
28 | constructor(properties) {
29 | super(properties);
30 | this.state = {
31 | childProps: sanitizeChildProps(properties),
32 | };
33 | }
34 |
35 | static displayName = 'Separator';
36 |
37 | static propTypes = separatorPropTypes;
38 |
39 | /**
40 | * Update the childProperties based on the updated properties passed to the
41 | * Separator.
42 | */
43 | componentWillReceiveProps(properties) {
44 | this.setState({ childProps: sanitizeChildProps(properties) });
45 | }
46 |
47 | render() {
48 | const computedStyle = {
49 | ...style.style,
50 | ...this.props.style,
51 | };
52 |
53 | return (
54 |
55 | { this.props.children }
56 |
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/components/Spinner.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import spinnerStyle from '../style/spinner';
4 |
5 | const animationDelay = (delay) => ({
6 | MozAnimationDelay: delay,
7 | WebkitAnimationDelay: delay,
8 | OAnimationDelay: delay,
9 | animationDelay: delay,
10 | });
11 |
12 | /**
13 | * Spinner component to be used as loading indicator.
14 | */
15 | export default class Spinner extends Component {
16 |
17 | static displayName = 'Spinner';
18 |
19 | static propTypes = {
20 | characterProps: PropTypes.object,
21 | characterStyle: PropTypes.object,
22 | style: PropTypes.object,
23 | };
24 |
25 | render() {
26 | const { style, characterProps, characterStyle, ...childProps } = this.props;
27 | const computedCharStyle = { ...spinnerStyle.characterStyle, ...characterStyle };
28 | return (
29 |
30 |
31 | .
32 |
33 |
34 | .
35 |
36 |
37 | .
38 |
39 |
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/components/TextInput.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import ReactDOM from 'react-dom';
4 | import calculateTextareaHeight from '../utils/calculate-textarea-height';
5 | import { injectStyles, removeStyle } from '../utils/inject-style';
6 | import unionClassNames from '../utils/union-class-names';
7 | import { omit, has, uniqueId } from '../utils/helpers';
8 | import style from '../style/text-input';
9 |
10 | const newLineRegex = /[\r\n]/g;
11 |
12 | const textInputPropTypes = {
13 | className: PropTypes.string,
14 | minHeight: PropTypes.number,
15 | maxHeight: PropTypes.number,
16 | minRows: PropTypes.number,
17 | maxRows: PropTypes.number,
18 | style: PropTypes.object,
19 | hoverStyle: PropTypes.object,
20 | focusStyle: PropTypes.object,
21 | allowNewLine: PropTypes.bool,
22 | disabled: PropTypes.bool,
23 | disabledStyle: PropTypes.object,
24 | disabledHoverStyle: PropTypes.object,
25 | onUpdate: PropTypes.func,
26 | onKeyDown: PropTypes.func,
27 | value: PropTypes.string,
28 | defaultValue: PropTypes.string,
29 | valueLink: PropTypes.shape({
30 | value: PropTypes.string.isRequired,
31 | requestChange: PropTypes.func.isRequired,
32 | }),
33 | };
34 |
35 | /**
36 | * Returns an object with properties that are relevant for the TextInput's textarea.
37 | *
38 | * As the height of the textarea needs to be calculated valueLink can not be
39 | * passed down to the textarea, but made available through this component.
40 | */
41 | function sanitizeChildProps(properties) {
42 | const childProps = omit(properties, Object.keys(textInputPropTypes));
43 | if (typeof properties.valueLink === 'object') {
44 | childProps.value = properties.valueLink.value;
45 | }
46 |
47 | return childProps;
48 | }
49 |
50 | /**
51 | * Update hover & focus style for the speficied styleId.
52 | *
53 | * @param styleId {string} - a unique id that exists as class attribute in the DOM
54 | * @param properties {object} - the components properties optionally containing hoverStyle & focusStyle
55 | */
56 | function updatePseudoClassStyle(styleId, properties) {
57 | const hoverStyle = {
58 | ...style.hoverStyle,
59 | ...properties.hoverStyle,
60 | };
61 | const focusStyle = {
62 | ...style.focusStyle,
63 | ...properties.focusStyle,
64 | };
65 | const disabledHoverStyle = {
66 | ...style.disabledHoverStyle,
67 | ...properties.disabledHoverStyle,
68 | };
69 |
70 | const styles = [
71 | {
72 | id: styleId,
73 | style: hoverStyle,
74 | pseudoClass: 'hover',
75 | },
76 | {
77 | id: styleId,
78 | style: focusStyle,
79 | pseudoClass: 'focus',
80 | },
81 | {
82 | id: styleId,
83 | style: disabledHoverStyle,
84 | pseudoClass: 'hover',
85 | disabled: true,
86 | },
87 | ];
88 | injectStyles(styles);
89 | }
90 |
91 | /**
92 | * TextInput component with great UX like autogrowing & handling states
93 | *
94 | * Note on styling: Right now this component doen't allow to change style after
95 | * initialisation.
96 | *
97 | * Note on resizing:
98 | * If you fill a textarea only with spaces and the cursor reaches the right end
99 | * it won't break the line. This leads to unexpected behaviour for the automatic
100 | * resizing.
101 | *
102 | * This component was highly inspired by the great work from these guys
103 | * - Andrey Popp: https://github.com/andreypopp/react-textarea-autosize
104 | * - Eugene: https://gist.github.com/eugene1g/5dbaa7d35d0c7d5c2c56
105 | */
106 | export default class TextInput extends Component {
107 |
108 | constructor(properties) {
109 | super(properties);
110 | let inputValue;
111 |
112 | if (has(properties, 'valueLink')) {
113 | inputValue = properties.valueLink.value;
114 | } else if (has(properties, 'value')) {
115 | inputValue = properties.value;
116 | } else if (has(properties, 'defaultValue')) {
117 | inputValue = properties.defaultValue;
118 | }
119 |
120 | this.state = {
121 | height: 'auto',
122 | inputValue,
123 | };
124 | this.textareaProps = sanitizeChildProps(properties);
125 | }
126 |
127 | static displayName = 'TextInput';
128 |
129 | static propTypes = textInputPropTypes;
130 |
131 | static defaultProps = {
132 | allowNewLine: false,
133 | disabled: false,
134 | };
135 |
136 | /**
137 | * Generates the style-id & inject the focus & hover style.
138 | */
139 | componentWillMount() {
140 | const id = uniqueId();
141 | this._styleId = `style-id${id}`;
142 | updatePseudoClassStyle(this._styleId, this.props);
143 | }
144 |
145 | /**
146 | * Right after the component go injected into the DOM it should be resized.
147 | */
148 | componentDidMount() {
149 | this._triggerResize(this.state.inputValue);
150 | }
151 |
152 | /**
153 | * Update the properties passed to the textarea and resize as with the new
154 | * properties the height might have changed.
155 | */
156 | componentWillReceiveProps(properties) {
157 | // Makes sure we have inputValue available when triggering a resize.
158 | const newState = {
159 | inputValue: this.state.inputValue,
160 | };
161 | if (has(properties, 'valueLink')) {
162 | newState.inputValue = properties.valueLink.value;
163 | } else if (has(properties, 'value')) {
164 | newState.inputValue = properties.value;
165 | }
166 |
167 | this.textareaProps = sanitizeChildProps(properties);
168 | removeStyle(this._styleId);
169 | updatePseudoClassStyle(this._styleId, properties);
170 | this.setState(newState, () => this._triggerResize(newState.inputValue));
171 | }
172 |
173 | /**
174 | * Remove a component's associated styles whenever it gets removed from the DOM.
175 | */
176 | componentWillUnmount() {
177 | removeStyle(this._styleId);
178 | }
179 |
180 | /**
181 | * Prevent any newline (except allowNewLine is active) and pass the event to
182 | * the onKeyDown property.
183 | *
184 | * This is an optimization to avoid adding a newline char & removing it right
185 | * away in the onUpdate callback.
186 | */
187 | _onKeyDown = (event) => {
188 | if (!this.props.allowNewLine && event.key === 'Enter') {
189 | event.preventDefault();
190 | }
191 |
192 | if (this.props.onKeyDown) {
193 | this.props.onKeyDown(event);
194 | }
195 | };
196 |
197 | /**
198 | * Update the height and calls the provided change callback for onUpdate
199 | * or valueLink.
200 | *
201 | * In addition newline characters are replaced by spaces in the textarea value
202 | * in case allowNewLine is set to false and newLine characters could be found.
203 | */
204 | _onChange = (event) => {
205 | let value = event.target.value;
206 |
207 | if (!this.props.allowNewLine && value.match(newLineRegex) !== null) {
208 | value = value.replace(newLineRegex, ' ');
209 | }
210 |
211 | if (has(this.props, 'valueLink')) {
212 | this.props.valueLink.requestChange(value);
213 | } else if (has(this.props, 'defaultValue')) {
214 | this.setState({
215 | inputValue: value,
216 | });
217 | }
218 |
219 | if (this.props.onUpdate) {
220 | this.props.onUpdate({ value });
221 | }
222 |
223 | this._triggerResize(value);
224 | };
225 |
226 | /**
227 | * Calculate the height and store the new height in the state to trigger a render.
228 | */
229 | _triggerResize(textareaValue) {
230 | const height = calculateTextareaHeight(ReactDOM.findDOMNode(this), textareaValue, this.props.minRows, this.props.maxRows, this.props.minHeight, this.props.maxHeight);
231 | this.setState({ height });
232 | }
233 |
234 | render() {
235 | let textareaStyle = {
236 | ...style.style,
237 | ...this.props.style,
238 | };
239 |
240 | if (this.props.disabled) {
241 | textareaStyle = {
242 | ...textareaStyle,
243 | ...style.disabledStyle,
244 | ...this.props.disabledStyle,
245 | };
246 | }
247 |
248 | textareaStyle.height = this.state.height;
249 | return (
250 |
259 | );
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/src/config/button.js:
--------------------------------------------------------------------------------
1 | const buttonConfig = {
2 | preventFocusStyleForTouchAndClick: true,
3 | };
4 |
5 | export default buttonConfig;
6 |
--------------------------------------------------------------------------------
/src/config/datePicker.js:
--------------------------------------------------------------------------------
1 | const datePickerConfig = {
2 | preventFocusStyleForTouchAndClick: true,
3 | };
4 |
5 | export default datePickerConfig;
6 |
--------------------------------------------------------------------------------
/src/config/i18n.js:
--------------------------------------------------------------------------------
1 | const i18nConfig = {
2 |
3 | localeData: {
4 | nl: {
5 | monthNames: ['januari', 'februari', 'maart', 'april', 'mei', 'juni',
6 | 'juli', 'augustus', 'september', 'oktober', 'november', 'december',
7 | ],
8 | dayNamesMin: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
9 | firstDay: 1,
10 | weekEnd: 0,
11 | isRTL: false,
12 | },
13 | ar: {
14 | monthNames: ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو',
15 | 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر',
16 | ],
17 | dayNamesMin: ['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'],
18 | firstDay: 6,
19 | weekEnd: 5,
20 | isRTL: true,
21 | },
22 | he: {
23 | monthNames: ['ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני',
24 | 'יולי', 'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר',
25 | ],
26 | dayNamesMin: ['א\'', 'ב\'', 'ג\'', 'ד\'', 'ה\'', 'ו\'', 'שבת'],
27 | firstDay: 0,
28 | weekEnd: 6,
29 | isRTL: true,
30 | },
31 | fr: {
32 | monthNames: ['janvier', 'février', 'mars', 'avril', 'mai', 'juin',
33 | 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre',
34 | ],
35 | dayNamesMin: ['D', 'L', 'M', 'M', 'J', 'V', 'S'],
36 | firstDay: 1,
37 | weekEnd: 0,
38 | isRTL: false,
39 | },
40 | 'zh-CN': {
41 | monthNames: ['一月', '二月', '三月', '四月', '五月', '六月',
42 | '七月', '八月', '九月', '十月', '十一月', '十二月',
43 | ],
44 | dayNamesMin: ['日', '一', '二', '三', '四', '五', '六'],
45 | firstDay: 1,
46 | weekEnd: 0,
47 | isRTL: false,
48 | },
49 | },
50 |
51 | };
52 |
53 | export default i18nConfig;
54 |
--------------------------------------------------------------------------------
/src/config/rating.js:
--------------------------------------------------------------------------------
1 | const ratingConfig = {
2 | preventFocusStyleForTouchAndClick: true,
3 | };
4 |
5 | export default ratingConfig;
6 |
--------------------------------------------------------------------------------
/src/config/select.js:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom';
2 | import isComponentOfType from '../utils/is-component-of-type.js';
3 | import { filterReactChildren, findIndex } from '../utils/helpers';
4 | import Option from '../components/Option';
5 | import Separator from '../components/Separator';
6 |
7 | /**
8 | * Returns the index of the entry with a certain value from the component's
9 | * children.
10 | *
11 | * The index search includes separator & option components.
12 | */
13 | const findIndexOfSelectedOption = (component) => {
14 | const filterFunction = (child) => (isComponentOfType(Option, child) || isComponentOfType(Separator, child));
15 | return findIndex(filterReactChildren(
16 | component.props.children, filterFunction),
17 | (element) => (element.props.value === component.state.selectedValue)
18 | );
19 | };
20 |
21 | const selectConfig = {
22 |
23 | shouldPositionOptions: true,
24 |
25 | /**
26 | * Repositions to the menu to position the focusedOption right on top
27 | * of the selected one.
28 | *
29 | * @param selectComponent {object} - the Select component itself accessible with `this`
30 | */
31 | positionOptions(selectComponent) {
32 | const menuNode = ReactDOM.findDOMNode(selectComponent.refs.menu);
33 | const menuStyle = window.getComputedStyle(menuNode, null);
34 | const menuWidth = parseFloat(menuStyle.getPropertyValue('width'));
35 |
36 | // In case of a placeholder no option is focused on initially
37 | let optionIndex;
38 | if (selectComponent.state.selectedValue) {
39 | optionIndex = findIndexOfSelectedOption(selectComponent);
40 | } else {
41 | optionIndex = 0;
42 | }
43 |
44 | const option = menuNode.childNodes[optionIndex];
45 |
46 | const menuHeight = parseFloat(menuStyle.getPropertyValue('height'));
47 | const menuTopBorderWidth = parseFloat(menuStyle.getPropertyValue('border-top-width'));
48 |
49 | // In order to work with legacy browsers the second paramter for pseudoClass
50 | // has to be provided http://caniuse.com/#feat=getcomputedstyle
51 | const optionStyle = window.getComputedStyle(option.childNodes[0], null);
52 | const optionPaddingTop = parseFloat(optionStyle.getPropertyValue('padding-top'));
53 | const optionPaddingLeft = parseFloat(optionStyle.getPropertyValue('padding-top'));
54 |
55 | const selectedOptionWrapperNode = ReactDOM.findDOMNode(selectComponent.refs.selectedOptionWrapper);
56 | const selectedOptionWrapperStyle = window.getComputedStyle(selectedOptionWrapperNode, null);
57 | const selectedOptionWrapperPaddingTop = parseFloat(selectedOptionWrapperStyle.getPropertyValue('padding-top'));
58 |
59 | const newTop = option.offsetTop + optionPaddingTop - selectedOptionWrapperPaddingTop + menuTopBorderWidth;
60 | const newLeft = option.offsetLeft + optionPaddingLeft;
61 |
62 | // Top positioning
63 | if (menuHeight < menuNode.scrollHeight) {
64 | if (newTop + menuHeight > menuNode.scrollHeight) {
65 | // In case scrolling is not enough the box needs to be moved more to
66 | // the top to match the same position.
67 | const maxScrollTop = menuNode.scrollHeight - menuHeight;
68 | menuNode.scrollTop = maxScrollTop;
69 | menuNode.style.top = `-${newTop - maxScrollTop}px`;
70 | } else {
71 | // In case it's the first entry scrolling is not used to respect the
72 | // menu's paddingTop.
73 | if (optionIndex === 0) {
74 | menuNode.scrollTop = 0;
75 | menuNode.style.top = `-${newTop}px`;
76 | } else {
77 | menuNode.scrollTop = newTop;
78 | }
79 | }
80 | } else {
81 | menuNode.style.top = `-${newTop}px`;
82 | }
83 |
84 | // Left positioning
85 | menuNode.style.left = `-${newLeft}px`;
86 |
87 | // Increasing the width
88 | //
89 | // Pro:
90 | // - It gives a option in the menu the same width
91 | // as in the selectedOptionWrapper.
92 | // - There is space to keep the text of the option on the exact same pixel
93 | // when opening. The menu is symetric in relation to the
94 | // selectedOptionWrapper.
95 | //
96 | // Con:
97 | // - Adding the padding could cause issue with design as it gets wider than
98 | // the original field.
99 | menuNode.style.width = `${menuWidth + newLeft * 2}px`;
100 | },
101 | };
102 |
103 | export default selectConfig;
104 |
--------------------------------------------------------------------------------
/src/config/toggle.js:
--------------------------------------------------------------------------------
1 | const toggleConfig = {
2 | preventFocusStyleForTouchAndClick: true,
3 | };
4 |
5 | export default toggleConfig;
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Button from './components/Button';
2 | import Card from './components/Card';
3 | import Choice from './components/Choice';
4 | import Option from './components/Option';
5 | import Placeholder from './components/Placeholder';
6 | import Select from './components/Select';
7 | import Separator from './components/Separator';
8 | import TextInput from './components/TextInput';
9 | import Rating from './components/Rating';
10 | import ComboBox from './components/ComboBox';
11 | import Spinner from './components/Spinner';
12 | import Toggle from './components/Toggle';
13 | import DatePicker from './components/DatePicker';
14 |
15 | import actionAreaStyle from './style/actionArea';
16 | import buttonStyle from './style/button';
17 | import cardStyle from './style/card';
18 | import placeholderStyle from './style/placeholder';
19 | import optionStyle from './style/option';
20 | import selectStyle from './style/select';
21 | import separatorStyle from './style/separator';
22 | import textInputStyle from './style/text-input';
23 | import spinnerStyle from './style/spinner';
24 | import toggleStyle from './style/toggle';
25 | import ratingStyle from './style/rating';
26 | import comboBoxStyle from './style/combo-box';
27 | import datePickerStyle from './style/date-picker';
28 |
29 | import selectConfig from './config/select';
30 | import buttonConfig from './config/button';
31 | import ratingConfig from './config/rating';
32 | import toggleConfig from './config/toggle';
33 | import i18nConfig from './config/i18n';
34 | import datePickerConfig from './config/datePicker';
35 |
36 | module.exports = {
37 | Button,
38 | Card,
39 | Choice,
40 | Option,
41 | Placeholder,
42 | Select,
43 | Separator,
44 | TextInput,
45 | Rating,
46 | ComboBox,
47 | Spinner,
48 | Toggle,
49 | DatePicker,
50 | style: {
51 | actionArea: actionAreaStyle,
52 | button: buttonStyle,
53 | card: cardStyle,
54 | comboBox: comboBoxStyle,
55 | datePicker: datePickerStyle,
56 | option: optionStyle,
57 | placeholder: placeholderStyle,
58 | rating: ratingStyle,
59 | select: selectStyle,
60 | separator: separatorStyle,
61 | spinner: spinnerStyle,
62 | textInput: textInputStyle,
63 | toggle: toggleStyle,
64 | },
65 | config: {
66 | select: selectConfig,
67 | button: buttonConfig,
68 | rating: ratingConfig,
69 | toggle: toggleConfig,
70 | i18n: i18nConfig,
71 | datePicker: datePickerConfig,
72 | },
73 | };
74 |
--------------------------------------------------------------------------------
/src/style/actionArea.js:
--------------------------------------------------------------------------------
1 | const actionAreaStyle = {
2 |
3 | style: {
4 | boxSizing: 'border-box',
5 | color: '#716D6D',
6 | fontSize: 17,
7 | paddingTop: '11px',
8 | paddingBottom: '11px',
9 | paddingLeft: '16px',
10 | paddingRight: '16px',
11 | marginTop: -1,
12 | textAlign: 'center',
13 | textDecoration: 'none',
14 | width: 48,
15 | borderLeft: '1px solid #E0E0E0',
16 | borderRight: '1px solid #E0E0E0',
17 | borderTop: '1px solid #E0E0E0',
18 | borderBottom: '1px solid #E0E0E0',
19 |
20 | /* animations */
21 | transition: 'background 0.1s, border-top 0.1s, border-bottom 0.1s, color 0.1s',
22 | transitionTimingFunction: 'ease-out',
23 |
24 | /*
25 | To avoid any kind of flickering the user won't get feedback
26 | for selecting the button text
27 | */
28 | WebkitUserSelect: 'none',
29 | MozUserSelect: 'none',
30 | MsUserSelect: 'none',
31 | userSelect: 'none',
32 |
33 | /* This button can only be pressed */
34 | MsTouchAction: 'manipulation',
35 | touchAction: 'manipulation',
36 |
37 | /*
38 | Prevent flickering while tapping on WebKit
39 | http://stackoverflow.com/a/3516243/837709
40 | */
41 | WebkitTapHighlightColor: 'transparent',
42 | },
43 |
44 | hoverStyle: {
45 | background: '#EEE',
46 | cursor: 'pointer',
47 | },
48 |
49 | activeStyle: {
50 | borderTop: '1px solid #B1B1B1',
51 | borderLeft: '1px solid #D0D0D0',
52 | borderRight: '1px solid #D0D0D0',
53 | borderBottom: '1px solid #D4D4D4',
54 | background: '#E0E0E0',
55 | },
56 | };
57 |
58 | export default actionAreaStyle;
59 |
--------------------------------------------------------------------------------
/src/style/animations.js:
--------------------------------------------------------------------------------
1 | const animations = [
2 | '\n@-webkit-keyframes belle-button-focus {\n0% { box-shadow: 0 0 0 0 rgba(140, 224, 255, 1); }\n99% { box-shadow: 0 0 0 8px #fff }\n100% { box-shadow: 0 0 0 0 rgba(0, 131, 180, 0); }\n}\n@-webkit-keyframes belle-toggle-focus {\n0% { box-shadow: 0 0 0 0 rgba(140, 224, 255, 1); }\n99% { box-shadow: 0 0 0 8px #fff }\n100% { box-shadow: 0 0 0 0 rgba(0, 131, 180, 0); }\n}\n@-webkit-keyframes belle-rating-focus {\n0% { background: rgba(140, 224, 255, 0); box-shadow: 0 0 0 0 rgba(140, 224, 255, 1); }\n25% { background: rgba(140, 224, 255, 0.15) }\n99% { background: transparent; box-shadow: 0 0 0 8px #fff }\n100% { background: transparent; box-shadow: 0 0 0 0 rgba(0, 131, 180, 0); }\n}\n\n@keyframes belle-button-focus {\n0% { box-shadow: 0 0 0 0 rgba(140, 224, 255, 1); }\n99% { box-shadow: 0 0 0 8px #fff }\n100% { box-shadow: 0 0 0 0 rgba(0, 131, 180, 0); }\n}\n@keyframes belle-toggle-focus {\n0% { box-shadow: 0 0 0 0 rgba(140, 224, 255, 1); }\n99% { box-shadow: 0 0 0 8px #fff }\n100% { box-shadow: 0 0 0 0 rgba(0, 131, 180, 0); }\n}\n@keyframes belle-rating-focus {\n0% { background: rgba(140, 224, 255, 0); box-shadow: 0 0 0 0 rgba(140, 224, 255, 1); }\n25% { background: rgba(140, 224, 255, 0.15) }\n99% { background: transparent; box-shadow: 0 0 0 8px #fff }\n100% { background: transparent; box-shadow: 0 0 0 0 rgba(0, 131, 180, 0); }\n}\n@-webkit-keyframes belle-spinner-pulse {\n 0%,\n 90%,\n 100% {\n opacity: 0;\n }\n 50% {\n opacity: 1;\n }\n }\n @keyframes belle-spinner-pulse {\n 0%,\n 90%,\n 100% {\n opacity: 0;\n }\n 50% {\n opacity: 1;\n }\n }',
3 | ];
4 |
5 | export default animations;
6 |
--------------------------------------------------------------------------------
/src/style/button.js:
--------------------------------------------------------------------------------
1 | const buttonStyle = {
2 |
3 | style: {
4 | background: '#EEEEEE',
5 | border: 0,
6 | borderTop: '1px solid #EEEEEE',
7 | borderBottom: '1px solid #BDBDBD',
8 | borderRadius: 2,
9 | boxSizing: 'border-box',
10 | color: '#616161',
11 | cursor: 'pointer',
12 | display: 'inline-block',
13 | fontSize: 17,
14 | lineHeight: '26px',
15 | padding: '8px 14px 6px 14px',
16 | textAlign: 'center',
17 | textDecoration: 'none',
18 | verticalAlign: 'bottom',
19 |
20 | /* animations */
21 | transition: 'background 0.1s, border-top 0.1s, border-bottom 0.1s, color 0.1s',
22 | transitionTimingFunction: 'ease-out',
23 |
24 | /*
25 | To avoid any kind of flickering the user won't get feedback
26 | for selecting the button text
27 | */
28 | WebkitUserSelect: 'none',
29 | MozUserSelect: 'none',
30 | MsUserSelect: 'none',
31 | userSelect: 'none',
32 |
33 | /* This button can only be pressed */
34 | MsTouchAction: 'manipulation',
35 | touchAction: 'manipulation',
36 |
37 | /*
38 | Prevent flickering while tapping on WebKit
39 | http://stackoverflow.com/a/3516243/837709
40 | */
41 | WebkitTapHighlightColor: 'transparent',
42 | },
43 |
44 | hoverStyle: {
45 | background: '#F5F5F5',
46 | borderTop: '1px solid #F5F5F5',
47 | color: '#757575',
48 | },
49 |
50 | focusStyle: {
51 | WebkitAnimation: 'belle-button-focus 2s',
52 | outline: 0, // avoid default focus behaviour
53 | },
54 |
55 | activeStyle: {
56 | background: '#E0E0E0',
57 | color: '#424242',
58 | borderBottom: '1px solid #E0E0E0',
59 | borderTop: '1px solid #BDBDBD',
60 | },
61 |
62 | disabledStyle: {
63 | color: '#C5C4C4',
64 | cursor: 'not-allowed',
65 | },
66 |
67 | disabledHoverStyle: {
68 | color: '#D0D0D0',
69 | },
70 |
71 | primaryStyle: {
72 | background: '#53C7F2',
73 | border: 0,
74 |
75 | // boxShadow: '0 1px 0px #3995B7',
76 | borderTop: '1px solid #53C7F2',
77 | borderBottom: '1px solid #3995B7',
78 |
79 | borderRadius: 2,
80 | boxSizing: 'border-box',
81 | color: '#FAFAFA',
82 | cursor: 'pointer',
83 | display: 'inline-block',
84 | fontSize: 17,
85 | lineHeight: '26px',
86 | padding: '8px 14px 6px 14px',
87 | textAlign: 'center',
88 | textDecoration: 'none',
89 | verticalAlign: 'bottom',
90 |
91 | /* animations */
92 | transition: 'border-top 0.1s, border-bottom 0.1s, color 0.1s',
93 | transitionTimingFunction: 'ease-out',
94 |
95 | /*
96 | To avoid any kind of flickering the user won't get feedback
97 | for selecting the button text
98 | */
99 | WebkitUserSelect: 'none',
100 | MozUserSelect: 'none',
101 | MsUserSelect: 'none',
102 | userSelect: 'none',
103 |
104 | /* This button can only be pressed */
105 | MsTouchAction: 'manipulation',
106 | touchAction: 'manipulation',
107 |
108 | /*
109 | Prevent flickering while tapping on WebKit
110 | http://stackoverflow.com/a/3516243/837709
111 | */
112 | WebkitTapHighlightColor: 'transparent',
113 | },
114 |
115 | primaryHoverStyle: {
116 | background: '#82D9F9',
117 | borderTop: '1px solid #82D9F9',
118 | color: '#FFF',
119 | },
120 |
121 | primaryFocusStyle: {
122 | WebkitAnimation: 'belle-button-focus 2s',
123 | outline: 0, // avoid default focus behaviour
124 | },
125 |
126 | primaryActiveStyle: {
127 | background: '#4DBEE8',
128 | borderBottom: '1px solid #4DBEE8',
129 | borderTop: '1px solid #3995B7',
130 | color: '#F5F5F5',
131 | },
132 |
133 | primaryDisabledStyle: {
134 | background: '#98DEF8',
135 | color: '#FAFAFA',
136 | borderTop: '1px solid #98DEF8',
137 | borderBottom: '1px solid #74B4CC',
138 | cursor: 'not-allowed',
139 | },
140 |
141 | primaryDisabledHoverStyle: {
142 | background: '#A7E4FB',
143 | color: '#FFF',
144 | borderTop: '1px solid #A7E4FB',
145 | },
146 | };
147 |
148 | export default buttonStyle;
149 |
--------------------------------------------------------------------------------
/src/style/card.js:
--------------------------------------------------------------------------------
1 | const cardStyle = {
2 |
3 | style: {
4 | marginBottom: 20,
5 | padding: 40,
6 | borderRadius: 2,
7 | background: '#fff',
8 | boxShadow: '0 1px 1px rgba(0, 0, 0, 0.2)',
9 | boxSizing: 'border-box',
10 | },
11 | };
12 |
13 | export default cardStyle;
14 |
--------------------------------------------------------------------------------
/src/style/combo-box.js:
--------------------------------------------------------------------------------
1 | const comboBoxStyle = {
2 |
3 | style: {
4 |
5 | background: 'transparent',
6 |
7 | /* normalize.css v3.0.1 */
8 | font: 'inherit',
9 | margin: 0,
10 |
11 | /* Reset the default borderRadius for Mobile Safari */
12 | borderRadius: 0,
13 |
14 | /* Belle TextInput style */
15 | overflow: 'hidden',
16 | resize: 'none',
17 | width: '100%',
18 | fontSize: 15,
19 | paddingTop: '7px',
20 | paddingBottom: '4px',
21 | paddingLeft: 0,
22 | color: '#505050',
23 | border: '0 solid #fff',
24 | borderBottom: '1px solid #ccc',
25 | display: 'block',
26 | boxSizing: 'border-box',
27 | position: 'relative',
28 |
29 | /* animations */
30 | transition: 'border-bottom 0.2s',
31 | transitionTimingFunction: 'ease-out',
32 |
33 | /* This button can only be pressed */
34 | MsTouchAction: 'manipulation',
35 | touchAction: 'manipulation',
36 |
37 | /*
38 | Prevent flickering while tapping on WebKit
39 | http://stackoverflow.com/a/3516243/837709
40 | */
41 | WebkitTapHighlightColor: 'transparent',
42 | },
43 |
44 | focusStyle: {
45 | borderBottom: '1px solid #6EB8D4',
46 | outline: 'none',
47 | },
48 |
49 | hoverStyle: {
50 | borderBottom: '1px solid #92D6EF',
51 | },
52 |
53 | disabledStyle: {
54 | borderBottom: '1px dotted #9F9F9F',
55 | },
56 |
57 | disabledHoverStyle: {
58 | borderBottom: '1px dotted #92D6EF',
59 | cursor: 'not-allowed',
60 | },
61 |
62 | wrapperStyle: {
63 | outline: 0, // to avoid default focus behaviour
64 | boxSizing: 'border-box',
65 | position: 'relative',
66 | },
67 |
68 | menuStyle: {
69 | display: 'block',
70 | listStyleType: 'none',
71 | background: '#FFF',
72 | padding: '6px 0',
73 | margin: 0,
74 | position: 'absolute',
75 | width: '100%',
76 | zIndex: 10000,
77 | boxSizing: 'border-box',
78 | borderRadius: 2,
79 | boxShadow: '0 1px 1px rgba(0, 0, 0, 0.2)',
80 | borderTop: '1px solid #f2f2f2',
81 | /* Improve scrolling for mobile Safari */
82 | WebkitOverflowScrolling: 'touch',
83 | },
84 |
85 | caretToOpenStyle: {
86 | height: 0,
87 | width: 0,
88 | content: '-', // Avoid this warning: was passed a numeric string value for CSS property `content` (value: ` `)
89 | position: 'absolute',
90 | top: 15,
91 | right: 8,
92 | cursor: 'pointer',
93 | borderTop: '6px solid #666',
94 | borderLeft: '5px solid transparent',
95 | borderRight: '5px solid transparent',
96 | },
97 |
98 | caretToCloseStyle: {
99 | height: 0,
100 | width: 0,
101 | content: '-', // Avoid this warning: was passed a numeric string value for CSS property `content` (value: ` `)
102 | position: 'absolute',
103 | top: 15,
104 | right: 8,
105 | cursor: 'pointer',
106 | borderBottom: '6px solid #666',
107 | borderLeft: '5px solid transparent',
108 | borderRight: '5px solid transparent',
109 | },
110 |
111 | caretFocusStyle: {
112 | outline: 0,
113 | },
114 |
115 | disabledCaretToOpenStyle: {
116 | borderTop: '6px solid #9F9F9F',
117 | },
118 |
119 | hintStyle: {
120 |
121 | position: 'absolute',
122 | top: 0,
123 | left: 0,
124 | outline: 'none',
125 | color: '#ccc',
126 | border: 'none',
127 |
128 | /* normalize.css v3.0.1 */
129 | font: 'inherit',
130 | margin: 0,
131 |
132 | /* Reset the default borderRadius for Mobile Safari */
133 | borderRadius: 0,
134 |
135 | /* Belle TextInput style */
136 | overflow: 'hidden',
137 | resize: 'none',
138 | width: '100%',
139 | fontSize: 15,
140 | paddingTop: 7,
141 | paddingBottom: 4,
142 | paddingLeft: 0,
143 | display: 'block',
144 | boxSizing: 'border-box',
145 |
146 | /* This button can only be pressed */
147 | MsTouchAction: 'manipulation',
148 | touchAction: 'manipulation',
149 |
150 | /*
151 | Prevent flickering while tapping on WebKit
152 | http://stackoverflow.com/a/3516243/837709
153 | */
154 | WebkitTapHighlightColor: 'transparent',
155 | },
156 | };
157 |
158 | export default comboBoxStyle;
159 |
--------------------------------------------------------------------------------
/src/style/date-picker.js:
--------------------------------------------------------------------------------
1 | const datePickerStyle = {
2 |
3 | // wrapper of entire component
4 | style: {
5 | borderRadius: 2,
6 | width: 267,
7 | fontSize: 14,
8 | textAlign: 'center',
9 | boxSizing: 'border-box',
10 | backgroundColor: 'white',
11 |
12 | /*
13 | To avoid any kind of flickering the user won't get feedback
14 | for selecting the button text
15 | */
16 | WebkitUserSelect: 'none',
17 | MozUserSelect: 'none',
18 | MsUserSelect: 'none',
19 | userSelect: 'none',
20 |
21 | MsTouchAction: 'manipulation',
22 | touchAction: 'manipulation',
23 |
24 | /*
25 | Prevent flickering while tapping on WebKit
26 | http://stackoverflow.com/a/3516243/837709
27 | */
28 | WebkitTapHighlightColor: 'transparent',
29 |
30 | transition: 'color 0.1s',
31 | transitionTimingFunction: 'ease-out',
32 | },
33 |
34 | disabledStyle: {
35 | },
36 |
37 | readOnlyStyle: {
38 | },
39 |
40 | hoverStyle: {
41 | },
42 |
43 | focusStyle: {
44 | WebkitAnimation: 'belle-button-focus 2s',
45 | outline: 0, // avoid default focus behaviour
46 | },
47 |
48 | disabledHoverStyle: {
49 | backgroundColor: '#E1E9EC',
50 | },
51 |
52 | // nav-bar at top for month navigation
53 | navBarStyle: {
54 | height: 38,
55 | border: '1px solid #E0E0E0',
56 | boxSizing: 'border-box',
57 | },
58 |
59 | // left button in nav-bar to go to previous month
60 | prevMonthNavStyle: {
61 | float: 'left',
62 | marginLeft: -1,
63 | paddingLeft: '15px',
64 | paddingRight: '19px',
65 | },
66 |
67 | prevMonthNavIconStyle: {
68 | width: 0,
69 | height: 0,
70 | borderTop: '7px solid transparent',
71 | borderBottom: '7px solid transparent',
72 | borderRight: '12px solid #666',
73 | borderRadius: 2,
74 | },
75 |
76 | hoverPrevMonthNavStyle: {
77 | },
78 |
79 | activePrevMonthNavStyle: {
80 | },
81 |
82 | // right button in nav-bar to go to previous month
83 | nextMonthNavStyle: {
84 | float: 'right',
85 | marginRight: -1,
86 | paddingLeft: '19px',
87 | paddingRight: '15px',
88 | },
89 |
90 | nextMonthNavIconStyle: {
91 | width: 0,
92 | height: 0,
93 | borderTop: '7px solid transparent',
94 | borderBottom: '7px solid transparent',
95 | borderLeft: '12px solid #666',
96 | borderRadius: 2,
97 | },
98 |
99 | hoverNextMonthNavStyle: {
100 | },
101 |
102 | activeNextMonthNavStyle: {
103 | },
104 |
105 | // styling for month label on top of calendar
106 | monthLabelStyle: {
107 | fontSize: 15,
108 | boxSizing: 'border-box',
109 | paddingTop: 7,
110 | display: 'inline-block',
111 |
112 | /*
113 | User should be able to copy date.
114 | */
115 | WebkitUserSelect: 'initial',
116 | MozUserSelect: 'initial',
117 | MsUserSelect: 'initial',
118 | userSelect: 'initial',
119 | },
120 |
121 | // styling for entire grid of week-header and weeks
122 | weekGridStyle: {
123 | boxSizing: 'border-box',
124 | overflow: 'auto',
125 | paddingBottom: 1,
126 | },
127 |
128 | weekHeaderStyle: {
129 | overflow: 'auto',
130 | boxSizing: 'border-box',
131 | boxShadow: '1px 0 0 0 #E0E0E0 inset, -1px 0 0 0 #E0E0E0 inset',
132 | },
133 |
134 | // styling for week's day label
135 | dayLabelStyle: {
136 | width: 39,
137 | height: 32,
138 | marginRight: -1,
139 | color: '#666',
140 | display: 'block',
141 | float: 'left',
142 | boxSizing: 'border-box',
143 | paddingTop: 5,
144 |
145 | /*
146 | User should be able to copy date.
147 | */
148 | WebkitUserSelect: 'initial',
149 | MozUserSelect: 'initial',
150 | MsUserSelect: 'initial',
151 | userSelect: 'initial',
152 | },
153 |
154 | disabledDayLabelStyle: {
155 | },
156 |
157 | weekendLabelStyle: {
158 | // color: '#8E8071',
159 | },
160 |
161 | // styling for individual day
162 | dayStyle: {
163 | width: 39,
164 | height: 32,
165 | borderLeft: '1px solid #E0E0E0',
166 | borderRight: '1px solid #E0E0E0',
167 | borderTop: '1px solid #E0E0E0',
168 | borderBottom: '1px solid #E0E0E0',
169 | color: '#716D6D',
170 | float: 'left',
171 | marginRight: -1,
172 | marginBottom: -1,
173 | boxSizing: 'border-box',
174 | paddingTop: 5,
175 | position: 'relative',
176 | zIndex: 100,
177 | cursor: 'default',
178 |
179 | /*
180 | To avoid any kind of flickering the user won't get feedback
181 | for selecting the button text
182 | */
183 | WebkitUserSelect: 'none',
184 | MozUserSelect: 'none',
185 | MsUserSelect: 'none',
186 | userSelect: 'none',
187 |
188 | /* This button can only be pressed */
189 | MsTouchAction: 'manipulation',
190 | touchAction: 'manipulation',
191 |
192 | /*
193 | Prevent flickering while tapping on WebKit
194 | http://stackoverflow.com/a/3516243/837709
195 | */
196 | WebkitTapHighlightColor: 'transparent',
197 | },
198 |
199 | readOnlyDayStyle: {
200 | },
201 |
202 | activeDayStyle: {
203 | borderTop: '1px solid #B1B1B1',
204 | borderLeft: '1px solid #D0D0D0',
205 | borderRight: '1px solid #D0D0D0',
206 | borderBottom: '1px solid #D4D4D4',
207 | background: '#E0E0E0',
208 | zIndex: 200,
209 | },
210 |
211 | focusDayStyle: {
212 | background: '#EEE',
213 | cursor: 'pointer',
214 | },
215 |
216 | disabledDayStyle: {
217 | color: '#C1BABA',
218 | cursor: 'not-allowed',
219 | },
220 |
221 | disabledFocusDayStyle: {
222 | cursor: 'not-allowed',
223 | },
224 |
225 | todayStyle: {
226 | color: '#2C87A9',
227 | },
228 |
229 | weekendStyle: {
230 | color: '#8E8071',
231 | },
232 |
233 | selectedDayStyle: {
234 | borderTop: '1px solid #B1B1B1',
235 | borderLeft: '1px solid #D0D0D0',
236 | borderRight: '1px solid #D0D0D0',
237 | borderBottom: '1px solid #D4D4D4',
238 | background: '#E0E0E0',
239 | zIndex: 200,
240 | },
241 |
242 | otherMonthDayStyle: {
243 | color: '#BDBDBD',
244 | },
245 | };
246 |
247 | export default datePickerStyle;
248 |
--------------------------------------------------------------------------------
/src/style/option.js:
--------------------------------------------------------------------------------
1 | const optionStyle = {
2 | style: {
3 | boxSizing: 'border-box',
4 | color: '#666',
5 | cursor: 'pointer',
6 | padding: 10,
7 | fontSize: 15,
8 | /*
9 | To avoid any kind of flickering the user won't get feedback
10 | for selecting the option text
11 | */
12 | WebkitUserSelect: 'none',
13 | MozUserSelect: 'none',
14 | MsUserSelect: 'none',
15 | userSelect: 'none',
16 | /* This button can only be pressed */
17 | MsTouchAction: 'manipulation',
18 | touchAction: 'manipulation',
19 | /*
20 | Prevent flickering while tapping on WebKit
21 | http://stackoverflow.com/a/3516243/837709
22 | */
23 | WebkitTapHighlightColor: 'transparent',
24 | },
25 |
26 | hoverStyle: {
27 | background: '#F5F5F5',
28 | color: '#444',
29 | },
30 |
31 | selectStyle: {
32 | boxSizing: 'border-box',
33 | color: '#666',
34 | padding: 0,
35 | fontSize: 15,
36 | /*
37 | To avoid any kind of flickering the user won't get feedback
38 | for selecting the button text
39 | */
40 | WebkitUserSelect: 'none',
41 | MozUserSelect: 'none',
42 | MsUserSelect: 'none',
43 | userSelect: 'none',
44 | /* This button can only be pressed */
45 | MsTouchAction: 'manipulation',
46 | touchAction: 'manipulation',
47 | /*
48 | Prevent flickering while tapping on WebKit
49 | http://stackoverflow.com/a/3516243/837709
50 | */
51 | WebkitTapHighlightColor: 'transparent',
52 | },
53 |
54 | disabledSelectStyle: {
55 | color: '#9F9F9F',
56 | padding: 0,
57 | },
58 | };
59 |
60 | export default optionStyle;
61 |
--------------------------------------------------------------------------------
/src/style/placeholder.js:
--------------------------------------------------------------------------------
1 | const placeholderStyle = {
2 | style: {
3 | boxSizing: 'border-box',
4 | color: '#666',
5 | cursor: 'pointer',
6 | padding: 0,
7 | fontSize: 15,
8 | /*
9 | To avoid any kind of flickering the user won't get feedback
10 | for selecting the button text
11 | */
12 | WebkitUserSelect: 'none',
13 | MozUserSelect: 'none',
14 | MsUserSelect: 'none',
15 | userSelect: 'none',
16 | /* This button can only be pressed */
17 | MsTouchAction: 'manipulation',
18 | touchAction: 'manipulation',
19 | /*
20 | Prevent flickering while tapping on WebKit
21 | http://stackoverflow.com/a/3516243/837709
22 | */
23 | WebkitTapHighlightColor: 'transparent',
24 | },
25 |
26 | disabledStyle: {
27 | color: '#9F9F9F',
28 | cursor: 'not-allowed',
29 | },
30 | };
31 |
32 | export default placeholderStyle;
33 |
--------------------------------------------------------------------------------
/src/style/rating.js:
--------------------------------------------------------------------------------
1 | const ratingStyle = {
2 | style: {
3 | position: 'relative',
4 | display: 'inline-block',
5 | cursor: 'pointer',
6 | fontSize: '2.6rem',
7 | lineHeight: '2.6rem',
8 | color: '#e3e3e3',
9 | textShadow: '0px 1px 0px #D2D1D1',
10 |
11 | /*
12 | To avoid any kind of flickering the user won't get feedback
13 | for selecting the rating stars
14 | */
15 | WebkitUserSelect: 'none',
16 | MozUserSelect: 'none',
17 | MsUserSelect: 'none',
18 | userSelect: 'none',
19 |
20 | /* This button can only be pressed */
21 | MsTouchAction: 'none',
22 | touchAction: 'none',
23 |
24 | /*
25 | Prevent flickering while tapping on WebKit
26 | http://stackoverflow.com/a/3516243/837709
27 | */
28 | WebkitTapHighlightColor: 'transparent',
29 | },
30 |
31 | disabledStyle: {
32 | opacity: 0.6,
33 | cursor: 'not-allowed',
34 | },
35 |
36 | focusStyle: {
37 | outline: 0,
38 | WebkitAnimation: 'belle-rating-focus 2s',
39 | borderRadius: 2,
40 | },
41 |
42 | hoverStyle: {
43 | opacity: 1,
44 | },
45 |
46 | disabledHoverStyle: {
47 | opacity: 0.6,
48 | },
49 |
50 | characterStyle: {
51 | color: '#FFCC00',
52 | textShadow: '0px 1px 0px #DCB000',
53 | top: 0,
54 |
55 | /* animations */
56 | transition: 'color 0.1s',
57 | transitionTimingFunction: 'ease-out',
58 | },
59 |
60 | hoverCharacterStyle: {
61 | color: '#FFDA46',
62 | },
63 |
64 | activeCharacterStyle: {
65 | textShadow: '0px -1px 0px #D6AB00',
66 | color: '#F3C200',
67 | position: 'relative',
68 | top: 1,
69 | },
70 | };
71 |
72 | export default ratingStyle;
73 |
--------------------------------------------------------------------------------
/src/style/select.js:
--------------------------------------------------------------------------------
1 | const selectStyle = {
2 | style: {
3 | borderBottom: '1px solid #CCC',
4 | boxSizing: 'border-box',
5 | cursor: 'pointer',
6 | /*
7 | While the Select should have the same padding as TextInput 4px for
8 | paddingBottom was chosen as in Chrome the Text is larger by 1px than in
9 | the textarea.
10 | */
11 | padding: '7px 0 4px 0',
12 | position: 'relative',
13 |
14 | /* animations */
15 | transition: 'border-bottom 0.2s',
16 | transitionTimingFunction: 'ease-out',
17 |
18 | /*
19 | To avoid any kind of flickering the user won't get feedback
20 | for selecting the button text
21 | */
22 | WebkitUserSelect: 'none',
23 | MozUserSelect: 'none',
24 | MsUserSelect: 'none',
25 | userSelect: 'none',
26 |
27 | /* This button can only be pressed */
28 | MsTouchAction: 'manipulation',
29 | touchAction: 'manipulation',
30 |
31 | /*
32 | Prevent flickering while tapping on WebKit
33 | http://stackoverflow.com/a/3516243/837709
34 | */
35 | WebkitTapHighlightColor: 'transparent',
36 | },
37 |
38 | focusStyle: {
39 | borderBottom: '1px solid #6EB8D4',
40 | },
41 |
42 | activeStyle: {
43 | borderBottom: '1px solid #6EB8D4',
44 | },
45 |
46 | hoverStyle: {
47 | borderBottom: '1px solid #92D6EF',
48 | },
49 |
50 | wrapperStyle: {
51 | outline: 0, // to avoid default focus behaviour
52 | boxSizing: 'border-box',
53 | position: 'relative',
54 | },
55 |
56 | disabledStyle: {
57 | borderBottom: '1px dotted #9F9F9F',
58 | },
59 |
60 | disabledHoverStyle: {
61 | borderBottom: '1px dotted #92D6EF',
62 | cursor: 'not-allowed',
63 | },
64 |
65 | menuStyle: {
66 | display: 'block',
67 | listStyleType: 'none',
68 | background: '#FFF',
69 | padding: '6px 0',
70 | margin: 0,
71 | position: 'absolute',
72 | width: '100%',
73 | zIndex: 10000,
74 | boxSizing: 'border-box',
75 | borderRadius: 2,
76 | boxShadow: '0 1px 1px rgba(0, 0, 0, 0.2)',
77 | borderTop: '1px solid #f2f2f2',
78 | top: 0,
79 | /* Improve scrolling for mobile Safari */
80 | WebkitOverflowScrolling: 'touch',
81 | },
82 |
83 | caretToOpenStyle: {
84 | height: 0,
85 | width: 0,
86 | content: '-', // Avoid this warning: was passed a numeric string value for CSS property `content` (value: ` `)
87 | position: 'absolute',
88 | top: 15,
89 | right: 8,
90 | borderTop: '6px solid #666',
91 | borderLeft: '5px solid transparent',
92 | borderRight: '5px solid transparent',
93 | },
94 |
95 | caretToCloseStyle: {
96 | height: 0,
97 | width: 0,
98 | content: '-', // Avoid this warning: was passed a numeric string value for CSS property `content` (value: ` `)
99 | position: 'absolute',
100 | top: 15,
101 | right: 8,
102 | borderBottom: '6px solid #666',
103 | borderLeft: '5px solid transparent',
104 | borderRight: '5px solid transparent',
105 | },
106 |
107 | disabledCaretToOpenStyle: {
108 | borderTop: '6px solid #9F9F9F',
109 | },
110 | };
111 |
112 | export default selectStyle;
113 |
--------------------------------------------------------------------------------
/src/style/separator.js:
--------------------------------------------------------------------------------
1 | const separatorStyle = {
2 | style: {
3 | boxSizing: 'border-box',
4 | color: '#666',
5 | fontWeight: 'bold',
6 | padding: 10,
7 | },
8 | };
9 |
10 | export default separatorStyle;
11 |
--------------------------------------------------------------------------------
/src/style/spinner.js:
--------------------------------------------------------------------------------
1 | const spinnerStyle = {
2 | style: {
3 | display: 'inline-block',
4 | fontSize: 15,
5 | textAlign: 'center',
6 | },
7 |
8 | characterStyle: {
9 | color: '#666',
10 | display: 'inline-block',
11 | WebkitAnimation: 'belle-spinner-pulse 2s infinite ease-in-out',
12 | OAnimation: 'belle-spinner-pulse 2s infinite ease-in-out',
13 | animation: 'belle-spinner-pulse 2s infinite ease-in-out',
14 | },
15 | };
16 |
17 | export default spinnerStyle;
18 |
--------------------------------------------------------------------------------
/src/style/text-input.js:
--------------------------------------------------------------------------------
1 | const textInputStyle = {
2 | style: {
3 | /* normalize.css v3.0.1 */
4 | font: 'inherit',
5 | margin: 0,
6 |
7 | /* Reset the default borderRadius for Mobile Safari */
8 | borderRadius: 0,
9 |
10 | /* Belle TextInput style */
11 | overflow: 'hidden',
12 | resize: 'none',
13 | width: '100%',
14 | fontSize: 15,
15 | padding: '7px 0 5px 0',
16 | color: '#505050',
17 | border: '0 solid #fff',
18 | borderBottom: '1px solid #ccc',
19 | background: 'none',
20 | display: 'block',
21 | boxSizing: 'border-box',
22 | minHeight: '0px',
23 |
24 | /* animations */
25 | transition: 'border-bottom 0.2s',
26 | transitionTimingFunction: 'ease-out',
27 | },
28 |
29 | hoverStyle: {
30 | borderBottom: '1px solid #92D6EF',
31 | },
32 |
33 | focusStyle: {
34 | outline: 0, // to avoid default focus behaviour
35 | borderBottom: '1px solid #6EB8D4',
36 | },
37 |
38 | disabledStyle: {
39 | borderBottom: '1px dotted #9F9F9F',
40 | color: '#9F9F9F',
41 | },
42 |
43 | disabledHoverStyle: {
44 | borderBottom: '1px dotted #92D6EF',
45 | color: '#9F9F9F',
46 | cursor: 'not-allowed',
47 | },
48 | };
49 |
50 | export default textInputStyle;
51 |
--------------------------------------------------------------------------------
/src/style/toggle.js:
--------------------------------------------------------------------------------
1 | const toggleStyle = {
2 | style: {
3 | boxSizing: 'border-box',
4 | borderRadius: 32,
5 | height: 32,
6 | width: 68,
7 | WebkitUserSelect: 'none',
8 | position: 'relative',
9 | cursor: 'pointer',
10 | display: 'inline-block',
11 | },
12 |
13 | focusStyle: {
14 | WebkitAnimation: 'belle-toggle-focus 2s',
15 | outline: 0, // avoid default focus behaviour
16 | },
17 |
18 | disabledStyle: {
19 | opacity: 0.6,
20 | cursor: 'not-allowed',
21 | },
22 |
23 | sliderStyle: {
24 | boxSizing: 'border-box',
25 | position: 'relative',
26 |
27 | // Calculated with 2 * the width of choice area
28 | width: 104,
29 | transition: 'left 0.1s',
30 | transitionTimingFunction: 'ease-in-out',
31 |
32 | /*
33 | Prevent flickering while tapping on WebKit
34 | http://stackoverflow.com/a/3516243/837709
35 | */
36 | WebkitTapHighlightColor: 'transparent',
37 | },
38 |
39 | sliderWrapperStyle: {
40 | boxSizing: 'border-box',
41 | overflow: 'hidden',
42 | borderRadius: 32,
43 | lineHeight: 1,
44 | boxShadow: 'inset 0 1px 0px 0px rgba(0,0,0,0.6)',
45 | },
46 |
47 | handleStyle: {
48 | position: 'absolute',
49 | top: 0,
50 | left: 0,
51 | boxSizing: 'border-box',
52 | borderRadius: 32,
53 | backgroundColor: 'rgb(243, 243, 243)',
54 | height: 31,
55 | width: 32,
56 | cursor: 'pointer',
57 | border: '1px solid rgb(220, 220, 220)',
58 | boxShadow: '0 1px 0px 0px rgb(185, 185, 185)',
59 |
60 | /* animations */
61 | transition: 'left 0.1s',
62 | transitionTimingFunction: 'ease-in-out',
63 |
64 | /*
65 | To avoid any kind of flickering the user won't get feedback
66 | for selecting the button text
67 | */
68 | WebkitUserSelect: 'none',
69 | MozUserSelect: 'none',
70 | MsUserSelect: 'none',
71 | userSelect: 'none',
72 |
73 | /* This button can only be pressed */
74 | MsTouchAction: 'none',
75 | touchAction: 'none',
76 |
77 | /*
78 | Prevent flickering while tapping on WebKit
79 | http://stackoverflow.com/a/3516243/837709
80 | */
81 | WebkitTapHighlightColor: 'transparent',
82 | },
83 |
84 | firstChoiceStyle: {
85 | display: 'inline-block',
86 | boxSizing: 'border-box',
87 | height: 32,
88 |
89 | // Calculated with the width of the whole toggle - half of the width from the handle
90 | //
91 | // This allows to have a round handle that is position exactly in on top of the
92 | // border between the two choice areas.
93 | width: 52,
94 | lineHeight: '32px',
95 | textAlign: 'center',
96 | color: '#FFF',
97 | backgroundColor: 'rgba(43, 206, 56, 0.8)',
98 | textIndent: -10,
99 | fontSize: 15,
100 |
101 | /*
102 | To avoid any kind of flickering the user won't get feedback
103 | for selecting the button text
104 | */
105 | WebkitUserSelect: 'none',
106 | MozUserSelect: 'none',
107 | MsUserSelect: 'none',
108 | userSelect: 'none',
109 |
110 | /* This button can only be pressed */
111 | MsTouchAction: 'manipulation',
112 | touchAction: 'manipulation',
113 |
114 | /*
115 | Prevent flickering while tapping on WebKit
116 | http://stackoverflow.com/a/3516243/837709
117 | */
118 | WebkitTapHighlightColor: 'transparent',
119 | },
120 |
121 | secondChoiceStyle: {
122 | display: 'inline-block',
123 | boxSizing: 'border-box',
124 | height: 32,
125 |
126 | // Calculated with the width of the whole toggle - half of the width from the handle
127 | //
128 | // This allows to have a round handle that is position exactly in on top of the
129 | // border between the two choice areas.
130 | width: 52,
131 | lineHeight: '32px',
132 | textAlign: 'center',
133 | color: '#FFF',
134 | backgroundColor: 'rgba(205, 205, 205, 0.8)',
135 | textIndent: 10,
136 | fontSize: 15,
137 |
138 | /*
139 | To avoid any kind of flickering the user won't get feedback
140 | for selecting the button text
141 | */
142 | WebkitUserSelect: 'none',
143 | MozUserSelect: 'none',
144 | MsUserSelect: 'none',
145 | userSelect: 'none',
146 |
147 | /* This button can only be pressed */
148 | MsTouchAction: 'manipulation',
149 | touchAction: 'manipulation',
150 |
151 | /*
152 | Prevent flickering while tapping on WebKit
153 | http://stackoverflow.com/a/3516243/837709
154 | */
155 | WebkitTapHighlightColor: 'transparent',
156 | },
157 |
158 | hoverHandleStyle: {
159 | backgroundColor: 'rgb(250, 250, 250)',
160 | },
161 |
162 | activeHandleStyle: {
163 | height: 32,
164 | backgroundColor: 'rgb(246, 246, 246)',
165 | boxShadow: '0 0 0 0 rgb(189, 189, 189)',
166 | },
167 |
168 | disabledHandleStyle: {
169 | cursor: 'not-allowed',
170 | },
171 | };
172 |
173 | export default toggleStyle;
174 |
--------------------------------------------------------------------------------
/src/utils/animation-frame-management.js:
--------------------------------------------------------------------------------
1 | // Inspired by https://gist.github.com/paulirish/1579671
2 |
3 | import { canUseDOM } from 'exenv';
4 |
5 | export let requestAnimationFrame;
6 | export let cancelAnimationFrame;
7 |
8 | let lastTime = 0;
9 |
10 | if (canUseDOM) {
11 | requestAnimationFrame = window.requestAnimationFrame;
12 | cancelAnimationFrame = window.cancelAnimationFrame;
13 |
14 | const vendors = ['ms', 'moz', 'webkit', 'o'];
15 | for (let index = 0; index < vendors.length && !requestAnimationFrame; ++index) {
16 | requestAnimationFrame = window[`${vendors[index]}RequestAnimationFrame`];
17 | cancelAnimationFrame = window[`${vendors[index]}CancelAnimationFrame`] ||
18 | window[`${vendors[index]}CancelRequestAnimationFrame`];
19 | }
20 | }
21 |
22 | if (!requestAnimationFrame) {
23 | requestAnimationFrame = (callback) => {
24 | const currTime = new Date().getTime();
25 | const timeToCall = Math.max(0, 16 - (currTime - lastTime));
26 | const id = window.setTimeout(() => {
27 | callback(currTime + timeToCall);
28 | }, timeToCall);
29 | lastTime = currTime + timeToCall;
30 | return id;
31 | };
32 | }
33 |
34 | if (!cancelAnimationFrame) {
35 | cancelAnimationFrame = (id) => {
36 | clearTimeout(id);
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/src/utils/calculate-textarea-height.js:
--------------------------------------------------------------------------------
1 | import { canUseDOM as exenvCanUseDOM } from 'exenv';
2 |
3 | // our height calculation logic is not compatible with jsdom
4 | const isNodeTest = typeof process !== 'undefined' && process.env.NODE_ENV === 'test';
5 | const canUseDOM = exenvCanUseDOM && !isNodeTest;
6 |
7 | let hiddenTextarea;
8 | const computedStyleCache = {};
9 |
10 | // !important is used here to avoid side-effects from global set CSS.
11 | const hiddenTextareaStyle = `
12 | min-height:none !important;
13 | max-height:none !important;
14 | height:0 !important;
15 | visibility:hidden !important;
16 | overflow:hidden !important;
17 | position:absolute !important;
18 | z-index:-1000 !important;
19 | top:0 !important;
20 | right:0 !important
21 | `;
22 |
23 | const stylesToCopy = [
24 | 'letter-spacing',
25 | 'line-height',
26 | 'padding-top',
27 | 'padding-bottom',
28 | 'font-family',
29 | 'font-weight',
30 | 'font-size',
31 | 'text-rendering',
32 | 'text-transform',
33 | 'width',
34 | 'padding-left',
35 | 'padding-right',
36 | 'border-width',
37 | 'box-sizing',
38 | ];
39 |
40 | /**
41 | * Returns an object containing the computed style and the combined vertical
42 | * padding size, combined vertical border size and box-sizing value.
43 | *
44 | * This style is returned as string to be applied as attribute of an element.
45 | */
46 | function calculateStyling(node) {
47 | const reactId = node.getAttribute('data-reactid');
48 |
49 | // calculate the computed style only once it's not in the cache
50 | if (!computedStyleCache[reactId]) {
51 | // In order to work with legacy browsers the second paramter for pseudoClass
52 | // has to be provided http://caniuse.com/#feat=getcomputedstyle
53 | const computedStyle = window.getComputedStyle(node, null);
54 |
55 | const boxSizing = (
56 | computedStyle.getPropertyValue('box-sizing') ||
57 | computedStyle.getPropertyValue('-moz-box-sizing') ||
58 | computedStyle.getPropertyValue('-webkit-box-sizing')
59 | );
60 |
61 | let verticalPaddingSize = 0;
62 | verticalPaddingSize = (
63 | parseFloat(computedStyle.getPropertyValue('padding-bottom')) +
64 | parseFloat(computedStyle.getPropertyValue('padding-top'))
65 | );
66 |
67 | let verticalBorderSize = 0;
68 | verticalBorderSize = (
69 | parseFloat(computedStyle.getPropertyValue('border-bottom-width')) +
70 | parseFloat(computedStyle.getPropertyValue('border-top-width'))
71 | );
72 |
73 | const sizingStyle = stylesToCopy
74 | .map(styleName => `${styleName}:${computedStyle.getPropertyValue(styleName)} !important`)
75 | .join(';');
76 |
77 | // store the style, vertical padding size, vertical border size and
78 | // boxSizing inside the cache
79 | computedStyleCache[reactId] = {
80 | style: sizingStyle,
81 | verticalPaddingSize,
82 | verticalBorderSize,
83 | boxSizing,
84 | };
85 | }
86 |
87 | return computedStyleCache[reactId];
88 | }
89 |
90 | /**
91 | * Returns an object containing height of the textare as if all the content
92 | * would be visible. The minHeight & maxHeight are in the object as well and are
93 | * based on minRows & maxRows.
94 | *
95 | * In order to improve the performance a hidden textarea is added to the DOM
96 | * and used for further caluculations. In addition the styling of each textarea
97 | * is cached to improve performance.
98 | */
99 | export default function calculateTextareaHeight(textareaElement, textareaValue = '-', minRows = null, maxRows = null, minHeight = null, maxHeight = null) {
100 | // Regarding textareaValue: IE will return a height of 0 in case the textare is empty.
101 | // To prevent reducing the size to 0 we simply use a dummy text.
102 | if (!canUseDOM) { return 0; }
103 |
104 | if (!hiddenTextarea) {
105 | hiddenTextarea = document.createElement('textarea');
106 | document.body.appendChild(hiddenTextarea);
107 | hiddenTextarea.setAttribute('class', 'belle-input-helper');
108 | }
109 |
110 | const { style, verticalPaddingSize, verticalBorderSize, boxSizing } = calculateStyling(textareaElement);
111 |
112 | hiddenTextarea.setAttribute('style', `${style};${hiddenTextareaStyle}`);
113 | hiddenTextarea.value = textareaValue;
114 |
115 | let calculatedMinHeight;
116 | let calculatedMaxHeight;
117 | let height = hiddenTextarea.scrollHeight;
118 |
119 | // for a textarea with border-box, the border width has to be added while
120 | // for content-box it's necessary to subtract the padding
121 | if (boxSizing === 'border-box') {
122 | // border-box: content + padding + border
123 | height = height + verticalBorderSize;
124 | } else if (boxSizing === 'content-box') {
125 | // content-box: content
126 | height = height - verticalPaddingSize;
127 | }
128 |
129 | if (minRows !== null && minHeight === null ||
130 | maxRows !== null && maxHeight === null) {
131 | // measure height of a textarea with a single row
132 | hiddenTextarea.value = '-';
133 | const singleRowHeight = hiddenTextarea.scrollHeight - verticalPaddingSize;
134 |
135 | if (minRows !== null && minHeight === null) {
136 | calculatedMinHeight = singleRowHeight * minRows;
137 | if (boxSizing === 'border-box') {
138 | calculatedMinHeight = calculatedMinHeight + verticalPaddingSize + verticalBorderSize;
139 | }
140 | }
141 |
142 | if (maxRows !== null && maxHeight === null) {
143 | calculatedMaxHeight = singleRowHeight * maxRows;
144 | if (boxSizing === 'border-box') {
145 | calculatedMaxHeight = calculatedMaxHeight + verticalPaddingSize + verticalBorderSize;
146 | }
147 | }
148 | }
149 |
150 | const finalMinHeight = minHeight || calculatedMinHeight;
151 | if (finalMinHeight) {
152 | height = Math.max(finalMinHeight, height);
153 | }
154 |
155 | const finalMaxHeight = maxHeight || calculatedMaxHeight;
156 | if (finalMaxHeight) {
157 | height = Math.min(finalMaxHeight, height);
158 | }
159 |
160 | return height;
161 | }
162 |
--------------------------------------------------------------------------------
/src/utils/date-helpers.js:
--------------------------------------------------------------------------------
1 | import i18n from '../config/i18n';
2 |
3 | /**
4 | * The function will take a month and year value and will return an array of weeks for that month.
5 | * Each element in this array will be in-turn an array of days in the week.
6 | * @param {number} month: the month for which array of weeks is needed
7 | * @param {number} year: the year for which array of weeks is needed
8 | * @param {number} firstDayOfWeek: first day of the week in the locale
9 | * @returns {Array}: Array of weeks in a month, each week is in turn array of days in that week
10 | */
11 | export const getWeekArrayForMonth = (month, year, firstDayOfWeek) => {
12 | const monthDay = new Date(year, month, 1);
13 |
14 | // Todo: simplify this calculation of first date
15 | let firstDate = (1 + firstDayOfWeek) - monthDay.getDay();
16 | firstDate = firstDate <= 1 ? firstDate : firstDate - 7;
17 | monthDay.setDate(firstDate);
18 | const lastDate = new Date(year, month + 1, 0);
19 |
20 | const weekArray = [];
21 | while (monthDay <= lastDate) {
22 | const newWeek = [];
23 | for (let dayCounter = 0; dayCounter < 7; dayCounter++) {
24 | const weekDate = new Date(monthDay.getFullYear(), monthDay.getMonth(), monthDay.getDate());
25 | newWeek.push(weekDate);
26 | monthDay.setDate(monthDay.getDate() + 1);
27 | }
28 |
29 | weekArray.push(newWeek);
30 | }
31 |
32 | return weekArray;
33 | };
34 |
35 | export const getLastDayForMonth = (year, month) => new Date(year, month + 1, 0);
36 |
37 | /**
38 | * Function will return locale data for locale. If data is not available in config files it will return default data.
39 | * @param locale - locale for which data is needed.
40 | * @returns {Object}: Object containing locale data.
41 | */
42 | export const getLocaleData = (locale) => {
43 | const localeResult = {};
44 | let lData;
45 | if (locale) {
46 | lData = i18n.localeData[locale];
47 | }
48 |
49 | const monthNames = [
50 | 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
51 | 'September', 'October', 'November', 'December',
52 | ];
53 |
54 | localeResult.monthNames = (lData && lData.monthNames) ? lData.monthNames : monthNames;
55 | localeResult.dayNamesMin = (lData && lData.dayNamesMin) ? lData.dayNamesMin : ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
56 | localeResult.firstDay = (lData && lData.firstDay) ? lData.firstDay : 0;
57 | localeResult.weekEnd = (lData && lData.weekEnd) ? lData.weekEnd : 0;
58 | localeResult.isRTL = (lData && lData.isRTL) ? lData.isRTL : false;
59 | return localeResult;
60 | };
61 |
62 | /**
63 | * Returns the string representation for a provided year, month & day.
64 | *
65 | * @param year {number} - any year
66 | * @param month {number} - can be between 1 and 12
67 | * @param day {number} - can be between 1 and 31 depending on the month
68 | * @returns {string}: a string representing the date in the format yyyy-mm-dd
69 | */
70 | export const getDateKey = (year, month, day) => `${year}-${month}-${day}`;
71 |
72 | /**
73 | * Returns the date for a date string representation.
74 | *
75 | * @param year {number} - any year
76 | * @param month {number} - can be between 1 and 12
77 | * @param day {number} - can be between 1 and 31 depending on the month
78 | * @returns {date} - the parse date
79 | */
80 | export const getDateForDateKey = (dateKey) => {
81 | const splittedDate = dateKey.split('-');
82 | return new Date(parseInt(splittedDate[0], 10), parseInt(splittedDate[1], 10) - 1, parseInt(splittedDate[2], 10));
83 | };
84 |
85 | /**
86 | * Returns the string representation for a provided date.
87 | *
88 | * @param date {date} - a valid date
89 | * @returns {string}: a string representing the date in the format yyyy-mm-dd
90 | */
91 | export const convertDateToDateKey = (date) => (
92 | getDateKey(date.getFullYear(), date.getMonth() + 1, date.getDate())
93 | );
94 |
95 | /**
96 | * Returns the date of today
97 | *
98 | * @returns {date}: today's date
99 | */
100 | export const today = () => new Date();
101 |
--------------------------------------------------------------------------------
/src/utils/inject-style.js:
--------------------------------------------------------------------------------
1 | import { flatten, mapObject } from '../utils/helpers';
2 | import CSSPropertyOperations from 'react-dom/lib/CSSPropertyOperations';
3 | import { canUseDOM } from 'exenv';
4 | import animations from '../style/animations';
5 |
6 | let styleElement;
7 | const styleStorage = {};
8 |
9 | /**
10 | * Injects the provided style into the styleStore.
11 | */
12 | function injectStyleTag() {
13 | if (!styleElement && canUseDOM) {
14 | styleElement = document.createElement('style');
15 | document.body.appendChild(styleElement);
16 | styleElement.setAttribute('class', 'belle-style');
17 | }
18 | }
19 |
20 | /**
21 | * Injects the provided style into the styleStore.
22 | */
23 | function updateStore(styleId, style, pseudoClass, disabled) {
24 | styleStorage[styleId] = styleStorage[styleId] || {};
25 | if (disabled) {
26 | styleStorage[styleId].disabledPseudoClasses = styleStorage[styleId].disabledPseudoClasses || {};
27 | styleStorage[styleId].disabledPseudoClasses[pseudoClass] = style;
28 | } else {
29 | styleStorage[styleId].pseudoClasses = styleStorage[styleId].pseudoClasses || {};
30 | styleStorage[styleId].pseudoClasses[pseudoClass] = style;
31 | }
32 | }
33 |
34 | /**
35 | * Constructs all the stored styles & injects them to the DOM.
36 | */
37 | function createMarkupOnPseudoClass(pseudoClasses, id, disabled) {
38 | return mapObject(pseudoClasses, (style, pseudoClass) => {
39 | if (style && Object.keys(style).length > 0) {
40 | const styleString = CSSPropertyOperations.createMarkupForStyles(style);
41 | const styleWithImportant = styleString.replace(/;/g, ' !important;');
42 |
43 | return disabled ?
44 | `.${id}[disabled]:${pseudoClass} {${styleWithImportant}}` :
45 | `.${id}:${pseudoClass} {${styleWithImportant}}`;
46 | }
47 |
48 | return undefined;
49 | });
50 | }
51 |
52 | function updateStyling() {
53 | const styles = mapObject(styleStorage, (storageEntry, id) => {
54 | const pseudoClassesArray = [];
55 |
56 | if (storageEntry.pseudoClasses) {
57 | pseudoClassesArray.push(createMarkupOnPseudoClass(storageEntry.pseudoClasses, id, false));
58 | }
59 |
60 | if (storageEntry.disabledPseudoClasses) {
61 | pseudoClassesArray.push(createMarkupOnPseudoClass(storageEntry.disabledPseudoClasses, id, true));
62 | }
63 |
64 | return pseudoClassesArray;
65 | });
66 | if (styleElement) {
67 | styleElement.innerHTML = flatten([animations, styles]).join(' ');
68 | }
69 | }
70 |
71 | /**
72 | * Injects a style tag and adds multiple passed styles.
73 | *
74 | * By using this function someone can make sure the DOM is updated only once.
75 | *
76 | * @example
77 | * ```
78 | * const styles = [
79 | * {
80 | * id: 'style-0.0.2',
81 | * style: { color: '#F00' },
82 | * pseudoClass: 'hover'
83 | * }
84 | * ];
85 | * injectStyles(styles);
86 | * ```
87 | */
88 | export function injectStyles(styles) {
89 | injectStyleTag();
90 | styles.forEach((style) => {
91 | updateStore(style.id, style.style, style.pseudoClass, style.disabled);
92 | });
93 | updateStyling();
94 | }
95 |
96 | /**
97 | * Removes all pseudoClass styles based on the provided styleId.
98 | */
99 | export function removeStyle(styleId) {
100 | delete styleStorage[styleId];
101 | updateStyling();
102 | }
103 |
104 | /**
105 | * Removes all pseudoClass styles based on all provided styleIds.
106 | */
107 | export function removeAllStyles(styleIds) {
108 | styleIds.forEach((styleId) => {
109 | delete styleStorage[styleId];
110 | });
111 | updateStyling();
112 | }
113 |
114 | /**
115 | * Injects a style tag and adds the passed style for the provided pseudoClass.
116 | */
117 | export default function (styleId, style, pseudoClass, disabled) {
118 | injectStyleTag();
119 | updateStore(styleId, style, pseudoClass, disabled);
120 | updateStyling();
121 | }
122 |
--------------------------------------------------------------------------------
/src/utils/is-component-of-type.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns true if the provided element is a component of the provided type.
3 | *
4 | * @param classType {ReactElement class} - the class of a React Element
5 | * @param reactElement {ReactElement} - any React Element (not a real DOM node)
6 | *
7 | * @example
8 | * // Checks if the component is an Autocomplete
9 | * isComponentType(Autocomplete, this.props.children[0]);
10 | */
11 | export default function isComponentOfType(classType, reactElement) {
12 | return reactElement &&
13 | reactElement.type === classType;
14 | }
15 |
--------------------------------------------------------------------------------
/src/utils/union-class-names.js:
--------------------------------------------------------------------------------
1 | import { union } from '../utils/helpers';
2 |
3 | /**
4 | * Returns a string containing all classes without duplicates.
5 | *
6 | * @param existingClassNames {String} - one or multiple classes
7 | * @param additionalClassNames {String} - one or multiple classes
8 | *
9 | * @example
10 | * // returns 'style-id-23 button buy-button'
11 | * unionClassNames('style-id-23 button', 'button buy-button')
12 | *
13 | * Originally inspired by https://github.com/rackt/react-autocomplete/blob/master/lib/union-class-names.js
14 | */
15 | export default function unionClassNames(existingClassNames, additionalClassNames) {
16 | if (!existingClassNames && !additionalClassNames) return '';
17 | if (!existingClassNames) return additionalClassNames;
18 | if (!additionalClassNames) return existingClassNames;
19 | return union(existingClassNames.split(' '), additionalClassNames.split(' ')).join(' ');
20 | }
21 |
--------------------------------------------------------------------------------