├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── .storybook ├── addons.js ├── config.js ├── manager-head.html └── webpack.config.js ├── LICENSE ├── README.md ├── babel.config.js ├── jest.config.js ├── package.json ├── postcss.config.js ├── public ├── assets │ └── image-placeholder.svg ├── favicon.ico ├── images │ └── image-placeholder-example.jpg ├── index.html └── video │ └── sintel │ ├── captions.vtt │ ├── thumbs.jpg │ └── thumbs.vtt ├── src ├── App.vue ├── assets │ ├── css │ │ ├── _anim.scss │ │ ├── _color.scss │ │ ├── _mixin.scss │ │ ├── _normalize.scss │ │ ├── _reset.scss │ │ └── _utils.scss │ ├── logo.png │ └── storybook+vue.png ├── components │ ├── HelloWorld.vue │ ├── core │ │ ├── Button.vue │ │ ├── ButtonWrapper.vue │ │ ├── Checkbox.vue │ │ ├── Input.vue │ │ ├── RadioButton.vue │ │ ├── SwitchButton.vue │ │ └── ToggleButton.vue │ └── media │ │ ├── Icon.vue │ │ ├── LazyImage.vue │ │ └── VideoPlayer.vue ├── layouts │ ├── default.vue │ └── no-navigation.vue ├── main.js ├── router.js ├── stories │ ├── AppDecorator.vue │ ├── Welcome.vue │ └── index.js ├── utils │ └── utils.js └── views │ ├── 404.vue │ ├── Home.vue │ ├── Media.vue │ └── Setup.vue ├── tests └── unit │ ├── .eslintrc.js │ └── example.spec.js └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | .out -------------------------------------------------------------------------------- /.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-storysource/register' 2 | import '@storybook/addon-links/register' 3 | import '@storybook/addon-viewport/register' 4 | import '@storybook/addon-options/register' 5 | import '@storybook/addon-knobs/register' 6 | import '@storybook/addon-actions/register' -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure, addDecorator } from '@storybook/vue' 2 | import { withOptions } from '@storybook/addon-options'; 3 | 4 | import Vue from 'vue' 5 | 6 | import DefaultLayout from '@/layouts/default' 7 | import NoNavigationLayout from '@/layouts/no-navigation' 8 | 9 | import Button from '@/components/core/Button' 10 | import Input from '@/components/core/Input' 11 | import Checkbox from '@/components/core/Checkbox' 12 | import RadioButton from '@/components/core/RadioButton' 13 | import SwitchButton from '@/components/core/SwitchButton' 14 | 15 | import LazyImage from '@/components/media/LazyImage' 16 | import VideoPlayer from '@/components/media/VideoPlayer' 17 | 18 | //setup preview styles 19 | import '@/assets/css/_normalize.scss' 20 | 21 | //register layouts 22 | Vue.component('default-layout', DefaultLayout); 23 | Vue.component('no-nav-layout', NoNavigationLayout); 24 | 25 | //register components 26 | Vue.component('custom-button', Button); 27 | Vue.component('input-text', Input); 28 | Vue.component('checkbox', Checkbox); 29 | Vue.component('radio-button', RadioButton); 30 | Vue.component('switch-button', SwitchButton); 31 | 32 | Vue.component('lazy-image', LazyImage); 33 | Vue.component('video-player', VideoPlayer); 34 | 35 | addDecorator( 36 | withOptions({ 37 | name: 'Storybook + Vue', 38 | url: 'https://github.com/janumedia/vue-storybook-example', 39 | hierarchyRootSeparator: /\|/, 40 | selectedAddonPanel: 'storybook/stories/stories-panel' 41 | }) 42 | ); 43 | 44 | function loadStories() { 45 | require('../src/stories'); 46 | }; 47 | 48 | configure(loadStories, module); -------------------------------------------------------------------------------- /.storybook/manager-head.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = (baseConfig, env, defaultConfig) => { 4 | //add addon-storysource 5 | defaultConfig.module.rules.push({ 6 | test: [/\.stories\.js$/, /index\.js$/], 7 | loaders: [require.resolve('@storybook/addon-storysource/loader')], 8 | include: [path.resolve(__dirname, '../src')], 9 | enforce: 'pre', 10 | }); 11 | // allow SCSS 12 | defaultConfig.module.rules.push({ 13 | test: /\.scss$/, 14 | loaders: ["style-loader", "css-loader", "sass-loader"], 15 | include: path.resolve(__dirname, "../") 16 | }); 17 | // setup URL Alias 18 | defaultConfig.resolve.alias = { 19 | ...defaultConfig.resolve.alias, 20 | "@": path.resolve(__dirname, "../src") 21 | }; 22 | return defaultConfig; 23 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Januartha 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-storybook-example 2 | Storybook.js for Vue example 3 | 4 | [![Netlify Status](https://api.netlify.com/api/v1/badges/3bbc08dc-4fda-4263-b558-cd4acfa94825/deploy-status)](https://app.netlify.com/sites/vue-storybook-example/deploys) 5 | 6 | [Live Demo](https://vue-storybook-example.netlify.com) 7 | 8 | ## Step by step setup 9 | 10 | > This Storybok example is based on Vue CLI 3.0, some libraries required by Storybook are already installed by Vue CLI except `babel-preset-vue` that you should install it manually. 11 | 12 | 1. Create Vue project using Vue CLI 13 | ``` 14 | vue create your-app 15 | cd your-app 16 | ``` 17 | 2. Add `@storybook/vue` 18 | ``` 19 | yarn add --dev @storybook/vue 20 | ``` 21 | 3. Add `babel-preset-vue` 22 | ``` 23 | yarn add --dev babel-preset-vue 24 | ``` 25 | 4. Add Storybook run and build scripts to your `package.json` 26 | ``` 27 | { 28 | "scripts": { 29 | "storybook": "start-storybook -p 9001 -c .storybook -s public", 30 | "storybook:build": "build-storybook -c .storybook -o .out -s public" 31 | } 32 | } 33 | ``` 34 | > The **-p** command refer to the port number where the Storybook will be run. 35 | > The **-c** command refer to the config directory. 36 | > The **-o** command refer to directory where to store built files. 37 | > The **-s** command refer to directory to load static / public files 38 | 5. Create `.storybook` directory as the config directory 39 | 6. Create `.storybook/config.js` as config file 40 | ``` 41 | import { configure } from '@storybook/vue' 42 | import Vue from 'vue' 43 | 44 | // import your vue plugins 45 | import Vuex from 'vuex' 46 | 47 | // import your custom components 48 | import Button from '../src/stories/Button.vue' 49 | 50 | // install your vue plugins 51 | Vue.use(Vuex); 52 | // register your custom components 53 | Vue.component('my-button', Button); 54 | function loadStories() { 55 | // you can require as many stories as you need 56 | require('../src/stories'); 57 | } 58 | configure(loadStories, module); 59 | ``` 60 | > **Note**: All custom components and Vue plugins should be registered before calling `configure()`. 61 | 7. Create `../src/stories` directory as the place your Storybook stories 62 | 8. Write your stories inside `../src/stories/index.js` 63 | ``` 64 | import Vue from 'vue' 65 | import { storiesOf } from '@storybook/vue' 66 | import Button from './Button.vue' 67 | storiesOf('Button', module) 68 | .add('Button as a template', () => ( 69 | 'button as template' 70 | ) 71 | add('Button as a component', () =>({ 72 | components: {'the-button': Button}, 73 | template: 'button as component' 74 | })); 75 | ``` 76 | 9. Write your custom component, in this case `../src/stories/Button.vue` 77 | ``` 78 | 81 | ``` 82 | 10. Run your Storybook 83 | ``` 84 | yarn storybook 85 | ``` 86 | 11. To build your Storybook 87 | ``` 88 | yarn storybook:build 89 | ``` 90 | > `.out` directory will be created with built files and ready to upload to your hosting server. 91 | 92 | ## Add Support to SCSS 93 | 94 | By default Storybook not support SCSS even project the created using Vue CLI support SCSS or other preprocesor CSS. This is because Storybook use different Webpack. You must extend Storybook Webpack's config by creating `webpack.config.js` inside `.storybook` directory and define SCSS or your other style loader. 95 | ``` 96 | //.storybook/webpack.config.js 97 | const path = require("path"); 98 | 99 | module.exports = (baseConfig, env, defaultConfig) => { 100 | defaultConfig.module.rules.push({ 101 | test: /\.scss$/, 102 | loaders: ["style-loader", "css-loader", "sass-loader"], 103 | include: path.resolve(__dirname, "../") 104 | }); 105 | return defaultConfig; 106 | }; 107 | ``` 108 | 109 | ## Resolve URL (Path) Alias 110 | 111 | With Vue CLI each time URL started with `@` it will aliases to `/src`. But since Storybook use different Webpack this URL alias will not work and your existing Vue components will not work. To fix this issue you should define URL alias setup manually by extend Storybook webpack's config. 112 | ``` 113 | //.storybook/webpack.config.js 114 | const path = require("path"); 115 | 116 | module.exports = (baseConfig, env, defaultConfig) => { 117 | defaultConfig.resolve.alias = { 118 | ...defaultConfig.resolve.alias, 119 | "@": path.resolve(__dirname, "../src") 120 | }; 121 | return defaultConfig; 122 | }; 123 | ``` 124 | ## Vue-Router 125 | 126 | To enable `vue-router` you can implement `addDecorator` and use [storybook-vue-router](https://github.com/gvaldambrini/storybook-router/tree/master/packages/vue) with the following steps: 127 | 1. Add `storybook-vue-router` 128 | ``` 129 | yarn add -D storybook-vue-router 130 | ``` 131 | 2. Add `@storybook/addon-actions` as `storybook-vue-router` require this addons. 132 | >**Note:** If you're already installed it you can jump to next step 133 | ``` 134 | yarn add -D @storybook/addon-actions 135 | ``` 136 | 3. Use `storybook-vue-router` as decorator in your stories 137 | ``` 138 | // .src/stories/index.js 139 | 140 | import { storiesOf } from '@storybook/vue' 141 | import StoryRouter from 'storybook-vue-router' 142 | 143 | import App from '@/App' 144 | 145 | //import your router 146 | import router from '@/router' 147 | 148 | storiesOf('App', module) 149 | .addDecorator(StoryRouter({}, router.options)) 150 | .add('app', () => ({ 151 | render: h => h(App) 152 | })); 153 | 154 | ``` 155 | 4. Read [storybook-vue-router-guide](https://github.com/gvaldambrini/storybook-router/blob/master/packages/vue/README.md) 156 | 157 | ## Further Reading 158 | 159 | [Storybook Quick Start Guide](https://storybook.js.org/basics/quick-start-guide/) 160 | 161 | [Storybook for Vue](https://storybook.js.org/basics/guide-vue/) 162 | 163 | [Writing Stories](https://storybook.js.org/basics/writing-stories/) 164 | 165 | [Custom Webpack Config](https://storybook.js.org/configurations/custom-webpack-config/) 166 | 167 | [Using Addons](https://storybook.js.org/addons/using-addons/) 168 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | "@vue/app", 4 | "@vue/babel-preset-jsx" 5 | ], 6 | //required if not installed by vue-cli 7 | plugins: ["@vue/babel-plugin-transform-vue-jsx"] 8 | } 9 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue' 7 | ], 8 | transform: { 9 | '^.+\\.vue$': 'vue-jest', 10 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 11 | '^.+\\.jsx?$': 'babel-jest' 12 | }, 13 | moduleNameMapper: { 14 | '^@/(.*)$': '/src/$1' 15 | }, 16 | snapshotSerializers: [ 17 | 'jest-serializer-vue' 18 | ], 19 | testMatch: [ 20 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 21 | ], 22 | testURL: 'http://localhost/' 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-storybook-example", 3 | "description": "Storybook.js Vue example based on Vue-CLI-3", 4 | "version": "0.1.0", 5 | "author": { 6 | "name": "I Nengah Januartha", 7 | "email": "janumedia@gmail.com" 8 | }, 9 | "license": "MIT", 10 | "keywords": [ 11 | "storybook", 12 | "vue", 13 | "vue-cli", 14 | "webpack", 15 | "babel", 16 | "es6", 17 | "jest", 18 | "ui-tests", 19 | "test-framework" 20 | ], 21 | "private": true, 22 | "scripts": { 23 | "serve": "vue-cli-service serve", 24 | "build": "vue-cli-service build", 25 | "lint": "vue-cli-service lint", 26 | "test:unit": "vue-cli-service test:unit", 27 | "storybook": "start-storybook -p 9001 -c .storybook -s public", 28 | "storybook:build": "build-storybook -c .storybook -o .out -s public" 29 | }, 30 | "dependencies": { 31 | "vue": "^2.5.21", 32 | "vue-router": "^3.0.2" 33 | }, 34 | "devDependencies": { 35 | "@storybook/addon-actions": "^4.1.4", 36 | "@storybook/addon-knobs": "^4.1.4", 37 | "@storybook/addon-links": "^4.1.4", 38 | "@storybook/addon-options": "^4.1.4", 39 | "@storybook/addon-storysource": "^4.1.4", 40 | "@storybook/addon-viewport": "^4.1.4", 41 | "@storybook/vue": "^4.1.4", 42 | "@vue/babel-plugin-transform-vue-jsx": "^1.0.0-beta.2", 43 | "@vue/babel-preset-jsx": "^1.0.0-beta.2", 44 | "@vue/babel-sugar-functional-vue": "^1.0.0-beta.2", 45 | "@vue/cli-plugin-babel": "^3.0.1", 46 | "@vue/cli-plugin-eslint": "^3.0.1", 47 | "@vue/cli-plugin-unit-jest": "^3.0.1", 48 | "@vue/cli-service": "^3.0.1", 49 | "@vue/test-utils": "^1.0.0-beta.20", 50 | "babel-core": "7.0.0-bridge.0", 51 | "babel-eslint": "^10.0.1", 52 | "babel-jest": "^23.6.0", 53 | "babel-preset-vue": "^2.0.2", 54 | "eslint": "^5.8.0", 55 | "eslint-plugin-vue": "^5.0.0", 56 | "node-sass": "^4.9.0", 57 | "sass-loader": "^7.0.1", 58 | "storybook-vue-router": "^1.0.2", 59 | "vue-template-compiler": "^2.5.21" 60 | }, 61 | "resolutions": { 62 | "kind-of": "^6.0.3", 63 | "acorn": "^6.4.1", 64 | "minimist": "^0.2.1", 65 | "websocket-extensions": "^0.1.4" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/assets/image-placeholder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janumedia/vue-storybook-example/c22ac54c00a11a54df085be1e0723262f93a742b/public/favicon.ico -------------------------------------------------------------------------------- /public/images/image-placeholder-example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janumedia/vue-storybook-example/c22ac54c00a11a54df085be1e0723262f93a742b/public/images/image-placeholder-example.jpg -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-storybook-example 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/video/sintel/captions.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 00:00:01.000 --> 00:00:02.042 4 | (drumbeat) 5 | 6 | 00:00:07.167 --> 00:00:12.025 7 | (plaintive violin solo playing) 8 | 9 | 00:00:15.000 --> 00:00:18.183 10 | (wind whistling) 11 | 12 | 00:00:24.167 --> 00:00:27.025 13 | (orchestra music swells) 14 | 15 | 00:00:43.033 --> 00:00:43.192 16 | (weapons clash) 17 | 18 | 00:00:44.000 --> 00:00:44.175 19 | (gasps) 20 | 21 | 00:00:44.183 --> 00:00:45.158 22 | (grunts) 23 | 24 | 00:00:45.167 --> 00:00:47.058 25 | (groaning) 26 | 27 | 00:00:54.192 --> 00:00:55.150 28 | (blade rings) 29 | 30 | 00:00:55.158 --> 00:00:57.008 31 | (bellowing) 32 | 33 | 00:00:57.017 --> 00:00:58.067 34 | (grunting) 35 | 36 | 00:00:59.075 --> 00:01:00.133 37 | (panting) 38 | 39 | 00:01:05.108 --> 00:01:06.125 40 | (cries out in agony) 41 | 42 | 00:01:08.050 --> 00:01:09.058 43 | (panting) 44 | 45 | 00:01:12.092 --> 00:01:13.142 46 | (panting) 47 | 48 | 00:01:14.017 --> 00:01:18.125 49 | (orchestra plays ominous low notes) 50 | 51 | 00:01:31.058 --> 00:01:35.133 52 | (plaintive violin solo returns) 53 | 54 | 00:01:46.158 --> 00:01:49.058 55 | This blade has a dark past. 56 | 57 | 00:01:51.092 --> 00:01:54.108 58 | It has shed much innocent blood. 59 | 60 | 00:01:57.083 --> 00:02:00.000 61 | You're a fool for traveling alone 62 | so completely unprepared. 63 | 64 | 00:02:01.100 --> 00:02:03.033 65 | You're lucky your blood's still flowing. 66 | 67 | 00:02:04.183 --> 00:02:06.075 68 | Thank you. 69 | 70 | 00:02:07.075 --> 00:02:08.125 71 | So... 72 | 73 | 00:02:09.050 --> 00:02:11.142 74 | What brings you to the land of the gatekeepers? 75 | 76 | 00:02:13.025 --> 00:02:16.150 77 | I'm... I'm searching for someone. 78 | 79 | 00:02:18.042 --> 00:02:19.092 80 | Someone very dear? 81 | 82 | 00:02:19.183 --> 00:02:21.008 83 | A kindred spirit? 84 | 85 | 00:02:23.033 --> 00:02:24.142 86 | A dragon. 87 | 88 | 00:02:26.117 --> 00:02:27.167 89 | (fire crackling) 90 | 91 | 00:02:29.000 --> 00:02:31.092 92 | A dangerous quest for a lone hunter. 93 | 94 | 00:02:31.133 --> 00:02:32.183 95 | (birds cawing) 96 | 97 | 00:02:33.100 --> 00:02:35.167 98 | I've been alone for as long as I can remember. 99 | 100 | 00:02:44.083 --> 00:02:47.100 101 | (flies buzzing) 102 | 103 | 00:02:49.008 --> 00:02:50.058 104 | (birds cawing) 105 | 106 | 00:02:52.192 --> 00:02:54.042 107 | (sniffs) 108 | 109 | 00:02:55.000 --> 00:02:56.075 110 | (hollow thump) 111 | 112 | 00:03:01.000 --> 00:03:02.017 113 | (gasps) 114 | 115 | 00:03:02.025 --> 00:03:04.092 116 | (whimpering) 117 | 118 | 00:03:08.108 --> 00:03:09.167 119 | (squeaking) 120 | 121 | 00:03:12.058 --> 00:03:14.133 122 | (whimpering) 123 | 124 | 00:03:15.058 --> 00:03:16.092 125 | Shh. 126 | 127 | 00:03:22.000 --> 00:03:23.033 128 | (sniffing) 129 | 130 | 00:03:25.017 --> 00:03:26.008 131 | (shrieking) 132 | 133 | 00:03:26.017 --> 00:03:27.067 134 | Shh. Hey, shh-shh. 135 | 136 | 00:03:27.183 --> 00:03:29.042 137 | We're almost done. 138 | 139 | 00:03:30.092 --> 00:03:31.167 140 | Hey, sit still. 141 | 142 | 00:03:37.183 --> 00:03:39.108 143 | (chuckles gently) 144 | 145 | 00:03:47.133 --> 00:03:49.075 146 | Good night, Scales. 147 | 148 | 00:03:54.042 --> 00:03:58.083 149 | (gentle music playing) 150 | 151 | 00:04:08.067 --> 00:04:09.117 152 | (chicken clucking) 153 | 154 | 00:04:09.158 --> 00:04:11.000 155 | SINTEL: 156 | Get him, Scales! 157 | 158 | 00:04:11.008 --> 00:04:12.075 159 | Come on! Get him! 160 | 161 | 00:04:14.167 --> 00:04:15.183 162 | (clucking) 163 | 164 | 00:04:15.192 --> 00:04:17.067 165 | (laughing softly as she runs) 166 | 167 | 00:04:17.167 --> 00:04:18.183 168 | Ooh! 169 | 170 | 00:04:24.092 --> 00:04:25.142 171 | Scales? 172 | 173 | 00:04:29.075 --> 00:04:30.100 174 | (chicken clucks) 175 | 176 | 00:04:41.108 --> 00:04:42.158 177 | (flock cawing) 178 | 179 | 00:04:43.117 --> 00:04:45.158 180 | (wings fluttering) 181 | 182 | 00:04:47.125 --> 00:04:49.083 183 | (panting) 184 | 185 | 00:04:52.042 --> 00:04:54.125 186 | (orchestra music swells) 187 | 188 | 00:04:59.100 --> 00:05:00.150 189 | (sintel laughing) 190 | 191 | 00:05:01.108 --> 00:05:03.183 192 | Yeah! 193 | 194 | 00:05:04.125 --> 00:05:05.175 195 | Yeah! 196 | 197 | 00:05:06.100 --> 00:05:07.150 198 | Come on! 199 | 200 | 00:05:08.008 --> 00:05:10.008 201 | Whoo! 202 | 203 | 00:05:14.075 --> 00:05:15.125 204 | (screeching) 205 | 206 | 00:05:15.142 --> 00:05:16.167 207 | (thud) 208 | 209 | 00:05:21.067 --> 00:05:22.067 210 | (snarls) 211 | 212 | 00:05:25.192 --> 00:05:27.067 213 | (crunching) 214 | 215 | 00:05:33.133 --> 00:05:34.100 216 | (grunts) 217 | 218 | 00:05:35.092 --> 00:05:37.117 219 | (screeching) 220 | 221 | 00:05:38.092 --> 00:05:39.092 222 | Scales! 223 | 224 | 00:05:48.058 --> 00:05:49.175 225 | (wind whistling softly) 226 | 227 | 00:05:51.125 --> 00:05:52.175 228 | (breathing hard) 229 | 230 | 00:05:53.100 --> 00:05:56.050 231 | (lyrical oboe playing over orchestra) 232 | 233 | 00:06:07.100 --> 00:06:13.050 234 | (ethereal chorus singing) 235 | 236 | 00:06:26.092 --> 00:06:28.050 237 | (beast growling) 238 | 239 | 00:06:30.017 --> 00:06:31.133 240 | (cries out in pain) 241 | 242 | 00:06:35.192 --> 00:06:38.142 243 | (wind roaring) 244 | 245 | 00:06:39.150 --> 00:06:41.025 246 | (groans) 247 | 248 | 00:06:42.133 --> 00:06:44.100 249 | (gasps) 250 | 251 | 00:06:57.058 --> 00:06:59.150 252 | (panting and gasping) 253 | 254 | 00:07:03.142 --> 00:07:06.017 255 | (wind whistling) 256 | 257 | 00:07:15.050 --> 00:07:16.083 258 | (weapons clash) 259 | 260 | 00:07:18.083 --> 00:07:22.117 261 | (panting) 262 | 263 | 00:07:25.133 --> 00:07:26.183 264 | I have failed. 265 | 266 | 00:07:27.150 --> 00:07:29.158 267 | Mmm... 268 | 269 | 00:07:32.133 --> 00:07:34.133 270 | You've only failed to see. 271 | 272 | 00:07:37.100 --> 00:07:38.175 273 | These are dragon lands, Sintel. 274 | 275 | 00:07:40.133 --> 00:07:42.083 276 | You are closer than you know. 277 | 278 | 00:07:51.192 --> 00:07:55.042 279 | (quiet music playing) 280 | 281 | 00:08:07.158 --> 00:08:11.042 282 | (footsteps crunching softly) 283 | 284 | 00:08:19.117 --> 00:08:20.192 285 | (growling softly) 286 | 287 | 00:08:22.150 --> 00:08:26.008 288 | (crunching loudly) 289 | 290 | 00:08:38.050 --> 00:08:39.192 291 | (meat ripping) 292 | 293 | 00:08:45.142 --> 00:08:46.158 294 | (soft thud) 295 | 296 | 00:08:58.108 --> 00:09:01.083 297 | (squealing) 298 | 299 | 00:09:02.050 --> 00:09:03.083 300 | (growls) 301 | 302 | 00:09:15.183 --> 00:09:17.033 303 | (growling) 304 | 305 | 00:09:17.100 --> 00:09:18.175 306 | Scales! 307 | 308 | 00:09:30.017 --> 00:09:31.067 309 | (bellowing) 310 | 311 | 00:09:31.125 --> 00:09:33.000 312 | (shards tinkling as they fall) 313 | 314 | 00:09:34.117 --> 00:09:37.175 315 | (suspenseful music playing) 316 | 317 | 00:09:39.033 --> 00:09:41.075 318 | (growling and grunting) 319 | 320 | 00:09:49.092 --> 00:09:53.008 321 | (growling) 322 | 323 | 00:09:56.033 --> 00:09:58.067 324 | (growls and sniffs) 325 | 326 | 00:10:00.183 --> 00:10:02.017 327 | (shrieks) 328 | 329 | 00:10:02.025 --> 00:10:06.108 330 | (bellowing) 331 | 332 | 00:10:07.033 --> 00:10:08.058 333 | (growling) 334 | 335 | 00:10:20.183 --> 00:10:21.192 336 | Scales? 337 | 338 | 00:10:26.033 --> 00:10:27.175 339 | Scales... 340 | 341 | 00:10:47.142 --> 00:10:49.025 342 | (moaning softly) 343 | 344 | 00:10:59.125 --> 00:11:01.083 345 | (rumbling and cracking) 346 | 347 | 00:11:12.142 --> 00:11:15.050 348 | (boulders crashing) 349 | 350 | 00:11:21.017 --> 00:11:23.000 351 | (silence) 352 | 353 | 00:11:33.033 --> 00:11:34.083 354 | (sobbing softly) 355 | 356 | 00:11:35.167 --> 00:11:37.192 357 | (flock squawking) 358 | 359 | 00:11:40.117 --> 00:11:42.000 360 | (sniffles) 361 | 362 | 00:11:57.158 --> 00:11:59.017 363 | (blade clanks) 364 | 365 | 00:12:02.017 --> 00:12:05.042 366 | (quiet music begins playing) 367 | 368 | 00:12:10.083 --> 00:12:12.092 369 | (scuffling) 370 | 371 | 00:12:27.175 --> 00:12:32.008 372 | ♪ Come take my journey into night ♪ 373 | 374 | 00:12:34.117 --> 00:12:39.058 375 | ♪ Come be my shadow, walk at my side ♪ 376 | 377 | 00:12:41.050 --> 00:12:46.050 378 | ♪ And when you see all that I have seen ♪ 379 | 380 | 00:12:47.150 --> 00:12:52.042 381 | ♪ Can you tell me love from pride? ♪ 382 | 383 | 00:12:54.117 --> 00:12:59.008 384 | ♪ I have been waiting all this time ♪ 385 | 386 | 00:13:01.092 --> 00:13:06.092 387 | ♪ For one to wake me, one to call mine ♪ 388 | 389 | 00:13:07.158 --> 00:13:12.158 390 | ♪ So when you're near all that you hold dear ♪ 391 | 392 | 00:13:14.008 --> 00:13:18.100 393 | ♪ Do you fear what you will find? ♪ 394 | 395 | 00:13:20.067 --> 00:13:24.158 396 | ♪ As the dawn breaks through the night ♪ 397 | 398 | 00:13:27.025 --> 00:13:31.008 399 | ♪ I move on, forever longing ♪ 400 | 401 | 00:13:33.025 --> 00:13:44.150 402 | ♪ For the home I found in your eyes. ♪ 403 | 404 | 00:13:48.042 --> 00:13:52.133 405 | ♪ I will be listening for the drum ♪ 406 | 407 | 00:13:54.108 --> 00:13:59.000 408 | ♪ To call me over, far away from... ♪ 409 | 410 | 00:14:01.108 --> 00:14:06.000 411 | ♪ My tender youth and the very truth ♪ 412 | 413 | 00:14:07.075 --> 00:14:11.058 414 | ♪ Showing me what I've become. ♪ 415 | 416 | 00:14:13.133 --> 00:14:18.025 417 | ♪ As the dawn breaks through the night ♪ 418 | 419 | 00:14:20.033 --> 00:14:24.017 420 | ♪ I move on, forever longing ♪ 421 | 422 | 00:14:25.100 --> 00:14:30.042 423 | ♪ For the home I found in your eyes ♪ 424 | 425 | 00:14:32.150 --> 00:14:40.000 426 | ♪ Your voice saw me through the night. ♪ -------------------------------------------------------------------------------- /public/video/sintel/thumbs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janumedia/vue-storybook-example/c22ac54c00a11a54df085be1e0723262f93a742b/public/video/sintel/thumbs.jpg -------------------------------------------------------------------------------- /public/video/sintel/thumbs.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 00:00.000 --> 00:11.100 4 | thumbs.jpg#xywh=0,0,160,68 5 | 6 | 00:11.100 --> 00:22.200 7 | thumbs.jpg#xywh=160,0,160,68 8 | 9 | 00:22.200 --> 00:33.300 10 | thumbs.jpg#xywh=320,0,160,68 11 | 12 | 00:33.300 --> 00:44.400 13 | thumbs.jpg#xywh=480,0,160,68 14 | 15 | 00:44.400 --> 00:55.500 16 | thumbs.jpg#xywh=0,68,160,68 17 | 18 | 00:55.500 --> 01:06.600 19 | thumbs.jpg#xywh=160,68,160,68 20 | 21 | 01:06.600 --> 01:17.700 22 | thumbs.jpg#xywh=320,68,160,68 23 | 24 | 01:17.700 --> 01:28.800 25 | thumbs.jpg#xywh=480,68,160,68 26 | 27 | 01:28.800 --> 01:39.900 28 | thumbs.jpg#xywh=0,136,160,68 29 | 30 | 01:39.900 --> 01:51.000 31 | thumbs.jpg#xywh=160,136,160,68 32 | 33 | 01:51.000 --> 02:02.100 34 | thumbs.jpg#xywh=320,136,160,68 35 | 36 | 02:02.100 --> 02:13.200 37 | thumbs.jpg#xywh=480,136,160,68 38 | 39 | 02:13.200 --> 02:24.300 40 | thumbs.jpg#xywh=0,204,160,68 41 | 42 | 02:24.300 --> 02:35.400 43 | thumbs.jpg#xywh=160,204,160,68 44 | 45 | 02:35.400 --> 02:46.500 46 | thumbs.jpg#xywh=320,204,160,68 47 | 48 | 02:46.500 --> 02:57.600 49 | thumbs.jpg#xywh=480,204,160,68 50 | 51 | 02:57.600 --> 03:08.700 52 | thumbs.jpg#xywh=0,272,160,68 53 | 54 | 03:08.700 --> 03:19.800 55 | thumbs.jpg#xywh=160,272,160,68 56 | 57 | 03:19.800 --> 03:30.900 58 | thumbs.jpg#xywh=320,272,160,68 59 | 60 | 03:30.900 --> 03:42.000 61 | thumbs.jpg#xywh=480,272,160,68 62 | 63 | 03:42.000 --> 03:53.100 64 | thumbs.jpg#xywh=0,340,160,68 65 | 66 | 03:53.100 --> 04:04.200 67 | thumbs.jpg#xywh=160,340,160,68 68 | 69 | 04:04.200 --> 04:15.300 70 | thumbs.jpg#xywh=320,340,160,68 71 | 72 | 04:15.300 --> 04:26.400 73 | thumbs.jpg#xywh=480,340,160,68 74 | 75 | 04:26.400 --> 04:37.500 76 | thumbs.jpg#xywh=0,408,160,68 77 | 78 | 04:37.500 --> 04:48.600 79 | thumbs.jpg#xywh=160,408,160,68 80 | 81 | 04:48.600 --> 04:59.700 82 | thumbs.jpg#xywh=320,408,160,68 83 | 84 | 04:59.700 --> 05:10.800 85 | thumbs.jpg#xywh=480,408,160,68 86 | 87 | 05:10.800 --> 05:21.900 88 | thumbs.jpg#xywh=0,476,160,68 89 | 90 | 05:21.900 --> 05:33.000 91 | thumbs.jpg#xywh=160,476,160,68 92 | 93 | 05:33.000 --> 05:44.100 94 | thumbs.jpg#xywh=320,476,160,68 95 | 96 | 05:44.100 --> 05:55.200 97 | thumbs.jpg#xywh=480,476,160,68 98 | 99 | 05:55.200 --> 06:06.300 100 | thumbs.jpg#xywh=0,544,160,68 101 | 102 | 06:06.300 --> 06:17.400 103 | thumbs.jpg#xywh=160,544,160,68 104 | 105 | 06:17.400 --> 06:28.500 106 | thumbs.jpg#xywh=320,544,160,68 107 | 108 | 06:28.500 --> 06:39.600 109 | thumbs.jpg#xywh=480,544,160,68 110 | 111 | 06:39.600 --> 06:50.700 112 | thumbs.jpg#xywh=0,612,160,68 113 | 114 | 06:50.700 --> 07:01.800 115 | thumbs.jpg#xywh=160,612,160,68 116 | 117 | 07:01.800 --> 07:12.900 118 | thumbs.jpg#xywh=320,612,160,68 119 | 120 | 07:12.900 --> 07:24.000 121 | thumbs.jpg#xywh=480,612,160,68 122 | 123 | 07:24.000 --> 07:35.100 124 | thumbs.jpg#xywh=0,680,160,68 125 | 126 | 07:35.100 --> 07:46.200 127 | thumbs.jpg#xywh=160,680,160,68 128 | 129 | 07:46.200 --> 07:57.300 130 | thumbs.jpg#xywh=320,680,160,68 131 | 132 | 07:57.300 --> 08:08.400 133 | thumbs.jpg#xywh=480,680,160,68 134 | 135 | 08:08.400 --> 08:19.500 136 | thumbs.jpg#xywh=0,748,160,68 137 | 138 | 08:19.500 --> 08:30.600 139 | thumbs.jpg#xywh=160,748,160,68 140 | 141 | 08:30.600 --> 08:41.700 142 | thumbs.jpg#xywh=320,748,160,68 143 | 144 | 08:41.700 --> 08:52.800 145 | thumbs.jpg#xywh=480,748,160,68 146 | 147 | 08:52.800 --> 09:03.900 148 | thumbs.jpg#xywh=0,816,160,68 149 | 150 | 09:03.900 --> 09:15.000 151 | thumbs.jpg#xywh=160,816,160,68 152 | 153 | 09:15.000 --> 09:26.100 154 | thumbs.jpg#xywh=320,816,160,68 155 | 156 | 09:26.100 --> 09:37.200 157 | thumbs.jpg#xywh=480,816,160,68 158 | 159 | 09:37.200 --> 09:48.300 160 | thumbs.jpg#xywh=0,884,160,68 161 | 162 | 09:48.300 --> 09:59.400 163 | thumbs.jpg#xywh=160,884,160,68 164 | 165 | 09:59.400 --> 10:10.500 166 | thumbs.jpg#xywh=320,884,160,68 167 | 168 | 10:10.500 --> 10:21.600 169 | thumbs.jpg#xywh=480,884,160,68 170 | 171 | 10:21.600 --> 10:32.700 172 | thumbs.jpg#xywh=0,952,160,68 173 | 174 | 10:32.700 --> 10:43.800 175 | thumbs.jpg#xywh=160,952,160,68 176 | 177 | 10:43.800 --> 10:54.900 178 | thumbs.jpg#xywh=320,952,160,68 179 | 180 | 10:54.900 --> 11:06.000 181 | thumbs.jpg#xywh=480,952,160,68 182 | 183 | 11:06.000 --> 11:17.100 184 | thumbs.jpg#xywh=0,1020,160,68 185 | 186 | 11:17.100 --> 11:28.200 187 | thumbs.jpg#xywh=160,1020,160,68 188 | 189 | 11:28.200 --> 11:39.300 190 | thumbs.jpg#xywh=320,1020,160,68 191 | 192 | 11:39.300 --> 11:50.400 193 | thumbs.jpg#xywh=480,1020,160,68 194 | 195 | 11:50.400 --> 12:01.500 196 | thumbs.jpg#xywh=0,1088,160,68 197 | 198 | 12:01.500 --> 12:12.600 199 | thumbs.jpg#xywh=160,1088,160,68 200 | 201 | 12:12.600 --> 12:23.700 202 | thumbs.jpg#xywh=320,1088,160,68 203 | 204 | 12:23.700 --> 12:34.800 205 | thumbs.jpg#xywh=480,1088,160,68 206 | 207 | 12:34.800 --> 12:45.900 208 | thumbs.jpg#xywh=0,1156,160,68 209 | 210 | 12:45.900 --> 12:57.000 211 | thumbs.jpg#xywh=160,1156,160,68 212 | 213 | 12:57.000 --> 13:08.100 214 | thumbs.jpg#xywh=320,1156,160,68 215 | 216 | 13:08.100 --> 13:19.200 217 | thumbs.jpg#xywh=480,1156,160,68 218 | 219 | 13:19.200 --> 13:30.300 220 | thumbs.jpg#xywh=0,1224,160,68 221 | 222 | 13:30.300 --> 13:41.400 223 | thumbs.jpg#xywh=160,1224,160,68 224 | 225 | 13:41.400 --> 13:52.500 226 | thumbs.jpg#xywh=320,1224,160,68 227 | 228 | 13:52.500 --> 14:03.600 229 | thumbs.jpg#xywh=480,1224,160,68 230 | 231 | 14:03.600 --> 14:14.700 232 | thumbs.jpg#xywh=0,1292,160,68 233 | 234 | 14:14.700 --> 14:25.800 235 | thumbs.jpg#xywh=160,1292,160,68 236 | 237 | 14:25.800 --> 14:36.900 238 | thumbs.jpg#xywh=320,1292,160,68 239 | 240 | 14:36.900 --> 14:48.000 241 | thumbs.jpg#xywh=480,1292,160,68 242 | 243 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 19 | 45 | -------------------------------------------------------------------------------- /src/assets/css/_anim.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janumedia/vue-storybook-example/c22ac54c00a11a54df085be1e0723262f93a742b/src/assets/css/_anim.scss -------------------------------------------------------------------------------- /src/assets/css/_color.scss: -------------------------------------------------------------------------------- 1 | $primary: #102E5A; 2 | $primary-text: #F3F2F3; 3 | $secondary: #6D9FB5; 4 | $secondary-text: #FFF;//#102E5A; 5 | $border-color: #6D9FB5; 6 | $error: red; 7 | $error-text: #FFF; 8 | $disabled: rgb(218, 218, 218); 9 | $disabled-text: rgb(187, 186, 186); 10 | $background: #fff; 11 | $font-color: #102E5A; -------------------------------------------------------------------------------- /src/assets/css/_mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin translate($valueX, $valueY) { 2 | -webkit-transform:translate($valueX, $valueY); 3 | -moz-transform:translate($valueX, $valueY); 4 | -ms-transform:translate($valueX, $valueY); 5 | -o-transform:translate($valueX, $valueY); 6 | transform:translate($valueX, $valueY); 7 | } 8 | @mixin translateX($value) { 9 | -webkit-transform:translateX($value); 10 | -moz-transform:translateX($value); 11 | -ms-transform:translateX($value); 12 | -o-transform:translateX($value); 13 | transform:translateX($value); 14 | } 15 | @mixin translateY($value) { 16 | -webkit-transform:translateY($value); 17 | -moz-transform:translateY($value); 18 | -o-transform:translateY($value); 19 | -ms-transform:translateY($value); 20 | transform:translateY($value); 21 | } 22 | @mixin rotate($value) { 23 | -webkit-transform: rotate($value); 24 | -moz-transform: rotate($value); 25 | -o-transform: rotate($value); 26 | transform: rotate($value); 27 | } 28 | @mixin transition($properties...) { 29 | -webkit-transition: $properties; 30 | -moz-transition: $properties; 31 | -ms-transition: $properties; 32 | transition: $properties; 33 | //fix flickring issue https://goo.gl/7yCcm7 34 | //CoreAnimation issue on safari https://stackoverflow.com/a/24469750/1578100 35 | //-webkit-transform-style: preserve-3d; 36 | //-webkit-transform: translateZ(0); 37 | //-webkit-backface-visibility: hidden; 38 | } 39 | @mixin transition-duration($value) { 40 | -webkit-transition-duration: $value; 41 | -moz-transition-duration: $value; 42 | -ms-transition-duration:$value; 43 | transition-duration: $value; 44 | } 45 | @mixin transition-delay($value) { 46 | -webkit-transition-delay: $value; 47 | -moz-transition-delay: $value; 48 | -ms-transition-delay: $value; 49 | transition-delay: $value; 50 | } 51 | @mixin transition-timing-function($value) { 52 | -webkit-transition-timing-function: $value; 53 | -moz-transition-timing-function: $value; 54 | -ms-transition-timing-function: $value; 55 | transition-timing-function: $value; 56 | } 57 | @mixin animation($properties...) { 58 | -webkit-animation: $properties; 59 | -moz-animation: $properties; 60 | -ms-animation: $properties; 61 | -o-animation: $properties; 62 | animation: $properties; 63 | } 64 | /* 65 | @mixin border-radius($properties...) { 66 | border-radius: $properties; 67 | -webkit-border-radius: $properties; 68 | -moz-border-radius: $properties; 69 | }*/ -------------------------------------------------------------------------------- /src/assets/css/_normalize.scss: -------------------------------------------------------------------------------- 1 | @import '_reset.scss'; 2 | @import '_color.scss'; 3 | 4 | body, html { 5 | font-family: 'Avenir', Helvetica, Arial, sans-serif; 6 | font-size: 16px; 7 | color: $font-color; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | padding: 0; 11 | margin: 10px; 12 | } -------------------------------------------------------------------------------- /src/assets/css/_reset.scss: -------------------------------------------------------------------------------- 1 | //reset box-size 2 | //https://css-tricks.com/box-sizing/ 3 | *, *:after, *:before { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } 4 | 5 | ul { padding: 0; margin: 0; } 6 | li { list-style: none; } 7 | :focus { outline: none; } 8 | a { text-decoration: none; outline: none; } 9 | a img { border: 0; } 10 | 11 | // iOS shadow removed 12 | input { 13 | -webkit-appearance: none; 14 | -moz-appearance: none; 15 | box-shadow: none; 16 | } 17 | 18 | // avoid zoom in iOS mobile device when entering input 19 | @media screen and (-webkit-min-device-pixel-ratio:0) { 20 | select, 21 | textarea, 22 | input, 23 | label, 24 | button { 25 | font-size: 16px; 26 | -webkit-tap-highlight-color: rgba(0,0,0,0); //remove tap highlight color 27 | } 28 | } -------------------------------------------------------------------------------- /src/assets/css/_utils.scss: -------------------------------------------------------------------------------- 1 | .text--center { 2 | text-align: center; 3 | } 4 | .text--left { 5 | text-align: left; 6 | } 7 | .text--right { 8 | text-align: right; 9 | } 10 | .font--bold { 11 | font-weight: bold; 12 | } -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janumedia/vue-storybook-example/c22ac54c00a11a54df085be1e0723262f93a742b/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/storybook+vue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janumedia/vue-storybook-example/c22ac54c00a11a54df085be1e0723262f93a742b/src/assets/storybook+vue.png -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 42 | 43 | 44 | 60 | -------------------------------------------------------------------------------- /src/components/core/Button.vue: -------------------------------------------------------------------------------- 1 | 9 | 22 | 102 | -------------------------------------------------------------------------------- /src/components/core/ButtonWrapper.vue: -------------------------------------------------------------------------------- 1 | 6 | 15 | 40 | -------------------------------------------------------------------------------- /src/components/core/Checkbox.vue: -------------------------------------------------------------------------------- 1 | 7 | 41 | 114 | -------------------------------------------------------------------------------- /src/components/core/Input.vue: -------------------------------------------------------------------------------- 1 | 18 | 83 | 125 | 126 | -------------------------------------------------------------------------------- /src/components/core/RadioButton.vue: -------------------------------------------------------------------------------- 1 | 7 | 26 | 95 | 96 | -------------------------------------------------------------------------------- /src/components/core/SwitchButton.vue: -------------------------------------------------------------------------------- 1 | 14 | 58 | 174 | -------------------------------------------------------------------------------- /src/components/core/ToggleButton.vue: -------------------------------------------------------------------------------- 1 | 19 | 60 | -------------------------------------------------------------------------------- /src/components/media/Icon.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/media/LazyImage.vue: -------------------------------------------------------------------------------- 1 | 6 | 111 | 120 | -------------------------------------------------------------------------------- /src/components/media/VideoPlayer.vue: -------------------------------------------------------------------------------- 1 | 204 | 269 | 399 | -------------------------------------------------------------------------------- /src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /src/layouts/no-navigation.vue: -------------------------------------------------------------------------------- 1 | 8 | 21 | 22 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | 5 | import DefaultLayout from './layouts/default' 6 | import NoNavigationLayout from './layouts/no-navigation' 7 | 8 | Vue.config.productionTip = false 9 | 10 | //register layouts 11 | Vue.component('default-layout', DefaultLayout); 12 | Vue.component('no-nav-layout', NoNavigationLayout); 13 | 14 | //register async components 15 | Vue.component('custom-button', () => import(/* webpackChunkName: "custom-button" */ '@/components/core/Button')); 16 | Vue.component('input-text', () => import(/* webpackChunkName: "input-text" */ '@/components/core/Input')); 17 | Vue.component('checkbox', () => import(/* webpackChunkName: "checkbox" */ '@/components/core/Checkbox')); 18 | Vue.component('radio-button', () => import(/* webpackChunkName: "radio-button" */ '@/components/core/RadioButton')); 19 | Vue.component('switch-button', () => import(/* webpackChunkName: "switch-button" */ '@/components/core/SwitchButton')); 20 | 21 | new Vue({ 22 | router, 23 | render: h => h(App), 24 | }).$mount('#app') 25 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from '@/views/Home.vue' 4 | 5 | Vue.use(Router); 6 | 7 | export default new Router({ 8 | mode: 'history', 9 | base: process.env.BASE_URL, 10 | routes: [ 11 | { 12 | path: '/', 13 | name: 'home', 14 | component: Home 15 | }, 16 | { 17 | path: '/setup', 18 | name: 'setup', 19 | component: () => import(/* webpackChunkName: "setup" */ '@/views/Setup.vue') 20 | }, 21 | { 22 | path: '/media', 23 | name: 'media', 24 | component: () => import(/* webpackChunkName: "setup" */ '@/views/Media.vue') 25 | }, 26 | { 27 | path: '*', 28 | name: '404', 29 | meta: { layout: 'no-nav' }, 30 | component: () => import(/* webpackChunkName: "404" */ '@/views/404.vue') 31 | } 32 | ] 33 | }) -------------------------------------------------------------------------------- /src/stories/AppDecorator.vue: -------------------------------------------------------------------------------- 1 | 6 | 26 | -------------------------------------------------------------------------------- /src/stories/Welcome.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 35 | 36 | 37 | 69 | -------------------------------------------------------------------------------- /src/stories/index.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | //import addons 3 | import { linkTo } from '@storybook/addon-links' 4 | import { action, decorate } from '@storybook/addon-actions' 5 | import { configureViewport, INITIAL_VIEWPORTS } from '@storybook/addon-viewport' 6 | import { withKnobs, text, boolean, number, select, color } from '@storybook/addon-knobs'; 7 | // 8 | import StoryRouter from 'storybook-vue-router' 9 | 10 | import App from '@/App' 11 | import Welcome from './Welcome' 12 | 13 | import * as Icons from '@/components/media/Icon' 14 | 15 | import router from '@/router' 16 | 17 | configureViewport({ 18 | viewports: { 19 | ...INITIAL_VIEWPORTS 20 | } 21 | }); 22 | 23 | storiesOf('Page|Welcome', module) 24 | .addParameters({ 25 | options: { 26 | selectedAddonPanel: 'storybook-addon-viewport/addon-panel' 27 | } 28 | }) 29 | .add('welcome', () => ({ 30 | render: h => h(Welcome) 31 | })); 32 | 33 | storiesOf('Page|App', module) 34 | .addDecorator(StoryRouter({}, router.options)) 35 | .addParameters({ 36 | options: { 37 | selectedAddonPanel: 'storybook-addon-viewport/addon-panel' 38 | } 39 | }) 40 | .add('app with router', () => ({ 41 | render: h => h(App) 42 | })); 43 | 44 | storiesOf('Components|Button', module) 45 | .add('default', () => ({ 46 | template: 'Default Button' 47 | })) 48 | .add('rounded', () => ({ 49 | template: 'Rounded Button' 50 | })) 51 | .add('uppercase', () => ({ 52 | template: 'Uppercase Button' 53 | })) 54 | .add('primary', () => ({ 55 | template: 'Primary Button' 56 | })) 57 | .add('disabled', () => ({ 58 | template: 'Disabled Button' 59 | })) 60 | .add('emoji & symbol', () => ({ 61 | template: '😎🌍🍺💯' 62 | })) 63 | .add('custom font size', () => ({ 64 | template: '
30px

