├── .npmignore
├── .gitattributes
├── .gitignore
├── .editorconfig
├── LICENSE
├── gulpfile.js
├── src
├── jade
│ ├── github.html
│ ├── site.css
│ └── index.jade
├── css
│ └── native-toast.styl
└── js
│ └── index.js
├── package.json
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | /site
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | .DS_Store
4 | /dist
5 | /site
6 | index.html
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) EGOIST <0x142857@gmail.com> (github.com/egoist)
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 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const gulp = require('gulp')
3 | const stylus = require('gulp-stylus')
4 | const jade = require('gulp-jade')
5 | const serve = require('gulp-serve')
6 | const autoprefixer = require('gulp-autoprefixer')
7 | const spawnSync = require('child_process').spawnSync
8 |
9 | gulp.task('serve', serve({
10 | hostname: 'localhost',
11 | port: 3099,
12 | root: './'
13 | }))
14 |
15 | gulp.task('js', () => {
16 | spawnSync('npm', ['run', 'build:js'], {stdio: 'inherit'})
17 | })
18 |
19 | gulp.task('css', () => {
20 | return gulp.src('./src/css/*.styl')
21 | .pipe(stylus())
22 | .pipe(autoprefixer())
23 | .pipe(gulp.dest('dist'))
24 | })
25 |
26 | gulp.task('jade', () => {
27 | return gulp.src('./src/jade/index.jade')
28 | .pipe(jade({
29 | locals: {
30 | title: 'Native Toast',
31 | time: Date.now()
32 | }
33 | }))
34 | .pipe(gulp.dest('.'))
35 | })
36 |
37 | gulp.task('watch', () => {
38 | gulp.watch('./src/js/*.js', ['js'])
39 | gulp.watch('./src/css/*.styl', ['css'])
40 | gulp.watch('./src/jade/*', ['jade'])
41 | })
42 |
43 | gulp.task('build', ['js', 'css', 'jade'])
44 |
45 | gulp.task('default', ['build', 'watch', 'serve'])
46 |
--------------------------------------------------------------------------------
/src/jade/github.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "native-toast",
3 | "version": "2.0.0",
4 | "description": "Native-like toast notification but for the web.",
5 | "license": "MIT",
6 | "repository": "egoist/native-toast",
7 | "author": {
8 | "name": "EGOIST",
9 | "email": "0x142857@gmail.com",
10 | "url": "https://github.com/egoist"
11 | },
12 | "engines": {
13 | "node": ">=4"
14 | },
15 | "scripts": {
16 | "test": "eslint src/js/*.js",
17 | "prepublishOnly": "npm run build",
18 | "build": "gulp build",
19 | "build:js": "bili src/js/index.js --js buble --format cjs,umd,umd-min --module-name nativeToast",
20 | "dev": "gulp",
21 | "gh": "npm run build && npm run site && gh-pages -d ./site",
22 | "site": "mkdir -p site && cp -r dist site/ && cp index.html site/"
23 | },
24 | "main": "dist/native-toast.cjs.js",
25 | "cdn": "dist/native-toast.js",
26 | "unpkg": "dist/native-toast.js",
27 | "jsdelivr": "dist/native-toast.js",
28 | "files": [
29 | "dist"
30 | ],
31 | "keywords": [
32 | "toast",
33 | "android",
34 | "notification"
35 | ],
36 | "devDependencies": {
37 | "bili": "^3.0.15",
38 | "eslint": "^3.13.1",
39 | "eslint-config-rem": "^2.0.2",
40 | "gh-pages": "^0.12.0",
41 | "gulp": "^3.9.1",
42 | "gulp-autoprefixer": "^5.0.0",
43 | "gulp-jade": "^1.1.0",
44 | "gulp-postcss": "^6.1.1",
45 | "gulp-serve": "^1.2.0",
46 | "postcss-cssnext": "^2.5.2",
47 | "postcss-mixins": "^6.2.0"
48 | },
49 | "dependencies": {
50 | "gulp-stylus": "^2.7.0",
51 | "nano-assign": "^1.0.0"
52 | },
53 | "eslintConfig": {
54 | "extends": "rem/esnext-browser"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/jade/site.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-size: 16px;
3 | }
4 |
5 | body {
6 | margin: 0;
7 | font-size: 1rem;
8 | line-height: 1.5;
9 | font-family: -apple-system, BlinkMacSystemFont,
10 | 'avenir next', avenir,
11 | helvetica, 'helvetica neue',
12 | Ubuntu,
13 | 'segoe ui', arial,
14 | sans-serif;
15 | }
16 |
17 | .main {
18 | display: flex;
19 | align-items: center;
20 | justify-content: center;
21 | text-align: center;
22 | padding-top: 120px;
23 | flex-direction: column;
24 | }
25 |
26 | .buttons {
27 | max-width: 600px;
28 | display: flex;
29 | }
30 |
31 | .buttons > div:first-child {
32 | margin-right: 20px;
33 | }
34 |
35 | .buttons > div {
36 | padding: 20px;
37 | margin-bottom: 20px;
38 | background-color: #ddd;
39 | box-shadow: 0 2px 5px #ccc;
40 | border: 1px solid #ccc;
41 | border-radius: 4px;
42 | overflow: hidden;
43 | }
44 |
45 | .buttons .buttons-right {
46 | }
47 |
48 | .md-button {
49 | background: #3f51b5;
50 | border: none;
51 | border-radius: 2px;
52 | color: #fff;
53 | position: relative;
54 | height: 36px;
55 | margin: 0;
56 | padding: 0 16px;
57 | display: inline-block;
58 | font-family: "Roboto","Helvetica","Arial",sans-serif;
59 | font-size: 14px;
60 | font-weight: 500;
61 | text-transform: uppercase;
62 | letter-spacing: 0;
63 | overflow: hidden;
64 | will-change: box-shadow;
65 | transition: box-shadow .2s cubic-bezier(.4,0,1,1),background-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1);
66 | outline: none;
67 | cursor: pointer;
68 | text-decoration: none;
69 | text-align: center;
70 | line-height: 36px;
71 | vertical-align: middle;
72 | box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);
73 | }
74 |
75 | .click-button {
76 | display: block;
77 | margin: 0 auto;
78 | }
79 |
80 | .click-button:not(:last-child) {
81 | margin-bottom: 20px;
82 | }
83 |
84 | .site-name {
85 | color: black;
86 | font-weight: 400;
87 | font-size: 2.4rem;
88 | margin: 0;
89 | margin-bottom: 30px;
90 | }
91 |
92 | .control {
93 | margin-bottom: 20px;
94 | }
95 |
96 | .control label {
97 | display: block;
98 | }
99 |
--------------------------------------------------------------------------------
/src/css/native-toast.styl:
--------------------------------------------------------------------------------
1 | .native-toast
2 | position fixed
3 | background-color rgba(50, 50, 50, 0.8)
4 | border-radius 3px
5 | color #fff
6 | text-align center
7 | padding 10px 12px
8 | opacity 0
9 | z-index 99999
10 | transition transform 0.25s, opacity 0.25s, top 0.25s, bottom 0.25s
11 | box-sizing border-box
12 | max-width: 18rem
13 |
14 | toast-position(position)
15 | .native-toast-{position}
16 | if match('north', position)
17 | left: 50%
18 | transform: translateX(-50%)
19 | top: -50px
20 | if match('south', position)
21 | left: 50%
22 | transform: translateX(-50%)
23 | bottom: -50px
24 | if match('west', position)
25 | left: 50px
26 | transform: translateX(0)
27 | if match('east', position)
28 | left: auto
29 | right: 50px
30 | transform: translateX(0)
31 | if position is center
32 | left: 50%
33 | transform: translate(-50%, -50%)
34 | if match('^(east|west|center)$', position)
35 | bottom: -50px
36 | &.native-toast-shown
37 | opacity 1
38 | if match('north', position)
39 | top: 50px
40 | if match('south', position)
41 | bottom: 50px
42 | if match('^(east|west|center)$', position)
43 | bottom: 50%
44 | &.native-toast-edge
45 | if match('north', position)
46 | top: 0
47 | if match('south', position)
48 | bottom: 0
49 |
50 | toast-position('north')
51 | toast-position('north-west')
52 | toast-position('north-east')
53 |
54 | toast-position('south')
55 | toast-position('south-west')
56 | toast-position('south-east')
57 |
58 | toast-position('center')
59 | toast-position('west')
60 | toast-position('east')
61 |
62 | .native-toast-edge
63 | border-radius 0
64 | max-width 100%
65 | width 100%
66 | text-align left
67 | left: 0
68 | transform: none
69 |
70 | .native-toast-error
71 | background-color #ff3d3d
72 | color #fff
73 |
74 | .native-toast-success
75 | background-color #62a465
76 | color #fff
77 |
78 | .native-toast-warning
79 | background-color darken(#ffce65, 15)
80 | color #fff
81 |
82 | .native-toast-info
83 | background-color #67daff
84 | color #fff
85 |
86 | [class^='native-toast-icon-']
87 | vertical-align middle
88 | margin-right 5px
89 | svg
90 | width 16px
91 | height 16px
92 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # native-toast [](https://npmjs.com/package/native-toast) [](https://npmjs.com/package/native-toast)
2 |
3 | > Native-like toast notification but for the web. (JS + CSS ≈ 4KB)
4 |
5 | ## Install
6 |
7 | ```bash
8 | $ npm install --save native-toast
9 | ```
10 |
11 | NPMCDN: https://unpkg.com/native-toast/dist/
12 |
13 | ## Usage
14 |
15 | First import `native-toast/dist/native-toast.css`, then:
16 |
17 | ```js
18 | import nativeToast from 'native-toast'
19 |
20 | nativeToast({
21 | message: 'wait wait!',
22 | position: 'north-east',
23 | // Self destroy in 5 seconds
24 | timeout: 5000,
25 | type: 'warning'
26 | })
27 | // or nativeToast.warning(options)
28 | ```
29 |
30 | Four types: `success` `warning` `info` `error`
31 |
32 | ## API
33 |
34 | ### nativeToast(options)
35 |
36 | #### options
37 |
38 | ##### message
39 |
40 | Type: `string`
41 | Default: `''`
42 |
43 | Toast message.
44 |
45 | ##### position
46 |
47 | Type: `string`
48 | Default: `south-east`
49 | Values: `center` `west` `east` `south` `south-west` `south-east` `north` `north-west` `north-east`
50 |
51 | Toast position.
52 |
53 | ##### rounded
54 |
55 | Type: `boolean`
56 | Default: `false`
57 |
58 | Set `border-radius` to `33px` instead of `3px`. Has no effect when `edge === false`.
59 |
60 | ##### timeout
61 |
62 | Type: `number`
63 | Default: `3000`
64 |
65 | Self destroy in specfic timeout. If given `0` or `false then toast will never self destroy.
66 |
67 | ##### type
68 |
69 | Type: `string`
70 | Default: `undefined`
71 |
72 | One of `success` `warning` `info` `error`.
73 |
74 | A short-hand usage: `nativeToast.success(opts)` `nativeToast.error(opts)` and such.
75 |
76 | ##### icon
77 |
78 | Type: `boolean`
79 | Default: `true`
80 |
81 | Set to `false` to disable icon.
82 |
83 | ##### edge
84 |
85 | Type: `boolean`
86 | Default: `false`
87 |
88 | Show toast on the edge.
89 |
90 | ##### closeOnClick
91 |
92 | Type: `boolean`
93 | Default: `false`
94 |
95 | Close the toast when clicked.
96 |
97 | ##### elements
98 |
99 | Type: `HTMLElement[]`
100 |
101 | Optionally provide an array of HTML elements to insert after the `message`.
102 |
103 | ## License
104 |
105 | MIT © [EGOIST](https://github.com/egoist)
106 |
--------------------------------------------------------------------------------
/src/js/index.js:
--------------------------------------------------------------------------------
1 | import assign from 'nano-assign'
2 |
3 | let prevToast = null
4 |
5 | const icons = {
6 | warning: ``,
7 | success: ``,
8 | info: ``,
9 | error: ``
10 | }
11 |
12 | class Toast {
13 | constructor({
14 | message = '',
15 | position = 'south-east',
16 | timeout = 3000,
17 | el = document.body,
18 | rounded = false,
19 | type = '',
20 | debug = false,
21 | edge = false,
22 | icon = true,
23 | closeOnClick = false,
24 | elements = []
25 | } = {}) {
26 | if (prevToast) {
27 | prevToast.destroy()
28 | }
29 |
30 | this.message = message
31 | this.position = position
32 | this.el = el
33 | this.timeout = timeout
34 | this.closeOnClick = closeOnClick
35 |
36 | this.toast = document.createElement('div')
37 | this.toast.className = `native-toast native-toast-${this.position}`
38 |
39 | if (type) {
40 | this.toast.className += ` native-toast-${type}`
41 |
42 | if (icon) {
43 | this.message = `${icons[type] || ''}${this.message}`
44 | }
45 | }
46 |
47 | const messageElement = document.createElement('div')
48 | messageElement.className = 'native-toast-message'
49 | messageElement.innerHTML = this.message
50 |
51 | ;[messageElement, ...elements].forEach(el => {
52 | this.toast.appendChild(el)
53 | })
54 |
55 | const isMobile = document.body.clientWidth < 768
56 | if (edge || isMobile) {
57 | this.toast.className += ' native-toast-edge'
58 | } else if (rounded) {
59 | this.toast.style.borderRadius = '33px'
60 | }
61 |
62 | this.el.appendChild(this.toast)
63 |
64 | prevToast = this
65 |
66 | this.show()
67 | if (!debug && timeout) {
68 | this.hide()
69 | }
70 |
71 | if (this.closeOnClick) {
72 | this.toast.addEventListener('click', () => {
73 | this.destroy()
74 | })
75 | }
76 | }
77 |
78 | show() {
79 | setTimeout(() => {
80 | this.toast.classList.add('native-toast-shown')
81 | }, 300)
82 | }
83 |
84 | hide() {
85 | setTimeout(() => {
86 | this.destroy()
87 | }, this.timeout)
88 | }
89 |
90 | destroy() {
91 | if (!this.toast) return
92 |
93 | this.toast.classList.remove('native-toast-shown')
94 |
95 | setTimeout(() => {
96 | if (this.toast) {
97 | this.el.removeChild(this.toast)
98 | this.toast = null
99 | }
100 | }, 300)
101 | }
102 | }
103 |
104 | function toast(options) {
105 | return new Toast(options)
106 | }
107 |
108 | for (const type of ['success', 'info', 'warning', 'error']) {
109 | toast[type] = options => toast(assign({type}, options))
110 | }
111 |
112 | export default toast
113 |
114 |
--------------------------------------------------------------------------------
/src/jade/index.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | head
4 | meta(charset="utf-8")
5 | meta(name="viewport" content="width=device-width, initial-scale=1")
6 | meta(http-equiv="X-UA-Compatible" content="IE=edge,chrome=1")
7 | title=title
8 | style
9 | include site.css
10 | link(rel="stylesheet", href="./dist/native-toast.css?t=#{time}")
11 | body
12 | include github.html
13 | .main
14 | h1.site-name Native Toast
15 | .control
16 | label
17 | input(type="checkbox" id="switch-edge-mode")
18 | | Edge mode (show on the edge)
19 | label
20 | input(type="checkbox" id="switch-debug-mode")
21 | | debug mode (do not self-destruct)
22 | label
23 | input(type="checkbox" id="switch-close-on-click-mode")
24 | | Close on click
25 | .buttons
26 | .buttons-group
27 | button.md-button.click-button(onclick="fromPosition('north')") North
28 | button.md-button.click-button(onclick="fromPosition('north-west')") North West
29 | button.md-button.click-button(onclick="fromPosition('north-east')") North East
30 | button.md-button.click-button(onclick="fromPosition('south')") South
31 | button.md-button.click-button(onclick="fromPosition('south-west')") South West
32 | button.md-button.click-button(onclick="fromPosition('south-east')") South East
33 | button.md-button.click-button(onclick="fromPosition('center')") Center
34 | button.md-button.click-button(onclick="fromPosition('west')") West
35 | button.md-button.click-button(onclick="fromPosition('east')") East
36 | button.md-button.click-button(onclick="rounded()") Rounded
37 | button.md-button.click-button(onclick="largeText()") Large Text
38 | .buttons-group
39 | button.md-button.click-button(onclick="error()") Error
40 | button.md-button.click-button(onclick="warning()") Warning
41 | button.md-button.click-button(onclick="success()") Success
42 | button.md-button.click-button(onclick="info()") Info
43 | script(src="./dist/native-toast.js?t=#{time}")
44 | script.
45 | var useEdge = false
46 | var useDebug = false
47 | var closeOnClick = false
48 | function fromPosition(position) {
49 | nativeToast({
50 | message: `No more posts`,
51 | position: position,
52 | edge: useEdge,
53 | debug: useDebug,
54 | closeOnClick: closeOnClick
55 | })
56 | }
57 | function rounded() {
58 | nativeToast({
59 | message: 'No more posts',
60 | rounded: true,
61 | edge: useEdge,
62 | debug: useDebug,
63 | closeOnClick: closeOnClick
64 | })
65 | }
66 | function largeText() {
67 | nativeToast({
68 | message: `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.`,
69 | edge: useEdge,
70 | debug: useDebug,
71 | closeOnClick: closeOnClick
72 | })
73 | }
74 | function error() {
75 | nativeToast({
76 | message: 'Something bad happened!',
77 | type: 'error',
78 | edge: useEdge,
79 | debug: useDebug,
80 | closeOnClick: closeOnClick
81 | })
82 | }
83 | function info() {
84 | nativeToast({
85 | message: 'Some information!',
86 | type: 'info',
87 | edge: useEdge,
88 | debug: useDebug,
89 | closeOnClick: closeOnClick
90 | })
91 | }
92 | function warning() {
93 | nativeToast.warning({
94 | message: 'Something warning!',
95 | type: 'warning',
96 | edge: useEdge,
97 | debug: useDebug,
98 | closeOnClick: closeOnClick
99 | })
100 | }
101 | function success() {
102 | nativeToast({
103 | message: 'Success message!',
104 | type: 'success',
105 | edge: useEdge,
106 | debug: useDebug,
107 | closeOnClick: closeOnClick
108 | })
109 | }
110 | function edge() {
111 | nativeToast({
112 | message: 'Bottom edge!',
113 | edge: useEdge,
114 | debug: useDebug,
115 | closeOnClick: closeOnClick
116 | })
117 | }
118 | document.getElementById('switch-edge-mode').addEventListener('change', function (e) {
119 | useEdge = e.target.checked
120 | })
121 | document.getElementById('switch-debug-mode').addEventListener('change', function (e) {
122 | useDebug = e.target.checked
123 | })
124 | document.getElementById('switch-close-on-click-mode').addEventListener('change', function (e) {
125 | closeOnClick = e.target.checked
126 | })
127 |
--------------------------------------------------------------------------------