├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── devServer.js
├── example
├── bower_components
│ └── primer-css
│ │ ├── .bower.json
│ │ ├── .bowerrc
│ │ ├── .editorconfig
│ │ ├── CONTRIBUTING.md
│ │ ├── LICENSE.md
│ │ ├── MAINTAINING.md
│ │ ├── README.md
│ │ ├── bower.json
│ │ ├── css
│ │ ├── .primer-stats.md
│ │ └── primer.css
│ │ ├── package.json
│ │ └── scss
│ │ ├── _alerts.scss
│ │ ├── _avatars.scss
│ │ ├── _base.scss
│ │ ├── _blankslate.scss
│ │ ├── _buttons.scss
│ │ ├── _counter.scss
│ │ ├── _filter-list.scss
│ │ ├── _flex-table.scss
│ │ ├── _forms.scss
│ │ ├── _layout.scss
│ │ ├── _menu.scss
│ │ ├── _mixins.scss
│ │ ├── _normalize.scss
│ │ ├── _states.scss
│ │ ├── _tabnav.scss
│ │ ├── _tooltips.scss
│ │ ├── _truncate.scss
│ │ ├── _type.scss
│ │ ├── _utility.scss
│ │ ├── _variables.scss
│ │ └── primer.scss
├── build
│ ├── 43a3909d45ff1632beed7e4fff7e04d5.png
│ ├── app.css
│ └── app.js
├── example.gif
├── index.html
└── src
│ ├── images
│ ├── congruent_pentagon.png
│ └── screenshot.jpg
│ ├── scripts
│ ├── App.jsx
│ ├── CustomElement.jsx
│ ├── NotificationGenerator.jsx
│ └── showcase.js
│ └── styles
│ ├── base.sass
│ ├── generator.sass
│ └── variables.sass
├── karma.conf.js
├── package.json
├── src
├── NotificationContainer.jsx
├── NotificationItem.jsx
├── NotificationSystem.jsx
├── constants.js
├── helpers.js
└── styles.js
├── test
└── notification-system.test.js
├── tests.webpack.js
├── webpack.config.dev.js
├── webpack.config.prod.js
├── webpack.config.umd.dev.js
├── webpack.config.umd.prod.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | # Change these settings to your own preference
10 | indent_style = space
11 | indent_size = 2
12 |
13 | # We recommend you to keep these unchanged
14 | end_of_line = lf
15 | charset = utf-8
16 | trim_trailing_whitespace = true
17 | insert_final_newline = true
18 |
19 | [*.md]
20 | trim_trailing_whitespace = false
21 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb/legacy",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "es6": true,
7 | "mocha": true
8 | },
9 | "plugins": [
10 | "react",
11 | "import"
12 | ],
13 | "parserOptions": {
14 | "ecmaFeatures": {
15 | "modules": true,
16 | "jsx": true
17 | }
18 | },
19 | "rules": {
20 | "react/display-name": 0,
21 | "react/jsx-curly-spacing": [2, "always"],
22 | "react/jsx-no-duplicate-props": 2,
23 | "react/jsx-no-undef": 2,
24 | "react/jsx-uses-react": 2,
25 | "react/jsx-uses-vars": 2,
26 | "react/no-did-update-set-state": 2,
27 | "react/no-multi-comp": 2,
28 | "react/no-unknown-property": 2,
29 | "react/prop-types": 2,
30 | "react/react-in-jsx-scope": 2,
31 | "react/self-closing-comp": 2,
32 | "react/jsx-wrap-multilines": 2,
33 | "react/sort-comp": 0,
34 |
35 | "import/extensions": 2,
36 |
37 | "space-before-function-paren": 0,
38 | "quotes": [2, "single", "avoid-escape"],
39 | "jsx-quotes": [2, "prefer-double"],
40 | "comma-dangle": [2, "never"],
41 | "indent": [2, 2],
42 | "object-curly-spacing": [2, "always"],
43 | "no-undef": 2,
44 | "no-underscore-dangle": 0,
45 | "func-names": 0,
46 | "no-else-return": 0,
47 | "no-console": 0,
48 | "no-throw-literal": 0,
49 | "id-length": 0,
50 | "max-len": 0
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | coverage/
3 | node_modules/
4 | dist/
5 | example/node_modules/
6 | example/build/.module-cache/
7 | *.log*
8 | .idea
9 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | example/
3 | coverage/
4 | test/
5 | dist/.module-cache/
6 | .gitignore
7 | .git/
8 | webpack.*
9 | karma.config.js
10 | devServer.js
11 | tests.webpack.js
12 | .travis.yml
13 | .editorconfig
14 | *.log
15 | .idea/
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "12"
4 | services:
5 | - xvfb
6 | before_script:
7 | - export DISPLAY=:99.0
8 | addons:
9 | chrome: "stable"
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 0.3.0 - Nov 14, 2019
4 |
5 | * Updated to class components and removed future deprecated lifecycle methods (thanks to @oskarer)
6 |
7 | ## 0.2.17 - Feb 21, 2018
8 |
9 | * Dismissible enhancements (thanks to @thepeted)
10 |
11 | ## 0.2.16 - Oct 19, 2017
12 |
13 | * Support for React 16 (thanks to @marudor)
14 |
15 | ## 0.2.15 - Aug 1, 2017
16 |
17 | * UMD build now specifies the library name (thanks to @franckamayou)
18 |
19 | ## 0.2.14 - May 3, 2017
20 |
21 | * Ability to [edit notifications](https://github.com/igorprado/react-notification-system#removenotificationnotification). (thanks to @syndbg)
22 | * Removed deprecation warning. Now using `prop-types` and `create-react-class`packages. (thanks to @andrewBalekha)
23 | * Fix calling `onRemove` before updating the notifications state. (thanks to @szdc)
24 |
25 | ## 0.2.13 - Mar 14, 2017
26 |
27 | * UMD support. (thanks to @jochenberger)
28 |
29 | ## 0.2.12 - Mar 01, 2017
30 |
31 | * Adds support for enter and exit animations for NotificationItem. (thanks to @OriR)
32 |
33 | ## 0.2.11 - Dec 06, 2016
34 |
35 | * Added `clearNotifications()` method (thanks to @saaqibz)
36 |
37 | ## 0.2.10 - Aug 29, 2016
38 |
39 | * Allows children content to override `action`. (thanks to @gor181)
40 |
41 | ## 0.2.9 - Aug 25, 2016
42 |
43 | * Improved CSS styles for better performance
44 | * Merged pull request to avoid warnings related to component state
45 |
46 | ## 0.2.7 - Nov 20, 2015
47 |
48 | **React 15 support:**
49 |
50 | * Version 0.2.x now supports React 15 too.
51 |
52 |
53 | ## 0.2.6 - Nov 20, 2015
54 |
55 | **Bugfix from PR:**
56 |
57 | * Fix wrapper styles override.
58 |
59 |
60 | ## 0.2.5 - Oct 15, 2015
61 |
62 | **Implemented enhancements:**
63 |
64 | * Action property no longer needs a callback, just a label.
65 |
66 | ## 0.2.4 - Oct 12, 2015
67 |
68 | **Implemented enhancements:**
69 |
70 | * Added React and ReactDOM as peerDependencies and devDependencies to help on component development.
71 |
72 | ## 0.2.3 - Oct 11, 2015
73 |
74 | **Implemented enhancements:**
75 |
76 | * Possibility to remove notification by uid.
77 | * Added onAdd property to notification object.
78 | * Improved styles.
79 |
80 | ## 0.2.2 - Oct 10, 2015
81 |
82 | ** Removed unused code**
83 |
84 | * Some unnecessary `console.logs` was left behind.
85 |
86 | ## 0.2.1 - Oct 9, 2015
87 |
88 | **Implemented enhancements:**
89 |
90 | * Improved function to get specific style based on element.
91 | * Improved notification styles.
92 | * Added ESLint and linted all src files.
93 |
94 | ## 0.2.0 - Oct 9, 2015
95 |
96 | **Implemented enhancements:**
97 |
98 | * Now supports React 0.14!
99 |
100 | ## 0.1.17 - Oct 9, 2015
101 |
102 | **Implemented enhancements, merged pull requrests:**
103 |
104 | * Fix dismissible false to not require an action.
105 | * Added CHANGELOG and LICENSE files.
106 |
107 | ## 0.1.15 - Oct 1, 2015
108 |
109 | **Implemented enhancements:**
110 |
111 | * `addNotification()` method now returns the notification object.
112 | * Added method `removeNotification()` to remove a notification programmatically based on returned notification object.
113 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Igor Prado
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Notification System
2 |
3 | [](http://badge.fury.io/js/react-notification-system) [](https://www.npmjs.com/package/react-notification-system) [](https://david-dm.org/igorprado/react-notification-system) [](https://david-dm.org/igorprado/react-notification-system#info=devDependencies) [](https://travis-ci.org/igorprado/react-notification-system) [](https://coveralls.io/github/igorprado/react-notification-system?branch=master)
4 |
5 | > A complete and totally customizable component for notifications in React.
6 |
7 | _Initially built for [Eterpret](http://dev.eterpret.com) @ [Scalable Path](http://www.scalablepath.com)._
8 |
9 |
10 |
11 | ## Installing
12 |
13 | This component is available as CommonJS and UMD module. Install via NPM running:
14 |
15 | ```
16 | npm install react-notification-system
17 | ```
18 |
19 | ### Important
20 |
21 | For **React ^0.14.x** or **React ^15.x.x**, use version 0.2.x:
22 |
23 | ```
24 | npm install react-notification-system@0.2.x
25 | ```
26 |
27 | For **React 0.13.x**, use version 0.1.x:
28 |
29 | ```
30 | npm install react-notification-system@0.1.x
31 | ```
32 |
33 |
34 |
35 | ## Using
36 |
37 | For optimal appearance, this component **must be rendered on a top level HTML element** in your application to avoid position conflicts.
38 |
39 | Here is a basic example. For a more advanced usage, please see the [example code](https://github.com/igorprado/react-notification-system/blob/master/example/src/scripts/App.jsx).
40 |
41 |
42 | Class-based components can also be used as follows
43 | ```jsx
44 | import React from 'react';
45 | import ReactDOM from 'react-dom';
46 | import NotificationSystem from 'react-notification-system';
47 |
48 | export default class MyComponent extends React.Component {
49 | notificationSystem = React.createRef();
50 |
51 | addNotification = event => {
52 | event.preventDefault();
53 | const notification = this.notificationSystem.current;
54 | notification.addNotification({
55 | message: 'Notification message',
56 | level: 'success'
57 | });
58 | };
59 |
60 | render() {
61 | return (
62 |
63 | Add notification
64 |
65 |
66 | );
67 | }
68 | }
69 |
70 | ReactDOM.render(
71 | React.createElement(MyComponent),
72 | document.getElementById('app')
73 | );
74 | ```
75 |
76 | ## Methods
77 |
78 | ### `addNotification(notification)`
79 |
80 | Add a notification object. This displays the notification based on the [object](#creating-a-notification) you passed.
81 |
82 | Returns the notification object to be used to programmatically dismiss a notification.
83 |
84 | ### `removeNotification(notification)`
85 |
86 | Remove a notification programmatically. You can pass an object returned by `addNotification()` or by `onAdd()` callback. If passing an object, you need to make sure it must contain the `uid` property. You can pass only the `uid` too: `removeNotification(uid)`.
87 |
88 |
89 | ### `editNotification(notification, newProperties)`
90 |
91 | Edit a notification programmatically. You can pass an object previously returned by `addNotification()` or by `onAdd()` callback as `notification`. If passing an object as `notification`, you need to make sure it must contain the `uid` property. You can pass only the `uid` too: `editNotification(uid, newProperties)`.
92 |
93 |
94 | ### `clearNotifications()`
95 |
96 | Removes ALL notifications programatically.
97 |
98 | ## Creating a notification
99 |
100 | The notification object has the following properties:
101 |
102 | | Name | Type | Default | Description |
103 | |------------ |--------------- |--------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
104 | | title | string | null | Title of the notification |
105 | | message | string | null | Message of the notification |
106 | | level | string | null | Level of the notification. Available: **success**, **error**, **warning** and **info** |
107 | | position | string | tr | Position of the notification. Available: **tr (top right)**, **tl (top left)**, **tc (top center)**, **br (bottom right)**, **bl (bottom left)**, **bc (bottom center)** |
108 | | autoDismiss | integer | 5 | Delay in seconds for the notification go away. Set this to **0** to not auto-dismiss the notification |
109 | | dismissible | string | both | Settings controlling how the user can dismiss the notification and whether the dismiss button is visible. Available: **both (The disable button is visible and the user can click anywhere on the notification to dismiss)**, **click (The disable button is NOT visible and the user can click anywhere on the notification to dismiss)**, **button (The user can click on the disable button to dismiss the notifiction)**, **none (None [See more](#dismissible))** |
110 | | action | object | null | Add a button with label and callback function (callback is optional). [See more](#action) |
111 | | children | element,string | null | Adds custom content, and overrides `action` (if defined) [See more](#children) |
112 | | onAdd | function | null | A callback function that will be called when the notification is successfully added. The first argument is the original notification e.g. `function (notification) { console.log(notification.title + 'was added'); }` |
113 | | onRemove | function | null | A callback function that will be called when the notification is about to be removed. The first argument is the original notification e.g. `function (notification) { console.log(notification.title + 'was removed'); }` |
114 | | uid | integer/string | null | Overrides the internal `uid`. Useful if you are managing your notifications id. Notifications with same `uid` won't be displayed. |
115 |
116 |
117 | ### Dismissible
118 |
119 | If set to 'none', the button will only be dismissible programmatically or after autoDismiss timeout. [See more](#removenotificationnotification)
120 |
121 | ### Action
122 |
123 | Add a button and a callback function to the notification. If this button is clicked, the callback function is called (if provided) and the notification is dismissed.
124 |
125 | ```js
126 | notification = {
127 | [...],
128 | action: {
129 | label: 'Button name',
130 | callback: function() {
131 | console.log('Notification button clicked!');
132 | }
133 | }
134 | }
135 |
136 | ```
137 |
138 | ### Children
139 |
140 | Add custom content / react elements
141 |
142 | ```js
143 | notification = {
144 | [...],
145 | children: (
146 |
147 |
Hello World
148 |
Anchor
149 |
150 | )
151 | }
152 |
153 | ```
154 |
155 | ## Styles
156 |
157 | This component was made to work as plug and play. For that, a handcrafted style was added to it and is used as inline CSS.
158 |
159 | You can change this style by overriding the default inline styles or disable all inline styles and use your own styles.
160 |
161 | ### Overriding
162 |
163 | For this, use the `style` prop to pass an object with your styles. Your object must be something like this:
164 |
165 | ```js
166 | var style = {
167 | NotificationItem: { // Override the notification item
168 | DefaultStyle: { // Applied to every notification, regardless of the notification level
169 | margin: '10px 5px 2px 1px'
170 | },
171 |
172 | success: { // Applied only to the success notification item
173 | color: 'red'
174 | }
175 | }
176 | }
177 |
178 |
179 |
180 | ```
181 |
182 | Refer to [this file](https://github.com/igorprado/react-notification-system/blob/master/src/styles.js) to see what can you override.
183 |
184 | ### Disabling inline styles
185 |
186 | To disable all inline styles, just pass `false` to the prop `style`.
187 |
188 | ```js
189 |
190 | ```
191 |
192 | Here is the notification HTML:
193 |
194 | ```html
195 |
196 |
197 |
198 |
Default title
199 |
Default message
200 |
×
201 |
202 | Action button
203 |
204 |
205 |
206 |
207 |
208 | ```
209 |
210 | #### Important
211 |
212 | Using this method you have to take care of **every style**, from containers positions to animations. To control animations, use the classes `notification-visible` and `notification-hidden`. If your CSS styles will not handle any animation (transition), you need to set the prop `noAnimation` to `true` when adding the Notification System component:
213 |
214 | ```js
215 |
216 | ```
217 |
218 | See [#74](https://github.com/igorprado/react-notification-system/issues/74) for more details.
219 |
220 | ### Appending/Prepending notifications
221 |
222 | You can control where should new notification appear (on the top or bottom of current notifications, defaults to bottom) by setting `newOnTop` boolean prop on ` ` component:
223 |
224 | ```js
225 |
226 | ```
227 |
228 | This will render new notifications on top of current ones
229 |
230 | ## Roadmap
231 |
232 | * Improve tests and coverage
233 | * Improve performance
234 |
235 | ## Contributions
236 |
237 | Clone this repo by running:
238 |
239 | ```
240 | git clone git@github.com:igorprado/react-notification-system.git
241 | ```
242 |
243 | Enter the project folder and install the dependencies:
244 |
245 | ```
246 | npm install
247 | ```
248 |
249 | To start a development server and use the `example` app to load the component, type:
250 |
251 | ```
252 | npm start
253 | ```
254 |
255 | Open `http://localhost:8000`.
256 |
257 | ---
258 |
259 | Run the tests:
260 |
261 | ```
262 | npm test
263 | ```
264 |
265 | You can find the coverage details under `coverage/` folder.
266 |
267 | After that, just edit the files under `src/` and `example/src/app.js`. It uses React hot reload.
268 |
269 | This component is under construction. I will add more guidelines to who wants to contribute.
270 |
--------------------------------------------------------------------------------
/devServer.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var express = require('express');
3 | var webpack = require('webpack');
4 | var config = require('./webpack.config.dev');
5 |
6 | var app = express();
7 | var compiler = webpack(config);
8 |
9 | app.use(require('webpack-dev-middleware')(compiler, {
10 | noInfo: true,
11 | publicPath: '/' + config.output.publicPath
12 | }));
13 |
14 | app.use(require('webpack-hot-middleware')(compiler));
15 |
16 | app.get('*', function(req, res) {
17 | res.sendFile(path.join(__dirname, 'example/index.html'));
18 | });
19 |
20 | app.listen(8000, 'localhost', function(err) {
21 | if (err) {
22 | console.log(err);
23 | return;
24 | }
25 |
26 | console.log('Listening at http://localhost:8000');
27 | });
28 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "primer-css",
3 | "ignore": [
4 | "docs/",
5 | ".gitignore",
6 | ".hound.yml",
7 | ".scss-lint.yml",
8 | "_config.yml",
9 | "Gemfile",
10 | "Gemfile.lock",
11 | "Gruntfile.js"
12 | ],
13 | "main": [
14 | "scss/primer.scss"
15 | ],
16 | "dependencies": {
17 | "octicons": "*"
18 | },
19 | "homepage": "https://github.com/primer/primer",
20 | "version": "2.3.5",
21 | "_release": "2.3.5",
22 | "_resolution": {
23 | "type": "version",
24 | "tag": "v2.3.5",
25 | "commit": "7c10c74c64e1788b8ccfee92031fbfa19d2088cd"
26 | },
27 | "_source": "git://github.com/primer/primer.git",
28 | "_target": "~2.3.5",
29 | "_originalSource": "primer-css",
30 | "_direct": true
31 | }
--------------------------------------------------------------------------------
/example/bower_components/primer-css/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "docs/bower_components"
3 | }
--------------------------------------------------------------------------------
/example/bower_components/primer-css/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | indent_size = 2
9 | indent_style = space
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing
2 |
3 | [fork]: https://github.com/github/primer/fork
4 | [pr]: https://github.com/github/primer/compare
5 | [style]: http://primercss.io/guidelines/
6 |
7 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
8 |
9 | After you open your first pull request, you will be asked to accept [this license agreement](https://cla.github.com/). Let us know in the PR if you have any hesitation or concerns.
10 |
11 | ## Using the issue tracker
12 |
13 | The [issue tracker](https://github.com/primer/primer/issues) is the preferred channel for [bug reports](#bug-reports), [features requests](#feature-requests) and [submitting pull requests](#pull-requests), but please respect the following restrictions:
14 |
15 | * Please **do not** use the issue tracker for personal support requests.
16 | * Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions of others.
17 | * Please **do not** open issues or pull requests regarding the code in [`Normalize`](https://github.com/necolas/normalize.css) (open them in their respective repositories).
18 |
19 | ## Bug reports
20 |
21 | A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports are extremely helpful, so thanks!
22 |
23 | Guidelines for bug reports:
24 |
25 | 0. **Validate and lint your code** — [validate your HTML](http://html5.validator.nu) to ensure your problem isn't caused by a simple error in your own code.
26 |
27 | 1. **Use the GitHub issue search** — check if the issue has already been reported.
28 |
29 | 2. **Check if the issue has been fixed** — try to reproduce it using the latest `master` or development branch in the repository.
30 |
31 | 3. **Isolate the problem** — ideally create a [reduced test case](https://css-tricks.com/reduced-test-cases/) and a live example. [This JS Bin](http://jsbin.com/lefey/1/edit?html,output) is a helpful template.
32 |
33 | A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. What is your environment? What steps will reproduce the issue? What browser(s) and OS experience the problem? Do other browsers show the bug differently? What would you expect to be the outcome? All these details will help people to fix any potential bugs.
34 |
35 | Example:
36 |
37 | > Short and descriptive example bug report title
38 | >
39 | > A summary of the issue and the browser/OS environment in which it occurs. If
40 | > suitable, include the steps required to reproduce the bug.
41 | >
42 | > 1. This is the first step
43 | > 2. This is the second step
44 | > 3. Further steps, etc.
45 | >
46 | > `` - a link to the reduced test case
47 | >
48 | > Any other information you want to share that is relevant to the issue being reported. This might include the lines of code that you have identified as causing the bug, and potential solutions (and your opinions on their merits).
49 |
50 | ## Feature requests
51 |
52 | Feature requests are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. It's up to *you* to make a strong case to convince the project's developers of the merits of this feature. Please provide as much detail and context as possible.
53 |
54 | ## Pull requests
55 |
56 | Good pull requests—patches, improvements, new features—are a fantastic help. They should remain focused in scope and avoid containing unrelated commits.
57 |
58 | **Please ask first** before embarking on any significant pull request (e.g. implementing features, refactoring code, porting to a different language), otherwise you risk spending a lot of time working on something that the project's developers might not want to merge into the project.
59 |
60 | Adhering to the following process is the best way to get your work included in the project:
61 |
62 | 1. Fork and clone the repository
63 | 2. Configure and install the dependencies: `bower install`
64 | 3. Create a new branch: `git checkout -b my-branch-name`
65 | 4. Make your change, add tests, and make sure the tests still pass
66 | 5. Push to your fork and [submit a pull request](https://help.github.com/articles/creating-a-pull-request/)
67 | 6. Pat your self on the back and wait for your pull request to be reviewed and merged.
68 |
69 | Here are a few things you can do that will increase the likelihood of your pull request being accepted:
70 |
71 | - Follow the [style guide][style].
72 | - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
73 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
74 |
75 | ## Resources
76 |
77 | - [Contributing to Open Source on GitHub](https://guides.github.com/activities/contributing-to-open-source/)
78 | - [Using Pull Requests](https://help.github.com/articles/using-pull-requests/)
79 | - [GitHub Help](https://help.github.com)
80 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 GitHub, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/MAINTAINING.md:
--------------------------------------------------------------------------------
1 | # Maintaining
2 |
3 | Steps for updating and releasing changes to Primer and it's site.
4 |
5 | ## Versioning
6 |
7 | Primer follows the semantic versioning approach:
8 |
9 | - Bug fixes and docs updates are patch releases, so `1.0.x`.
10 | - New additions are minor updates, so `1.x.x`.
11 | - Deleting or rewriting anything are major updates, so `x.x.x`.
12 |
13 | ## Changelogs and milestones
14 |
15 | Changelogs are handled with dedicated tracking issues ([see example](https://github.com/primer/primer/issues/108)). When starting work on a new release:
16 |
17 | 1. Open a new milestone.
18 | 2. Open a new tracking issue and immediately lock it. (No comments are needed, ship lists are just for us.)
19 | 3. As you close issues and merge pull requests, add a link to those threads to the tracking issue.
20 |
21 | When the release and milestone are about ready to ship, move on the the releasing flow.
22 |
23 | ## Releasing
24 |
25 | Have a new version to release? Hell yeah, let's do it.
26 |
27 | 1. Bump the version numbers in `_config.yml` for our docs and `package.json` for dependency management.
28 | 2. Run `$ grunt` to generate the latest compiled CSS and Parker stats.
29 | 3. Recompile Jekyll for the latest docs changes.
30 | 4. Punt any remaining open issues and PRs on the milestone to the next milestone, then close that milestone.
31 | 5. Head to and create a new release. Title it `vX.X.X` and post the changelog to the body.
32 | 6. Run `$ grunt publish` to push the latest docs and CSS changes to .
33 | 7. Rejoice!
34 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/README.md:
--------------------------------------------------------------------------------
1 | # Primer
2 |
3 | Primer is the CSS toolkit that powers GitHub's front-end design. It's purposefully limited to common components to provide our developers with the most flexibility, and to keep GitHub uniquely *GitHubby*. It's built with SCSS and available via Bower, so it's easy to include all or part of it within your own project.
4 |
5 | [**Read the Primer documentation**](http://primercss.io) to learn more.
6 |
7 | _**Heads up!** We love open source, but Primer is unlikely to add new features that are not used in GitHub.com. It's first and foremost our CSS toolkit. We really love to share though, so hopefully that means we're still friends <3._
8 |
9 | ## Contents
10 |
11 | - [Install](#install)
12 | - [Usage](#usage)
13 | - [Documentation](#documentation)
14 | - [Dependencies](#dependencies)
15 | - [Running locally](#running-locally)
16 | - [Publishing](#publishing)
17 | - [Primer stats](#primer-stats)
18 | - [Updating](#updating)
19 | - [Contributing](#contributing)
20 | - [Versioning](#versioning)
21 | - [License](#license)
22 |
23 | ## Install
24 |
25 | ### Manually
26 |
27 | Download the [latest release](https://github.com/primer/primer/releases/latest) and copy the SCSS files over to your own project. Once your files are in place, jump to the [usage guidelines](#usage) for including Primer into your own CSS.
28 |
29 | ### Bower
30 |
31 | ```
32 | $ bower install primer-css --save
33 | ```
34 |
35 | ### Things to know
36 |
37 | **Hey, GitHubbers!** For GitHub.com, you'll need to `cd` into `vendor/assets` and run `bower install` there. Be sure to commit and push all the changes, including the `bower.json` and everything under `bower_components`.
38 |
39 | ## Usage
40 |
41 | Once included, simply `@import` either the master SCSS file, or the individual files as you need them.
42 |
43 | ```scss
44 | // Example: All of Primer
45 | @import "primer-css/scss/primer";
46 |
47 | // Example: Individual files
48 | @import "primer-css/scss/variables";
49 | @import "primer-css/scss/mixins";
50 | @import "primer-css/scss/base";
51 | ```
52 |
53 | ## Documentation
54 |
55 | Primer's documentation is built with Jekyll and published to `http://primercss.io` via the `gh-pages` branch.
56 |
57 | ### Dependencies
58 |
59 | You'll need the following installed:
60 |
61 | - Latest Jekyll (minimum v2.2.0): `$ gem install jekyll`
62 | - Latest Rouge: `$ gem install rouge`
63 | - Latest Sass: `$ gem install sass`
64 | - Latest Grunt CLI: `$ npm install -g grunt-cli`
65 | - [Node.js and npm](http://nodejs.org/download/)
66 |
67 | Chances are you have all this already if you work on `github/github` or similar projects. If you have all those set up, now you can install the dependencies:
68 |
69 | ```bash
70 | $ npm install
71 | $ bower install
72 | ```
73 |
74 | ### Running locally
75 |
76 | From the Terminal, start a local Jekyll server:
77 |
78 | ```bash
79 | $ jekyll serve
80 | ```
81 |
82 | Open a second Terminal tab to automatically recompile the Sass files, run autoprefixer, and update our [Primer stats file](#primer-stats):
83 |
84 | ```bash
85 | $ grunt watch
86 | ```
87 |
88 | Alternatively, you can manually run `grunt` and `jekyll serve` when needed.
89 |
90 | ### Publishing
91 |
92 | Use the included Grunt task to generate and publish Primer's docs to the `gh-pages` branch.
93 |
94 | ```bash
95 | $ grunt publish
96 | ```
97 |
98 | This takes the `_site` directory, generates it's own Git repository there, and publishes the contents to the `gh-pages` branch here on GitHub. Changes are reflected in the hosted docs within a minute or so.
99 |
100 | ### Primer stats
101 |
102 | When compiling or watching the Sass files, Primer will automatically generate a `.primer-stats.md` file. This is tracked in the Git repository to provide us historical and contextual information on the changes we introduce. For example, we'll know when the number of selectors or declarations rises sharply within a single change.
103 |
104 | ## Updating
105 |
106 | Within `bower.json`, update to a new release by changing the version number that follows the `#` in the dependency URL.
107 |
108 | ```json
109 | {
110 | "name": "myapp",
111 | "dependencies": {
112 | "primer-css": "x.x.x"
113 | }
114 | }
115 | ```
116 |
117 | To pull down the updated package, `cd` into `vendor/assets`, and run `bower install`.
118 |
119 | ```
120 | $ cd vendor/assets
121 | $ bower install
122 | ```
123 |
124 | Check in `bower.json` and all changes under `vendor/assets/bower_components`.
125 |
126 | ## Development
127 |
128 | Development of Primer happens in our primary branch, `master`. For stable versions, see the [releases page](https://github.com/primer/primer/releases). `master` will always be up to date with the latest changes, including those which have yet to be released.
129 |
130 | ## Contributing
131 |
132 | By contributing to Primer, you agree to the terms presented in [this license agreement](https://cla.github.com/). *More information will be provided here soon.*
133 |
134 | When contributing changes to Primer, be sure to do the following steps when opening a pull request:
135 |
136 | 1. Bump the version number in `bower.json` (it's purely placebo right now, but it's good habit) and `package.json`.
137 | 2. Run `grunt` and commit the changes. This compiles the SCSS to CSS so we can do basic analysis on the number of selectors, file size, etc.
138 |
139 | In addition, please read through our [contributing guidelines](https://github.com/primer/primer/blob/master/CONTRIBUTING.md). Included are directions for opening issues, coding standards, and notes on development.
140 |
141 | All HTML and CSS should conform to the [style guidelines](http://primercss.io/guidelines).
142 |
143 | Editor preferences are available in the [editor config](https://github.com/primer/primer/blob/master/.editorconfig) for easy use in common text editors. Read more and download plugins at .
144 |
145 | ## Versioning
146 |
147 | For transparency into our release cycle and in striving to maintain backward compatibility, Primer is maintained under [the Semantic Versioning guidelines](http://semver.org/). Sometimes we screw up, but we'll adhere to those rules whenever possible.
148 |
149 | ## License
150 |
151 | Created by and copyright GitHub, Inc. Released under the [MIT license](LICENSE.md).
152 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "primer-css",
3 | "ignore": [
4 | "docs/",
5 | ".gitignore",
6 | ".hound.yml",
7 | ".scss-lint.yml",
8 | "_config.yml",
9 | "Gemfile",
10 | "Gemfile.lock",
11 | "Gruntfile.js"
12 | ],
13 | "main": [
14 | "scss/primer.scss"
15 | ],
16 | "dependencies": {
17 | "octicons": "*"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/css/.primer-stats.md:
--------------------------------------------------------------------------------
1 | # [primer]( http://primercss.io )
2 |
3 | **Version:** `2.3.3`
4 |
5 | ## Parker Report
6 |
7 | ### css/primer.css
8 |
9 | - **Total Stylesheets:** 1
10 | - **Total Stylesheet Size:** 28889
11 | - **Total Media Queries:** 1
12 | - **Total Rules:** 372
13 | - **Selectors Per Rule:** 1.521505376344086
14 | - **Total Selectors:** 566
15 | - **Identifiers Per Selector:** 2.2703180212014136
16 | - **Specificity Per Selector:** 17.32155477031802
17 | - **Top Selector Specificity:** 50
18 | - **Top Selector Specificity Selector:** .fullscreen-overlay-enabled.dark-theme .tooltipped .tooltipped-s:before
19 | - **Total Id Selectors:** 0
20 | - **Total Identifiers:** 1285
21 | - **Total Declarations:** 924
22 | - **Total Unique Colors:** 81
23 | - **Total Important Keywords:** 1
24 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "primer-css",
3 | "version": "2.3.5",
4 | "homepage": "http://primercss.io",
5 | "author": "GitHub, Inc.",
6 | "scss": "./scss/primer.scss",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/primer/primer.git"
11 | },
12 | "devDependencies": {
13 | "autoprefixer-core": "~5.2.1",
14 | "grunt": "~0.4.5",
15 | "grunt-build-control": "~0.2.0",
16 | "grunt-jekyll": "~0.4.2",
17 | "grunt-parker": "~0.1.0",
18 | "grunt-sass": "~0.18.0",
19 | "grunt-contrib-watch": "~0.6.1",
20 | "grunt-postcss": "~0.5.1"
21 | },
22 | "description": "Primer is the CSS toolkit that powers GitHub's front-end design. It's purposefully limited to common components to provide our developers with the most flexibility, and to keep GitHub uniquely *GitHubby*. It's built with SCSS and available via Bower, so it's easy to include all or part of it within your own project.",
23 | "bugs": {
24 | "url": "https://github.com/primer/primer/issues"
25 | },
26 | "main": "css/primer.css",
27 | "directories": {
28 | "doc": "docs"
29 | },
30 | "dependencies": {
31 | "grunt-jekyll": "^0.4.2",
32 | "grunt-autoprefixer": "^2.2.0",
33 | "grunt-contrib-watch": "^0.6.1",
34 | "grunt-sass": "^0.18.1",
35 | "grunt-parker": "^0.1.3",
36 | "grunt": "^0.4.5",
37 | "grunt-build-control": "^0.2.2"
38 | },
39 | "scripts": {
40 | "test": "echo \"Error: no test specified\" && exit 1"
41 | },
42 | "keywords": [
43 | "primer",
44 | "css",
45 | "github",
46 | "primercss"
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_alerts.scss:
--------------------------------------------------------------------------------
1 | // Default flash
2 | .flash {
3 | position: relative;
4 | padding: 15px;
5 | font-size: 14px;
6 | line-height: 1.5;
7 | color: #246;
8 | background-color: #e2eef9;
9 | border: 1px solid #bac6d3;
10 | border-radius: 3px;
11 |
12 | p:last-child {
13 | margin-bottom: 0;
14 | }
15 | }
16 |
17 | // Contain the flash messages
18 | .flash-messages {
19 | margin-bottom: 20px;
20 | }
21 |
22 | // Close button
23 | .flash-close {
24 | float: right;
25 | width: 34px;
26 | height: 44px;
27 | margin: -11px;
28 | color: inherit;
29 | line-height: 40px;
30 | text-align: center;
31 | cursor: pointer;
32 | opacity: 0.6;
33 | // Undo `` styles
34 | background: none;
35 | border: 0;
36 | -webkit-appearance: none;
37 |
38 | &:hover {
39 | opacity: 1;
40 | }
41 | }
42 |
43 | // Action button
44 | .flash-action {
45 | float: right;
46 | margin-top: -4px;
47 | margin-left: 20px;
48 | }
49 |
50 |
51 | //
52 | // Variations
53 | //
54 |
55 | .flash-warn {
56 | color: #4c4a42;
57 | background-color: #fff9ea;
58 | border-color: #dfd8c2;
59 | }
60 |
61 | .flash-error {
62 | color: #911;
63 | background-color: #fcdede;
64 | border-color: #d2b2b2;
65 | }
66 |
67 | .flash-full {
68 | margin-top: -1px;
69 | border-width: 1px 0;
70 | border-radius: 0;
71 | }
72 |
73 | .flash-with-icon {
74 | .container {
75 | padding-left: 40px;
76 | }
77 |
78 | .flash-icon {
79 | float: left;
80 | margin-top: 3px;
81 | margin-left: -25px;
82 | }
83 | }
84 |
85 | // Content within
86 | //
87 | // Reset margins on headings and paragraphs within alerts.
88 | .flash-content {
89 | margin-top: 0;
90 | margin-bottom: 0;
91 | line-height: 1.5;
92 | }
93 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_avatars.scss:
--------------------------------------------------------------------------------
1 | .avatar {
2 | display: inline-block;
3 | overflow: hidden; // Ensure page layout in Firefox should images fail to load
4 | line-height: 1;
5 | vertical-align: middle;
6 | border-radius: 3px;
7 | }
8 |
9 | .avatar-small { border-radius: 2px; }
10 |
11 | .avatar-link {
12 | float: left;
13 | line-height: 1;
14 | }
15 |
16 | // User for example on /stars and /user for grids of avatars
17 | .avatar-group-item {
18 | display: inline-block;
19 | margin-bottom: 3px;
20 | }
21 |
22 | // .avatar-parent-child is when you see a small avatar at the bottom right
23 | // corner of a larger avatar.
24 | //
25 | // No Styleguide version
26 | .avatar-parent-child {
27 | position: relative;
28 | }
29 |
30 | .avatar-child {
31 | position: absolute;
32 | right: -15%;
33 | bottom: -9%;
34 | background-color: #fff; // For transparent backgrounds
35 | border-radius: 2px;
36 | box-shadow: -2px -2px 0 rgba(255, 255, 255, 0.8);
37 | }
38 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_base.scss:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | input,
6 | select,
7 | textarea,
8 | button {
9 | font: #{$body-font-size}/1.4 $body-font;
10 | }
11 |
12 | body {
13 | font: #{$body-font-size}/1.4 $body-font;
14 | color: $brand-gray-dark;
15 | background-color: #fff;
16 | }
17 |
18 | a {
19 | color: $brand-blue;
20 | text-decoration: none;
21 |
22 | &:hover,
23 | &:active {
24 | text-decoration: underline;
25 | }
26 | }
27 |
28 | // Horizontal lines
29 | //
30 | // TODO-MDO: Remove `.rule` from everywhere and replace with ` `s
31 | hr,
32 | .rule {
33 | height: 0;
34 | margin: 15px 0;
35 | overflow: hidden;
36 | background: transparent;
37 | border: 0;
38 | border-bottom: 1px solid #ddd;
39 | @include clearfix();
40 | }
41 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_blankslate.scss:
--------------------------------------------------------------------------------
1 | .blankslate {
2 | position: relative;
3 | padding: 30px;
4 | text-align: center;
5 | background-color: #fafafa;
6 | border: 1px solid #e5e5e5;
7 | border-radius: 3px;
8 | box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.05);
9 |
10 | &.clean-background {
11 | background: none;
12 | border: 0;
13 | box-shadow: none;
14 | }
15 |
16 | &.capped {
17 | border-radius: 0 0 3px 3px;
18 | }
19 |
20 | &.spacious {
21 | padding: 100px 60px 120px;
22 | }
23 |
24 | &.has-fixed-width {
25 | width: 485px;
26 | margin: 0 auto;
27 | }
28 |
29 | &.large-format {
30 | h3 {
31 | margin: 0.75em 0;
32 | font-size: 20px;
33 | }
34 |
35 | p {
36 | font-size: 16px;
37 |
38 | &.has-fixed-width {
39 | width: 540px;
40 | margin: 0 auto;
41 | text-align: left;
42 | }
43 | }
44 |
45 | .mega-octicon {
46 | width: 40px;
47 | height: 40px;
48 | font-size: 40px;
49 | color: #aaa;
50 | }
51 |
52 | .octicon-inbox {
53 | font-size: 48px;
54 | line-height: 40px;
55 | }
56 | }
57 |
58 | code {
59 | padding: 2px 5px 3px;
60 | font-size: 14px;
61 | background: #fff;
62 | border: 1px solid #eee;
63 | border-radius: 3px;
64 | }
65 |
66 | > .mega-octicon { color: #aaa; }
67 |
68 | .mega-octicon + .mega-octicon { margin-left: 10px; }
69 |
70 | .tabnav + & { margin-top: 20px; }
71 |
72 | .context-loader.large-format-loader { padding-top: 50px; }
73 | }
74 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_buttons.scss:
--------------------------------------------------------------------------------
1 | // Shared styles
2 | .btn {
3 | position: relative;
4 | display: inline-block;
5 | padding: 6px 12px;
6 | font-size: 13px;
7 | font-weight: bold;
8 | line-height: 20px;
9 | color: #333;
10 | white-space: nowrap;
11 | vertical-align: middle;
12 | cursor: pointer;
13 | background-color: #eee;
14 | background-image: linear-gradient(#fcfcfc, #eee);
15 | border: 1px solid #d5d5d5;
16 | border-radius: 3px;
17 | user-select: none;
18 | -webkit-appearance: none; // Corrects inability to style clickable `input` types in iOS.
19 |
20 | i {
21 | font-style: normal;
22 | font-weight: 500;
23 | opacity: 0.6;
24 | }
25 |
26 | .octicon {
27 | vertical-align: text-top;
28 | }
29 |
30 | // Darken for just a tad more contrast against the button background
31 | .counter {
32 | text-shadow: none;
33 | background-color: #e5e5e5;
34 | }
35 |
36 | &:focus {
37 | text-decoration: none;
38 | border-color: #51a7e8;
39 | outline: none;
40 | box-shadow: 0 0 5px rgba(81, 167, 232, 0.5);
41 | }
42 |
43 | &:focus:hover,
44 | &.selected:focus {
45 | border-color: #51a7e8;
46 | }
47 |
48 | &:hover,
49 | &:active,
50 | &.zeroclipboard-is-hover,
51 | &.zeroclipboard-is-active {
52 | text-decoration: none;
53 | background-color: #ddd;
54 | background-image: linear-gradient(#eee, #ddd);
55 | border-color: #ccc;
56 | }
57 |
58 | &:active,
59 | &.selected,
60 | &.zeroclipboard-is-active {
61 | background-color: #dcdcdc;
62 | background-image: none;
63 | border-color: #b5b5b5;
64 | box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15);
65 | }
66 |
67 | &.selected:hover {
68 | background-color: darken(#dcdcdc, 5%);
69 | }
70 |
71 | &:disabled,
72 | &.disabled {
73 | &,
74 | &:hover {
75 | color: rgba(102, 102, 102, 0.5);
76 | cursor: default;
77 | background-color: rgba(229, 229, 229, 0.5);
78 | background-image: none;
79 | border-color: rgba(197, 197, 197, 0.5);
80 | box-shadow: none;
81 | }
82 | }
83 | }
84 |
85 | // Green primary button
86 | .btn-primary {
87 | color: #fff;
88 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.15);
89 | background-color: #60b044;
90 | background-image: linear-gradient(#8add6d, #60b044);
91 | border-color: darken(#60b044, 2%);
92 |
93 | .counter {
94 | color: #60b044;
95 | background-color: #fff;
96 | }
97 |
98 | &:hover {
99 | color: #fff;
100 | background-color: darken(#60b044, 5%);
101 | background-image: linear-gradient(darken(#8add6d, 5%), darken(#60b044, 5%));
102 | border-color: #4a993e;
103 | }
104 |
105 | &:active,
106 | &.selected {
107 | text-shadow: 0 1px 0 rgba(0, 0, 0, 0.15);
108 | background-color: darken(#60b044, 5%);
109 | background-image: none;
110 | border-color: darken(#4a993e, 5%);
111 | }
112 |
113 | &.selected:hover {
114 | background-color: darken(#60b044, 10%);
115 | }
116 |
117 | &:disabled,
118 | &.disabled {
119 | &,
120 | &:hover {
121 | color: #fefefe;
122 | background-color: #add39f;
123 | background-image: linear-gradient(#c3ecb4, #add39f);
124 | border-color: #b9dcac #b9dcac #a7c89b;
125 | }
126 | }
127 | }
128 |
129 | // Red danger button
130 | .btn-danger {
131 | color: #900;
132 |
133 | &:hover {
134 | color: #fff;
135 | background-color: #b33630;
136 | background-image: linear-gradient(#dc5f59, #b33630);
137 | border-color: #cd504a;
138 | }
139 |
140 | &:active,
141 | &.selected {
142 | color: #fff;
143 | background-color: #b33630;
144 | background-image: none;
145 | border-color: darken(#cd504a, 15%);
146 | }
147 |
148 | &.selected:hover {
149 | background-color: darken(#b33630, 5%);
150 | }
151 |
152 | &:disabled,
153 | &.disabled {
154 | &,
155 | &:hover {
156 | color: #cb7f7f;
157 | background-color: #efefef;
158 | background-image: linear-gradient(#fefefe, #efefef);
159 | border-color: #e1e1e1;
160 | }
161 | }
162 |
163 | &:hover,
164 | &:active,
165 | &.selected {
166 | .counter {
167 | color: #b33630;
168 | background-color: #fff;
169 | }
170 | }
171 | }
172 |
173 | // Outline button
174 | //
175 | // For when we need a linky-action that's not too heavy in busier
176 | // areas like conversation timelines.
177 | .btn-outline {
178 | color: $brand-blue;
179 | background-color: #fff; // Reset default gradient backgrounds and colors
180 | background-image: none;
181 | border: 1px solid #e5e5e5;
182 |
183 | .counter {
184 | background-color: #eee;
185 | }
186 |
187 | &:hover,
188 | &:active,
189 | &.selected,
190 | &.zeroclipboard-is-hover,
191 | &.zeroclipboard-is-active {
192 | color: #fff;
193 | background-color: $brand-blue;
194 | background-image: none;
195 | border-color: $brand-blue;
196 |
197 | .counter {
198 | color: $brand-blue;
199 | background-color: #fff;
200 | }
201 | }
202 |
203 | &.selected:hover {
204 | background-color: darken($brand-blue, 5%);
205 | }
206 |
207 | &:disabled,
208 | &.disabled {
209 | &,
210 | &:hover {
211 | color: $brand-gray;
212 | background-color: #fff;
213 | background-image: none;
214 | border-color: #e5e5e5;
215 | }
216 | }
217 | }
218 |
219 | // Social button count
220 | .btn-with-count {
221 | float: left;
222 | border-top-right-radius: 0;
223 | border-bottom-right-radius: 0;
224 | }
225 |
226 |
227 | // Minibutton overrides
228 | //
229 | // Tweak `line-height` to make them smaller.
230 | .btn-sm {
231 | padding: 2px 10px;
232 | }
233 |
234 |
235 | // Hidden text button
236 | //
237 | // Use `.hidden-text-expander` to indicate and expand hidden text.
238 | .hidden-text-expander {
239 | display: block;
240 |
241 | &.inline {
242 | position: relative;
243 | top: -1px;
244 | display: inline-block;
245 | margin-left: 5px;
246 | line-height: 0;
247 | }
248 |
249 | a {
250 | display: inline-block;
251 | height: 12px;
252 | padding: 0 5px;
253 | font-size: 12px;
254 | font-weight: bold;
255 | line-height: 6px;
256 | color: #555;
257 | text-decoration: none;
258 | vertical-align: middle;
259 | background: #ddd;
260 | border-radius: 1px;
261 |
262 | &:hover {
263 | text-decoration: none;
264 | background-color: #ccc;
265 | }
266 |
267 | &:active {
268 | color: #fff;
269 | background-color: #4183c4;
270 | }
271 | }
272 | }
273 |
274 |
275 | // Social count bubble
276 | //
277 | // A container that is used for social bubbles counts.
278 | .social-count {
279 | float: left;
280 | padding: 2px 7px;
281 | font-size: 11px;
282 | font-weight: bold;
283 | line-height: 20px;
284 | color: $brand-gray-dark;
285 | vertical-align: middle;
286 | background-color: #fff;
287 | border: 1px solid #ddd;
288 | border-left: 0;
289 | border-top-right-radius: 3px;
290 | border-bottom-right-radius: 3px;
291 |
292 | &:hover,
293 | &:active {
294 | text-decoration: none;
295 | }
296 |
297 | &:hover {
298 | color: $brand-blue;
299 | cursor: pointer;
300 | }
301 | }
302 |
303 |
304 | // Full-width button
305 | //
306 | // These buttons expand to the full width of their parent container
307 | .btn-block {
308 | display: block;
309 | width: 100%;
310 | text-align: center;
311 | }
312 |
313 |
314 | // Button group
315 | //
316 | // A button group is a series of buttons laid out next to each other, all part
317 | // of one visual button, but separated by rules to be separate.
318 | .btn-group {
319 | display: inline-block;
320 | vertical-align: middle;
321 | @include clearfix();
322 |
323 | .btn {
324 | position: relative;
325 | float: left;
326 |
327 | &:not(:first-child):not(:last-child) {
328 | border-radius: 0;
329 | }
330 |
331 | &:first-child:not(:last-child) {
332 | border-top-right-radius: 0;
333 | border-bottom-right-radius: 0;
334 | }
335 |
336 | &:last-child:not(:first-child) {
337 | border-top-left-radius: 0;
338 | border-bottom-left-radius: 0;
339 | }
340 |
341 | // Bring any button into forefront for proper borders given negative margin below
342 | &:hover,
343 | &:active,
344 | &.selected {
345 | z-index: 2;
346 | }
347 |
348 | &:focus {
349 | z-index: 3;
350 | }
351 |
352 | // Tuck buttons into one another to prevent double border
353 | + .btn {
354 | margin-left: -1px;
355 | }
356 | }
357 |
358 | .btn + .button_to,
359 | .button_to + .btn,
360 | .button_to + .button_to {
361 | margin-left: -1px;
362 | }
363 |
364 | .button_to {
365 | float: left;
366 |
367 | .btn {
368 | border-radius: 0;
369 | }
370 |
371 | &:first-child {
372 | .btn {
373 | border-top-left-radius: 3px;
374 | border-bottom-left-radius: 3px;
375 | }
376 | }
377 |
378 | &:last-child {
379 | .btn {
380 | border-top-right-radius: 3px;
381 | border-bottom-right-radius: 3px;
382 | }
383 | }
384 | }
385 | }
386 |
387 | // Proper spacing for multiple button groups (a la, gollum editor)
388 | .btn-group + .btn-group,
389 | .btn-group + .btn {
390 | margin-left: 5px;
391 | }
392 |
393 |
394 | // Radio buttons
395 | //
396 | // Buttons backed by radio or checkbox control. Requires the use of `.hidden`
397 | // on the `input` to properly hide it.
398 | .btn-link {
399 | display: inline-block;
400 | padding: 0;
401 | font-size: inherit;
402 | color: $brand-blue;
403 | white-space: nowrap;
404 | cursor: pointer;
405 | background-color: transparent;
406 | border: 0;
407 | user-select: none;
408 | -webkit-appearance: none; // Corrects inability to style clickable `input` types in iOS.
409 |
410 | &:hover,
411 | &:focus {
412 | text-decoration: underline;
413 | }
414 |
415 | &:focus {
416 | outline: none; // Prevents the blue ring when clicked.
417 | }
418 | }
419 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_counter.scss:
--------------------------------------------------------------------------------
1 | .counter {
2 | display: inline-block;
3 | padding: 2px 5px;
4 | font-size: 11px;
5 | font-weight: bold;
6 | line-height: 1;
7 | color: #666;
8 | background-color: #eee;
9 | border-radius: 20px;
10 | }
11 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_filter-list.scss:
--------------------------------------------------------------------------------
1 | // Filters list
2 | //
3 | // A vertical list of filters.
4 | .filter-list {
5 | list-style-type: none;
6 |
7 | &.small .filter-item {
8 | padding: 4px 10px;
9 | margin: 0 0 2px;
10 | font-size: 12px;
11 | }
12 |
13 | &.pjax-active .filter-item {
14 | color: $brand-gray;
15 | background-color: transparent;
16 |
17 | &.pjax-active {
18 | color: #fff;
19 | background-color: $brand-blue;
20 | }
21 | }
22 | }
23 |
24 | .filter-item {
25 | position: relative;
26 | display: block;
27 | padding: 8px 10px;
28 | margin-bottom: 5px;
29 | overflow: hidden;
30 | font-size: 14px;
31 | color: $brand-gray;
32 | text-decoration: none;
33 | text-overflow: ellipsis;
34 | white-space: nowrap;
35 | cursor: pointer;
36 | border-radius: 3px;
37 |
38 | &:hover {
39 | text-decoration: none;
40 | background-color: #eee;
41 | }
42 |
43 | &.selected {
44 | color: #fff;
45 | background-color: $brand-blue;
46 |
47 | .octicon-remove-close {
48 | float: right;
49 | opacity: 0.8;
50 | }
51 | }
52 |
53 | .count {
54 | float: right;
55 | font-weight: bold;
56 | }
57 |
58 | .bar {
59 | position: absolute;
60 | top: 2px;
61 | right: 0;
62 | bottom: 2px;
63 | z-index: -1;
64 | display: inline-block;
65 | background-color: #f1f1f1;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_flex-table.scss:
--------------------------------------------------------------------------------
1 | // Flex table is a module for creating dynamically resizable elements that
2 | // always sit on the same horizontal line (e.g., they never wrap). Using
3 | // tables means it's cross browser friendly.
4 |
5 | .flex-table {
6 | display: table;
7 | }
8 |
9 | // Place this on every "cell"
10 | .flex-table-item {
11 | display: table-cell;
12 | width: 1%;
13 | white-space: nowrap;
14 | vertical-align: middle;
15 | }
16 |
17 | // Place this on the largest or most important "cell"
18 | .flex-table-item-primary {
19 | width: 99%;
20 | }
21 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_forms.scss:
--------------------------------------------------------------------------------
1 | // Base form controls
2 | //
3 | // Overrides for common inputs for easier styling.
4 |
5 | fieldset {
6 | padding: 0;
7 | margin: 0;
8 | border: 0;
9 | }
10 |
11 | label {
12 | font-size: 13px;
13 | font-weight: bold;
14 | }
15 |
16 | .form-control,
17 | input[type="text"],
18 | input[type="password"],
19 | input[type="email"],
20 | input[type="number"],
21 | input[type="tel"],
22 | input[type="url"],
23 | select,
24 | textarea {
25 | min-height: 34px;
26 | padding: 7px 8px;
27 | font-size: 13px;
28 | color: #333;
29 | vertical-align: middle;
30 | background-color: #fff;
31 | background-repeat: no-repeat; // Repeat and position set for form states (success, error, etc)
32 | background-position: right 8px center; // For form validation. This keeps images 8px from right and centered vertically.
33 | border: 1px solid #ccc;
34 | border-radius: 3px;
35 | outline: none;
36 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075);
37 |
38 | &.focus,
39 | &:focus {
40 | border-color: #51a7e8;
41 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 5px rgba(81, 167, 232, 0.5);
42 | }
43 | }
44 |
45 | select {
46 | &:not([multiple]) {
47 | height: 34px;
48 | vertical-align: middle;
49 | }
50 | }
51 |
52 | // Inputs with contrast for easy light gray backgrounds against white.
53 | // input.class is needed here to increase specificity over input[…]
54 | input.input-contrast,
55 | .input-contrast {
56 | background-color: #fafafa;
57 |
58 | &:focus { background-color: #fff; }
59 | }
60 |
61 | // Custom styling for HTML5 validation bubbles (WebKit only)
62 | ::placeholder {
63 | color: #aaa;
64 | }
65 |
66 | // Mini inputs, to match .minibutton
67 | input.input-mini {
68 | min-height: 26px;
69 | padding-top: 4px;
70 | padding-bottom: 4px;
71 | font-size: 12px;
72 | }
73 |
74 | // Large inputs
75 | input.input-large {
76 | padding: 6px 10px;
77 | font-size: 16px;
78 | }
79 |
80 | // Full-width inputs
81 | .input-block {
82 | display: block;
83 | width: 100%;
84 | }
85 |
86 | // Inputs with monospace text
87 | .input-monospace {
88 | font-family: $mono-font;
89 | }
90 |
91 | // Form groups
92 | //
93 | // Typical form groups - `` with a `` containing the label and
94 | // ` containing the form elements.
95 | dl.form {
96 | margin: 15px 0;
97 |
98 | input[type="text"],
99 | input[type="password"],
100 | input[type="email"],
101 | input[type="url"],
102 | select,
103 | textarea {
104 | background-color: #fafafa;
105 |
106 | &:focus {
107 | background-color: #fff;
108 | }
109 | }
110 |
111 | // The Label
112 | > dt {
113 | margin: 0 0 6px;
114 |
115 | label {
116 | position: relative;
117 | }
118 | }
119 |
120 | &.flattened > dt {
121 | float: left;
122 | margin: 0;
123 | line-height: 32px;
124 | }
125 |
126 | &.flattened > dd {
127 | line-height: 32px;
128 | }
129 |
130 | //
131 | // Form Elements
132 | //
133 |
134 | > dd {
135 | // Text fields
136 | input[type="text"],
137 | input[type="password"],
138 | input[type="email"],
139 | input[type="url"] {
140 | width: 440px;
141 | max-width: 100%;
142 | margin-right: 5px;
143 | }
144 |
145 | input {
146 | &.shorter { width: 130px; }
147 |
148 | &.short { width: 250px; }
149 |
150 | &.long { width: 100%; }
151 | }
152 |
153 | // Textarea
154 | textarea {
155 | width: 100%;
156 | height: 200px;
157 | min-height: 200px;
158 |
159 | &.short {
160 | height: 50px;
161 | min-height: 50px;
162 | }
163 | }
164 |
165 | h4 {
166 | margin: 4px 0 0;
167 |
168 | &.is-error { color: $brand-red; }
169 |
170 | &.is-success { color: $brand-green; }
171 |
172 | + p.note {
173 | margin-top: 0;
174 | }
175 | }
176 | }
177 |
178 | //
179 | // Variants
180 | //
181 |
182 | &.required {
183 | > dt > label:after {
184 | padding-left: 5px;
185 | color: #9f1006;
186 | content: "*";
187 | }
188 | }
189 |
190 | // Form AJAX states
191 | //
192 | // Form fields that need feedback for AJAX loading, success
193 | // states and errored states.
194 | .success,
195 | .error,
196 | .indicator {
197 | display: none;
198 | font-size: 12px;
199 | font-weight: bold;
200 | }
201 |
202 | &.loading {
203 | opacity: 0.5;
204 |
205 | .indicator {
206 | display: inline;
207 | }
208 |
209 | .spinner {
210 | display: inline-block;
211 | vertical-align: middle;
212 | }
213 | }
214 |
215 | &.successful {
216 | .success {
217 | display: inline;
218 | color: #390;
219 | }
220 | }
221 |
222 | // Form validation
223 | //
224 | // Our inline errors
225 |
226 | &.warn,
227 | &.errored {
228 | dd.warning,
229 | dd.error {
230 | display: inline-block;
231 | position: absolute;
232 | max-width: 450px; // Keep our long errors readable
233 | z-index: 10;
234 | margin: 2px 0 0;
235 | padding: 5px 8px;
236 | font-size: 13px;
237 | font-weight: normal;
238 | border-radius: 3px;
239 | }
240 |
241 | dd.warning:after,
242 | dd.warning:before,
243 | dd.error:after,
244 | dd.error:before {
245 | bottom: 100%;
246 | z-index: 15;
247 | left: 10px;
248 | border: solid transparent;
249 | content: " ";
250 | height: 0;
251 | width: 0;
252 | position: absolute;
253 | pointer-events: none;
254 | }
255 |
256 | dd.warning:after,
257 | dd.error:after {
258 | border-width: 5px;
259 | }
260 |
261 | dd.warning:before,
262 | dd.error:before {
263 | border-width: 6px;
264 | margin-left: -1px;
265 | }
266 | }
267 |
268 | &.warn {
269 | dd.warning {
270 | color: #4e401e;
271 | background-color: #ffe5a7;
272 | border: 1px solid #e7ce94;
273 |
274 | &:after {
275 | border-bottom-color: #ffe5a7;
276 | }
277 |
278 | &:before {
279 | border-bottom-color: #cdb683;
280 | }
281 | }
282 | }
283 |
284 | &.errored {
285 | > dt label {
286 | color: $brand-red;
287 | }
288 |
289 | dd.error {
290 | color: #fff;
291 | background-color: #bf1515;
292 | border-color: #911;
293 | font-size: 13px;
294 |
295 | &:after {
296 | border-bottom-color: #bf1515;
297 | }
298 |
299 | &:before {
300 | border-bottom-color: #911;
301 | }
302 | }
303 | }
304 | }
305 |
306 | .note {
307 | min-height: 17px;
308 | margin: 4px 0 2px;
309 | font-size: 12px;
310 | color: $brand-gray;
311 |
312 | .spinner {
313 | margin-right: 3px;
314 | vertical-align: middle;
315 | }
316 | }
317 |
318 |
319 | // Checkboxes and Radiobuttons
320 | //
321 | // For checkboxes and radio button selections.
322 | .form-checkbox {
323 | padding-left: 20px;
324 | margin: 15px 0;
325 | vertical-align: middle;
326 |
327 | label {
328 |
329 | em.highlight {
330 | position: relative;
331 | left: -4px;
332 | padding: 2px 4px;
333 | font-style: normal;
334 | background: #fffbdc;
335 | border-radius: 3px;
336 | }
337 | }
338 |
339 | input[type=checkbox],
340 | input[type=radio] {
341 | float: left;
342 | margin: 2px 0 0 -20px;
343 | vertical-align: middle;
344 | }
345 |
346 | .note {
347 | display: block;
348 | margin: 0;
349 | font-size: 12px;
350 | font-weight: normal;
351 | color: #666;
352 | }
353 | }
354 |
355 |
356 | // Field groups
357 | //
358 | // Wrap field groups in `` to lay them out horizontally - great for
359 | // the top of pages with autosave.
360 | .hfields {
361 | margin: 15px 0;
362 | @include clearfix;
363 |
364 | dl.form {
365 | float: left;
366 | margin: 0 30px 0 0;
367 |
368 | > dt {
369 | label {
370 | display: inline-block;
371 | margin: 5px 0 0;
372 | color: #666;
373 | }
374 |
375 | img {
376 | position: relative;
377 | top: -2px;
378 | }
379 | }
380 | }
381 |
382 | .btn {
383 | float: left;
384 | margin: 28px 25px 0 -20px;
385 | }
386 |
387 | select { margin-top: 5px; }
388 | }
389 |
390 |
391 | // Hide the up/down buttons in in the login form, the
392 | // input is used for two-factor auth codes, type="number" makes it more usable
393 | // on phones
394 | input::-webkit-outer-spin-button,
395 | input::-webkit-inner-spin-button {
396 | margin: 0;
397 | -webkit-appearance: none;
398 | }
399 |
400 |
401 | // Input groups
402 | .input-group {
403 | display: table;
404 |
405 | input {
406 | position: relative;
407 | width: 100%;
408 |
409 | &:focus {
410 | z-index: 2;
411 | }
412 | }
413 |
414 | input[type="text"] + .btn {
415 | margin-left: 0;
416 | }
417 |
418 | // For when you want the input group to behave like inline-block.
419 | &.inline {
420 | display: inline-table;
421 | }
422 | }
423 |
424 | .input-group input,
425 | .input-group-button {
426 | display: table-cell;
427 | }
428 |
429 | .input-group-button {
430 | width: 1%;
431 | vertical-align: middle; // Match the inputs
432 | }
433 |
434 | .input-group input:first-child,
435 | .input-group-button:first-child .btn {
436 | border-top-right-radius: 0;
437 | border-bottom-right-radius: 0;
438 | }
439 |
440 | .input-group-button:first-child .btn {
441 | margin-right: -1px;
442 | }
443 |
444 | .input-group input:last-child,
445 | .input-group-button:last-child .btn {
446 | border-top-left-radius: 0;
447 | border-bottom-left-radius: 0;
448 | }
449 |
450 | .input-group-button:last-child .btn {
451 | margin-left: -1px;
452 | }
453 |
454 | .form-actions {
455 | @include clearfix;
456 |
457 | .btn {
458 | float: right;
459 |
460 | + .btn {
461 | margin-right: 5px;
462 | }
463 | }
464 | }
465 |
466 | .form-warning {
467 | padding: 8px 10px;
468 | margin: 10px 0;
469 | font-size: 14px;
470 | color: #333;
471 | background: #ffffe2;
472 | border: 1px solid #e7e4c2;
473 | border-radius: 4px;
474 |
475 | p {
476 | margin: 0;
477 | line-height: 1.5;
478 | }
479 |
480 | strong { color: #000; }
481 |
482 | a { font-weight: bold; }
483 | }
484 |
485 |
486 | // Inline verification
487 | //
488 | // Used for example when autosaving checkboxes in settings.
489 | .status-indicator {
490 | @include icon-bootstrap(16px);
491 | margin-left: 5px;
492 | }
493 |
494 | .status-indicator-success:before {
495 | color: $brand-green;
496 | content: "\f03a"; // octicon-check
497 | }
498 |
499 | .status-indicator-failed:before {
500 | color: $brand-red;
501 | content: "\f02d"; // octicon-x
502 | }
503 |
504 |
505 | // Custom select
506 | //
507 | // Apply `.select` to any `` element for custom styles.
508 |
509 | .select {
510 | display: inline-block;
511 | max-width: 100%;
512 | padding: 7px 24px 7px 8px;
513 | vertical-align: middle;
514 | background: #fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC) no-repeat right 8px center;
515 | background-size: 8px 10px;
516 | box-shadow: inset 0 -1px 2px rgba(0, 0, 0, 0.075);
517 | // Have to include vendor prefixes as the `appearance` property isn't part of the CSS spec.
518 | -webkit-appearance: none;
519 | -moz-appearance: none;
520 | appearance: none;
521 |
522 | // IE9 hacks to hide the background-image and reduce padding
523 | padding-right: 8px \9;
524 | background-image: none \9;
525 |
526 | &:focus {
527 | outline: none;
528 | border-color: #51a7e8;
529 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 5px rgba(81, 167, 232, 0.5);
530 | }
531 |
532 | // Hides the default caret in IE11
533 | &::-ms-expand {
534 | opacity: 0;
535 | }
536 | }
537 |
538 | .select-sm {
539 | padding-top: 3px;
540 | padding-bottom: 3px;
541 | font-size: 12px;
542 |
543 | &:not([multiple]) {
544 | height: 26px;
545 | min-height: 26px;
546 | }
547 | }
548 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_layout.scss:
--------------------------------------------------------------------------------
1 | // Fixed-width, centered column for site content.
2 | .container {
3 | width: $container-width;
4 | margin-right: auto;
5 | margin-left: auto;
6 | @include clearfix;
7 | }
8 |
9 | // Grid system
10 | //
11 | // Create rows with `.columns` to clear the floated columns and outdent the
12 | // padding on `.column`s with negative margin for alignment.
13 |
14 | .columns {
15 | margin-right: -$grid-gutter;
16 | margin-left: -$grid-gutter;
17 | @include clearfix;
18 | }
19 |
20 | // Base class for every column (requires a column width from below)
21 | .column {
22 | float: left;
23 | padding-right: $grid-gutter;
24 | padding-left: $grid-gutter;
25 | }
26 |
27 | // Column widths
28 | .one-third {
29 | width: 33.333333%;
30 | }
31 |
32 | .two-thirds {
33 | width: 66.666667%;
34 | }
35 |
36 | .one-fourth {
37 | width: 25%;
38 | }
39 |
40 | .one-half {
41 | width: 50%;
42 | }
43 |
44 | .three-fourths {
45 | width: 75%;
46 | }
47 |
48 | .one-fifth {
49 | width: 20%;
50 | }
51 |
52 | .four-fifths {
53 | width: 80%;
54 | }
55 |
56 | // Single column hack
57 | .single-column {
58 | padding-right: $grid-gutter;
59 | padding-left: $grid-gutter;
60 | }
61 |
62 | // Equal width columns via table sorcery.
63 | .table-column {
64 | display: table-cell;
65 | width: 1%;
66 | padding-right: $grid-gutter;
67 | padding-left: $grid-gutter;
68 | vertical-align: top;
69 | }
70 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_menu.scss:
--------------------------------------------------------------------------------
1 | // Side menu
2 | //
3 | // A menu on the side of a page, defaults to left side. e.g. github.com/about
4 |
5 | .menu {
6 | margin-bottom: 15px;
7 | list-style: none;
8 | background-color: #fff;
9 | border: 1px solid #d8d8d8;
10 | border-radius: 3px;
11 | }
12 |
13 | .menu-item {
14 | position: relative;
15 | display: block;
16 | padding: 8px 10px;
17 | text-shadow: 0 1px 0 #fff;
18 | border-bottom: 1px solid #eee;
19 |
20 | &:first-child {
21 | border-top: 0;
22 | border-top-right-radius: 2px;
23 | border-top-left-radius: 2px;
24 |
25 | &:before { border-top-left-radius: 2px; }
26 | }
27 |
28 | &:last-child {
29 | border-bottom: 0;
30 | border-bottom-right-radius: 2px;
31 | border-bottom-left-radius: 2px;
32 |
33 | &:before { border-bottom-left-radius: 2px; }
34 | }
35 |
36 | &:hover {
37 | text-decoration: none;
38 | background-color: #f9f9f9;
39 | }
40 |
41 | &.selected {
42 | font-weight: bold;
43 | color: #222;
44 | cursor: default;
45 | background-color: #fff;
46 |
47 | &:before {
48 | position: absolute;
49 | top: 0;
50 | left: 0;
51 | bottom: 0;
52 | width: 2px;
53 | content: "";
54 | background-color: #d26911;
55 | }
56 | }
57 |
58 | .octicon {
59 | margin-right: 5px;
60 | width: 16px;
61 | color: $brand-gray-dark;
62 | text-align: center;
63 | }
64 |
65 | .counter {
66 | float: right;
67 | margin-left: 5px;
68 | }
69 |
70 | .menu-warning {
71 | float: right;
72 | color: #d26911;
73 | }
74 |
75 | .avatar {
76 | float: left;
77 | margin-right: 5px;
78 | }
79 |
80 | &.alert {
81 | .counter {
82 | color: $brand-red;
83 | }
84 | }
85 | }
86 |
87 | .menu-heading {
88 | display: block;
89 | padding: 8px 10px;
90 | margin-top: 0;
91 | margin-bottom: 0;
92 | font-size: 13px;
93 | font-weight: bold;
94 | line-height: 20px;
95 | color: #555;
96 | background-color: #f7f7f7;
97 | border-bottom: 1px solid #eee;
98 |
99 | &:hover {
100 | text-decoration: none;
101 | }
102 |
103 | &:first-child {
104 | border-top-right-radius: 2px;
105 | border-top-left-radius: 2px;
106 | }
107 |
108 | &:last-child {
109 | border-bottom-right-radius: 2px;
110 | border-bottom-left-radius: 2px;
111 | border-bottom: 0;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_mixins.scss:
--------------------------------------------------------------------------------
1 | // Clearfix
2 | //
3 | // Clears floats via mixin (avoid using as a class).
4 |
5 | @mixin clearfix {
6 | &:before {
7 | display: table;
8 | content: "";
9 | }
10 |
11 | &:after {
12 | display: table;
13 | clear: both;
14 | content: "";
15 | }
16 | }
17 |
18 | // Creates a linear gradient background, from top to bottom.
19 | //
20 | // $start - The color hex at the top.
21 | // $end - The color hex at the bottom.
22 |
23 | @mixin gradient($start: #fafafa, $end: #eaeaea) {
24 | @warn "Gradient mixin is deprecated.";
25 | // scss-lint:disable VendorPrefix
26 | background-color: $end;
27 | // FF 3.6+
28 | background-image: -moz-linear-gradient($start, $end);
29 | // Safari 5.1+, Chrome 10+
30 | background-image: -webkit-linear-gradient($start, $end);
31 | background-image: linear-gradient($start, $end);
32 | background-repeat: repeat-x;
33 | }
34 |
35 | // Text hiding for image based text replacement.
36 | // Higher performance than -9999px because it only renders
37 | // the size of the actual text, not a full 9999px box.
38 |
39 | @mixin hide-text() {
40 | overflow: hidden;
41 | text-indent: 100%;
42 | white-space: nowrap;
43 | }
44 |
45 | // Octicon bootstrap
46 | //
47 | // Quickly load the typography requirements for Octicons icon font.
48 |
49 | @mixin icon-bootstrap($size) {
50 | font: normal normal #{$size}/1 "octicons";
51 | display: inline-block;
52 | text-decoration: none;
53 | -webkit-font-smoothing: antialiased;
54 | }
55 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_normalize.scss:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS and IE text size adjust after device orientation change,
6 | * without disabling user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | text-size-adjust: 100%; /* 2 */
12 | }
13 |
14 | /**
15 | * Remove default margin.
16 | */
17 |
18 | body {
19 | margin: 0;
20 | }
21 |
22 | /* HTML5 display definitions
23 | ========================================================================== */
24 |
25 | /**
26 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
27 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
28 | * and Firefox.
29 | * Correct `block` display not defined for `main` in IE 11.
30 | */
31 |
32 | article,
33 | aside,
34 | details,
35 | figcaption,
36 | figure,
37 | footer,
38 | header,
39 | hgroup,
40 | main,
41 | menu,
42 | nav,
43 | section,
44 | summary {
45 | display: block;
46 | }
47 |
48 | /**
49 | * 1. Correct `inline-block` display not defined in IE 8/9.
50 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
51 | */
52 |
53 | audio,
54 | canvas,
55 | progress,
56 | video {
57 | display: inline-block; /* 1 */
58 | vertical-align: baseline; /* 2 */
59 | }
60 |
61 | /**
62 | * Prevent modern browsers from displaying `audio` without controls.
63 | * Remove excess height in iOS 5 devices.
64 | */
65 |
66 | audio:not([controls]) {
67 | display: none;
68 | height: 0;
69 | }
70 |
71 | /**
72 | * Address `[hidden]` styling not present in IE 8/9/10.
73 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
74 | */
75 |
76 | [hidden],
77 | template {
78 | display: none;
79 | }
80 |
81 | /* Links
82 | ========================================================================== */
83 |
84 | /**
85 | * Remove the gray background color from active links in IE 10.
86 | */
87 |
88 | a {
89 | background-color: transparent;
90 | }
91 |
92 | /**
93 | * Improve readability of focused elements when they are also in an
94 | * active/hover state.
95 | */
96 |
97 | a:active,
98 | a:hover {
99 | outline: 0;
100 | }
101 |
102 | /* Text-level semantics
103 | ========================================================================== */
104 |
105 | /**
106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
107 | */
108 |
109 | abbr[title] {
110 | border-bottom: 1px dotted;
111 | }
112 |
113 | /**
114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
115 | */
116 |
117 | b,
118 | strong {
119 | font-weight: bold;
120 | }
121 |
122 | /**
123 | * Address styling not present in Safari and Chrome.
124 | */
125 |
126 | dfn {
127 | font-style: italic;
128 | }
129 |
130 | /**
131 | * Address variable `h1` font-size and margin within `section` and `article`
132 | * contexts in Firefox 4+, Safari, and Chrome.
133 | */
134 |
135 | h1 {
136 | font-size: 2em;
137 | margin: 0.67em 0;
138 | }
139 |
140 | /**
141 | * Address styling not present in IE 8/9.
142 | */
143 |
144 | mark {
145 | background: #ff0;
146 | color: #000;
147 | }
148 |
149 | /**
150 | * Address inconsistent and variable font size in all browsers.
151 | */
152 |
153 | small {
154 | font-size: 80%;
155 | }
156 |
157 | /**
158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
159 | */
160 |
161 | sub,
162 | sup {
163 | font-size: 75%;
164 | line-height: 0;
165 | position: relative;
166 | vertical-align: baseline;
167 | }
168 |
169 | sup {
170 | top: -0.5em;
171 | }
172 |
173 | sub {
174 | bottom: -0.25em;
175 | }
176 |
177 | /* Embedded content
178 | ========================================================================== */
179 |
180 | /**
181 | * Remove border when inside `a` element in IE 8/9/10.
182 | */
183 |
184 | img {
185 | border: 0;
186 | }
187 |
188 | /**
189 | * Correct overflow not hidden in IE 9/10/11.
190 | */
191 |
192 | svg:not(:root) {
193 | overflow: hidden;
194 | }
195 |
196 | /* Grouping content
197 | ========================================================================== */
198 |
199 | /**
200 | * Address margin not present in IE 8/9 and Safari.
201 | */
202 |
203 | figure {
204 | margin: 1em 40px;
205 | }
206 |
207 | /**
208 | * Address differences between Firefox and other browsers.
209 | */
210 |
211 | hr {
212 | box-sizing: content-box;
213 | height: 0;
214 | }
215 |
216 | /**
217 | * Contain overflow in all browsers.
218 | */
219 |
220 | pre {
221 | overflow: auto;
222 | }
223 |
224 | /**
225 | * Address odd `em`-unit font size rendering in all browsers.
226 | */
227 |
228 | code,
229 | kbd,
230 | pre,
231 | samp {
232 | font-family: monospace, monospace;
233 | font-size: 1em;
234 | }
235 |
236 | /* Forms
237 | ========================================================================== */
238 |
239 | /**
240 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
241 | * styling of `select`, unless a `border` property is set.
242 | */
243 |
244 | /**
245 | * 1. Correct color not being inherited.
246 | * Known issue: affects color of disabled elements.
247 | * 2. Correct font properties not being inherited.
248 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
249 | */
250 |
251 | button,
252 | input,
253 | optgroup,
254 | select,
255 | textarea {
256 | color: inherit; /* 1 */
257 | font: inherit; /* 2 */
258 | margin: 0; /* 3 */
259 | }
260 |
261 | /**
262 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
263 | */
264 |
265 | button {
266 | overflow: visible;
267 | }
268 |
269 | /**
270 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
271 | * All other form control elements do not inherit `text-transform` values.
272 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
273 | * Correct `select` style inheritance in Firefox.
274 | */
275 |
276 | button,
277 | select {
278 | text-transform: none;
279 | }
280 |
281 | /**
282 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
283 | * and `video` controls.
284 | * 2. Correct inability to style clickable `input` types in iOS.
285 | * 3. Improve usability and consistency of cursor style between image-type
286 | * `input` and others.
287 | */
288 |
289 | button,
290 | html input[type="button"], /* 1 */
291 | input[type="reset"],
292 | input[type="submit"] {
293 | -webkit-appearance: button; /* 2 */
294 | cursor: pointer; /* 3 */
295 | }
296 |
297 | /**
298 | * Re-set default cursor for disabled elements.
299 | */
300 |
301 | button[disabled],
302 | html input[disabled] {
303 | cursor: default;
304 | }
305 |
306 | /**
307 | * Remove inner padding and border in Firefox 4+.
308 | */
309 |
310 | button::-moz-focus-inner,
311 | input::-moz-focus-inner {
312 | border: 0;
313 | padding: 0;
314 | }
315 |
316 | /**
317 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
318 | * the UA stylesheet.
319 | */
320 |
321 | input {
322 | line-height: normal;
323 | }
324 |
325 | /**
326 | * It's recommended that you don't attempt to style these elements.
327 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
328 | *
329 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
330 | * 2. Remove excess padding in IE 8/9/10.
331 | */
332 |
333 | input[type="checkbox"],
334 | input[type="radio"] {
335 | box-sizing: border-box; /* 1 */
336 | padding: 0; /* 2 */
337 | }
338 |
339 | /**
340 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
341 | * `font-size` values of the `input`, it causes the cursor style of the
342 | * decrement button to change from `default` to `text`.
343 | */
344 |
345 | input[type="number"]::-webkit-inner-spin-button,
346 | input[type="number"]::-webkit-outer-spin-button {
347 | height: auto;
348 | }
349 |
350 | /**
351 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
352 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
353 | */
354 |
355 | input[type="search"] {
356 | -webkit-appearance: textfield; /* 1 */
357 | box-sizing: content-box; /* 2 */
358 | }
359 |
360 | /**
361 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
362 | * Safari (but not Chrome) clips the cancel button when the search input has
363 | * padding (and `textfield` appearance).
364 | */
365 |
366 | input[type="search"]::-webkit-search-cancel-button,
367 | input[type="search"]::-webkit-search-decoration {
368 | -webkit-appearance: none;
369 | }
370 |
371 | /**
372 | * Define consistent border, margin, and padding.
373 | */
374 |
375 | fieldset {
376 | border: 1px solid #c0c0c0;
377 | margin: 0 2px;
378 | padding: 0.35em 0.625em 0.75em;
379 | }
380 |
381 | /**
382 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
383 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
384 | */
385 |
386 | legend {
387 | border: 0; /* 1 */
388 | padding: 0; /* 2 */
389 | }
390 |
391 | /**
392 | * Remove default vertical scrollbar in IE 8/9/10/11.
393 | */
394 |
395 | textarea {
396 | overflow: auto;
397 | }
398 |
399 | /**
400 | * Don't inherit the `font-weight` (applied by a rule above).
401 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
402 | */
403 |
404 | optgroup {
405 | font-weight: bold;
406 | }
407 |
408 | /* Tables
409 | ========================================================================== */
410 |
411 | /**
412 | * Remove most spacing between table cells.
413 | */
414 |
415 | table {
416 | border-collapse: collapse;
417 | border-spacing: 0;
418 | }
419 |
420 | td,
421 | th {
422 | padding: 0;
423 | }
424 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_states.scss:
--------------------------------------------------------------------------------
1 | // A rounded corner box containing a label "open" or "closed"
2 | // Without a state it is grey.
3 | //
4 | // open - green background
5 | // reopened - green background
6 | // closed - red background
7 | // merged - purple background
8 | // renamed - orange background
9 | //
10 | // No styleguide reference
11 | .state {
12 | display: inline-block;
13 | padding: 4px 8px;
14 | font-weight: bold;
15 | line-height: 20px;
16 | color: #fff;
17 | text-align: center;
18 | border-radius: 3px;
19 | background-color: #999;
20 | }
21 |
22 | .state-open,
23 | .state-proposed,
24 | .state-reopened {
25 | background-color: $status-open;
26 | }
27 |
28 | .state-merged { background-color: $status-merged; }
29 |
30 | .state-closed { background-color: $status-closed; }
31 |
32 | .state-renamed { background-color: $status-renamed; }
33 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_tabnav.scss:
--------------------------------------------------------------------------------
1 | // Outer wrapper
2 | .tabnav {
3 | margin-top: 0;
4 | margin-bottom: 15px;
5 | border-bottom: 1px solid #ddd;
6 |
7 | .counter {
8 | margin-left: 5px;
9 | }
10 | }
11 |
12 | .tabnav-tabs {
13 | margin-bottom: -1px;
14 | }
15 |
16 | .tabnav-tab {
17 | display: inline-block;
18 | padding: 8px 12px;
19 | font-size: 14px;
20 | line-height: 20px;
21 | color: #666;
22 | text-decoration: none;
23 | border: 1px solid transparent;
24 | border-bottom: 0;
25 |
26 | &.selected {
27 | color: #333;
28 | background-color: #fff;
29 | border-color: #ddd;
30 | border-radius: 3px 3px 0 0;
31 | }
32 |
33 | &:hover { text-decoration: none; }
34 | }
35 |
36 | // Tabnav extras
37 | //
38 | // Tabnav extras are non-tab elements that sit in the tabnav. Usually they're
39 | // inline text or links.
40 |
41 | .tabnav-extra {
42 | display: inline-block;
43 | padding-top: 10px;
44 | margin-left: 10px;
45 | font-size: 12px;
46 | color: #666;
47 |
48 | > .octicon {
49 | margin-right: 2px;
50 | }
51 | }
52 |
53 | a.tabnav-extra:hover {
54 | color: $brand-blue;
55 | text-decoration: none;
56 | }
57 |
58 | // Tabnav buttons
59 | //
60 | // For when there are multiple buttons, space them out appropriately. Requires
61 | // the buttons to be floated or inline-block.
62 |
63 | .tabnav-btn {
64 | margin-left: 10px;
65 | }
66 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_tooltips.scss:
--------------------------------------------------------------------------------
1 | $multiline-max-width: 250px;
2 | $tooltip-background-color: rgba(0, 0, 0, 0.8);
3 | $tooltip-text-color: #fff;
4 |
5 | .tooltipped {
6 | position: relative;
7 | }
8 |
9 | // This is the tooltip bubble
10 | .tooltipped:after {
11 | position: absolute;
12 | z-index: 1000000;
13 | display: none;
14 | padding: 5px 8px;
15 | font: normal normal 11px/1.5 $body-font;
16 | color: $tooltip-text-color;
17 | text-align: center;
18 | text-decoration: none;
19 | text-shadow: none;
20 | text-transform: none;
21 | letter-spacing: normal;
22 | word-wrap: break-word;
23 | white-space: pre;
24 | pointer-events: none;
25 | content: attr(aria-label);
26 | background: $tooltip-background-color;
27 | border-radius: 3px;
28 | -webkit-font-smoothing: subpixel-antialiased;
29 | }
30 |
31 | // This is the tooltip arrow
32 | .tooltipped:before {
33 | position: absolute;
34 | z-index: 1000001;
35 | display: none;
36 | width: 0;
37 | height: 0;
38 | color: $tooltip-background-color;
39 | pointer-events: none;
40 | content: "";
41 | border: 5px solid transparent;
42 | }
43 |
44 | // This will indicate when we'll activate the tooltip
45 | .tooltipped:hover,
46 | .tooltipped:active,
47 | .tooltipped:focus {
48 | &:before,
49 | &:after {
50 | display: inline-block;
51 | text-decoration: none;
52 | }
53 | }
54 |
55 | .tooltipped-multiline:hover,
56 | .tooltipped-multiline:active,
57 | .tooltipped-multiline:focus {
58 | &:after {
59 | display: table-cell;
60 | }
61 | }
62 |
63 | // Tooltipped south
64 | .tooltipped-s,
65 | .tooltipped-se,
66 | .tooltipped-sw {
67 | &:after {
68 | top: 100%;
69 | right: 50%;
70 | margin-top: 5px;
71 | }
72 |
73 | &:before {
74 | top: auto;
75 | right: 50%;
76 | bottom: -5px;
77 | margin-right: -5px;
78 | border-bottom-color: $tooltip-background-color;
79 | }
80 | }
81 |
82 | .tooltipped-se {
83 | &:after {
84 | right: auto;
85 | left: 50%;
86 | margin-left: -15px;
87 | }
88 | }
89 |
90 | .tooltipped-sw:after {
91 | margin-right: -15px;
92 | }
93 |
94 | // Tooltips above the object
95 | .tooltipped-n,
96 | .tooltipped-ne,
97 | .tooltipped-nw {
98 | &:after {
99 | right: 50%;
100 | bottom: 100%;
101 | margin-bottom: 5px;
102 | }
103 |
104 | &:before {
105 | top: -5px;
106 | right: 50%;
107 | bottom: auto;
108 | margin-right: -5px;
109 | border-top-color: $tooltip-background-color;
110 | }
111 | }
112 |
113 | .tooltipped-ne {
114 | &:after {
115 | right: auto;
116 | left: 50%;
117 | margin-left: -15px;
118 | }
119 | }
120 |
121 | .tooltipped-nw:after {
122 | margin-right: -15px;
123 | }
124 |
125 | // Move the tooltip body to the center of the object.
126 | .tooltipped-s:after,
127 | .tooltipped-n:after {
128 | transform: translateX(50%);
129 | }
130 |
131 | // Tooltipped to the left
132 | .tooltipped-w {
133 | &:after {
134 | right: 100%;
135 | bottom: 50%;
136 | margin-right: 5px;
137 | transform: translateY(50%);
138 | }
139 |
140 | &:before {
141 | top: 50%;
142 | bottom: 50%;
143 | left: -5px;
144 | margin-top: -5px;
145 | border-left-color: $tooltip-background-color;
146 | }
147 | }
148 |
149 | // tooltipped to the right
150 | .tooltipped-e {
151 | &:after {
152 | bottom: 50%;
153 | left: 100%;
154 | margin-left: 5px;
155 | transform: translateY(50%);
156 | }
157 |
158 | &:before {
159 | top: 50%;
160 | right: -5px;
161 | bottom: 50%;
162 | margin-top: -5px;
163 | border-right-color: $tooltip-background-color;
164 | }
165 | }
166 |
167 | // Multiline tooltips
168 | //
169 | // `.tooltipped-multiline` Add this class when you have long content.
170 | // The downside is you cannot preformat the text with newlines and `[w,e]`
171 | // are always `$multiline-max-width` wide.
172 | .tooltipped-multiline {
173 | &:after {
174 | width: max-content;
175 | max-width: $multiline-max-width;
176 | word-break: break-word;
177 | word-wrap: normal;
178 | white-space: pre-line;
179 | border-collapse: separate;
180 | }
181 |
182 | &.tooltipped-s:after,
183 | &.tooltipped-n:after {
184 | right: auto;
185 | left: 50%;
186 | transform: translateX(-50%);
187 | }
188 |
189 | &.tooltipped-w:after,
190 | &.tooltipped-e:after {
191 | right: 100%;
192 | }
193 | }
194 |
195 | @media screen and (min-width:0\0) {
196 | // IE9 and IE10 rule sets go here
197 | .tooltipped-multiline:after {
198 | width: $multiline-max-width;
199 | }
200 | }
201 |
202 | // Sticky tooltips
203 | //
204 | // Always show the tooltip.
205 | .tooltipped-sticky {
206 | &:before,
207 | &:after {
208 | display: inline-block;
209 | }
210 |
211 | &.tooltipped-multiline {
212 | &:after {
213 | display: table-cell;
214 | }
215 | }
216 | }
217 |
218 | // Alert tooltips
219 | //
220 | // Colors for different alert states.
221 | @mixin colorizeTooltip($text-color, $background-color) {
222 | &:after {
223 | color: $text-color;
224 | background: $background-color;
225 | }
226 |
227 | .tooltipped-s,
228 | .tooltipped-se,
229 | .tooltipped-sw {
230 | &:before {
231 | border-bottom-color: $background-color;
232 | }
233 | }
234 |
235 | &.tooltipped-n,
236 | &.tooltipped-ne,
237 | &.tooltipped-nw {
238 | &:before {
239 | border-top-color: $background-color;
240 | }
241 | }
242 |
243 | &.tooltipped-e:before {
244 | border-right-color: $background-color;
245 | }
246 |
247 | &.tooltipped-w:before {
248 | border-left-color: $background-color;
249 | }
250 | }
251 |
252 | .fullscreen-overlay-enabled.dark-theme .tooltipped {
253 | @include colorizeTooltip(#000, rgba(255, 255, 255, 0.8));
254 | }
255 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_truncate.scss:
--------------------------------------------------------------------------------
1 | // Truncate
2 | //
3 | // css-truncate will shorten text with an ellipsis.
4 |
5 | .css-truncate {
6 | // Truncate double target
7 | //
8 | // css-truncate will shorten text with an ellipsis. The maximum width
9 | // of the truncated text can be changed by overriding the max-width
10 | // of the .css-truncate-target
11 | &.css-truncate-target,
12 | .css-truncate-target {
13 | display: inline-block;
14 | max-width: 125px;
15 | overflow: hidden;
16 | text-overflow: ellipsis;
17 | white-space: nowrap;
18 | vertical-align: top;
19 | }
20 |
21 | &.expandable.zeroclipboard-is-hover .css-truncate-target,
22 | &.expandable.zeroclipboard-is-hover.css-truncate-target,
23 | &.expandable:hover .css-truncate-target,
24 | &.expandable:hover.css-truncate-target {
25 | max-width: 10000px !important;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_type.scss:
--------------------------------------------------------------------------------
1 | // Headings
2 | // --------------------------------------------------
3 |
4 | h1,
5 | h2,
6 | h3,
7 | h4,
8 | h5,
9 | h6 {
10 | margin-top: 15px;
11 | margin-bottom: 15px;
12 | line-height: 1.1;
13 | }
14 |
15 | h1 { font-size: 30px; }
16 | h2 { font-size: 21px; }
17 | h3 { font-size: 16px; }
18 | h4 { font-size: 14px; }
19 | h5 { font-size: 12px; }
20 | h6 { font-size: 11px; }
21 |
22 |
23 | // Body text
24 | // --------------------------------------------------
25 |
26 | small {
27 | font-size: 90%;
28 | }
29 |
30 | blockquote {
31 | margin: 0;
32 | }
33 |
34 | // Large leading paragraphs
35 | .lead {
36 | margin-bottom: 30px;
37 | font-size: 20px;
38 | font-weight: 300;
39 | color: #555;
40 | }
41 |
42 | // Text utilities
43 | .text-muted { color: $brand-gray; }
44 | .text-danger { color: $brand-red; }
45 |
46 | .text-emphasized {
47 | font-weight: bold;
48 | color: #333;
49 | }
50 |
51 |
52 | // Lists
53 | // --------------------------------------------------
54 |
55 | ul,
56 | ol {
57 | padding: 0;
58 | margin-top: 0;
59 | margin-bottom: 0;
60 | }
61 |
62 | ol ol,
63 | ul ol {
64 | list-style-type: lower-roman;
65 | }
66 |
67 | ul ul ol,
68 | ul ol ol,
69 | ol ul ol,
70 | ol ol ol {
71 | list-style-type: lower-alpha;
72 | }
73 |
74 | dd {
75 | margin-left: 0;
76 | }
77 |
78 |
79 | // Code
80 | // --------------------------------------------------
81 |
82 | tt,
83 | code {
84 | font-family: $mono-font;
85 | font-size: 12px;
86 | }
87 |
88 | pre {
89 | margin-top: 0;
90 | margin-bottom: 0;
91 | font: 12px $mono-font;
92 | }
93 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_utility.scss:
--------------------------------------------------------------------------------
1 | // Clear floats
2 | .clearfix {
3 | @include clearfix;
4 | }
5 |
6 | // Floats
7 | .right { float: right; }
8 |
9 | .left { float: left; }
10 |
11 |
12 | // Layout
13 |
14 | // Centers content horizontally. Can be used inside or outside the grid.
15 | .centered {
16 | display: block;
17 | float: none;
18 | margin-left: auto;
19 | margin-right: auto;
20 | }
21 |
22 |
23 | // Text alignment
24 | .text-right { text-align: right; }
25 |
26 | .text-left { text-align: left; }
27 |
28 | .text-center { text-align: center; }
29 |
30 |
31 | // Text states
32 | .danger { color: #c00; }
33 |
34 | .mute { color: #000; }
35 |
36 | .text-diff-added { color: darken($brand-green, 10%); }
37 |
38 | .text-diff-deleted { color: $brand-red; }
39 |
40 | .text-open,
41 | .text-success { color: $status-open; }
42 |
43 | .text-closed { color: $status-closed; }
44 |
45 | .text-reverted { color: $status-reverted; }
46 |
47 | .text-merged { color: $status-merged; }
48 |
49 | .text-renamed { color: $status-renamed; }
50 |
51 | .text-pending { color: $status-pending; }
52 |
53 | .text-error,
54 | .text-failure { color: $brand-red; }
55 |
56 |
57 | // Muted link
58 | //
59 | // Have a link you need to be gray to start, and blue on hover? Use this.
60 | .muted-link {
61 | color: $brand-gray;
62 |
63 | &:hover {
64 | color: $brand-blue;
65 | text-decoration: none;
66 | }
67 | }
68 |
69 | // Misc
70 | .hidden {
71 | display: none;
72 | }
73 |
74 | .warning {
75 | padding: 0.5em;
76 | margin-bottom: 0.8em;
77 | font-weight: bold;
78 | background-color: #fffccc;
79 | }
80 |
81 | .error_box {
82 | padding: 1em;
83 | font-weight: bold;
84 | background-color: #ffebe8;
85 | border: 1px solid #dd3c10;
86 | }
87 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/_variables.scss:
--------------------------------------------------------------------------------
1 | $container-width: 980px !default;
2 | $grid-gutter: 10px !default;
3 |
4 | // Brand colors
5 | $brand-blue: #4078c0 !default;
6 | $brand-gray-light: #999 !default;
7 | $brand-gray: #767676 !default;
8 | $brand-gray-dark: #333 !default;
9 | $brand-green: #6cc644 !default;
10 | $brand-red: #bd2c00 !default;
11 | $brand-orange: #c9510c !default;
12 | $brand-purple: #6e5494 !default;
13 |
14 | // Font stack
15 | $body-font: Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol" !default;
16 |
17 | // The base body size
18 | $body-font-size: 13px !default;
19 |
20 | // Monospace font stack
21 | $mono-font: Consolas, "Liberation Mono", Menlo, Courier, monospace !default;
22 |
23 | // State indicators.
24 | $status-open: $brand-green;
25 | $status-closed: $brand-red;
26 | $status-reverted: $status-closed;
27 | $status-merged: $brand-purple;
28 | $status-renamed: #fffa5d;
29 | $status-pending: #cea61b;
30 |
31 | // Repository type colors
32 | $repo-private-text: #a1882b;
33 | $repo-private-bg: #fff9ea;
34 | $repo-private-icon: #e9dba5;
35 |
--------------------------------------------------------------------------------
/example/bower_components/primer-css/scss/primer.scss:
--------------------------------------------------------------------------------
1 | /*
2 | * Primer
3 | * http://primercss.io
4 | *
5 | * Released under MIT license. Copyright 2015 GitHub, Inc.
6 | */
7 |
8 | // Primer master file
9 | //
10 | // Imports all Primer files in their intended order for easy mass-inclusion.
11 | // Should you need specific files, you can easily use separate `@import`s.
12 |
13 | // Global requirements
14 | @import "variables";
15 | @import "mixins";
16 |
17 | // Basecoat
18 | @import "normalize";
19 | @import "base";
20 | @import "type";
21 | @import "layout";
22 | @import "forms";
23 | @import "utility";
24 |
25 | // Components
26 | @import "alerts";
27 | @import "avatars";
28 | @import "blankslate";
29 | @import "counter";
30 | @import "buttons";
31 | @import "menu";
32 | @import "tabnav";
33 | @import "filter-list";
34 | @import "states";
35 | @import "tooltips";
36 |
37 | // Utilities
38 | @import "flex-table";
39 | @import "truncate";
40 |
--------------------------------------------------------------------------------
/example/build/43a3909d45ff1632beed7e4fff7e04d5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorprado/react-notification-system/4954ae9ca3e76367cde619b6e3ca78fad046680c/example/build/43a3909d45ff1632beed7e4fff7e04d5.png
--------------------------------------------------------------------------------
/example/build/app.css:
--------------------------------------------------------------------------------
1 | .generator h2{text-align:center;font-size:32px}.generator .form-group{margin:15px 0 20px}.generator .form-group label{font-size:15px}.generator .form-group>label{width:20%;display:inline-block}.generator .form-control{width:80%;font-size:16px}.generator small{font-size:80%;display:block;margin:5px 0 0 20%}.generator .btn-notify{padding:12px 0;font-size:18px}/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}*{box-sizing:border-box}body,button,input,select,textarea{font:16px/1.4 Roboto,sans-serif}body{color:#333;background-color:#fff}a{color:#4078c0;text-decoration:none}a:active,a:hover{text-decoration:underline}.rule,hr{height:0;margin:15px 0;overflow:hidden;background:transparent;border:0;border-bottom:1px solid #ddd}.rule:after,.rule:before,hr:after,hr:before{display:table;content:""}.rule:after,hr:after{clear:both}h1,h2,h3,h4,h5,h6{margin-top:15px;margin-bottom:15px;line-height:1.1}h1{font-size:30px}h2{font-size:21px}h3{font-size:16px}h4{font-size:14px}h5{font-size:12px}h6{font-size:11px}small{font-size:90%}blockquote{margin:0}.lead{margin-bottom:30px;font-size:20px;font-weight:300;color:#555}.text-muted{color:#767676}.text-danger{color:#bd2c00}.text-emphasized{font-weight:700;color:#333}ol,ul{padding:0;margin-top:0;margin-bottom:0}ol ol,ul ol{list-style-type:lower-roman}ol ol ol,ol ul ol,ul ol ol,ul ul ol{list-style-type:lower-alpha}dd{margin-left:0}code,tt{font-family:Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:12px}pre{margin-top:0;margin-bottom:0;font:12px Consolas,Liberation Mono,Menlo,Courier,monospace}.container{width:980px;margin-right:auto;margin-left:auto}.container:after,.container:before{display:table;content:""}.container:after{clear:both}.columns{margin-right:-10px;margin-left:-10px}.columns:after,.columns:before{display:table;content:""}.columns:after{clear:both}.column{float:left;padding-right:10px;padding-left:10px}.one-third{width:33.333333%}.two-thirds{width:66.666667%}.one-fourth{width:25%}.one-half{width:50%}.three-fourths{width:75%}.one-fifth{width:20%}.four-fifths{width:80%}.single-column,.table-column{padding-right:10px;padding-left:10px}.table-column{display:table-cell;width:1%;vertical-align:top}fieldset{padding:0;margin:0;border:0}label{font-size:13px;font-weight:700}.form-control,input[type=email],input[type=number],input[type=password],input[type=tel],input[type=text],input[type=url],select,textarea{min-height:34px;padding:7px 8px;font-size:13px;color:#333;vertical-align:middle;background-color:#fff;background-repeat:no-repeat;background-position:right 8px center;border:1px solid #ccc;border-radius:3px;outline:none;box-shadow:inset 0 1px 2px rgba(0,0,0,.075)}.form-control.focus,.form-control:focus,input[type=email].focus,input[type=email]:focus,input[type=number].focus,input[type=number]:focus,input[type=password].focus,input[type=password]:focus,input[type=tel].focus,input[type=tel]:focus,input[type=text].focus,input[type=text]:focus,input[type=url].focus,input[type=url]:focus,select.focus,select:focus,textarea.focus,textarea:focus{border-color:#51a7e8;box-shadow:inset 0 1px 2px rgba(0,0,0,.075),0 0 5px rgba(81,167,232,.5)}select:not([multiple]){height:34px;vertical-align:middle}.input-contrast,input.input-contrast{background-color:#fafafa}.input-contrast:focus,input.input-contrast:focus{background-color:#fff}:-ms-input-placeholder{color:#aaa}::placeholder{color:#aaa}input.input-mini{min-height:26px;padding-top:4px;padding-bottom:4px;font-size:12px}input.input-large{padding:6px 10px;font-size:16px}.input-block{display:block;width:100%}.input-monospace{font-family:Consolas,Liberation Mono,Menlo,Courier,monospace}dl.form{margin:15px 0}dl.form input[type=email],dl.form input[type=password],dl.form input[type=text],dl.form input[type=url],dl.form select,dl.form textarea{background-color:#fafafa}dl.form input[type=email]:focus,dl.form input[type=password]:focus,dl.form input[type=text]:focus,dl.form input[type=url]:focus,dl.form select:focus,dl.form textarea:focus{background-color:#fff}dl.form>dt{margin:0 0 6px}dl.form>dt label{position:relative}dl.form.flattened>dt{float:left;margin:0;line-height:32px}dl.form.flattened>dd{line-height:32px}dl.form>dd input[type=email],dl.form>dd input[type=password],dl.form>dd input[type=text],dl.form>dd input[type=url]{width:440px;max-width:100%;margin-right:5px}dl.form>dd input.shorter{width:130px}dl.form>dd input.short{width:250px}dl.form>dd input.long{width:100%}dl.form>dd textarea{width:100%;height:200px;min-height:200px}dl.form>dd textarea.short{height:50px;min-height:50px}dl.form>dd h4{margin:4px 0 0}dl.form>dd h4.is-error{color:#bd2c00}dl.form>dd h4.is-success{color:#6cc644}dl.form>dd h4+p.note{margin-top:0}dl.form.required>dt>label:after{padding-left:5px;color:#9f1006;content:"*"}dl.form .error,dl.form .indicator,dl.form .success{display:none;font-size:12px;font-weight:700}dl.form.loading{opacity:.5}dl.form.loading .indicator{display:inline}dl.form.loading .spinner{display:inline-block;vertical-align:middle}dl.form.successful .success{display:inline;color:#390}dl.form.errored dd.error,dl.form.errored dd.warning,dl.form.warn dd.error,dl.form.warn dd.warning{display:inline-block;position:absolute;max-width:450px;z-index:10;margin:2px 0 0;padding:5px 8px;font-size:13px;font-weight:400;border-radius:3px}dl.form.errored dd.error:after,dl.form.errored dd.error:before,dl.form.errored dd.warning:after,dl.form.errored dd.warning:before,dl.form.warn dd.error:after,dl.form.warn dd.error:before,dl.form.warn dd.warning:after,dl.form.warn dd.warning:before{bottom:100%;z-index:15;left:10px;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}dl.form.errored dd.error:after,dl.form.errored dd.warning:after,dl.form.warn dd.error:after,dl.form.warn dd.warning:after{border-width:5px}dl.form.errored dd.error:before,dl.form.errored dd.warning:before,dl.form.warn dd.error:before,dl.form.warn dd.warning:before{border-width:6px;margin-left:-1px}dl.form.warn dd.warning{color:#4e401e;background-color:#ffe5a7;border:1px solid #e7ce94}dl.form.warn dd.warning:after{border-bottom-color:#ffe5a7}dl.form.warn dd.warning:before{border-bottom-color:#cdb683}dl.form.errored>dt label{color:#bd2c00}dl.form.errored dd.error{color:#fff;background-color:#bf1515;border-color:#911;font-size:13px}dl.form.errored dd.error:after{border-bottom-color:#bf1515}dl.form.errored dd.error:before{border-bottom-color:#911}.note{min-height:17px;margin:4px 0 2px;font-size:12px;color:#767676}.note .spinner{margin-right:3px;vertical-align:middle}.form-checkbox{padding-left:20px;margin:15px 0;vertical-align:middle}.form-checkbox label em.highlight{position:relative;left:-4px;padding:2px 4px;font-style:normal;background:#fffbdc;border-radius:3px}.form-checkbox input[type=checkbox],.form-checkbox input[type=radio]{float:left;margin:2px 0 0 -20px;vertical-align:middle}.form-checkbox .note{display:block;margin:0;font-size:12px;font-weight:400;color:#666}.hfields{margin:15px 0}.hfields:after,.hfields:before{display:table;content:""}.hfields:after{clear:both}.hfields dl.form{float:left;margin:0 30px 0 0}.hfields dl.form>dt label{display:inline-block;margin:5px 0 0;color:#666}.hfields dl.form>dt img{position:relative;top:-2px}.hfields .btn{float:left;margin:28px 25px 0 -20px}.hfields select{margin-top:5px}input::-webkit-inner-spin-button,input::-webkit-outer-spin-button{margin:0;-webkit-appearance:none}.input-group{display:table}.input-group input{position:relative;width:100%}.input-group input:focus{z-index:2}.input-group input[type=text]+.btn{margin-left:0}.input-group.inline{display:inline-table}.input-group-button,.input-group input{display:table-cell}.input-group-button{width:1%;vertical-align:middle}.input-group-button:first-child .btn,.input-group input:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-button:first-child .btn{margin-right:-1px}.input-group-button:last-child .btn,.input-group input:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-button:last-child .btn{margin-left:-1px}.form-actions:after,.form-actions:before{display:table;content:""}.form-actions:after{clear:both}.form-actions .btn{float:right}.form-actions .btn+.btn{margin-right:5px}.form-warning{padding:8px 10px;margin:10px 0;font-size:14px;color:#333;background:#ffffe2;border:1px solid #e7e4c2;border-radius:4px}.form-warning p{margin:0;line-height:1.5}.form-warning strong{color:#000}.form-warning a{font-weight:700}.status-indicator{font:normal normal 16px/1 octicons;display:inline-block;text-decoration:none;-webkit-font-smoothing:antialiased;margin-left:5px}.status-indicator-success:before{color:#6cc644;content:"\F03A"}.status-indicator-failed:before{color:#bd2c00;content:"\F02D"}.select{display:inline-block;max-width:100%;padding:7px 24px 7px 8px;vertical-align:middle;background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC) no-repeat right 8px center;background-size:8px 10px;box-shadow:inset 0 -1px 2px rgba(0,0,0,.075);-webkit-appearance:none;-moz-appearance:none;appearance:none;padding-right:8px\9;background-image:none\9}.select:focus{outline:none;border-color:#51a7e8;box-shadow:inset 0 1px 2px rgba(0,0,0,.075),0 0 5px rgba(81,167,232,.5)}.select::-ms-expand{opacity:0}.select-sm{padding-top:3px;padding-bottom:3px;font-size:12px}.select-sm:not([multiple]){height:26px;min-height:26px}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}.right{float:right}.left{float:left}.centered{display:block;float:none;margin-left:auto;margin-right:auto}.text-right{text-align:right}.text-left{text-align:left}.text-center{text-align:center}.danger{color:#c00}.mute{color:#000}.text-diff-added{color:#55a532}.text-diff-deleted{color:#bd2c00}.text-open,.text-success{color:#6cc644}.text-closed,.text-reverted{color:#bd2c00}.text-merged{color:#6e5494}.text-renamed{color:#fffa5d}.text-pending{color:#cea61b}.text-error,.text-failure{color:#bd2c00}.muted-link{color:#767676}.muted-link:hover{color:#4078c0;text-decoration:none}.hidden{display:none}.warning{padding:.5em;margin-bottom:.8em;font-weight:700;background-color:#fffccc}.error_box{padding:1em;font-weight:700;background-color:#ffebe8;border:1px solid #dd3c10}.btn{position:relative;display:inline-block;padding:6px 12px;font-size:13px;font-weight:700;line-height:20px;color:#333;white-space:nowrap;vertical-align:middle;cursor:pointer;background-color:#eee;background-image:linear-gradient(#fcfcfc,#eee);border:1px solid #d5d5d5;border-radius:3px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-appearance:none}.btn i{font-style:normal;font-weight:500;opacity:.6}.btn .octicon{vertical-align:text-top}.btn .counter{text-shadow:none;background-color:#e5e5e5}.btn:focus{text-decoration:none;outline:none;box-shadow:0 0 5px rgba(81,167,232,.5)}.btn.selected:focus,.btn:focus,.btn:focus:hover{border-color:#51a7e8}.btn.zeroclipboard-is-active,.btn.zeroclipboard-is-hover,.btn:active,.btn:hover{text-decoration:none;background-color:#ddd;background-image:linear-gradient(#eee,#ddd);border-color:#ccc}.btn.selected,.btn.zeroclipboard-is-active,.btn:active{background-color:#dcdcdc;background-image:none;border-color:#b5b5b5;box-shadow:inset 0 2px 4px rgba(0,0,0,.15)}.btn.selected:hover{background-color:#cfcfcf}.btn.disabled,.btn.disabled:hover,.btn:disabled,.btn:disabled:hover{color:hsla(0,0%,40%,.5);cursor:default;background-color:hsla(0,0%,90%,.5);background-image:none;border-color:hsla(0,0%,77%,.5);box-shadow:none}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.15);background-color:#60b044;background-image:linear-gradient(#8add6d,#60b044);border-color:#5ca941}.btn-primary .counter{color:#60b044;background-color:#fff}.btn-primary:hover{color:#fff;background-color:#569e3d;background-image:linear-gradient(#79d858,#569e3d);border-color:#4a993e}.btn-primary.selected,.btn-primary:active{text-shadow:0 1px 0 rgba(0,0,0,.15);background-color:#569e3d;background-image:none;border-color:#418737}.btn-primary.selected:hover{background-color:#4c8b36}.btn-primary.disabled,.btn-primary.disabled:hover,.btn-primary:disabled,.btn-primary:disabled:hover{color:#fefefe;background-color:#add39f;background-image:linear-gradient(#c3ecb4,#add39f);border-color:#b9dcac #b9dcac #a7c89b}.btn-danger{color:#900}.btn-danger:hover{color:#fff;background-color:#b33630;background-image:linear-gradient(#dc5f59,#b33630);border-color:#cd504a}.btn-danger.selected,.btn-danger:active{color:#fff;background-color:#b33630;background-image:none;border-color:#9f312c}.btn-danger.selected:hover{background-color:#9f302b}.btn-danger.disabled,.btn-danger.disabled:hover,.btn-danger:disabled,.btn-danger:disabled:hover{color:#cb7f7f;background-color:#efefef;background-image:linear-gradient(#fefefe,#efefef);border-color:#e1e1e1}.btn-danger.selected .counter,.btn-danger:active .counter,.btn-danger:hover .counter{color:#b33630;background-color:#fff}.btn-outline{color:#4078c0;background-color:#fff;background-image:none;border:1px solid #e5e5e5}.btn-outline .counter{background-color:#eee}.btn-outline.selected,.btn-outline.zeroclipboard-is-active,.btn-outline.zeroclipboard-is-hover,.btn-outline:active,.btn-outline:hover{color:#fff;background-color:#4078c0;background-image:none;border-color:#4078c0}.btn-outline.selected .counter,.btn-outline.zeroclipboard-is-active .counter,.btn-outline.zeroclipboard-is-hover .counter,.btn-outline:active .counter,.btn-outline:hover .counter{color:#4078c0;background-color:#fff}.btn-outline.selected:hover{background-color:#396cad}.btn-outline.disabled,.btn-outline.disabled:hover,.btn-outline:disabled,.btn-outline:disabled:hover{color:#767676;background-color:#fff;background-image:none;border-color:#e5e5e5}.btn-with-count{float:left;border-top-right-radius:0;border-bottom-right-radius:0}.btn-sm{padding:2px 10px}.hidden-text-expander{display:block}.hidden-text-expander.inline{position:relative;top:-1px;display:inline-block;margin-left:5px;line-height:0}.hidden-text-expander a{display:inline-block;height:12px;padding:0 5px;font-size:12px;font-weight:700;line-height:6px;color:#555;text-decoration:none;vertical-align:middle;background:#ddd;border-radius:1px}.hidden-text-expander a:hover{text-decoration:none;background-color:#ccc}.hidden-text-expander a:active{color:#fff;background-color:#4183c4}.social-count{float:left;padding:2px 7px;font-size:11px;font-weight:700;line-height:20px;color:#333;vertical-align:middle;background-color:#fff;border:1px solid #ddd;border-left:0;border-top-right-radius:3px;border-bottom-right-radius:3px}.social-count:active,.social-count:hover{text-decoration:none}.social-count:hover{color:#4078c0;cursor:pointer}.btn-block{display:block;width:100%;text-align:center}.btn-group{display:inline-block;vertical-align:middle}.btn-group:after,.btn-group:before{display:table;content:""}.btn-group:after{clear:both}.btn-group .btn{position:relative;float:left}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0}.btn-group .btn:first-child:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group .btn:last-child:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .btn.selected,.btn-group .btn:active,.btn-group .btn:hover{z-index:2}.btn-group .btn:focus{z-index:3}.btn-group .btn+.btn,.btn-group .btn+.button_to,.btn-group .button_to+.btn,.btn-group .button_to+.button_to{margin-left:-1px}.btn-group .button_to{float:left}.btn-group .button_to .btn{border-radius:0}.btn-group .button_to:first-child .btn{border-top-left-radius:3px;border-bottom-left-radius:3px}.btn-group .button_to:last-child .btn{border-top-right-radius:3px;border-bottom-right-radius:3px}.btn-group+.btn,.btn-group+.btn-group{margin-left:5px}.btn-link{display:inline-block;padding:0;font-size:inherit;color:#4078c0;white-space:nowrap;cursor:pointer;background-color:transparent;border:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-appearance:none}.btn-link:focus,.btn-link:hover{text-decoration:underline}.btn-link:focus{outline:none}body,html{height:100%}.header{padding:50px 15px 0;text-align:center;border-bottom:6px solid #0c6d6d;position:relative;height:auto}@media (min-width:520px){.header{padding:150px 0 0}}.overlay{background:url(../build/43a3909d45ff1632beed7e4fff7e04d5.png) 0 0 repeat;position:absolute;top:0;left:0;width:100%;height:100%;opacity:.3;background-attachment:fixed}.content{position:relative}.gradient{background:#00b7ea;background:linear-gradient(135deg,#00b7ea,#8ca246);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#00b7ea",endColorstr="#8ca246",GradientType=1);background-attachment:fixed}.title{color:#fff;font-size:40px;letter-spacing:-1px}@media (min-width:520px){.title{font-size:64px}}.subtitle{color:#0c6d6d;font-size:20px}@media (min-width:520px){.subtitle{font-size:28px}}.versions{color:#0f8484}.btn-show-magic,.btn-show-magic:active,.btn-show-magic:focus{margin-top:30px;padding:18px 20px;font-size:18px;line-height:28px;border-color:#fff;background:transparent;color:#fff;transition:all .3s ease-in-out}@media (min-width:520px){.btn-show-magic,.btn-show-magic:active,.btn-show-magic:focus{margin-top:50px;padding:18px 30px;font-size:24px;line-height:32px}}.btn-show-magic:active:hover,.btn-show-magic:focus:hover,.btn-show-magic:hover{border-color:#0c6d6d;color:#fff;background-color:#0c6d6d;box-shadow:none}.btn-show-magic:active:hover:focus,.btn-show-magic:focus:hover:focus,.btn-show-magic:hover:focus{border-color:#0c6d6d}.width-warning{display:block;font-size:12px;margin:10px 0;color:#9e0000}@media (min-width:520px){.width-warning{display:none}}.more-magic{display:block;color:#fff;margin-top:5px;color:#0c6d6d}.github-buttons{margin-top:20px;padding:20px 0}@media (min-width:520px){.github-buttons{margin-top:70px}}.github-buttons iframe{margin:0 10px}.wrapper{width:90%;margin:20px auto 0;position:relative}@media (min-width:520px){.wrapper{width:auto;max-width:520px;margin:40px auto 0}}a,h2{color:#0c6d6d}a{font-weight:700}.hide{display:none!important}.footer{color:#fff;text-align:center;padding:20px 0;position:relative}
--------------------------------------------------------------------------------
/example/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorprado/react-notification-system/4954ae9ca3e76367cde619b6e3ca78fad046680c/example/example.gif
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React Notification System
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/src/images/congruent_pentagon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorprado/react-notification-system/4954ae9ca3e76367cde619b6e3ca78fad046680c/example/src/images/congruent_pentagon.png
--------------------------------------------------------------------------------
/example/src/images/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorprado/react-notification-system/4954ae9ca3e76367cde619b6e3ca78fad046680c/example/src/images/screenshot.jpg
--------------------------------------------------------------------------------
/example/src/scripts/App.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var ReactDOM = require('react-dom');
3 | var NotificationSystem = require('NotificationSystem');
4 | var constants = require('constants');
5 | var NotificationGenerator = require('./NotificationGenerator');
6 | const showcase = require('./showcase');
7 |
8 | var _getRandomPosition = function() {
9 | var positions = Object.keys(constants.positions);
10 | return positions[Math.floor(Math.random() * ((positions.length - 1) + 1)) + 0];
11 | };
12 |
13 | // Styles
14 | require('styles/base');
15 |
16 | class NotificationSystemExample extends React.Component {
17 | constructor() {
18 | super();
19 | this._notificationSystem = React.createRef();
20 | this._magicCount = 0;
21 |
22 | this.state = {
23 | allowHTML: false,
24 | newOnTop: false,
25 | viewHeight: null
26 | };
27 | }
28 |
29 | _notificationSystemInstance() {
30 | return this._notificationSystem.current;
31 | }
32 |
33 | _allowHTML(allow) {
34 | this.setState({ allowHTML: allow });
35 | }
36 |
37 | _newOnTop(newOnTop) {
38 | this.setState({ newOnTop });
39 | }
40 |
41 | _showTheMagic() {
42 | showcase.forEach((notification) => {
43 | var _notification = notification;
44 | if (this._magicCount > 0) {
45 | _notification.position = _getRandomPosition();
46 | }
47 |
48 | this._notificationSystemInstance().addNotification(_notification);
49 | });
50 | this._magicCount += 1;
51 | }
52 |
53 | componentDidMount() {
54 | this.setState({ viewHeight: window.innerHeight });
55 | }
56 |
57 | render() {
58 | return (
59 |
60 |
80 |
81 | this._notificationSystemInstance() } allowHTML={ this._allowHTML.bind(this) } newOnTop={ this._newOnTop.bind(this) } />
82 |
83 |
89 |
90 |
91 | );
92 | }
93 | }
94 |
95 | ReactDOM.render(React.createElement(NotificationSystemExample), document.getElementById('app'));
96 |
--------------------------------------------------------------------------------
/example/src/scripts/CustomElement.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var PropTypes = require('prop-types');
3 |
4 | function buttonClicked() {
5 | alert('I\'m a custom button inside a custom element that was clicked');
6 | }
7 |
8 | function CustomElement(props) {
9 | return (
10 |
11 |
I'm a custom react element with prop: {props.name}
12 |
I'm a custom button
13 |
14 | );
15 | }
16 |
17 | CustomElement.propTypes = {
18 | name: PropTypes.string
19 | };
20 |
21 | module.exports = CustomElement;
22 |
--------------------------------------------------------------------------------
/example/src/scripts/NotificationGenerator.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var PropTypes = require('prop-types');
3 |
4 | // Styles
5 | require('styles/generator');
6 |
7 | class NotificationGenerator extends React.Component {
8 | constructor() {
9 | super();
10 | this._notificationSystem = null;
11 | this._lastNotificationAdded = null;
12 |
13 | this.state = {
14 | notification: {
15 | title: 'Default title',
16 | message: 'Default message',
17 | level: 'error',
18 | position: 'tr',
19 | autoDismiss: 5,
20 | dismissible: 'both',
21 | action: null,
22 | actionState: false
23 | },
24 | allowHTML: false,
25 | newOnTop: false
26 | };
27 |
28 | this._changed = this._changed.bind(this);
29 | }
30 |
31 | _notify(event) {
32 | var notification = this.state.notification;
33 | event.preventDefault();
34 |
35 | notification.onRemove = this._onRemove.bind(this);
36 |
37 | console.log('Notification object', notification);
38 |
39 | this._lastNotificationAdded = this._notificationSystem()
40 | .addNotification(notification);
41 | this.setState({});
42 | }
43 |
44 | _removeLastNotification(event) {
45 | event.preventDefault();
46 | if (this._lastNotificationAdded) {
47 | this._notificationSystem()
48 | .removeNotification(this._lastNotificationAdded);
49 | }
50 | }
51 |
52 | _changed(event) {
53 | var notification = this.state.notification;
54 | var prop = event.target.name;
55 | var value = event.target.value;
56 |
57 | if (prop === 'autoDismiss') {
58 | if (value === '') {
59 | value = 0;
60 | }
61 |
62 | value = parseInt(value, 10);
63 | }
64 |
65 | notification[prop] = value;
66 |
67 | this.setState({
68 | notification: notification
69 | });
70 | }
71 |
72 | _onRemove(notification) {
73 | if (this._lastNotificationAdded && notification.uid === this._lastNotificationAdded.uid) {
74 | this._lastNotificationAdded = null;
75 | }
76 | this.setState({});
77 | console.log('%cNotification ' + notification.uid + ' was removed.', 'font-weight: bold; color: #eb4d00');
78 | }
79 |
80 | _changedAllowHTML() {
81 | var state = this.state;
82 | var allowHTML = !this.state.allowHTML;
83 |
84 | if (allowHTML) {
85 | state.notification.message += ' I\'m bold! ';
86 | }
87 | state.allowHTML = allowHTML;
88 | this.setState(state);
89 | this.props.allowHTML(allowHTML);
90 | }
91 |
92 | _changeNewOnTop() {
93 | this.setState({
94 | newOnTop: !this.state.newOnTop
95 | });
96 | this.props.newOnTop(!this.state.newOnTop);
97 | }
98 |
99 | static _callbackForAction() {
100 | console.log('%cYou clicked an action button inside a notification!', 'font-weight: bold; color: #008feb');
101 | }
102 |
103 | _changedAction() {
104 | var notification = this.state.notification;
105 | notification.actionState = !notification.actionState;
106 |
107 | if (notification.actionState) {
108 | notification.action = {
109 | label: 'I\'m a button',
110 | callback: NotificationGenerator._callbackForAction
111 | };
112 | } else {
113 | notification.action = null;
114 | }
115 |
116 | this.setState({
117 | notification: notification
118 | });
119 | }
120 |
121 | _changedActionLabel(event) {
122 | var notification = this.state.notification;
123 | var value = event.target.value;
124 |
125 | notification.action.label = value;
126 |
127 | this.setState({
128 | notification: notification
129 | });
130 | }
131 |
132 | componentDidMount() {
133 | this._notificationSystem = this.props.notifications;
134 | }
135 |
136 | render() {
137 | var notification = this.state.notification;
138 | var error = {
139 | position: 'hide',
140 | dismissible: 'hide',
141 | level: 'hide',
142 | action: 'hide'
143 | };
144 | var action = null;
145 | var removeButton = null;
146 |
147 | if (notification.actionState) {
148 | action = (
149 |
150 | Label:
151 |
152 |
153 | );
154 | }
155 |
156 | if (this._lastNotificationAdded) {
157 | removeButton = (
158 |
159 | Programmatically remove last notification!
160 |
161 | );
162 | }
163 |
164 | if (notification.position === 'in') {
165 | error.position = 'text-danger';
166 | }
167 |
168 | if (notification.level === 'in') {
169 | error.level = 'text-danger';
170 | }
171 |
172 | if (!notification.dismissible && !notification.actionState) {
173 | error.dismissible = 'text-danger';
174 | error.action = 'text-danger';
175 | }
176 |
177 | return (
178 |
179 |
Notification generator
180 |
Open your console to see some logs from the component.
181 |
182 |
183 | Title:
184 |
185 | Leave empty to hide.
186 |
187 |
188 |
189 | Message:
190 |
191 |
192 |
193 | Allow HTML in message?
194 |
195 |
196 |
197 |
198 |
199 | Position:
200 |
201 | Top left (tl)
202 | Top right (tr)
203 | Top center (tc)
204 | Bottom left (bl)
205 | Bottom right (br)
206 | Bottom center (bc)
207 | Invalid position
208 |
209 | Open console to see the error after creating a notification.
210 |
211 |
212 |
213 | Level:
214 |
215 | Success (success)
216 | Error (error)
217 | Warning (warning)
218 | Info (info)
219 | Invalid level
220 |
221 | Open console to see the error after creating a notification.
222 |
223 |
224 |
225 | Dismissible:
226 |
227 | Both (both)
228 | Click (no dismiss button) (click)
229 | Dismiss button only (button)
230 | None (none)
231 |
232 |
233 |
234 |
235 | Auto Dismiss:
236 |
237 | secs (0 means infinite)
238 |
239 |
240 |
241 |
242 |
243 | Set up an action?
244 |
245 |
246 | { action }
247 |
248 |
249 |
250 |
251 | New notifications on top?
252 |
253 |
254 |
255 |
This notification will be only dismissible programmatically or after "autoDismiss" timeout (if set).
256 |
257 | { removeButton }
258 |
259 |
260 | Notify!
261 |
262 |
263 |
264 | );
265 | }
266 | }
267 |
268 | NotificationGenerator.propTypes = {
269 | notifications: PropTypes.func.isRequired,
270 | allowHTML: PropTypes.func,
271 | newOnTop: PropTypes.func
272 | };
273 |
274 | module.exports = NotificationGenerator;
275 |
--------------------------------------------------------------------------------
/example/src/scripts/showcase.js:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | const CustomElement = require('./CustomElement');
3 |
4 | const showcase = [
5 | {
6 | title: 'Hey, it\'s good to see you!',
7 | message: 'Now you can see how easy it is to use notifications in React!',
8 | level: 'success',
9 | position: 'tr',
10 | action: {
11 | label: 'Awesome!',
12 | callback: function() {
13 | console.log('Clicked');
14 | }
15 | }
16 | },
17 | {
18 | title: 'Hey, it\'s good to see you!',
19 | message: 'I come with custom content!',
20 | level: 'success',
21 | position: 'tr',
22 | children: (
23 |
24 |
25 |
26 | )
27 | },
28 | {
29 | title: 'I\'ll be here forever!',
30 | message: 'Just kidding, you can click me.',
31 | level: 'success',
32 | position: 'tr',
33 | autoDismiss: 0
34 | },
35 | {
36 | title: 'I don\'t have a dismiss button...',
37 | message: 'But you can still click to get rid of me.',
38 | autoDismiss: 0,
39 | level: 'success',
40 | position: 'tr',
41 | dismissible: 'click'
42 | },
43 | {
44 | title: 'Bad things can happen too!',
45 | message: 'Four notification types: `success`, `error`, `warning` and `info`',
46 | level: 'error',
47 | position: 'tl'
48 | },
49 | {
50 | title: 'Advise!',
51 | message: 'Showing all possible notifications works better on a larger screen',
52 | level: 'info',
53 | position: 'tc'
54 | },
55 | {
56 | title: 'Warning!',
57 | message: 'It\'s not a good idea show all these notifications at the same time!',
58 | level: 'warning',
59 | position: 'bc',
60 | action: {
61 | label: 'Got it!'
62 | }
63 | },
64 | {
65 | title: 'Success!',
66 | message: 'I\'m out of ideas',
67 | level: 'success',
68 | position: 'bl'
69 | },
70 | {
71 | title: 'I\'m here forever...',
72 | message: 'Until you click me.',
73 | autoDismiss: 0,
74 | level: 'error',
75 | position: 'br'
76 | },
77 | {
78 | title: 'I\'m here forever...',
79 | message: 'Until you click the dismiss button.',
80 | autoDismiss: 0,
81 | level: 'error',
82 | position: 'br',
83 | dismissible: 'button'
84 | }
85 | ];
86 |
87 | module.exports = showcase;
88 |
89 |
--------------------------------------------------------------------------------
/example/src/styles/base.sass:
--------------------------------------------------------------------------------
1 | // Import defaults
2 | @import "../../bower_components/primer-css/scss/variables"
3 | @import "../../bower_components/primer-css/scss/mixins"
4 |
5 | // My overrides & variables
6 | @import "variables"
7 |
8 | // Import base and used components
9 | @import "../../bower_components/primer-css/scss/normalize"
10 | @import "../../bower_components/primer-css/scss/base"
11 | @import "../../bower_components/primer-css/scss/type"
12 | @import "../../bower_components/primer-css/scss/layout"
13 | @import "../../bower_components/primer-css/scss/forms"
14 | @import "../../bower_components/primer-css/scss/utility"
15 | @import "../../bower_components/primer-css/scss/buttons"
16 |
17 | html, body
18 | height: 100%
19 |
20 | .header
21 | padding: 50px 15px 0
22 | text-align: center
23 | border-bottom: 6px solid $blue-green
24 | position: relative
25 | height: auto
26 |
27 | @media(min-width: 520px)
28 | padding: 150px 0 0
29 |
30 | .overlay
31 | background: url(../images/congruent_pentagon.png) top left repeat
32 | position: absolute
33 | top: 0
34 | left: 0
35 | width: 100%
36 | height: 100%
37 | opacity: 0.3
38 | background-attachment: fixed
39 |
40 | .content
41 | position: relative
42 |
43 | .gradient
44 | background: #00b7ea; /* Old browsers */
45 | background: -moz-linear-gradient(-45deg, #00b7ea 0%, #8ca246 100%); /* FF3.6+ */
46 | background: -webkit-gradient(linear, left top, right bottom, color-stop(0%,#00b7ea), color-stop(100%,#8ca246)); /* Chrome,Safari4+ */
47 | background: -webkit-linear-gradient(-45deg, #00b7ea 0%,#8ca246 100%); /* Chrome10+,Safari5.1+ */
48 | background: -o-linear-gradient(-45deg, #00b7ea 0%,#8ca246 100%); /* Opera 11.10+ */
49 | background: -ms-linear-gradient(-45deg, #00b7ea 0%,#8ca246 100%); /* IE10+ */
50 | background: linear-gradient(135deg, #00b7ea 0%,#8ca246 100%); /* W3C */
51 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00b7ea', endColorstr='#8ca246',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
52 | background-attachment: fixed
53 |
54 | .title
55 | color: #FFF
56 | font-size: 40px
57 | letter-spacing: -1px
58 |
59 | @media(min-width: 520px)
60 | font-size: 64px
61 |
62 | .subtitle
63 | color: $blue-green
64 | font-size: 20px
65 |
66 | @media(min-width: 520px)
67 | font-size: 28px
68 |
69 | .versions
70 | color: lighten($blue-green, 5%)
71 |
72 | .btn-show-magic, .btn-show-magic:active, .btn-show-magic:focus
73 | margin-top: 30px
74 | padding: 18px 20px
75 | font-size: 18px
76 | line-height: 28px
77 | border-color: #FFF
78 | background: transparent
79 | color: #FFF
80 | transition: all 0.3s ease-in-out
81 |
82 | @media(min-width: 520px)
83 | margin-top: 50px
84 | padding: 18px 30px
85 | font-size: 24px
86 | line-height: 32px
87 |
88 | &:hover
89 | border-color: $blue-green
90 | color: #FFF
91 | background-color: $blue-green
92 | box-shadow: none
93 |
94 | &:focus
95 | border-color: $blue-green
96 |
97 | .width-warning
98 | display: block
99 | font-size: 12px
100 | margin: 10px 0
101 | color: #9e0000
102 |
103 | @media(min-width: 520px)
104 | display: none
105 |
106 | .more-magic
107 | display: block
108 | color: #FFF
109 | margin-top: 5px
110 | color: $blue-green
111 |
112 | .github-buttons
113 | margin-top: 20px
114 | padding: 20px 0
115 |
116 | @media(min-width: 520px)
117 | margin-top: 70px
118 |
119 | iframe
120 | margin: 0 10px
121 |
122 | .wrapper
123 | width: 90%;
124 | margin: 20px auto 0
125 | position: relative
126 |
127 | @media(min-width: 520px)
128 | width: auto
129 | max-width: 520px
130 | margin: 40px auto 0
131 |
132 |
133 | h2
134 | color: $blue-green
135 |
136 | a
137 | color: $blue-green
138 | font-weight: bold
139 |
140 | .hide
141 | display: none !important
142 |
143 | .footer
144 | color: #FFF
145 | text-align: center
146 | padding: 20px 0
147 | position: relative
148 |
--------------------------------------------------------------------------------
/example/src/styles/generator.sass:
--------------------------------------------------------------------------------
1 | // My overrides & variables
2 | @import "variables"
3 |
4 | .generator
5 |
6 | h2
7 | text-align: center
8 | font-size: 32px;
9 |
10 | .form-group
11 | margin: 15px 0 20px
12 |
13 | label
14 | font-size: $body-font-size - 1
15 |
16 | &>label
17 | width: 20%
18 | display: inline-block
19 |
20 | .form-control
21 | width: 80%
22 | font-size: $body-font-size
23 |
24 | small
25 | font-size: 80%
26 | display: block
27 | margin: 5px 0 0 20%
28 |
29 | .btn-notify
30 | padding: 12px 0
31 | font-size: 18px
32 |
--------------------------------------------------------------------------------
/example/src/styles/variables.sass:
--------------------------------------------------------------------------------
1 | // Primer overrides
2 | $body-font: 'Roboto', sans-serif
3 | $body-font-size: 16px
4 |
5 | // Variables
6 | $blue-green: #0C6D6D
7 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | var webpack = require('webpack');
4 |
5 | var coverage;
6 | var reporters;
7 | if (process.env.CONTINUOUS_INTEGRATION) {
8 | coverage = {
9 | type: 'lcov',
10 | dir: 'coverage/'
11 | };
12 | reporters = ['coverage', 'coveralls'];
13 | }
14 | else {
15 | coverage = {
16 | type: 'html',
17 | dir: 'coverage/'
18 | };
19 | reporters = ['progress', 'coverage'];
20 | }
21 |
22 | module.exports = function (config) {
23 | config.set({
24 | browsers: ['Firefox'],
25 | browserNoActivityTimeout: 30000,
26 | frameworks: ['mocha', 'chai', 'sinon-chai'],
27 | files: ['tests.webpack.js'],
28 | preprocessors: {'tests.webpack.js': ['webpack', 'sourcemap']},
29 | reporters: reporters,
30 | coverageReporter: coverage,
31 | webpack: {
32 | devtool: 'inline-source-map',
33 | module: {
34 | loaders: [
35 | // TODO: fix sourcemaps
36 | // see: https://github.com/deepsweet/isparta-loader/issues/1
37 | {
38 | test: /\.js$|.jsx$/,
39 | loader: 'babel?presets=airbnb',
40 | exclude: /node_modules/
41 | },
42 | {
43 | test: /\.js$|.jsx$/,
44 | loader: 'isparta?{babel: {stage: 0}}',
45 | exclude: /node_modules|test|utils/
46 | }
47 | ]
48 | },
49 | plugins: [
50 | new webpack.DefinePlugin({
51 | 'process.env': {
52 | BROWSER: JSON.stringify(true),
53 | NODE_ENV: JSON.stringify('test')
54 | }
55 | })
56 | ],
57 | resolve: {
58 | extensions: ['', '.js', '.jsx'],
59 | modulesDirectories: ['node_modules', 'src']
60 | }
61 | },
62 | webpackServer: {
63 | noInfo: true
64 | }
65 | });
66 | };
67 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-notification-system",
3 | "version": "0.4.0",
4 | "description": "A React Notification System fully customized",
5 | "main": "dist/NotificationSystem.js",
6 | "scripts": {
7 | "test": "karma start --single-run",
8 | "test-watch": "karma start",
9 | "prepare-build": "npm run lint && rimraf dist/",
10 | "prebuild": "npm run prepare-build",
11 | "build": "jsx -x jsx ./src ./dist & jsx ./src ./dist && webpack --stats --config webpack.config.umd.prod.js && webpack --stats --config webpack.config.umd.dev.js",
12 | "lint": "eslint src --ext .jsx,.js",
13 | "start": "NODE_ENV=development node devServer.js",
14 | "build:example": "rimraf example/build/ && webpack --stats --config webpack.config.prod.js"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/igorprado/react-notification-system"
19 | },
20 | "keywords": [
21 | "react",
22 | "notification",
23 | "notification system",
24 | "component",
25 | "react component",
26 | "react-component"
27 | ],
28 | "author": "Igor Prado",
29 | "license": "MIT",
30 | "bugs": {
31 | "url": "https://github.com/igorprado/react-notification-system/issues"
32 | },
33 | "homepage": "https://github.com/igorprado/react-notification-system",
34 | "dependencies": {
35 | "object-assign": "^4.0.1",
36 | "prop-types": "^15.5.6"
37 | },
38 | "peerDependencies": {
39 | "react": "0.14.x || ^15.0.0 || ^16.0.0",
40 | "react-dom": "0.14.x || ^15.0.0 || ^16.0.0"
41 | },
42 | "devDependencies": {
43 | "autoprefixer-loader": "^3.1.0",
44 | "babel-core": "^6.14.0",
45 | "babel-eslint": "^6.1.2",
46 | "babel-loader": "^6.2.5",
47 | "babel-plugin-react-class-display-name": "^0.1.0",
48 | "babel-plugin-react-transform": "^2.0.2",
49 | "babel-preset-airbnb": "^2.0.0",
50 | "chai": "^4.1.2",
51 | "css-loader": "^0.24.0",
52 | "eslint": "4.9.0",
53 | "eslint-config-airbnb": "^16.0.0",
54 | "eslint-plugin-import": "^2.7.0",
55 | "eslint-plugin-jsx-a11y": "^6.0.2",
56 | "eslint-plugin-react": "^7.4.0",
57 | "express": "^4.13.3",
58 | "extract-text-webpack-plugin": "^0.8.2",
59 | "file-loader": "^0.8.4",
60 | "isparta-loader": "^1.0.0",
61 | "karma": "^1.7.1",
62 | "karma-chai-plugins": "^0.9.0",
63 | "karma-chrome-launcher": "^2.2.0",
64 | "karma-cli": "^1.0.1",
65 | "karma-coverage": "^1.1.1",
66 | "karma-coveralls": "^1.1.2",
67 | "karma-firefox-launcher": "^1.0.1",
68 | "karma-mocha": "^1.3.0",
69 | "karma-sourcemap-loader": "^0.3.6",
70 | "karma-webpack": "^1.7.0",
71 | "mocha": "^4.0.1",
72 | "node-sass": "^4.13.0",
73 | "react": "^16.11.0",
74 | "react-dom": "^16.11.0",
75 | "react-tools": "^0.13.2",
76 | "react-transform-catch-errors": "^1.0.0",
77 | "react-transform-hmr": "^1.0.1",
78 | "redbox-react": "^1.1.1",
79 | "rimraf": "^2.4.3",
80 | "sass-loader": "^3.0.0",
81 | "style-loader": "^0.12.4",
82 | "webpack": "^1.12.2",
83 | "webpack-dev-middleware": "^1.2.0",
84 | "webpack-hot-middleware": "^2.4.1"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/NotificationContainer.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var PropTypes = require('prop-types');
3 | var NotificationItem = require('./NotificationItem');
4 | var Constants = require('./constants');
5 |
6 | class NotificationContainer extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | // Fix position if width is overrided
10 | this._style = props.getStyles.container(props.position);
11 |
12 | if (
13 | props.getStyles.overrideWidth &&
14 | (props.position === Constants.positions.tc ||
15 | props.position === Constants.positions.bc)
16 | ) {
17 | this._style.marginLeft = -(props.getStyles.overrideWidth / 2);
18 | }
19 | }
20 |
21 | render() {
22 | var notifications;
23 |
24 | if (
25 | [
26 | Constants.positions.bl,
27 | Constants.positions.br,
28 | Constants.positions.bc
29 | ].indexOf(this.props.position) > -1
30 | ) {
31 | this.props.notifications.reverse();
32 | }
33 |
34 | notifications = this.props.notifications.map((notification) => {
35 | return (
36 |
46 | );
47 | });
48 |
49 | return (
50 |
54 | {notifications}
55 |
56 | );
57 | }
58 | }
59 |
60 | NotificationContainer.propTypes = {
61 | position: PropTypes.string.isRequired,
62 | notifications: PropTypes.array.isRequired,
63 | getStyles: PropTypes.object,
64 | onRemove: PropTypes.func,
65 | noAnimation: PropTypes.bool,
66 | allowHTML: PropTypes.bool,
67 | children: PropTypes.oneOfType([PropTypes.string, PropTypes.element])
68 | };
69 |
70 | module.exports = NotificationContainer;
71 |
--------------------------------------------------------------------------------
/src/NotificationItem.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var PropTypes = require('prop-types');
3 | var ReactDOM = require('react-dom');
4 | var Constants = require('./constants');
5 | var Helpers = require('./helpers');
6 | var merge = require('object-assign');
7 |
8 | /* From Modernizr */
9 | var whichTransitionEvent = function() {
10 | var el = document.createElement('fakeelement');
11 | var transition;
12 | var transitions = {
13 | transition: 'transitionend',
14 | OTransition: 'oTransitionEnd',
15 | MozTransition: 'transitionend',
16 | WebkitTransition: 'webkitTransitionEnd'
17 | };
18 |
19 | Object.keys(transitions).forEach(function(transitionKey) {
20 | if (el.style[transitionKey] !== undefined) {
21 | transition = transitions[transitionKey];
22 | }
23 | });
24 |
25 | return transition;
26 | };
27 |
28 | function _allowHTML(string) {
29 | return { __html: string };
30 | }
31 |
32 | class NotificationItem extends React.Component {
33 | constructor(props) {
34 | super(props);
35 | this._notificationTimer = null;
36 | this._height = 0;
37 | this._noAnimation = null;
38 | this._isMounted = false;
39 | this._removeCount = 0;
40 |
41 | this.state = {
42 | visible: undefined,
43 | removed: false
44 | };
45 |
46 | const getStyles = props.getStyles;
47 | const level = props.notification.level;
48 | const dismissible = props.notification.dismissible;
49 |
50 | this._noAnimation = props.noAnimation;
51 |
52 | this._styles = {
53 | notification: getStyles.byElement('notification')(level),
54 | title: getStyles.byElement('title')(level),
55 | dismiss: getStyles.byElement('dismiss')(level),
56 | messageWrapper: getStyles.byElement('messageWrapper')(level),
57 | actionWrapper: getStyles.byElement('actionWrapper')(level),
58 | action: getStyles.byElement('action')(level)
59 | };
60 |
61 | if (!dismissible || dismissible === 'none' || dismissible === 'button') {
62 | this._styles.notification.cursor = 'default';
63 | }
64 |
65 | this._getCssPropertyByPosition = this._getCssPropertyByPosition.bind(this);
66 | this._defaultAction = this._defaultAction.bind(this);
67 | this._hideNotification = this._hideNotification.bind(this);
68 | this._removeNotification = this._removeNotification.bind(this);
69 | this._dismiss = this._dismiss.bind(this);
70 | this._showNotification = this._showNotification.bind(this);
71 | this._onTransitionEnd = this._onTransitionEnd.bind(this);
72 | this._handleMouseEnter = this._handleMouseEnter.bind(this);
73 | this._handleMouseLeave = this._handleMouseLeave.bind(this);
74 | this._handleNotificationClick = this._handleNotificationClick.bind(this);
75 | }
76 |
77 | _getCssPropertyByPosition() {
78 | var position = this.props.notification.position;
79 | var css = {};
80 |
81 | switch (position) {
82 | case Constants.positions.tl:
83 | case Constants.positions.bl:
84 | css = {
85 | property: 'left',
86 | value: -200
87 | };
88 | break;
89 |
90 | case Constants.positions.tr:
91 | case Constants.positions.br:
92 | css = {
93 | property: 'right',
94 | value: -200
95 | };
96 | break;
97 |
98 | case Constants.positions.tc:
99 | css = {
100 | property: 'top',
101 | value: -100
102 | };
103 | break;
104 |
105 | case Constants.positions.bc:
106 | css = {
107 | property: 'bottom',
108 | value: -100
109 | };
110 | break;
111 |
112 | default:
113 | }
114 |
115 | return css;
116 | }
117 |
118 | _defaultAction(event) {
119 | var notification = this.props.notification;
120 |
121 | event.preventDefault();
122 | this._hideNotification();
123 | if (typeof notification.action.callback === 'function') {
124 | notification.action.callback();
125 | }
126 | }
127 |
128 | _hideNotification() {
129 | if (this._notificationTimer) {
130 | this._notificationTimer.clear();
131 | }
132 |
133 | if (this._isMounted) {
134 | this.setState({
135 | visible: false,
136 | removed: true
137 | });
138 | }
139 |
140 | if (this._noAnimation) {
141 | this._removeNotification();
142 | }
143 | }
144 |
145 | _removeNotification() {
146 | this.props.onRemove(this.props.notification.uid);
147 | }
148 |
149 | _dismiss() {
150 | if (!this.props.notification.dismissible) {
151 | return;
152 | }
153 |
154 | this._hideNotification();
155 | }
156 |
157 | _showNotification() {
158 | setTimeout(() => {
159 | if (this._isMounted) {
160 | this.setState({
161 | visible: true
162 | });
163 | }
164 | }, 50);
165 | }
166 |
167 | _onTransitionEnd() {
168 | if (this._removeCount > 0) return;
169 | if (this.state.removed) {
170 | this._removeCount += 1;
171 | this._removeNotification();
172 | }
173 | }
174 |
175 | componentDidMount() {
176 | var self = this;
177 | var transitionEvent = whichTransitionEvent();
178 | var notification = this.props.notification;
179 | var element = ReactDOM.findDOMNode(this);
180 |
181 | this._height = element.offsetHeight;
182 |
183 | this._isMounted = true;
184 |
185 | // Watch for transition end
186 | if (!this._noAnimation) {
187 | if (transitionEvent) {
188 | element.addEventListener(transitionEvent, this._onTransitionEnd);
189 | } else {
190 | this._noAnimation = true;
191 | }
192 | }
193 |
194 | if (notification.autoDismiss) {
195 | this._notificationTimer = new Helpers.Timer(function() {
196 | self._hideNotification();
197 | }, notification.autoDismiss * 1000);
198 | }
199 |
200 | this._showNotification();
201 | }
202 |
203 | _handleMouseEnter() {
204 | var notification = this.props.notification;
205 | if (notification.autoDismiss) {
206 | this._notificationTimer.pause();
207 | }
208 | }
209 |
210 | _handleMouseLeave() {
211 | var notification = this.props.notification;
212 | if (notification.autoDismiss) {
213 | this._notificationTimer.resume();
214 | }
215 | }
216 |
217 | _handleNotificationClick() {
218 | var dismissible = this.props.notification.dismissible;
219 | if (
220 | dismissible === 'both' ||
221 | dismissible === 'click' ||
222 | dismissible === true
223 | ) {
224 | this._dismiss();
225 | }
226 | }
227 |
228 | componentWillUnmount() {
229 | var element = ReactDOM.findDOMNode(this);
230 | var transitionEvent = whichTransitionEvent();
231 | element.removeEventListener(transitionEvent, this._onTransitionEnd);
232 | this._isMounted = false;
233 | }
234 |
235 | render() {
236 | var notification = this.props.notification;
237 | var className = 'notification notification-' + notification.level;
238 | var notificationStyle = merge({}, this._styles.notification);
239 | var cssByPos = this._getCssPropertyByPosition();
240 | var dismiss = null;
241 | var actionButton = null;
242 | var title = null;
243 | var message = null;
244 |
245 | if (this.props.notification.className) {
246 | className += ' ' + this.props.notification.className;
247 | }
248 |
249 | if (this.state.visible) {
250 | className += ' notification-visible';
251 | } else if (this.state.visible === false) {
252 | className += ' notification-hidden';
253 | }
254 |
255 | if (notification.dismissible === 'none') {
256 | className += ' notification-not-dismissible';
257 | }
258 |
259 | if (this.props.getStyles.overrideStyle) {
260 | if (!this.state.visible && !this.state.removed) {
261 | notificationStyle[cssByPos.property] = cssByPos.value;
262 | }
263 |
264 | if (this.state.visible && !this.state.removed) {
265 | notificationStyle.height = this._height;
266 | notificationStyle[cssByPos.property] = 0;
267 | }
268 |
269 | if (this.state.removed) {
270 | notificationStyle.overlay = 'hidden';
271 | notificationStyle.height = 0;
272 | notificationStyle.marginTop = 0;
273 | notificationStyle.paddingTop = 0;
274 | notificationStyle.paddingBottom = 0;
275 | }
276 |
277 | if (this._styles.notification.isVisible && this._styles.notification.isHidden) {
278 | notificationStyle.opacity = this.state.visible
279 | ? this._styles.notification.isVisible.opacity
280 | : this._styles.notification.isHidden.opacity;
281 | }
282 | }
283 |
284 | if (notification.title) {
285 | title = (
286 |
287 | {notification.title}
288 |
289 | );
290 | }
291 |
292 | if (notification.message) {
293 | if (this.props.allowHTML) {
294 | message = (
295 |
300 | );
301 | } else {
302 | message = (
303 |
307 | {notification.message}
308 |
309 | );
310 | }
311 | }
312 |
313 | if (
314 | notification.dismissible === 'both' ||
315 | notification.dismissible === 'button' ||
316 | notification.dismissible === true
317 | ) {
318 | dismiss = (
319 |
325 | ×
326 |
327 | );
328 | }
329 |
330 | if (notification.action) {
331 | actionButton = (
332 |
336 |
341 | {notification.action.label}
342 |
343 |
344 | );
345 | }
346 |
347 | if (notification.children) {
348 | actionButton = notification.children;
349 | }
350 |
351 | return (
352 |
360 | {title}
361 | {message}
362 | {dismiss}
363 | {actionButton}
364 |
365 | );
366 | }
367 | }
368 |
369 | NotificationItem.propTypes = {
370 | notification: PropTypes.object,
371 | getStyles: PropTypes.object,
372 | onRemove: PropTypes.func,
373 | allowHTML: PropTypes.bool,
374 | noAnimation: PropTypes.bool,
375 | children: PropTypes.oneOfType([PropTypes.string, PropTypes.element])
376 | };
377 |
378 | NotificationItem.defaultProps = {
379 | noAnimation: false,
380 | onRemove: function() {},
381 | allowHTML: false
382 | };
383 |
384 | module.exports = NotificationItem;
385 |
--------------------------------------------------------------------------------
/src/NotificationSystem.jsx:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var PropTypes = require('prop-types');
3 | var merge = require('object-assign');
4 | var NotificationContainer = require('./NotificationContainer');
5 | var Constants = require('./constants');
6 | var Styles = require('./styles');
7 |
8 | class NotificationSystem extends React.Component {
9 | constructor() {
10 | super();
11 | this.state = {
12 | notifications: []
13 | };
14 | this.uid = 3400;
15 | this._isMounted = false;
16 | this.overrideWidth = null;
17 | this.overrideStyle = {};
18 | this.elements = {
19 | notification: 'NotificationItem',
20 | title: 'Title',
21 | messageWrapper: 'MessageWrapper',
22 | dismiss: 'Dismiss',
23 | action: 'Action',
24 | actionWrapper: 'ActionWrapper'
25 | };
26 |
27 | this.setOverrideStyle = this.setOverrideStyle.bind(this);
28 | this.wrapper = this.wrapper.bind(this);
29 | this.container = this.container.bind(this);
30 | this.byElement = this.byElement.bind(this);
31 | this._didNotificationRemoved = this._didNotificationRemoved.bind(this);
32 | this.addNotification = this.addNotification.bind(this);
33 | this.getNotificationRef = this.getNotificationRef.bind(this);
34 | this.removeNotification = this.removeNotification.bind(this);
35 | this.editNotification = this.editNotification.bind(this);
36 | this.clearNotifications = this.clearNotifications.bind(this);
37 |
38 | this._getStyles = {
39 | overrideWidth: this.overrideWidth,
40 | overrideStyle: this.overrideStyle,
41 | elements: this.elements,
42 | setOverrideStyle: this.setOverrideStyle,
43 | wrapper: this.wrapper,
44 | container: this.container,
45 | byElement: this.byElement
46 | };
47 | }
48 |
49 | componentDidMount() {
50 | this.setOverrideStyle(this.props.style);
51 | this._isMounted = true;
52 | }
53 |
54 | componentWillUnmount() {
55 | this._isMounted = false;
56 | }
57 |
58 | setOverrideStyle(style) {
59 | this.overrideStyle = style;
60 | }
61 |
62 | wrapper() {
63 | if (!this.overrideStyle) return {};
64 | return merge({}, Styles.Wrapper, this.overrideStyle.Wrapper);
65 | }
66 |
67 | container(position) {
68 | var override = this.overrideStyle.Containers || {};
69 | if (!this.overrideStyle) return {};
70 |
71 | this.overrideWidth = Styles.Containers.DefaultStyle.width;
72 |
73 | if (override.DefaultStyle && override.DefaultStyle.width) {
74 | this.overrideWidth = override.DefaultStyle.width;
75 | }
76 |
77 | if (override[position] && override[position].width) {
78 | this.overrideWidth = override[position].width;
79 | }
80 |
81 | return merge(
82 | {},
83 | Styles.Containers.DefaultStyle,
84 | Styles.Containers[position],
85 | override.DefaultStyle,
86 | override[position]
87 | );
88 | }
89 |
90 | byElement(element) {
91 | return (level) => {
92 | var _element = this.elements[element];
93 | var override = this.overrideStyle[_element] || {};
94 | if (!this.overrideStyle) return {};
95 | return merge(
96 | {},
97 | Styles[_element].DefaultStyle,
98 | Styles[_element][level],
99 | override.DefaultStyle,
100 | override[level]
101 | );
102 | };
103 | }
104 |
105 | _didNotificationRemoved(uid) {
106 | var notification;
107 | var notifications = this.state.notifications.filter(function(toCheck) {
108 | if (toCheck.uid === uid) {
109 | notification = toCheck;
110 | return false;
111 | }
112 | return true;
113 | });
114 |
115 | if (this._isMounted) {
116 | this.setState({ notifications: notifications });
117 | }
118 |
119 | if (notification && notification.onRemove) {
120 | notification.onRemove(notification);
121 | }
122 | }
123 |
124 | addNotification(notification) {
125 | var _notification = merge({}, Constants.notification, notification);
126 | var notifications = this.state.notifications;
127 | var i;
128 |
129 |
130 | if (!_notification.level) {
131 | throw new Error('notification level is required.');
132 | }
133 |
134 | if (Object.keys(Constants.levels).indexOf(_notification.level) === -1) {
135 | throw new Error("'" + _notification.level + "' is not a valid level.");
136 | }
137 |
138 | // eslint-disable-next-line
139 | if (isNaN(_notification.autoDismiss)) {
140 | throw new Error("'autoDismiss' must be a number.");
141 | }
142 |
143 | if (
144 | Object.keys(Constants.positions).indexOf(_notification.position) === -1
145 | ) {
146 | throw new Error("'" + _notification.position + "' is not a valid position.");
147 | }
148 |
149 | // Some preparations
150 | _notification.position = _notification.position.toLowerCase();
151 | _notification.level = _notification.level.toLowerCase();
152 | _notification.autoDismiss = parseInt(_notification.autoDismiss, 10);
153 |
154 | _notification.uid = _notification.uid || this.uid;
155 | _notification.ref = 'notification-' + _notification.uid;
156 | this.uid += 1;
157 |
158 |
159 | // do not add if the notification already exists based on supplied uid
160 | for (i = 0; i < notifications.length; i += 1) {
161 | if (notifications[i].uid === _notification.uid) {
162 | return false;
163 | }
164 | }
165 |
166 | if (this.props.newOnTop) {
167 | notifications.unshift(_notification);
168 | } else {
169 | notifications.push(_notification);
170 | }
171 |
172 |
173 | if (typeof _notification.onAdd === 'function') {
174 | notification.onAdd(_notification);
175 | }
176 |
177 | this.setState({
178 | notifications: notifications
179 | });
180 |
181 | return _notification;
182 | }
183 |
184 | getNotificationRef(notification) {
185 | var foundNotification = null;
186 |
187 | Object.keys(this.refs).forEach((container) => {
188 | if (container.indexOf('container') > -1) {
189 | Object.keys(this.refs[container].refs).forEach((_notification) => {
190 | var uid = notification.uid ? notification.uid : notification;
191 | if (_notification === 'notification-' + uid) {
192 | // NOTE: Stop iterating further and return the found notification.
193 | // Since UIDs are uniques and there won't be another notification found.
194 | foundNotification = this.refs[container].refs[_notification];
195 | }
196 | });
197 | }
198 | });
199 |
200 | return foundNotification;
201 | }
202 |
203 | removeNotification(notification) {
204 | var foundNotification = this.getNotificationRef(notification);
205 | return foundNotification && foundNotification._hideNotification();
206 | }
207 |
208 | editNotification(notification, newNotification) {
209 | var foundNotification = null;
210 | // NOTE: Find state notification to update by using
211 | // `setState` and forcing React to re-render the component.
212 | var uid = notification.uid ? notification.uid : notification;
213 |
214 | var newNotifications = this.state.notifications.filter(function(stateNotification) {
215 | if (uid === stateNotification.uid) {
216 | foundNotification = stateNotification;
217 | return false;
218 | }
219 |
220 | return true;
221 | });
222 |
223 | if (!foundNotification) {
224 | return;
225 | }
226 |
227 | newNotifications.push(merge({}, foundNotification, newNotification));
228 |
229 | this.setState({
230 | notifications: newNotifications
231 | });
232 | }
233 |
234 | clearNotifications() {
235 | Object.keys(this.refs).forEach((container) => {
236 | if (container.indexOf('container') > -1) {
237 | Object.keys(this.refs[container].refs).forEach((_notification) => {
238 | this.refs[container].refs[_notification]._hideNotification();
239 | });
240 | }
241 | });
242 | }
243 |
244 | render() {
245 | var containers = null;
246 | var notifications = this.state.notifications;
247 |
248 | if (notifications.length) {
249 | containers = Object.keys(Constants.positions).map((position) => {
250 | var _notifications = notifications.filter((notification) => {
251 | return position === notification.position;
252 | });
253 |
254 | if (!_notifications.length) {
255 | return null;
256 | }
257 |
258 | return (
259 |
269 | );
270 | });
271 | }
272 |
273 | return (
274 |
275 | {containers}
276 |
277 | );
278 | }
279 | }
280 |
281 | NotificationSystem.propTypes = {
282 | style: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
283 | noAnimation: PropTypes.bool,
284 | allowHTML: PropTypes.bool,
285 | newOnTop: PropTypes.bool
286 | };
287 |
288 | NotificationSystem.defaultProps = {
289 | style: {},
290 | noAnimation: false,
291 | allowHTML: false,
292 | newOnTop: false
293 | };
294 |
295 | module.exports = NotificationSystem;
296 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | var CONSTANTS = {
2 |
3 | // Positions
4 | positions: {
5 | tl: 'tl',
6 | tr: 'tr',
7 | tc: 'tc',
8 | bl: 'bl',
9 | br: 'br',
10 | bc: 'bc'
11 | },
12 |
13 | // Levels
14 | levels: {
15 | success: 'success',
16 | error: 'error',
17 | warning: 'warning',
18 | info: 'info'
19 | },
20 |
21 | // Notification defaults
22 | notification: {
23 | title: null,
24 | message: null,
25 | level: null,
26 | position: 'tr',
27 | autoDismiss: 5,
28 | dismissible: 'both',
29 | action: null
30 | }
31 | };
32 |
33 |
34 | module.exports = CONSTANTS;
35 |
--------------------------------------------------------------------------------
/src/helpers.js:
--------------------------------------------------------------------------------
1 | var Helpers = {
2 | Timer: function(callback, delay) {
3 | var timerId;
4 | var start;
5 | var remaining = delay;
6 |
7 | this.pause = function() {
8 | clearTimeout(timerId);
9 | remaining -= new Date() - start;
10 | };
11 |
12 | this.resume = function() {
13 | start = new Date();
14 | clearTimeout(timerId);
15 | timerId = setTimeout(callback, remaining);
16 | };
17 |
18 | this.clear = function() {
19 | clearTimeout(timerId);
20 | };
21 |
22 | this.resume();
23 | }
24 | };
25 |
26 | module.exports = Helpers;
27 |
--------------------------------------------------------------------------------
/src/styles.js:
--------------------------------------------------------------------------------
1 | // Used for calculations
2 | var defaultWidth = 320;
3 | var defaultColors = {
4 | success: {
5 | rgb: '94, 164, 0',
6 | hex: '#5ea400'
7 | },
8 | error: {
9 | rgb: '236, 61, 61',
10 | hex: '#ec3d3d'
11 | },
12 | warning: {
13 | rgb: '235, 173, 23',
14 | hex: '#ebad1a'
15 | },
16 | info: {
17 | rgb: '54, 156, 199',
18 | hex: '#369cc7'
19 | }
20 | };
21 | var defaultShadowOpacity = '0.9';
22 |
23 | var STYLES = {
24 |
25 | Wrapper: {},
26 | Containers: {
27 | DefaultStyle: {
28 | fontFamily: 'inherit',
29 | position: 'fixed',
30 | width: defaultWidth,
31 | padding: '0 10px 10px 10px',
32 | zIndex: 9998,
33 | WebkitBoxSizing: 'border-box',
34 | MozBoxSizing: 'border-box',
35 | boxSizing: 'border-box',
36 | height: 'auto'
37 | },
38 |
39 | tl: {
40 | top: '0px',
41 | bottom: 'auto',
42 | left: '0px',
43 | right: 'auto'
44 | },
45 |
46 | tr: {
47 | top: '0px',
48 | bottom: 'auto',
49 | left: 'auto',
50 | right: '0px'
51 | },
52 |
53 | tc: {
54 | top: '0px',
55 | bottom: 'auto',
56 | margin: '0 auto',
57 | left: '50%',
58 | marginLeft: -(defaultWidth / 2)
59 | },
60 |
61 | bl: {
62 | top: 'auto',
63 | bottom: '0px',
64 | left: '0px',
65 | right: 'auto'
66 | },
67 |
68 | br: {
69 | top: 'auto',
70 | bottom: '0px',
71 | left: 'auto',
72 | right: '0px'
73 | },
74 |
75 | bc: {
76 | top: 'auto',
77 | bottom: '0px',
78 | margin: '0 auto',
79 | left: '50%',
80 | marginLeft: -(defaultWidth / 2)
81 | }
82 |
83 | },
84 |
85 | NotificationItem: {
86 | DefaultStyle: {
87 | position: 'relative',
88 | width: '100%',
89 | cursor: 'pointer',
90 | borderRadius: '2px',
91 | fontSize: '13px',
92 | margin: '10px 0 0',
93 | padding: '10px',
94 | display: 'block',
95 | WebkitBoxSizing: 'border-box',
96 | MozBoxSizing: 'border-box',
97 | boxSizing: 'border-box',
98 | opacity: 0,
99 | transition: '0.3s ease-in-out',
100 | WebkitTransform: 'translate3d(0, 0, 0)',
101 | transform: 'translate3d(0, 0, 0)',
102 | willChange: 'transform, opacity',
103 |
104 | isHidden: {
105 | opacity: 0
106 | },
107 |
108 | isVisible: {
109 | opacity: 1
110 | }
111 | },
112 |
113 | success: {
114 | borderTop: '2px solid ' + defaultColors.success.hex,
115 | backgroundColor: '#f0f5ea',
116 | color: '#4b583a',
117 | WebkitBoxShadow: '0 0 1px rgba(' + defaultColors.success.rgb + ',' + defaultShadowOpacity + ')',
118 | MozBoxShadow: '0 0 1px rgba(' + defaultColors.success.rgb + ',' + defaultShadowOpacity + ')',
119 | boxShadow: '0 0 1px rgba(' + defaultColors.success.rgb + ',' + defaultShadowOpacity + ')'
120 | },
121 |
122 | error: {
123 | borderTop: '2px solid ' + defaultColors.error.hex,
124 | backgroundColor: '#f4e9e9',
125 | color: '#412f2f',
126 | WebkitBoxShadow: '0 0 1px rgba(' + defaultColors.error.rgb + ',' + defaultShadowOpacity + ')',
127 | MozBoxShadow: '0 0 1px rgba(' + defaultColors.error.rgb + ',' + defaultShadowOpacity + ')',
128 | boxShadow: '0 0 1px rgba(' + defaultColors.error.rgb + ',' + defaultShadowOpacity + ')'
129 | },
130 |
131 | warning: {
132 | borderTop: '2px solid ' + defaultColors.warning.hex,
133 | backgroundColor: '#f9f6f0',
134 | color: '#5a5343',
135 | WebkitBoxShadow: '0 0 1px rgba(' + defaultColors.warning.rgb + ',' + defaultShadowOpacity + ')',
136 | MozBoxShadow: '0 0 1px rgba(' + defaultColors.warning.rgb + ',' + defaultShadowOpacity + ')',
137 | boxShadow: '0 0 1px rgba(' + defaultColors.warning.rgb + ',' + defaultShadowOpacity + ')'
138 | },
139 |
140 | info: {
141 | borderTop: '2px solid ' + defaultColors.info.hex,
142 | backgroundColor: '#e8f0f4',
143 | color: '#41555d',
144 | WebkitBoxShadow: '0 0 1px rgba(' + defaultColors.info.rgb + ',' + defaultShadowOpacity + ')',
145 | MozBoxShadow: '0 0 1px rgba(' + defaultColors.info.rgb + ',' + defaultShadowOpacity + ')',
146 | boxShadow: '0 0 1px rgba(' + defaultColors.info.rgb + ',' + defaultShadowOpacity + ')'
147 | }
148 | },
149 |
150 | Title: {
151 | DefaultStyle: {
152 | fontSize: '14px',
153 | margin: '0 0 5px 0',
154 | padding: 0,
155 | fontWeight: 'bold'
156 | },
157 |
158 | success: {
159 | color: defaultColors.success.hex
160 | },
161 |
162 | error: {
163 | color: defaultColors.error.hex
164 | },
165 |
166 | warning: {
167 | color: defaultColors.warning.hex
168 | },
169 |
170 | info: {
171 | color: defaultColors.info.hex
172 | }
173 |
174 | },
175 |
176 | MessageWrapper: {
177 | DefaultStyle: {
178 | margin: 0,
179 | padding: 0
180 | }
181 | },
182 |
183 | Dismiss: {
184 | DefaultStyle: {
185 | cursor: 'pointer',
186 | fontFamily: 'Arial',
187 | fontSize: '17px',
188 | position: 'absolute',
189 | top: '4px',
190 | right: '5px',
191 | lineHeight: '15px',
192 | backgroundColor: '#dededf',
193 | color: '#ffffff',
194 | borderRadius: '50%',
195 | width: '14px',
196 | height: '14px',
197 | fontWeight: 'bold',
198 | textAlign: 'center'
199 | },
200 |
201 | success: {
202 | color: '#f0f5ea',
203 | backgroundColor: '#b0ca92'
204 | },
205 |
206 | error: {
207 | color: '#f4e9e9',
208 | backgroundColor: '#e4bebe'
209 | },
210 |
211 | warning: {
212 | color: '#f9f6f0',
213 | backgroundColor: '#e1cfac'
214 | },
215 |
216 | info: {
217 | color: '#e8f0f4',
218 | backgroundColor: '#a4becb'
219 | }
220 | },
221 |
222 | Action: {
223 | DefaultStyle: {
224 | background: '#ffffff',
225 | borderRadius: '2px',
226 | padding: '6px 20px',
227 | fontWeight: 'bold',
228 | margin: '10px 0 0 0',
229 | border: 0
230 | },
231 |
232 | success: {
233 | backgroundColor: defaultColors.success.hex,
234 | color: '#ffffff'
235 | },
236 |
237 | error: {
238 | backgroundColor: defaultColors.error.hex,
239 | color: '#ffffff'
240 | },
241 |
242 | warning: {
243 | backgroundColor: defaultColors.warning.hex,
244 | color: '#ffffff'
245 | },
246 |
247 | info: {
248 | backgroundColor: defaultColors.info.hex,
249 | color: '#ffffff'
250 | }
251 | },
252 |
253 | ActionWrapper: {
254 | DefaultStyle: {
255 | margin: 0,
256 | padding: 0
257 | }
258 | }
259 | };
260 |
261 | module.exports = STYLES;
262 |
--------------------------------------------------------------------------------
/test/notification-system.test.js:
--------------------------------------------------------------------------------
1 | /* global sinon */
2 |
3 | import React, { Component } from 'react';
4 | import TestUtils from 'react-dom/test-utils';
5 | import { expect } from 'chai';
6 | import NotificationSystem from 'NotificationSystem';
7 | import { positions, levels } from 'constants';
8 | import merge from 'object-assign';
9 |
10 | const defaultNotification = {
11 | title: 'This is a title',
12 | message: 'This is a message',
13 | level: 'success'
14 | };
15 |
16 | const style = {
17 | Containers: {
18 | DefaultStyle: {
19 | width: 600
20 | },
21 |
22 | tl: {
23 | width: 800
24 | }
25 | }
26 | };
27 |
28 | describe('Notification Component', function() {
29 | let node;
30 | let instance;
31 | let component;
32 | let clock;
33 | let notificationObj;
34 | const ref = 'notificationSystem';
35 |
36 | this.timeout(10000);
37 |
38 | beforeEach(() => {
39 | // We need to create this wrapper so we can use refs
40 | class ElementWrapper extends Component {
41 | render() {
42 | return ;
43 | }
44 | }
45 | node = window.document.createElement('div');
46 | instance = TestUtils.renderIntoDocument(React.createElement(ElementWrapper), node);
47 | component = instance.refs[ref];
48 | notificationObj = merge({}, defaultNotification);
49 |
50 | clock = sinon.useFakeTimers();
51 | });
52 |
53 | afterEach(() => {
54 | clock.restore();
55 | });
56 |
57 | it('should be rendered', done => {
58 | component = TestUtils.findRenderedDOMComponentWithClass(instance, 'notifications-wrapper');
59 | expect(component).to.not.be.null;
60 | done();
61 | });
62 |
63 | it('should hold the component ref', done => {
64 | expect(component).to.not.be.null;
65 | done();
66 | });
67 |
68 | it('should render a single notification', done => {
69 | component.addNotification(defaultNotification);
70 | let notification = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
71 | expect(notification.length).to.equal(1);
72 | done();
73 | });
74 |
75 | it('should not set a notification visibility class when the notification is initially added', done => {
76 | component.addNotification(defaultNotification);
77 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
78 | expect(notification.className).to.not.match(/notification-hidden/);
79 | expect(notification.className).to.not.match(/notification-visible/);
80 | done();
81 | });
82 |
83 | it('should set the notification class to visible after added', done => {
84 | component.addNotification(defaultNotification);
85 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
86 | expect(notification.className).to.match(/notification/);
87 | clock.tick(400);
88 | expect(notification.className).to.match(/notification-visible/);
89 | done();
90 | });
91 |
92 | it('should add additional classes to the notification if specified', done => {
93 | component.addNotification(Object.assign({},defaultNotification, {className: 'FOO'}));
94 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
95 | expect(notification.className).to.contain(' FOO');
96 | done();
97 | });
98 |
99 | it('should render notifications in all positions with all levels', done => {
100 | let count = 0;
101 | for (let position of Object.keys(positions)) {
102 | for (let level of Object.keys(levels)) {
103 | notificationObj.position = positions[position];
104 | notificationObj.level = levels[level];
105 | component.addNotification(notificationObj);
106 | count++;
107 | }
108 | }
109 |
110 | let containers = [];
111 |
112 | for (let position of Object.keys(positions)) {
113 | containers.push(TestUtils.findRenderedDOMComponentWithClass(instance, 'notifications-' + positions[position]));
114 | }
115 |
116 | containers.forEach(function(container) {
117 | for (let level of Object.keys(levels)) {
118 | let notification = container.getElementsByClassName('notification-' + levels[level]);
119 | expect(notification).to.not.be.null;
120 | }
121 | });
122 |
123 | let notifications = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
124 | expect(notifications.length).to.equal(count);
125 | done();
126 | });
127 |
128 | it('should render multiple notifications', done => {
129 | const randomNumber = Math.floor(Math.random(5, 10));
130 |
131 | for (let i = 1; i <= randomNumber; i++) {
132 | component.addNotification(defaultNotification);
133 | }
134 |
135 | let notifications = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
136 | expect(notifications.length).to.equal(randomNumber);
137 | done();
138 | });
139 |
140 | it('should not render notifications with the same uid', done => {
141 | notificationObj.uid = 500;
142 | component.addNotification(notificationObj);
143 | component.addNotification(notificationObj);
144 | let notification = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
145 | expect(notification.length).to.equal(1);
146 | done();
147 | });
148 |
149 | it('should remove a notification after autoDismiss', function(done) {
150 | notificationObj.autoDismiss = 2;
151 | component.addNotification(notificationObj);
152 | clock.tick(3000);
153 | let notification = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
154 | expect(notification.length).to.equal(0);
155 | done();
156 | });
157 |
158 | it('should remove a notification using returned object', done => {
159 | let notificationCreated = component.addNotification(defaultNotification);
160 | let notification = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
161 | expect(notification.length).to.equal(1);
162 |
163 | component.removeNotification(notificationCreated);
164 | clock.tick(1000);
165 | let notificationRemoved = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
166 | expect(notificationRemoved.length).to.equal(0);
167 | done();
168 | });
169 |
170 | it('should remove a notification using uid', done => {
171 | let notificationCreated = component.addNotification(defaultNotification);
172 | let notification = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
173 | expect(notification.length).to.equal(1);
174 |
175 | component.removeNotification(notificationCreated.uid);
176 | clock.tick(200);
177 | let notificationRemoved = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
178 | expect(notificationRemoved.length).to.equal(0);
179 | done();
180 | });
181 |
182 | it('should edit an existing notification using returned object', (done) => {
183 | const notificationCreated = component.addNotification(defaultNotification);
184 | const notification = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
185 | expect(notification.length).to.equal(1);
186 |
187 | const newTitle = 'foo';
188 | const newContent = 'foobar';
189 |
190 | component.editNotification(notificationCreated, { title: newTitle, message: newContent });
191 | clock.tick(1000);
192 | const notificationEdited = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
193 | expect(notificationEdited.getElementsByClassName('notification-title')[0].textContent).to.equal(newTitle);
194 | expect(notificationEdited.getElementsByClassName('notification-message')[0].textContent).to.equal(newContent);
195 | done();
196 | });
197 |
198 | it('should edit an existing notification using uid', (done) => {
199 | const notificationCreated = component.addNotification(defaultNotification);
200 | const notification = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
201 | expect(notification.length).to.equal(1);
202 |
203 | const newTitle = 'foo';
204 | const newContent = 'foobar';
205 |
206 | component.editNotification(notificationCreated.uid, { title: newTitle, message: newContent });
207 | clock.tick(1000);
208 | const notificationEdited = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
209 | expect(notificationEdited.getElementsByClassName('notification-title')[0].textContent).to.equal(newTitle);
210 | expect(notificationEdited.getElementsByClassName('notification-message')[0].textContent).to.equal(newContent);
211 | done();
212 | });
213 |
214 | it('should remove all notifications', done => {
215 | component.addNotification(defaultNotification);
216 | component.addNotification(defaultNotification);
217 | component.addNotification(defaultNotification);
218 | let notification = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
219 | expect(notification.length).to.equal(3);
220 | component.clearNotifications();
221 | clock.tick(200);
222 | let notificationRemoved = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
223 | expect(notificationRemoved.length).to.equal(0);
224 | done();
225 | });
226 |
227 | it('should dismiss notification on click', done => {
228 | component.addNotification(notificationObj);
229 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
230 | TestUtils.Simulate.click(notification);
231 | clock.tick(1000);
232 | let notificationRemoved = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
233 | expect(notificationRemoved.length).to.equal(0);
234 | done();
235 | });
236 |
237 | it('should dismiss notification on click of dismiss button', done => {
238 | component.addNotification(notificationObj);
239 | let dismissButton = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification-dismiss');
240 | TestUtils.Simulate.click(dismissButton);
241 | clock.tick(1000);
242 | let notificationRemoved = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
243 | expect(notificationRemoved.length).to.equal(0);
244 | done();
245 | });
246 |
247 | it('should not render title if not provided', done => {
248 | delete notificationObj.title;
249 | component.addNotification(notificationObj);
250 | let notification = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification-title');
251 | expect(notification.length).to.equal(0);
252 | done();
253 | });
254 |
255 | it('should not render message if not provided', done => {
256 | delete notificationObj.message;
257 | component.addNotification(notificationObj);
258 | let notification = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification-message');
259 | expect(notification.length).to.equal(0);
260 | done();
261 | });
262 |
263 | it('should not dismiss the notificaion on click if dismissible is false', done => {
264 | notificationObj.dismissible = false;
265 | component.addNotification(notificationObj);
266 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
267 | TestUtils.Simulate.click(notification);
268 | let notificationAfterClicked = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
269 | expect(notificationAfterClicked).to.not.be.null;
270 | done();
271 | });
272 |
273 | it('should not dismiss the notification on click if dismissible is none', done => {
274 | notificationObj.dismissible = 'none';
275 | component.addNotification(notificationObj);
276 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
277 | TestUtils.Simulate.click(notification);
278 | let notificationAfterClicked = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
279 | expect(notificationAfterClicked).to.exist;
280 | done();
281 | });
282 |
283 | it('should not dismiss the notification on click if dismissible is button', done => {
284 | notificationObj.dismissible = 'button';
285 | component.addNotification(notificationObj);
286 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
287 | TestUtils.Simulate.click(notification);
288 | let notificationAfterClicked = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
289 | expect(notificationAfterClicked).to.exist;
290 | done();
291 | });
292 |
293 | it('should render a button if action property is passed', done => {
294 | defaultNotification.action = {
295 | label: 'Click me',
296 | callback: function() {}
297 | };
298 |
299 | component.addNotification(defaultNotification);
300 | let button = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification-action-button');
301 | expect(button).to.not.be.null;
302 | done();
303 | });
304 |
305 | it('should execute a callback function when notification button is clicked', done => {
306 | let testThis = false;
307 | notificationObj.action = {
308 | label: 'Click me',
309 | callback: function() {
310 | testThis = true;
311 | }
312 | };
313 |
314 | component.addNotification(notificationObj);
315 | let button = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification-action-button');
316 | TestUtils.Simulate.click(button);
317 | expect(testThis).to.equal(true);
318 | done();
319 | });
320 |
321 | it('should accept an action without callback function defined', done => {
322 | notificationObj.action = {
323 | label: 'Click me'
324 | };
325 |
326 | component.addNotification(notificationObj);
327 | let button = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification-action-button');
328 | TestUtils.Simulate.click(button);
329 | let notification = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
330 | expect(notification.length).to.equal(0);
331 | done();
332 | });
333 |
334 | it('should execute a callback function on add a notification', done => {
335 | let testThis = false;
336 | notificationObj.onAdd = function() {
337 | testThis = true;
338 | };
339 |
340 | component.addNotification(notificationObj);
341 | expect(testThis).to.equal(true);
342 | done();
343 | });
344 |
345 | it('should execute a callback function on remove a notification', done => {
346 | let testThis = false;
347 | notificationObj.onRemove = function() {
348 | testThis = true;
349 | };
350 |
351 | component.addNotification(notificationObj);
352 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
353 | TestUtils.Simulate.click(notification);
354 | expect(testThis).to.equal(true);
355 | done();
356 | });
357 |
358 | it('should render a children if passed', done => {
359 | defaultNotification.children = (
360 |
361 | );
362 |
363 | component.addNotification(defaultNotification);
364 | let customContainer = TestUtils.findRenderedDOMComponentWithClass(instance, 'custom-container');
365 | expect(customContainer).to.not.be.null;
366 | done();
367 | });
368 |
369 | it('should pause the timer if a notification has a mouse enter', done => {
370 | notificationObj.autoDismiss = 2;
371 | component.addNotification(notificationObj);
372 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
373 | TestUtils.Simulate.mouseEnter(notification);
374 | clock.tick(4000);
375 | let _notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
376 | expect(_notification).to.not.be.null;
377 | done();
378 | });
379 |
380 | it('should resume the timer if a notification has a mouse leave', done => {
381 | notificationObj.autoDismiss = 2;
382 | component.addNotification(notificationObj);
383 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
384 | TestUtils.Simulate.mouseEnter(notification);
385 | clock.tick(800);
386 | TestUtils.Simulate.mouseLeave(notification);
387 | clock.tick(2000);
388 | let _notification = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
389 | expect(_notification.length).to.equal(0);
390 | done();
391 | });
392 |
393 | it('should allow HTML inside messages', done => {
394 | defaultNotification.message = 'Strong ';
395 | component.addNotification(defaultNotification);
396 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification-message');
397 | let htmlElement = notification.getElementsByClassName('allow-html-strong');
398 | expect(htmlElement.length).to.equal(1);
399 | done();
400 | });
401 |
402 | it('should render containers with a overriden width', done => {
403 | notificationObj.position = 'tc';
404 | component.addNotification(notificationObj);
405 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notifications-tc');
406 | let width = notification.style.width;
407 | expect(width).to.equal('600px');
408 | done();
409 | });
410 |
411 | it('should render a notification with specific style based on position', done => {
412 | notificationObj.position = 'bc';
413 | component.addNotification(notificationObj);
414 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notification');
415 | let bottomPosition = notification.style.bottom;
416 | expect(bottomPosition).to.equal('-100px');
417 | done();
418 | });
419 |
420 | it('should render containers with a overriden width for a specific position', done => {
421 | notificationObj.position = 'tl';
422 | component.addNotification(notificationObj);
423 | let notification = TestUtils.findRenderedDOMComponentWithClass(instance, 'notifications-tl');
424 | let width = notification.style.width;
425 | expect(width).to.equal('800px');
426 | done();
427 | });
428 |
429 | it('should throw an error if no level is defined', done => {
430 | delete notificationObj.level;
431 | expect(() => component.addNotification(notificationObj)).to.throw(/notification level is required/);
432 | done();
433 | });
434 |
435 | it('should throw an error if a invalid level is defined', done => {
436 | notificationObj.level = 'invalid';
437 | expect(() => component.addNotification(notificationObj)).to.throw(/is not a valid level/);
438 | done();
439 | });
440 |
441 | it('should throw an error if a invalid position is defined', done => {
442 | notificationObj.position = 'invalid';
443 | expect(() => component.addNotification(notificationObj)).to.throw(/is not a valid position/);
444 | done();
445 | });
446 |
447 | it('should throw an error if autoDismiss is not a number', done => {
448 | notificationObj.autoDismiss = 'string';
449 | expect(() => component.addNotification(notificationObj)).to.throw(/\'autoDismiss\' must be a number./);
450 | done();
451 | });
452 |
453 | it('should render 2nd notification below 1st one', done => {
454 | component.addNotification(merge({}, defaultNotification, {title: '1st'}));
455 | component.addNotification(merge({}, defaultNotification, {title: '2nd'}));
456 |
457 | const notifications = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
458 | expect(notifications[0].getElementsByClassName('notification-title')[0].textContent).to.equal('1st');
459 | expect(notifications[1].getElementsByClassName('notification-title')[0].textContent).to.equal('2nd');
460 | done();
461 | });
462 | });
463 |
464 |
465 | describe('Notification Component with newOnTop=true', function() {
466 | let node;
467 | let instance;
468 | let component;
469 | let clock;
470 | let notificationObj;
471 | const ref = 'notificationSystem';
472 |
473 | this.timeout(10000);
474 |
475 | beforeEach(() => {
476 | // We need to create this wrapper so we can use refs
477 | class ElementWrapper extends Component {
478 | render() {
479 | return ;
480 | }
481 | }
482 | node = window.document.createElement("div");
483 | instance = TestUtils.renderIntoDocument(React.createElement(ElementWrapper), node);
484 | component = instance.refs[ref];
485 | notificationObj = merge({}, defaultNotification);
486 |
487 | clock = sinon.useFakeTimers();
488 | });
489 |
490 | afterEach(() => {
491 | clock.restore();
492 | });
493 |
494 | it('should render 2nd notification above 1st one', done => {
495 | component.addNotification(merge({}, defaultNotification, {title: '1st'}));
496 | component.addNotification(merge({}, defaultNotification, {title: '2nd'}));
497 |
498 | const notifications = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'notification');
499 | expect(notifications[0].getElementsByClassName('notification-title')[0].textContent).to.equal('2nd');
500 | expect(notifications[1].getElementsByClassName('notification-title')[0].textContent).to.equal('1st');
501 | done();
502 | });
503 | });
--------------------------------------------------------------------------------
/tests.webpack.js:
--------------------------------------------------------------------------------
1 | // Browser ES6 Polyfill
2 | // require('babel/polyfill');
3 | const context = require.context('./test', true, /\.test\.jsx$|\.test\.js$/);
4 | context.keys().forEach(context);
5 |
--------------------------------------------------------------------------------
/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | var JS_REGEX = /\.js$|\.jsx$|\.es6$|\.babel$/;
5 |
6 | module.exports = {
7 | devtool: 'eval',
8 | entry: [
9 | 'webpack-hot-middleware/client',
10 | './example/src/scripts/App'
11 | ],
12 | output: {
13 | path: path.join(__dirname, 'example/build'),
14 | filename: 'app.js',
15 | publicPath: 'build/'
16 | },
17 | plugins: [
18 | new webpack.HotModuleReplacementPlugin(),
19 | new webpack.NoErrorsPlugin()
20 | ],
21 | resolve: {
22 | extensions: ['', '.js', '.jsx', '.sass'],
23 | modulesDirectories: ['node_modules', 'src']
24 | },
25 | module: {
26 | loaders: [
27 | {
28 | test: JS_REGEX,
29 | include: [
30 | path.resolve(__dirname, 'src'),
31 | path.resolve(__dirname, 'example/src')
32 | ],
33 | loader: 'babel?presets=airbnb'
34 | },
35 | {
36 | test: /\.sass$/,
37 | loaders: [
38 | 'style-loader',
39 | 'css-loader',
40 | 'autoprefixer-loader?browsers=last 2 version',
41 | 'sass-loader?indentedSyntax=sass&includePaths[]=' + path.resolve(__dirname, 'example/src')
42 | ]
43 | },
44 | {
45 | test: /\.(jpe?g|png|gif|svg|woff|eot|ttf)$/,
46 | loader: 'file-loader',
47 | exclude: /node_modules/
48 | }
49 | ]
50 | }
51 | };
52 |
--------------------------------------------------------------------------------
/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
4 |
5 | var JS_REGEX = /\.js$|\.jsx$|\.es6$|\.babel$/;
6 |
7 | var sassLoaders = [
8 | 'css-loader',
9 | 'autoprefixer-loader?browsers=last 2 version',
10 | 'sass-loader?indentedSyntax=sass&includePaths[]=' + path.resolve(__dirname, './example/src')
11 | ];
12 |
13 | module.exports = {
14 | entry: [
15 | './example/src/scripts/App'
16 | ],
17 | output: {
18 | path: path.join(__dirname, 'example/build'),
19 | filename: 'app.js',
20 | publicPath: '../build/'
21 | },
22 | plugins: [
23 | new ExtractTextPlugin('app.css', { allChunks: true }),
24 | // set env
25 | new webpack.DefinePlugin({
26 | 'process.env': {
27 | BROWSER: JSON.stringify(true),
28 | NODE_ENV: JSON.stringify('production')
29 | }
30 | }),
31 |
32 | // optimizations
33 | new webpack.optimize.DedupePlugin(),
34 | new webpack.optimize.OccurenceOrderPlugin(),
35 | new webpack.optimize.UglifyJsPlugin({
36 | compress: {
37 | warnings: false,
38 | screw_ie8: true,
39 | sequences: true,
40 | dead_code: true,
41 | drop_debugger: true,
42 | comparisons: true,
43 | conditionals: true,
44 | evaluate: true,
45 | booleans: true,
46 | loops: true,
47 | unused: true,
48 | hoist_funs: true,
49 | if_return: true,
50 | join_vars: true,
51 | cascade: true,
52 | drop_console: false
53 | },
54 | output: {
55 | comments: false
56 | }
57 | })
58 | ],
59 | resolve: {
60 | extensions: ['', '.js', '.jsx', '.sass'],
61 | modulesDirectories: ['node_modules', 'src']
62 | },
63 | module: {
64 | loaders: [
65 | {
66 | test: JS_REGEX,
67 | include: [
68 | path.resolve(__dirname, 'src'),
69 | path.resolve(__dirname, 'example/src')
70 | ],
71 | loader: 'babel?presets=airbnb'
72 | },
73 | {
74 | test: /\.sass$/,
75 | loader: ExtractTextPlugin.extract('style-loader', sassLoaders.join('!'))
76 | },
77 | {
78 | test: /\.(jpe?g|png|gif|svg|woff|eot|ttf)$/,
79 | loader: 'file-loader',
80 | exclude: /node_modules/
81 | }
82 | ]
83 | }
84 | };
85 |
--------------------------------------------------------------------------------
/webpack.config.umd.dev.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | var JS_REGEX = /\.js$|\.jsx$|\.es6$|\.babel$/;
5 |
6 | module.exports = {
7 | entry: [
8 | './src/NotificationSystem.jsx'
9 | ],
10 | output: {
11 | path: path.join(__dirname, 'dist'),
12 | filename: 'react-notification-system.js',
13 | libraryTarget: 'umd',
14 | library: "ReactNotificationSystem"
15 | },
16 | externals: [
17 | {
18 | react: {
19 | root: 'React',
20 | commonjs2: 'react',
21 | commonjs: 'react',
22 | amd: 'react'
23 | }
24 | },
25 | {
26 | 'react-dom': {
27 | root: 'ReactDOM',
28 | commonjs2: 'react-dom',
29 | commonjs: 'react-dom',
30 | amd: 'react-dom'
31 | }
32 | }
33 | ],
34 | plugins: [
35 | new webpack.NoErrorsPlugin()
36 | ],
37 | resolve: {
38 | extensions: ['', '.js', '.jsx'],
39 | modulesDirectories: ['node_modules', 'src']
40 | },
41 | module: {
42 | loaders: [
43 | {
44 | test: JS_REGEX,
45 | include: [
46 | path.resolve(__dirname, 'src'),
47 | path.resolve(__dirname, 'example/src')
48 | ],
49 | loader: 'babel?presets=airbnb'
50 | }
51 | ]
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/webpack.config.umd.prod.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | var JS_REGEX = /\.js$|\.jsx$|\.es6$|\.babel$/;
5 |
6 | module.exports = {
7 | entry: [
8 | './src/NotificationSystem.jsx'
9 | ],
10 | output: {
11 | path: path.join(__dirname, 'dist'),
12 | filename: 'react-notification-system.min.js',
13 | libraryTarget: 'umd',
14 | library: "ReactNotificationSystem"
15 | },
16 | devtool: 'source-map',
17 | externals: [
18 | {
19 | react: {
20 | root: 'React',
21 | commonjs2: 'react',
22 | commonjs: 'react',
23 | amd: 'react'
24 | }
25 | },
26 | {
27 | 'react-dom': {
28 | root: 'ReactDOM',
29 | commonjs2: 'react-dom',
30 | commonjs: 'react-dom',
31 | amd: 'react-dom'
32 | }
33 | }
34 | ],
35 | plugins: [
36 | // set env
37 | new webpack.DefinePlugin({
38 | 'process.env': {
39 | BROWSER: JSON.stringify(true),
40 | NODE_ENV: JSON.stringify('production')
41 | }
42 | }),
43 |
44 | // optimizations
45 | new webpack.optimize.DedupePlugin(),
46 | new webpack.optimize.OccurenceOrderPlugin(),
47 | new webpack.optimize.UglifyJsPlugin({
48 | compress: {
49 | warnings: false,
50 | screw_ie8: true,
51 | sequences: true,
52 | dead_code: true,
53 | drop_debugger: true,
54 | comparisons: true,
55 | conditionals: true,
56 | evaluate: true,
57 | booleans: true,
58 | loops: true,
59 | unused: true,
60 | hoist_funs: true,
61 | if_return: true,
62 | join_vars: true,
63 | cascade: true,
64 | drop_console: false
65 | },
66 | output: {
67 | comments: false
68 | }
69 | })
70 | ],
71 | resolve: {
72 | extensions: ['', '.js', '.jsx'],
73 | modulesDirectories: ['node_modules', 'src']
74 | },
75 | module: {
76 | loaders: [
77 | {
78 | test: JS_REGEX,
79 | include: [
80 | path.resolve(__dirname, 'src'),
81 | path.resolve(__dirname, 'example/src')
82 | ],
83 | loader: 'babel?presets=airbnb'
84 | }
85 | ]
86 | }
87 | };
88 |
--------------------------------------------------------------------------------