浏览代码

Vue integration (#2500)

* Basic setup

* Updated some documentation

* Cleanup some useless files

* Added docs to website

* Re-add package.json

* Added module

* Update vue.md

* Update DOCS.md

* Starting writing docs

* Fix my own spelling mistake

* Add a proper README and LICENSE

* Some quick fixes

* Update Dashboard.vue

* Added shallow-equal

* Try adding an example to test vue integration locally

* Temporarily fixed build and example

* Small documentation fixes

* Added Vue 3 example

* Added proper bundling and typescript for vue

* Added full typescript support for integration

All @uppy/vue components now support TypeScript and are strongly typed.

* Massive docs improvement for @uppy/vue

* Fix small type error in dashboard-modal

* Slight documentation changes

* Add typescript types file to package.json

* Trim down dependencies

* Fix building with Vue

I added it to a list of ignored packages as it builds a little weird

* Updated example

* Converted .vue files to .ts

* Setup .js files for @uppy/vue

* Fixed small rendering bug

* Delete package-lock.json

* Delete package-lock.json

* Delete package-lock.json

* Update packages/@uppy/vue/package.json

Co-authored-by: Artur Paikin <artur@arturpaikin.com>

* Some cleaning for @uppy/vue

* Delete components.d.ts

* Removed Vue 3 example

* Update typescript stuff for @uppy/vue

* Documentation update for @uppy/vue

I changed to menu text to 'Other Integrations'

Co-authored-by: Artur Paikin <artur@arturpaikin.com>
Andrew 4 年之前
父节点
当前提交
b082e54029

+ 1 - 1
bin/locale-packs.js

@@ -52,7 +52,7 @@ function buildPluginsList () {
   for (const file of files) {
     const dirName = path.dirname(file)
     const pluginName = path.basename(dirName)
-    if (pluginName === 'locales' || pluginName === 'react-native') {
+    if (pluginName === 'locales' || pluginName === 'react-native' || pluginName === 'vue') {
       continue
     }
     const Plugin = require(dirName)

+ 24 - 0
examples/vue/.gitignore

@@ -0,0 +1,24 @@
+.DS_Store
+node_modules
+/dist
+/public
+
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 24 - 0
examples/vue/README.md

@@ -0,0 +1,24 @@
+# uppy-vue
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Lints and fixes files
+```
+npm run lint
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 5 - 0
examples/vue/babel.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  presets: [
+    '@vue/cli-plugin-babel/preset'
+  ]
+}

+ 26 - 0
examples/vue/package.json

@@ -0,0 +1,26 @@
+{
+  "name": "@uppy-example/vue-example",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "start": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "core-js": "^3.6.5",
+    "shallow-equal": "^1.2.1",
+    "vue": "^2.6.11",
+    "@uppy/vue": "file:../../packages/@uppy/vue"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "~4.5.0",
+    "@vue/cli-service": "~4.5.0",
+    "vue-template-compiler": "^2.6.11"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not dead"
+  ]
+}

+ 106 - 0
examples/vue/src/App.vue

@@ -0,0 +1,106 @@
+<template>
+  <div id="app">
+    <!-- <HelloWorld msg="Welcome to Uppy Vue Demo"/> -->
+    <h1>Welcome to Uppy Vue Demo!</h1>
+    <h2>Inline Dashboard</h2>
+    <label>
+      <input
+        type="checkbox"
+        :checked="showInlineDashboard"
+        @change="(event) => {
+          showInlineDashboard = event.target.checked
+        }"
+      />
+      Show Dashboard
+    </label>
+    <dashboard
+      v-if="showInlineDashboard"
+      :uppy="uppy"
+      :props="{
+        metaFields: [{ id: 'name', name: 'Name', placeholder: 'File name' }]
+      }"
+    />
+    <h2>Modal Dashboard</h2>
+    <div>
+      <button @click="open = true">Show Dashboard</button>
+    <dashboard-modal
+      :uppy="uppy2" 
+      :open="open" 
+      :props="{
+        onRequestCloseModal: handleClose
+      }"
+    />
+    </div>
+
+    <h2>Drag Drop Area</h2>
+    <drag-drop 
+      :uppy="uppy"
+      :props="{
+        locale: {
+          strings: {
+            chooseFile: 'Boop a file',
+            orDragDrop: 'or yoink it here'
+          }
+        }
+      }"
+    />
+
+    <h2>Progress Bar</h2>
+    <progress-bar 
+      :uppy="uppy"
+      :props="{
+        hideAfterFinish: false
+      }"
+    />
+  </div>
+</template>
+
+<script>
+// import HelloWorld from './components/HelloWorld.vue'
+import Vue from 'vue'
+import Uppy from '@uppy/core'
+import Tus from '@uppy/tus'
+import { Dashboard, DashboardModal, DragDrop, ProgressBar } from '@uppy/vue'
+
+export default {
+  name: 'App',
+  components: {
+    Dashboard,
+    DashboardModal,
+    DragDrop,
+    ProgressBar
+  },
+  computed: {
+    uppy: () => new Uppy({ id: 'uppy1', autoProceed: true, debug: true })
+      .use(Tus, { endpoint: 'https://master.tus.io/files/' }),
+    uppy2: () => new Uppy({ id: 'uppy2', autoProceed: false, debug: true })
+      .use(Tus, { endpoint: 'https://master.tus.io/files/' }),
+  },
+  data () {
+    return {
+      open: false,
+      showInlineDashboard: false
+    }
+  },
+  methods: {
+    handleClose() { this.open = false }
+  },
+  
+}
+</script>
+<style src='@uppy/core/dist/style.css'></style> 
+<style src='@uppy/dashboard/dist/style.css'></style> 
+<style src='@uppy/drag-drop/dist/style.css'></style> 
+<style src='@uppy/progress-bar/dist/style.css'></style> 
+
+
+<style>
+#app {
+  font-family: Avenir, Helvetica, Arial, sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  text-align: center;
+  color: #2c3e50;
+  margin-top: 60px;
+}
+</style>

+ 58 - 0
examples/vue/src/components/HelloWorld.vue

@@ -0,0 +1,58 @@
+<template>
+  <div class="hello">
+    <h1>{{ msg }}</h1>
+    <p>
+      For a guide and recipes on how to configure / customize this project,<br>
+      check out the
+      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
+    </p>
+    <h3>Installed CLI Plugins</h3>
+    <ul>
+      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
+      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
+    </ul>
+    <h3>Essential Links</h3>
+    <ul>
+      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
+      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
+      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
+      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
+      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
+    </ul>
+    <h3>Ecosystem</h3>
+    <ul>
+      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
+      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
+      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
+      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
+      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
+    </ul>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'HelloWorld',
+  props: {
+    msg: String
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+h3 {
+  margin: 40px 0 0;
+}
+ul {
+  list-style-type: none;
+  padding: 0;
+}
+li {
+  display: inline-block;
+  margin: 0 10px;
+}
+a {
+  color: #42b983;
+}
+</style>

+ 8 - 0
examples/vue/src/main.js

@@ -0,0 +1,8 @@
+import Vue from 'vue'
+import App from './App.vue'
+
+Vue.config.productionTip = false
+
+new Vue({
+  render: h => h(App),
+}).$mount('#app')

+ 16 - 0
package-lock.json

@@ -15725,6 +15725,12 @@
       "integrity": "sha512-ccnYgKC0/hPSGXxj7Ju6AV/BP4HUkXC2u15mikXT5mX9YorEaoi1bEKOmAqdkJHN4EEkmAf97SpH66Try5Mbeg==",
       "dev": true
     },
+    "de-indent": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
+      "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
+      "dev": true
+    },
     "deasync": {
       "version": "0.1.20",
       "resolved": "https://registry.npmjs.org/deasync/-/deasync-0.1.20.tgz",
@@ -41418,6 +41424,16 @@
       "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
       "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
     },
+    "vue-template-compiler": {
+      "version": "2.6.12",
+      "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz",
+      "integrity": "sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==",
+      "dev": true,
+      "requires": {
+        "de-indent": "^1.0.2",
+        "he": "^1.1.0"
+      }
+    },
     "w3c-hr-time": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",

+ 2 - 1
package.json

@@ -204,7 +204,8 @@
     "watchify": "3.11.1",
     "webdriverio": "5.18.6",
     "webpack": "4.44.1",
-    "whatwg-fetch": "3.0.0"
+    "whatwg-fetch": "3.0.0",
+    "vue-template-compiler": "^2.6.11"
   },
   "scripts": {
     "bootstrap": "lerna bootstrap",

+ 26 - 0
packages/@uppy/vue/.gitignore

@@ -0,0 +1,26 @@
+.DS_Store
+node_modules
+/dist
+
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+lib
+*.vue.js

+ 21 - 0
packages/@uppy/vue/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2020 Transloadit
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 64 - 0
packages/@uppy/vue/README.md

@@ -0,0 +1,64 @@
+# @uppy/vue
+
+<img src="https://uppy.io/images/logos/uppy-dog-head-arrow.svg" width="120" alt="Uppy logo: a superman puppy in a pink suit" align="right">
+
+<a href="https://www.npmjs.com/package/@uppy/vue"><img src="https://img.shields.io/npm/v/@uppy/vue.svg?style=flat-square"></a>
+<a href="https://travis-ci.org/transloadit/uppy"><img src="https://img.shields.io/travis/transloadit/uppy/master.svg?style=flat-square" alt="Build Status"></a>
+
+Vue component wrappers around Uppy's officially maintained UI plugins.
+
+Uppy is being developed by the folks at [Transloadit](https://transloadit.com), a versatile file encoding service.
+
+## Example
+
+```vue
+<template>
+  <dashboard-modal 
+  :uppy="uppy" 
+  :open="open" 
+  :props="{
+    onRequestCloseModal: handleClose
+  }"/>
+</template>
+
+<script>
+import Uppy from '@uppy/core'
+import { DashboardModal } from '@uppy/vue'
+
+export default {
+  components: {
+    DashboardModal
+  },
+  computed: {
+    uppy: () => new Uppy()
+  },
+  data () {
+    return {
+      open: false
+    }
+  },
+  methods: {
+    handleClose() { this.open = false }
+  }
+}
+</script>
+```
+
+## Installation
+
+```bash
+$ npm install @uppy/vue
+```
+
+We recommend installing from npm and then using a module bundler such as [Webpack](https://webpack.js.org/), [Browserify](http://browserify.org/) or [Rollup.js](http://rollupjs.org/).
+
+Alternatively, you can also use this plugin in a pre-built bundle from Transloadit's CDN: Edgly. In that case `Uppy` will attach itself to the global `window.Uppy` object. See the [main Uppy documentation](https://uppy.io/docs/#Installation) for instructions.
+
+## Documentation
+
+Documentation for this plugin can be found on the [Uppy website](https://uppy.io/docs/vue).
+
+## License
+
+[The MIT License](./LICENSE).
+

+ 30 - 0
packages/@uppy/vue/package.json

@@ -0,0 +1,30 @@
+{
+  "name": "@uppy/vue",
+  "version": "0.0.1",
+  "private": false,
+  "main": "lib/index.js",
+  "types": "types/index.d.ts",
+  "peerDependencies": {
+    "@uppy/core": "^1.0.0",
+    "vue": ">=2.6.11"
+  },
+  "dependencies": {
+    "shallow-equal": "^1.2.1",
+    "@uppy/dashboard": "^1.12.5",
+    "@uppy/drag-drop": "^1.4.18",
+    "@uppy/progress-bar": "^1.3.18",
+    "@uppy/status-bar": "^1.7.5"
+  },
+  "devDependencies": {
+    "@uppy/core": "^1.0.0",
+    "@uppy/dashboard": "^1.12.5",
+    "@uppy/drag-drop": "^1.4.18",
+    "@uppy/progress-bar": "^1.3.18",
+    "@uppy/status-bar": "^1.7.5",
+    "typescript": "^4.0.3",
+    "vue": "^2.6.12"
+  },
+  "publishConfig": {
+    "access": "public"
+  }
+}

+ 76 - 0
packages/@uppy/vue/src/dashboard-modal.js

@@ -0,0 +1,76 @@
+import DashboardPlugin from '@uppy/dashboard'
+import { shallowEqualObjects } from 'shallow-equal'
+
+export default {
+  data () {
+    return {
+      plugin: {}
+    }
+  },
+  props: {
+    uppy: {
+      required: true
+    },
+    props: {
+      type: Object
+    },
+    plugins: {
+      type: Array
+    },
+    open: {
+      type: Boolean,
+      required: true
+    }
+  },
+  mounted () {
+    this.installPlugin()
+  },
+  methods: {
+    installPlugin () {
+      const uppy = this.uppy
+      const options = {
+        id: 'vue:DashboardModal',
+        plugins: this.plugins,
+        ...this.props,
+        target: this.$refs.container
+      }
+      uppy.use(DashboardPlugin, options)
+      this.plugin = uppy.getPlugin(options.id)
+      if (this.open) {
+        this.plugin.openModal()
+      }
+    },
+    uninstallPlugin (uppy) {
+      uppy.removePlugin(this.plugin)
+    }
+  },
+  beforeDestroy () {
+    this.uninstallPlugin(this.uppy)
+  },
+  watch: {
+    uppy (current, old) {
+      if (old !== current) {
+        this.uninstallPlugin(old)
+        this.installPlugin()
+      }
+    },
+    open (current, old) {
+      if (current && !old) {
+        this.plugin.openModal()
+      }
+      if (!current && old) {
+        this.plugin.closeModal()
+      }
+    },
+    props (current, old) {
+      if (!shallowEqualObjects(current, old)) {
+        this.plugin.setOptions({ ...current })
+      }
+    }
+  },
+  render (createElement) {
+    return createElement('div', {
+      ref: 'container'
+    })
+  }
+}

+ 62 - 0
packages/@uppy/vue/src/dashboard.js

@@ -0,0 +1,62 @@
+import DashboardPlugin from '@uppy/dashboard'
+import { shallowEqualObjects } from 'shallow-equal'
+
+export default {
+  data () {
+    return {
+      plugin: {}
+    }
+  },
+  props: {
+    uppy: {
+      required: true
+    },
+    props: {
+      type: Object
+    },
+    plugins: {
+      type: Array
+    }
+  },
+  mounted () {
+    this.installPlugin()
+  },
+  methods: {
+    installPlugin () {
+      const uppy = this.uppy
+      const options = {
+        id: 'vue:Dashboard',
+        inline: true,
+        plugins: this.plugins,
+        ...this.props,
+        target: this.$refs.container
+      }
+      uppy.use(DashboardPlugin, options)
+      this.plugin = uppy.getPlugin(options.id)
+    },
+    uninstallPlugin (uppy) {
+      uppy.removePlugin(this.plugin)
+    }
+  },
+  beforeDestroy () {
+    this.uninstallPlugin(this.uppy)
+  },
+  watch: {
+    uppy (current, old) {
+      if (old !== current) {
+        this.uninstallPlugin(old)
+        this.installPlugin()
+      }
+    },
+    props (current, old) {
+      if (!shallowEqualObjects(current, old)) {
+        this.plugin.setOptions({ ...current })
+      }
+    }
+  },
+  render (createElement) {
+    return createElement('div', {
+      ref: 'container'
+    })
+  }
+}

+ 57 - 0
packages/@uppy/vue/src/drag-drop.js

@@ -0,0 +1,57 @@
+import DragDropPlugin from '@uppy/drag-drop'
+import { shallowEqualObjects } from 'shallow-equal'
+
+export default {
+  data () {
+    return {
+      plugin: {}
+    }
+  },
+  props: {
+    uppy: {
+      required: true
+    },
+    props: {
+      type: Object
+    }
+  },
+  mounted () {
+    this.installPlugin()
+  },
+  methods: {
+    installPlugin () {
+      const uppy = this.uppy
+      const options = {
+        id: 'vue:DragDrop',
+        ...this.props,
+        target: this.$refs.container
+      }
+      uppy.use(DragDropPlugin, options)
+      this.plugin = uppy.getPlugin(options.id)
+    },
+    uninstallPlugin (uppy) {
+      uppy.removePlugin(this.plugin)
+    }
+  },
+  beforeDestroy () {
+    this.uninstallPlugin(this.uppy)
+  },
+  watch: {
+    uppy (current, old) {
+      if (old !== current) {
+        this.uninstallPlugin(old)
+        this.installPlugin()
+      }
+    },
+    props (current, old) {
+      if (!shallowEqualObjects(current, old)) {
+        this.plugin.setOptions({ ...current })
+      }
+    }
+  },
+  render (createElement) {
+    return createElement('div', {
+      ref: 'container'
+    })
+  }
+}

+ 5 - 0
packages/@uppy/vue/src/index.js

@@ -0,0 +1,5 @@
+export { default as Dashboard } from './dashboard'
+export { default as DashboardModal } from './dashboard-modal'
+export { default as DragDrop } from './drag-drop'
+export { default as ProgressBar } from './progress-bar'
+export { default as StatusBar } from './status-bar'

+ 57 - 0
packages/@uppy/vue/src/progress-bar.js

@@ -0,0 +1,57 @@
+import ProgressBarPlugin from '@uppy/progress-bar'
+import { shallowEqualObjects } from 'shallow-equal'
+
+export default {
+  data () {
+    return {
+      plugin: {}
+    }
+  },
+  props: {
+    uppy: {
+      required: true
+    },
+    props: {
+      type: Object
+    }
+  },
+  mounted () {
+    this.installPlugin()
+  },
+  methods: {
+    installPlugin () {
+      const uppy = this.uppy
+      const options = {
+        id: 'vue:ProgressBar',
+        ...this.props,
+        target: this.$refs.container
+      }
+      uppy.use(ProgressBarPlugin, options)
+      this.plugin = uppy.getPlugin(options.id)
+    },
+    uninstallPlugin (uppy) {
+      uppy.removePlugin(this.plugin)
+    }
+  },
+  beforeDestroy () {
+    this.uninstallPlugin(this.uppy)
+  },
+  watch: {
+    uppy (current, old) {
+      if (old !== current) {
+        this.uninstallPlugin(old)
+        this.installPlugin()
+      }
+    },
+    props (current, old) {
+      if (!shallowEqualObjects(current, old)) {
+        this.plugin.setOptions({ ...current })
+      }
+    }
+  },
+  render (createElement) {
+    return createElement('div', {
+      ref: 'container'
+    })
+  }
+}

+ 57 - 0
packages/@uppy/vue/src/status-bar.js

@@ -0,0 +1,57 @@
+import StatusBarPlugin from '@uppy/status-bar'
+import { shallowEqualObjects } from 'shallow-equal'
+
+export default {
+  data () {
+    return {
+      plugin: {}
+    }
+  },
+  props: {
+    uppy: {
+      required: true
+    },
+    props: {
+      type: Object
+    }
+  },
+  mounted () {
+    this.installPlugin()
+  },
+  methods: {
+    installPlugin () {
+      const uppy = this.uppy
+      const options = {
+        id: 'vue:StatusBar',
+        ...this.props,
+        target: this.$refs.container
+      }
+      uppy.use(StatusBarPlugin, options)
+      this.plugin = uppy.getPlugin(options.id)
+    },
+    uninstallPlugin (uppy) {
+      uppy.removePlugin(this.plugin)
+    }
+  },
+  beforeDestroy () {
+    this.uninstallPlugin(this.uppy)
+  },
+  watch: {
+    uppy (current, old) {
+      if (old !== current) {
+        this.uninstallPlugin(old)
+        this.installPlugin()
+      }
+    },
+    props (current, old) {
+      if (!shallowEqualObjects(current, old)) {
+        this.plugin.setOptions({ ...current })
+      }
+    }
+  },
+  render (createElement) {
+    return createElement('div', {
+      ref: 'container'
+    })
+  }
+}

+ 18 - 0
packages/@uppy/vue/types/dashboard-modal.d.ts

@@ -0,0 +1,18 @@
+import Vue from 'vue';
+import { Uppy, Plugin } from '@uppy/core';
+import * as DashboardPlugin from '@uppy/dashboard';
+interface Data {
+    plugin: DashboardPlugin;
+}
+interface Props {
+    uppy: Uppy;
+    props: Object;
+    plugins: Plugin[];
+    open: boolean;
+}
+interface Methods {
+    installPlugin(): void;
+    uninstallPlugin(uppy: Uppy): void;
+}
+declare const _default: import("vue/types/vue").ExtendedVue<Vue, Data, Methods, unknown, Props>;
+export default _default;

+ 17 - 0
packages/@uppy/vue/types/dashboard.d.ts

@@ -0,0 +1,17 @@
+import Vue from 'vue';
+import { Uppy, Plugin } from '@uppy/core';
+import * as DashboardPlugin from '@uppy/dashboard';
+interface Data {
+    plugin: DashboardPlugin;
+}
+interface Props {
+    uppy: Uppy;
+    props: Object;
+    plugins: Plugin[];
+}
+interface Methods {
+    installPlugin(): void;
+    uninstallPlugin(uppy: Uppy): void;
+}
+declare const _default: import("vue/types/vue").ExtendedVue<Vue, Data, Methods, unknown, Props>;
+export default _default;

+ 15 - 0
packages/@uppy/vue/types/drag-drop.d.ts

@@ -0,0 +1,15 @@
+import Vue from 'vue';
+import { Uppy, Plugin } from '@uppy/core';
+interface Data {
+    plugin: Plugin;
+}
+interface Props {
+    uppy: Uppy;
+    props: Object;
+}
+interface Methods {
+    installPlugin(): void;
+    uninstallPlugin(uppy: Uppy): void;
+}
+declare const _default: import("vue/types/vue").ExtendedVue<Vue, Data, Methods, unknown, Props>;
+export default _default;

+ 5 - 0
packages/@uppy/vue/types/index.d.ts

@@ -0,0 +1,5 @@
+export { default as Dashboard } from './dashboard';
+export { default as DashboardModal } from './dashboard-modal';
+export { default as DragDrop } from './drag-drop';
+export { default as ProgressBar } from './progress-bar';
+export { default as StatusBar } from './status-bar';

+ 1 - 0
packages/@uppy/vue/types/index.test-d.ts

@@ -0,0 +1 @@
+import { Dashboard, DashboardModal, DragDrop, StatusBar, ProgressBar } from './'

+ 15 - 0
packages/@uppy/vue/types/progress-bar.d.ts

@@ -0,0 +1,15 @@
+import Vue from 'vue';
+import { Uppy, Plugin } from '@uppy/core';
+interface Data {
+    plugin: Plugin;
+}
+interface Props {
+    uppy: Uppy;
+    props: Object;
+}
+interface Methods {
+    installPlugin(): void;
+    uninstallPlugin(uppy: Uppy): void;
+}
+declare const _default: import("vue/types/vue").ExtendedVue<Vue, Data, Methods, unknown, Props>;
+export default _default;

+ 15 - 0
packages/@uppy/vue/types/status-bar.d.ts

@@ -0,0 +1,15 @@
+import Vue from 'vue';
+import { Uppy, Plugin } from '@uppy/core';
+interface Data {
+    plugin: Plugin;
+}
+interface Props {
+    uppy: Uppy;
+    props: Object;
+}
+interface Methods {
+    installPlugin(): void;
+    uninstallPlugin(uppy: Uppy): void;
+}
+declare const _default: import("vue/types/vue").ExtendedVue<Vue, Data, Methods, unknown, Props>;
+export default _default;

+ 201 - 0
website/src/docs/vue.md

@@ -0,0 +1,201 @@
+---
+title: "Vue"
+type: docs
+module: "@uppy/vue"
+permalink: docs/vue/
+order: 0
+category: "Other Integrations"
+---
+
+Uppy provides [Vue][] components for the included UI plugins.
+
+Note: *All plugin names are in kebab-case for the HTML element, and in CamelCase for the JavaScript imports, following Vue conventions*
+
+## Installation
+
+All Vue components are provided through the `@uppy/vue` package
+
+Install from NPM:
+
+```shell
+npm install @uppy/vue
+# Or with yarn
+yarn add @uppy/vue
+```
+## CSS
+
+Make sure to also include the necessary CSS files for each Uppy Vue component you are using.
+
+## Usage
+
+The components can be used with [Vue][] and frameworks that use it, like [Nuxt][].
+
+Instead of adding a UI plugin to an Uppy instance with `.use()`, the Uppy instance can be passed into components as an `uppy` prop. 
+
+```html
+<template>
+  <div id="app">
+    <dashboard :uppy="uppy" :plugins="['Webcam']"/>
+  </div>
+</template>
+
+<script>
+import { Dashboard } from '@uppy/vue'
+
+import '@uppy/core/dist/style.css'
+import '@uppy/dashboard/dist/style.css'
+
+import Uppy from '@uppy/core'
+import Webcam from '@uppy/webcam'
+
+export default {
+  name: 'App',
+  components: {
+    Dashboard
+  },
+  computed: {
+    uppy: () => new Uppy().use(Webcam)
+  },
+  beforeDestroy () {
+    this.uppy.close()
+  }
+}
+</script>
+```
+
+The following plugins are available as Vue component wrappers:
+
+ - `<dashboard />` - renders an inline `@uppy/dashboard`
+ - `<dashboard-modal />` - renders a `@uppy/dashboard` modal
+ - `<drag-drop />` - renders a `@uppy/drag-drop` area
+ - `<progress-bar />` - renders a `@uppy/progress-bar`
+ - `<status-bar />` - renders a `@uppy/status-bar`
+
+Each component takes a `props` prop that will be passed to the UI Plugin. Both `@uppy/dashboard` based plugins also take a `plugins` array as a props, make it easy to add your plugins. 
+
+### Initializing Uppy
+
+The easiest way to initialize Uppy is creating a new instance in your `data` or `computed` and to run `uppy.close()` in the `beforeDestroy` method. You can do additional configuration with plugins where-ever you're initializing Uppy
+
+```js
+import Uppy from '@uppy/core'
+import Webcam from '@uppy/webcam'
+
+import '@uppy/core/dist/style.css'
+import '@uppy/dashboard/dist/style.css'
+
+export default {
+  computed: {
+    uppy: () => new Uppy().use(Webcam, {
+    // Config
+    })
+  },
+  beforeDestroy () {
+    this.uppy.close()
+  }
+}
+```
+
+## Components
+
+### `<dashboard />` 
+  
+#### CSS
+
+The `Dashboard` component requires the following CSS for styling:
+
+```html
+<style src='@uppy/core/dist/style.css'></style> 
+<style src='@uppy/dashboard/dist/style.css'></style> 
+```
+
+Import general Core styles from `@uppy/core/dist/style.css` first, then add the Dashboard styles from `@uppy/dashboard/dist/style.css`. A minified version is also available as `style.min.css` at the same path. The way to do import depends on your build system. With default Vue, you can just add a `style` tag and make the `src` attribute the file you need.
+
+⚠️ The `@uppy/dashboard` plugin includes CSS for the Dashboard itself, and the various plugins used by the Dashboard, such as ([`@uppy/status-bar`](/docs/status-bar) and [`@uppy/informer`](/docs/informer)). If you also use the `@uppy/status-bar` or `@uppy/informer` plugin directly, you should not include their CSS files, but instead only use the one from the `@uppy/dashboard` plugin.
+
+Styles for Provider plugins, like Google Drive and Instagram, are also bundled with Dashboard styles. Styles for other plugins, such as `@uppy/url` and `@uppy/webcam`, are not included. If you are using those, please see their docs and make sure to include styles for them as well.
+
+#### Props
+
+The `<dashboard />` component supports all `@uppy/dashboard` options to be passed as an object on the `props` prop. An Uppy instance must be provided in the `:uppy=''` prop. 
+
+The `<dashboard />` cannot be passed to a `target:` option of a remote provider or plugins such as [`@uppy/webcam`][]. To use other plugins like [`@uppy/webcam`][] with the `<dashboard />` component, first add them to the Uppy instance, and then specify their `id` in the [`plugins`](/docs/dashboard/#plugins) prop:
+
+### `<dashboard-modal />` 
+  
+#### CSS
+
+The `DashboardModal` component requires the following CSS for styling:
+
+```html
+<style src='@uppy/core/dist/style.css'></style> 
+<style src='@uppy/dashboard/dist/style.css'></style> 
+```
+
+Import general Core styles from `@uppy/core/dist/style.css` first, then add the Dashboard styles from `@uppy/dashboard/dist/style.css`. A minified version is also available as `style.min.css` at the same path. The way to do import depends on your build system. With default Vue, you can just add a `style` tag and make the `src` attribute the file you need.
+
+⚠️ The `@uppy/dashboard` plugin includes CSS for the Dashboard itself, and the various plugins used by the Dashboard, such as ([`@uppy/status-bar`](/docs/status-bar) and [`@uppy/informer`](/docs/informer)). If you also use the `@uppy/status-bar` or `@uppy/informer` plugin directly, you should not include their CSS files, but instead only use the one from the `@uppy/dashboard` plugin.
+
+Styles for Provider plugins, like Google Drive and Instagram, are also bundled with Dashboard styles. Styles for other plugins, such as `@uppy/url` and `@uppy/webcam`, are not included. If you are using those, please see their docs and make sure to include styles for them as well.
+
+#### Props
+
+The `<dashboard-modal />` component supports all `@uppy/dashboard` options to be passed as an object on the `props` prop. An Uppy instance must be provided in the `:uppy=''` prop. 
+
+The `<dashboard-modal />` cannot be passed to a `target:` option of a remote provider or plugins such as [`@uppy/webcam`][]. To use other plugins like [`@uppy/webcam`][] with the `<dashboard-modal />` component, first add them to the Uppy instance, and then specify their `id` in the [`plugins`](/docs/dashboard/#plugins) prop:
+
+### `<drag-drop />`
+
+#### CSS
+
+The `DragDrop` component includes some simple styles, like shown in the [example](/examples/dragdrop). You can also choose not to use it and provide your own styles instead:
+
+```html
+<style src='@uppy/core/dist/style.css'></style> 
+<style src='@uppy/drag-drop/dist/style.css'></style> 
+```
+
+Import general Core styles from `@uppy/core/dist/style.css` first, then add the Drag & Drop styles from `@uppy/drag-drop/dist/style.css`. A minified version is also available as `style.min.css` at the same path. The way to do import depends on your build system.
+
+#### Props
+
+The `<drag-drop />` component supports all `@uppy/drag-drop` options to be passed as an object on the `props` prop. An Uppy instance must be provided in the `:uppy=''` prop. 
+
+### `<progress-bar />`
+
+#### CSS
+
+The `ProgressBar` plugin requires the following CSS for styling:
+
+```html
+<style src='@uppy/core/dist/style.css'></style> 
+<style src='@uppy/progress-bar/dist/style.css'></style> 
+```
+
+Import general Core styles from `@uppy/core/dist/style.css` first, then add the Progress Bar styles from `@uppy/progress-bar/dist/style.css`. A minified version is also available as `style.min.css` at the same path. The way to do import depends on your build system.
+
+#### Props
+
+The `<progress-bar />` component supports all `@uppy/progress-bar` options to be passed as an object on the `props` prop. An Uppy instance must be provided in the `:uppy=''` prop. 
+
+### `<status-bar />`
+
+#### CSS
+
+The `StatusBar` plugin requires the following CSS for styling:
+
+```html
+<style src='@uppy/core/dist/style.css'></style> 
+<style src='@uppy/status-bar/dist/style.css'></style> 
+```
+
+Import general Core styles from `@uppy/core/dist/style.css` first, then add the Status Bar styles from `@uppy/status-bar/dist/style.css`. A minified version is also available as `style.min.css` at the same path. The way to do import depends on your build system.
+
+#### Props
+
+The `<status-bar />` component supports all `@uppy/status-bar` options to be passed as an object on the `props` prop. An Uppy instance must be provided in the `:uppy=''` prop. 
+
+[Vue]: https://vuejs.org
+[Nuxt]: https://nuxtjs.org
+
+[`@uppy/webcam`]: /docs/webcam/

+ 1 - 0
website/themes/uppy/layout/partials/docs_menu.ejs

@@ -11,6 +11,7 @@
       { category: 'Miscellaneous', path: 'docs/form/', link: false },
       { category: 'Community Projects', path: 'docs/community-projects/', link: true },
       { category: 'React', path: 'docs/react/', link: true },
+      { category: 'Other Integrations', path: 'docs/vue/', link: true },
       { category: 'Contributing', path: 'docs/contributing/', link: true },
     ]
   } else if (page.type === 'examples') {