40px
' 65 | })); 66 | 67 | storiesOf('Components|Input', module) 68 | .add('default', () => ({ 69 | template: '' 70 | })) 71 | .add('placeholder', () => ({ 72 | template: '' 73 | })) 74 | .add('prefix', () => ({ 75 | template: '' 76 | })) 77 | .add('suffix', () => ({ 78 | template: '' 79 | })) 80 | .add('emoji & symbol', () => ({ 81 | template: ` 82 |
83 |

84 |

85 |
86 | ` 87 | })) 88 | .add('custom width', () => ({ 89 | template: '' 90 | })); 91 | 92 | storiesOf('Components|Checkbox', module) 93 | .add('default', () => ({ 94 | template: ` 95 |
96 | Default One

97 | Default Two 98 |
` 99 | })) 100 | .add('fill', () => ({ 101 | template: ` 102 |
103 | Fill One

104 | Fill Two 105 |
` 106 | })) 107 | .add('custom font size', () => ({ 108 | template: ` 109 |
110 | 30px

111 | 40px 112 |
` 113 | })); 114 | 115 | storiesOf('Components|RadioButton', module) 116 | .add('default', () => ({ 117 | template: ` 118 |
119 | Default One

120 | Default Two 121 |
122 | ` 123 | })) 124 | .add('fill', () => ({ 125 | template: ` 126 |
127 | Fill One

