├── .editorconfig
├── .gitignore
├── .nvmrc
├── .travis.yml
├── LICENSE
├── README.md
├── config
├── storybook
│ ├── addons.js
│ ├── config.js
│ ├── preview-head.html
│ └── webpack.config.js
└── webpack
│ └── webpack.config.js
├── dist
├── .nojekyll
└── index.html
├── package-lock.json
├── package.json
└── src
├── components
├── atoms
│ ├── base-button
│ │ ├── index.vue
│ │ └── stories.js
│ ├── base-input
│ │ ├── index.vue
│ │ └── stories.js
│ ├── base-text
│ │ ├── index.vue
│ │ └── stories.js
│ ├── base-title
│ │ ├── index.vue
│ │ └── stories.js
│ └── todo-row
│ │ ├── index.vue
│ │ └── stories.js
├── molecules
│ ├── new-todo
│ │ ├── index.vue
│ │ └── stories.js
│ └── todo
│ │ ├── index.vue
│ │ └── stories.js
├── organisms
│ └── todo-list
│ │ ├── index.vue
│ │ └── stories.js
├── pages
│ └── app
│ │ └── index.vue
└── templates
│ └── app-layout
│ ├── index.vue
│ └── stories.js
├── containers
├── all-todos.js
└── new-todo.js
├── index.js
└── store
├── index.js
└── modules
└── todo
├── actionTypes.js
├── actions.js
├── getterTypes.js
├── getters.js
├── index.js
├── mutationTypes.js
└── mutations.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | charset = utf-8
3 | end_of_line = lf
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
7 | [*.{js,vue}]
8 | indent_size = 2
9 | indent_style = space
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist/main.js
3 | storybook-static
4 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v10.4.0
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | before_deploy:
4 | - npm run build
5 |
6 | deploy:
7 | provider: pages
8 | skip-cleanup: true
9 | local-dir: dist
10 | github-token: $GITHUB_TOKEN
11 | keep-history: true
12 | on:
13 | branch: master
14 |
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Shota Fuji(pocka)
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-container-component-example
2 |
3 | Example repository for Container Component Pattern with Vue.js and Vuex
4 |
5 | - [Live demo](https://pocka.github.io/vue-container-component-example/)
6 | - [storybook(styleguide)](https://vue-container-component-example.netlify.com/)
7 |
8 | ## for developers
9 |
10 | ```sh
11 | # Starts dev server (listen 8080)
12 | npm run dev
13 |
14 | # Starts storybook (listen 9001)
15 | npm run storybook
16 |
17 | # Builds app
18 | npm run build
19 | ```
20 |
--------------------------------------------------------------------------------
/config/storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-knobs/register'
2 | import '@storybook/addon-actions/register'
3 |
--------------------------------------------------------------------------------
/config/storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/vue'
2 |
3 | const req = require.context('../../src/components', true, /\.?stories\.js$/)
4 |
5 | function loadStories() {
6 | req.keys().forEach(file => req(file))
7 | }
8 |
9 | configure(loadStories, module)
10 |
--------------------------------------------------------------------------------
/config/storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/config/storybook/webpack.config.js:
--------------------------------------------------------------------------------
1 | const appConfig = require('../webpack/webpack.config.js')
2 |
3 | const { module: $module, plugins, resolve } = appConfig
4 |
5 | module.exports = {
6 | module: {
7 | ...$module,
8 | rules: $module.rules.filter(rule => !rule.loader.includes('vue-loader'))
9 | },
10 | plugins,
11 | resolve: {
12 | ...resolve,
13 | alias: {
14 | ...resolve.alias,
15 | vue$: 'vue/dist/vue.esm.js'
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/config/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const VueLoaderPlugin = require('vue-loader/lib/plugin')
4 |
5 | module.exports = {
6 | entry: './src/index.js',
7 | output: {
8 | filename: 'main.js',
9 | path: path.resolve(__dirname, '../../dist')
10 | },
11 | devServer: {
12 | contentBase: './dist'
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.js$/,
18 | exclude: /node_modules/,
19 | loader: 'babel-loader'
20 | },
21 | {
22 | test: /\.vue$/,
23 | loader: 'vue-loader'
24 | },
25 | {
26 | test: /\.css$/,
27 | loader: 'style-loader!css-loader?modules=true'
28 | }
29 | ]
30 | },
31 | plugins: [new VueLoaderPlugin()],
32 | resolve: {
33 | extensions: ['.js', '.vue'],
34 | alias: {
35 | '~': path.resolve(__dirname, '../../src')
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/dist/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pocka/vue-container-component-example/0922b4f77af24c3ec819d9f7663c7a3219c7440e/dist/.nojekyll
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Container Component Pattern in Vue.js
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-container-component-example",
3 | "version": "1.0.0",
4 | "private": true,
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/pocka/vue-container-component-example.git"
8 | },
9 | "scripts": {
10 | "dev": "webpack-dev-server --config config/webpack/webpack.config.js --mode=development",
11 | "build": "webpack --config config/webpack/webpack.config.js --mode=production",
12 | "storybook": "start-storybook -p 9001 -c config/storybook",
13 | "build:storybook": "build-storybook -c config/storybook"
14 | },
15 | "author": "pocka",
16 | "license": "MIT",
17 | "bugs": {
18 | "url": "https://github.com/pocka/vue-container-component-example/issues"
19 | },
20 | "homepage": "https://github.com/pocka/vue-container-component-example#readme",
21 | "devDependencies": {
22 | "@storybook/addon-actions": "^3.4.7",
23 | "@storybook/addon-knobs": "^3.4.7",
24 | "@storybook/vue": "^4.0.0-alpha.9",
25 | "babel": "^6.23.0",
26 | "babel-core": "^6.26.3",
27 | "babel-loader": "^7.1.4",
28 | "babel-preset-env": "^1.7.0",
29 | "css-loader": "^0.28.11",
30 | "style-loader": "^0.21.0",
31 | "vue-loader": "^15.2.4",
32 | "vue-template-compiler": "^2.5.16",
33 | "webpack": "^4.12.0",
34 | "webpack-cli": "^3.0.3",
35 | "webpack-dev-server": "^3.1.4"
36 | },
37 | "dependencies": {
38 | "babel-polyfill": "^6.26.0",
39 | "vue": "^2.5.16",
40 | "vuex": "^3.0.1",
41 | "vuex-connect": "^2.1.0"
42 | },
43 | "prettier": {
44 | "semi": false,
45 | "singleQuote": true
46 | },
47 | "browserslist": [
48 | "last 2 versions"
49 | ],
50 | "babel": {
51 | "presets": [
52 | "babel-preset-env"
53 | ]
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/atoms/base-button/index.vue:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 |
33 |
34 |
35 |
36 |
37 |
85 |
--------------------------------------------------------------------------------
/src/components/atoms/base-button/stories.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/vue'
2 |
3 | import { action } from '@storybook/addon-actions'
4 | import { withKnobs, text, select, boolean } from '@storybook/addon-knobs'
5 |
6 | import BaseButton from './'
7 |
8 | storiesOf('Atoms/base-button', module)
9 | .addDecorator(withKnobs)
10 | .add('A basic button', () => ({
11 | components: { BaseButton },
12 | methods: {
13 | click: action('click')
14 | },
15 | template: `${text(
20 | 'Text',
21 | 'Button'
22 | )}`
23 | }))
24 |
--------------------------------------------------------------------------------
/src/components/atoms/base-input/index.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
26 |
27 |
28 |
55 |
--------------------------------------------------------------------------------
/src/components/atoms/base-input/stories.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/vue'
2 |
3 | import { action } from '@storybook/addon-actions'
4 | import { withKnobs, text } from '@storybook/addon-knobs'
5 |
6 | import BaseInput from './'
7 |
8 | storiesOf('Atoms/base-input', module)
9 | .addDecorator(withKnobs)
10 | .add('A basic button', () => ({
11 | components: { BaseInput },
12 | methods: {
13 | input: action('input')
14 | },
15 | data() {
16 | return {
17 | value: ''
18 | }
19 | },
20 | template: ``
24 | }))
25 |
--------------------------------------------------------------------------------
/src/components/atoms/base-text/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
17 |
--------------------------------------------------------------------------------
/src/components/atoms/base-text/stories.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/vue'
2 |
3 | import { withKnobs, text } from '@storybook/addon-knobs'
4 |
5 | import BaseText from './'
6 |
7 | storiesOf('Atoms/base-text', module)
8 | .addDecorator(withKnobs)
9 | .add('A basic text', () => ({
10 | components: { BaseText },
11 | template: `${text('Text', 'Text')}`
12 | }))
13 |
--------------------------------------------------------------------------------
/src/components/atoms/base-title/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
--------------------------------------------------------------------------------
/src/components/atoms/base-title/stories.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/vue'
2 |
3 | import { withKnobs, text } from '@storybook/addon-knobs'
4 |
5 | import BaseTitle from './'
6 |
7 | storiesOf('Atoms/base-title', module)
8 | .addDecorator(withKnobs)
9 | .add('A title', () => ({
10 | components: { BaseTitle },
11 | template: `${text('Title', 'Title')}`
12 | }))
13 |
--------------------------------------------------------------------------------
/src/components/atoms/todo-row/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
20 |
21 |
--------------------------------------------------------------------------------
/src/components/atoms/todo-row/stories.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/vue'
2 |
3 | import TodoRow from './'
4 |
5 | storiesOf('Atoms/todo-row', module).add('A todo row container', () => ({
6 | components: { TodoRow },
7 | template: `
8 | foo
9 | bar
10 | `
11 | }))
12 |
--------------------------------------------------------------------------------
/src/components/molecules/new-todo/index.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
30 |
31 |
36 | Append
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/components/molecules/new-todo/stories.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/vue'
2 |
3 | import { action } from '@storybook/addon-actions'
4 | import { withKnobs, text } from '@storybook/addon-knobs'
5 |
6 | import NewTodo from './'
7 |
8 | storiesOf('Molecules/new-todo', module)
9 | .addDecorator(withKnobs)
10 | .add('Todo registration form', () => ({
11 | components: { NewTodo },
12 | methods: {
13 | append: action('append')
14 | },
15 | template: ``
16 | }))
17 |
--------------------------------------------------------------------------------
/src/components/molecules/todo/index.vue:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
24 | {{todo}}
25 | Done
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/components/molecules/todo/stories.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/vue'
2 |
3 | import { action } from '@storybook/addon-actions'
4 | import { withKnobs, text } from '@storybook/addon-knobs'
5 |
6 | import Todo from './'
7 |
8 | storiesOf('Molecules/todo', module)
9 | .addDecorator(withKnobs)
10 | .add('Todo component', () => ({
11 | components: { Todo },
12 | methods: {
13 | complete: action('complete')
14 | },
15 | template: ``
16 | }))
17 |
--------------------------------------------------------------------------------
/src/components/organisms/todo-list/index.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 | onComplete(t)"
27 | />
28 |
29 |
30 |
31 |
36 |
37 |
--------------------------------------------------------------------------------
/src/components/organisms/todo-list/stories.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/vue'
2 |
3 | import { action } from '@storybook/addon-actions'
4 |
5 | import TodoList from './'
6 |
7 | storiesOf('Organisms/todo-list', module).add('A list of todos', () => ({
8 | components: { TodoList },
9 | data() {
10 | return {
11 | todos: ['foo', 'bar', 'baz']
12 | }
13 | },
14 | methods: {
15 | complete: action('complete')
16 | },
17 | template: ``
18 | }))
19 |
--------------------------------------------------------------------------------
/src/components/pages/app/index.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/components/templates/app-layout/index.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
33 |
34 |
35 |
83 |
--------------------------------------------------------------------------------
/src/components/templates/app-layout/stories.js:
--------------------------------------------------------------------------------
1 | import { storiesOf } from '@storybook/vue'
2 |
3 | import AppLayout from './'
4 |
5 | storiesOf('Templates/app-layout', module).add('A layout of app', () => ({
6 | components: { AppLayout },
7 | template: `foo
bar`
8 | }))
9 |
--------------------------------------------------------------------------------
/src/containers/all-todos.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'vuex-connect'
2 |
3 | import TodoList from '~/components/organisms/todo-list'
4 |
5 | import { Todos, CompleteTodo } from '~/store/modules/todo'
6 |
7 | export default connect({
8 | gettersToProps: {
9 | todos: Todos
10 | },
11 | methodsToEvents: {
12 | complete: ({ dispatch }, todo) => dispatch(CompleteTodo, todo)
13 | }
14 | })('all-todos', TodoList)
15 |
--------------------------------------------------------------------------------
/src/containers/new-todo.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'vuex-connect'
2 |
3 | import NewTodo from '~/components/molecules/new-todo'
4 |
5 | import { AppendTodo } from '~/store/modules/todo'
6 |
7 | export default connect({
8 | methodsToEvents: {
9 | append: ({ dispatch }, todo) => dispatch(AppendTodo, todo)
10 | }
11 | })('new-todo', NewTodo)
12 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill'
2 |
3 | import Vue from 'vue'
4 |
5 | import App from '~/components/pages/app'
6 |
7 | import store from './store'
8 |
9 | new Vue({
10 | components: { App },
11 | el: '#app',
12 | render(h) {
13 | return h(App)
14 | },
15 | store
16 | })
17 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | import todoModule from './modules/todo'
5 |
6 | Vue.use(Vuex)
7 |
8 | const store = new Vuex.Store({
9 | modules: {
10 | todo: todoModule
11 | }
12 | })
13 |
14 | export default store
15 |
--------------------------------------------------------------------------------
/src/store/modules/todo/actionTypes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Append new todo.
3 | */
4 | export const AppendTodo = 'todo/APPEND_TODO'
5 |
6 | /**
7 | * Mark todo as completed.
8 | */
9 | export const CompleteTodo = 'todo/COMPLETE_TODO'
10 |
--------------------------------------------------------------------------------
/src/store/modules/todo/actions.js:
--------------------------------------------------------------------------------
1 | import { AppendTodo, CompleteTodo } from './actionTypes'
2 |
3 | import { SetTodos } from './mutationTypes'
4 |
5 | const actions = {
6 | async [AppendTodo]({ commit, state }, todo) {
7 | commit(SetTodos, [...state.todos, todo])
8 | },
9 | async [CompleteTodo]({ commit, state }, todo) {
10 | const updated = state.todos.filter(t => t !== todo)
11 |
12 | commit(SetTodos, updated)
13 | }
14 | }
15 |
16 | export default actions
17 |
--------------------------------------------------------------------------------
/src/store/modules/todo/getterTypes.js:
--------------------------------------------------------------------------------
1 | export const Todos = 'todo/TODOS'
2 |
--------------------------------------------------------------------------------
/src/store/modules/todo/getters.js:
--------------------------------------------------------------------------------
1 | import { Todos } from './getterTypes'
2 |
3 | const getters = {
4 | [Todos](state) {
5 | return state.todos
6 | }
7 | }
8 |
9 | export default getters
10 |
--------------------------------------------------------------------------------
/src/store/modules/todo/index.js:
--------------------------------------------------------------------------------
1 | import actions from './actions'
2 | import getters from './getters'
3 | import mutations from './mutations'
4 |
5 | const store = {
6 | state() {
7 | return {
8 | todos: []
9 | }
10 | },
11 | actions,
12 | getters,
13 | mutations
14 | }
15 |
16 | export default store
17 |
18 | export * from './actionTypes'
19 | export * from './getterTypes'
20 | export * from './mutationTypes'
21 |
--------------------------------------------------------------------------------
/src/store/modules/todo/mutationTypes.js:
--------------------------------------------------------------------------------
1 | export const SetTodos = 'todo/SET_TODOS'
2 |
--------------------------------------------------------------------------------
/src/store/modules/todo/mutations.js:
--------------------------------------------------------------------------------
1 | import { SetTodos } from './mutationTypes'
2 |
3 | const mutations = {
4 | [SetTodos](state, todos) {
5 | state.todos = todos
6 | }
7 | }
8 |
9 | export default mutations
10 |
--------------------------------------------------------------------------------