128 | Fill Two 129 |
130 | ` 131 | })) 132 | .add('custom font size', () => ({ 133 | template: ` 134 |
135 | 30px

136 | 40px 137 |
138 | ` 139 | })) 140 | 141 | storiesOf('Components|SwitchButton', module) 142 | .add('default', () => ({ 143 | template: `` 144 | })) 145 | .add('rounded', () => ({ 146 | template: `` 147 | })) 148 | .add('rounded + label', () => ({ 149 | template: `My Label` 150 | })) 151 | .add('rounded + on + off', () => ({ 152 | template: `` 153 | })) 154 | .add('rounded + disabled', () => ({ 155 | template: `Disabled` 156 | })) 157 | .add('custom font size', () => ({ 158 | template: ` 159 |
160 | 20px

161 | 40px 162 |
163 | ` 164 | })); 165 | 166 | storiesOf('Components|LazyImage', module) 167 | .addParameters({ 168 | options: { 169 | selectedAddonPanel: 'storybook-addon-viewport/addon-panel' 170 | } 171 | }) 172 | .add('default', () => ({ 173 | template: ` 174 |
175 |
176 |
177 |
178 |
179 | 180 |
181 | ` 182 | })) 183 | .add('custom placeHolder', () => ({ 184 | template: ` 185 |
186 |
187 |
188 |
189 |
190 | 191 |
192 | ` 193 | })); 194 | 195 | storiesOf('Components|Icon', module) 196 | .addDecorator(withKnobs) 197 | .addParameters({ 198 | options: { 199 | addonPanelInRight: true, 200 | selectedAddonPanel: 'storybooks/storybook-addon-knobs' 201 | } 202 | }) 203 | .add('Icon List', () => ({ 204 | props: { 205 | color: { 206 | type: String, 207 | default: color('color', '#F8E71C') 208 | }, 209 | width: { 210 | type: Number, 211 | default: number('width', 80, { 212 | range: true, 213 | min: 80, 214 | max: 200, 215 | step: 10 216 | }) 217 | }, 218 | height: { 219 | type: Number, 220 | default: number('height', 80, { 221 | range: true, 222 | min: 80, 223 | max: 200, 224 | step: 10 225 | }) 226 | } 227 | }, 228 | render(h) { 229 | return ( 230 |
231 | {Object.keys(Icons).map(iconName => { 232 | if(iconName !== 'default') return( 233 |
234 | {h(Icons[iconName], {props:{width:this.width, height:this.height, color:this.color}})} 235 |
{iconName}
236 |
237 |
238 | ) 239 | })} 240 |
241 | ) 242 | } 243 | })); 244 | 245 | storiesOf('Components|VideoPlayer', module) 246 | .addParameters({ 247 | options: { 248 | selectedAddonPanel: 'storybook-addon-viewport/addon-panel' 249 | } 250 | }) 251 | .add('Video default', () => ({ 252 | template:` 253 | 254 | 255 | 256 | 257 | Video Type Not Supported!!! 258 | 259 | ` 260 | })) 261 | 262 | // decorate return first argument as new value 263 | const firstArg = decorate([args => { 264 | return Array.isArray(args[0]) ? args[0] : [args[0]] 265 | }]); 266 | 267 | storiesOf('Addons|Actions', module) 268 | .addParameters({ 269 | options: { 270 | addonPanelInRight: true, 271 | selectedAddonPanel: 'storybook/actions/actions-panel' 272 | } 273 | }) 274 | .add('Button: click', () => ({ 275 | template: 'Click Me!', 276 | methods: { 277 | handleClick: action('click') 278 | } 279 | })) 280 | .add('Checkbox: v-model', () => ({ 281 | template: ` 282 |
283 | Select your favorite fruit:

284 | Manggo

285 | Orange 286 |
`, 287 | data() { 288 | return { 289 | listValues: ["Orange"] 290 | } 291 | }, 292 | watch: firstArg.actions('listValues') 293 | })) 294 | .add('RadioButton: v-model', () => ({ 295 | template: ` 296 |
297 | Select the best browser:

298 | Chrome

299 | Safari

300 | Firefox 301 |
`, 302 | data() { 303 | return { 304 | value: null 305 | } 306 | }, 307 | watch: { 308 | value: firstArg.action('value') 309 | } 310 | })) 311 | .add('SwitchButton: input', () => ({ 312 | template: '', 313 | methods: { 314 | handleInput: action('input') 315 | } 316 | })) 317 | .add('SwitchButton: v-model', () => ({ 318 | template: '', 319 | data() { 320 | return { 321 | value: null 322 | } 323 | }, 324 | watch: { 325 | value: firstArg.action('value') 326 | } 327 | })); 328 | 329 | storiesOf('Addons|Knobs', module) 330 | .addDecorator(withKnobs) 331 | .addParameters({ 332 | options: { 333 | addonPanelInRight: true, 334 | selectedAddonPanel: 'storybooks/storybook-addon-knobs' 335 | } 336 | }) 337 | .add('Button', () => ({ 338 | props: { 339 | label: { 340 | type: String, 341 | default: text('label', 'Button Label 💯') 342 | }, 343 | rounded: { 344 | type: Boolean, 345 | default: boolean('rounded', true) 346 | }, 347 | primary: { 348 | type: Boolean, 349 | default: boolean('primary', false) 350 | }, 351 | disabled: { 352 | type: Boolean, 353 | default: boolean('disabled', false) 354 | }, 355 | fontSize: { 356 | type: String, 357 | default: `${number('font-size', 16, { 358 | range: true, 359 | min: 0, 360 | max: 60, 361 | step: 5 362 | })}px` 363 | } 364 | }, 365 | template: `{{ label }}` 366 | })) 367 | .add('SwitchButton', () => ({ 368 | props: { 369 | on: { 370 | type: String, 371 | default: text('on', 'ON') 372 | }, 373 | off: { 374 | type: String, 375 | default: text('off', 'OFF') 376 | }, 377 | rounded: { 378 | type: Boolean, 379 | default: boolean('rounded', true) 380 | }, 381 | disabled: { 382 | type: Boolean, 383 | default: boolean('disabled', false) 384 | }, 385 | fontSize: { 386 | type: String, 387 | default: `${number('font-size', 16, { 388 | range: true, 389 | min: 0, 390 | max: 60, 391 | step: 5 392 | })}px` 393 | } 394 | }, 395 | template: `` 396 | })) 397 | .add('Input', () => ({ 398 | props: { 399 | placeholder: { 400 | type: String, 401 | default: text('placeholder', 'Place Holder') 402 | }, 403 | prefix: { 404 | type: String, 405 | default: text('prefix', '💲') 406 | }, 407 | suffix: { 408 | type: String, 409 | default: text('suffix', '℉') 410 | }, 411 | fontSize: { 412 | type: String, 413 | default: `${number('font-size', 16, { 414 | range: true, 415 | min: 0, 416 | max: 60, 417 | step: 5 418 | })}px` 419 | } 420 | }, 421 | template: '' 422 | })); 423 | 424 | storiesOf("Addons|Links", module) 425 | .add('linkTo', () => ({ 426 | template: 'Go to Welcome', 427 | methods: { 428 | handleClick: linkTo('Welcome', 'welcome') 429 | } 430 | })); 431 | 432 | // Writing Stories using Decorators 433 | // https://storybook.js.org/basics/writing-stories/#using-decorators 434 | // custom styles 435 | const centerWrapper = { 436 | position: 'absolute', 437 | top: 0, 438 | bottom: 0, 439 | left: 0, 440 | right: 0, 441 | display: 'table', 442 | width: '100%', 443 | height: '100vh', 444 | textAlign: 'center', 445 | padding: '0.5em' 446 | } 447 | const center = { 448 | position: 'relative', 449 | display: 'table-cell', 450 | verticalAlign: 'middle', 451 | } 452 | 453 | //custom decorator using story function 454 | const storyFunction = (storyFn) => { 455 | const storyFnWrapper = storyFn(); 456 | return { 457 | components: {storyFnWrapper}, 458 | data(){ 459 | return { 460 | centerWrapper, 461 | center 462 | } 463 | }, 464 | template: '
' 465 | } 466 | }; 467 | //custom decorator using story component 468 | const storyComponent = () => { 469 | return { 470 | data(){ 471 | return { 472 | centerWrapper: {...centerWrapper, backgroundColor: '#eef'}, 473 | center 474 | } 475 | }, 476 | template: '
' 477 | } 478 | }; 479 | //custom decorator using custom vue component 480 | import AppDecorator from './AppDecorator'; 481 | const vueComponent = () => ({ 482 | components: {AppDecorator}, 483 | template: '' 484 | }); 485 | 486 | storiesOf('Customs|Decorator/with story function', module) 487 | .addDecorator(storyFunction) 488 | .add('Button', () => ({ 489 | template: 'Centered Button' 490 | })) 491 | .add('SwitchButton', () => ({ 492 | template: `Centered` 493 | })); 494 | 495 | storiesOf('Customs|Decorator/with story component', module) 496 | .addDecorator(storyComponent) 497 | .add('Button', () => ({ 498 | template: 'Centered Button' 499 | })) 500 | .add('SwitchButton', () => ({ 501 | template: `Centered` 502 | })); 503 | 504 | storiesOf('Customs|Decorator/with vue component', module) 505 | .addDecorator(vueComponent) 506 | .add('Button', () => ({ 507 | template: 'Centered Button' 508 | })) 509 | .add('SwitchButton', () => ({ 510 | template: `Centered` 511 | })) 512 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | let intervalData = {}; 2 | 3 | export const iOSDevice = () => /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; 4 | export const isMobile = () => { 5 | //https://stackoverflow.com/a/21742107/1578100 6 | const userAgent = navigator.userAgent || navigator.vendor || window.opera; 7 | return /android/i.test(userAgent) || /iPad|iPhone|iPhone/.test(userAgent) && !window.MSStream 8 | } 9 | 10 | export const addClass = (el, className) => { 11 | el.className += ` ${className}`; 12 | } 13 | export const removeClass = (el, className) => { 14 | el.className = el.className.replace(new RegExp(`(?:^|\\s)${className}(?!\\S)`),''); 15 | } 16 | 17 | export default { 18 | 19 | hide(el) { 20 | el.style.display = "none"; 21 | return new Promise((resolve) => resolve); 22 | }, 23 | show(el) { 24 | el.style.opacity = 1; 25 | el.style.display = "block"; 26 | return new Promise((resolve) => resolve); 27 | }, 28 | setOpacity(el, value) { 29 | if(el) el.style.opacity = value; 30 | }, 31 | fadeOut(el, doneRemoveAfterDone, duration) { 32 | el.style.opacity = el.style.opacity || 1; 33 | clearTimeout(intervalData[el.innerHTML]); 34 | return new Promise((resolve) => { 35 | (function fade() { 36 | if((el.style.opacity -= .1) < 0) 37 | { 38 | if(!doneRemoveAfterDone) el.style.display = "none"; 39 | resolve(); 40 | } else 41 | { 42 | intervalData[el.innerHTML] = setTimeout(fade, duration || 20); 43 | } 44 | })(); 45 | }) 46 | }, 47 | fadeIn(el, duration) { 48 | if(!el.style.display || el.style.display == "none") el.style.display = "block"; 49 | el.style.opacity = el.style.opacity || 0; 50 | clearTimeout(intervalData[el.innerHTML]); 51 | let opacity = 0; 52 | return new Promise((resolve) => { 53 | (function fade() { 54 | opacity = parseFloat(el.style.opacity); 55 | if((opacity += .1) > 1) 56 | { 57 | resolve(); 58 | } else 59 | { 60 | el.style.opacity = opacity; 61 | intervalData[el.innerHTML] = setTimeout(fade, duration || 20); 62 | } 63 | })(); 64 | }) 65 | }, 66 | clearFade() { 67 | Object.keys(intervalData).map(key => { 68 | clearTimeout(intervalData[key]); 69 | intervalData[key] = null; 70 | }) 71 | }, 72 | delay(duration) { 73 | return new Promise((resolve) => setTimeout(resolve , duration)); 74 | }, 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 8 | 24 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 15 | -------------------------------------------------------------------------------- /src/views/Media.vue: -------------------------------------------------------------------------------- 1 | 26 | 38 | -------------------------------------------------------------------------------- /src/views/Setup.vue: -------------------------------------------------------------------------------- 1 | 25 | 35 | 50 | 51 | -------------------------------------------------------------------------------- /tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } -------------------------------------------------------------------------------- /tests/unit/example.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import HelloWorld from '@/components/HelloWorld.vue' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message' 7 | const wrapper = shallowMount(HelloWorld, { 8 | propsData: { msg } 9 | }) 10 | expect(wrapper.text()).toMatch(msg) 11 | }) 12 | }) 13 | --------------------------------------------------------------------------------