├── .gitignore ├── README.md ├── babel.config.js ├── package.json ├── packages └── m16y │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── build │ ├── build.prod.js │ └── webpack.config.js │ ├── package.json │ ├── publish.sh │ └── src │ ├── components │ ├── AccessibilityControls.vue │ └── ImageWrapper.vue │ └── index.js ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── logo.png │ └── logo.svg ├── main.js ├── plugins │ └── vuetify.js ├── router.js └── views │ ├── About.vue │ └── Home.vue └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | package-lock.json 6 | yarn-lock.json 7 | yarn.lock 8 | 9 | # local env files 10 | .env.local 11 | .env.*.local 12 | 13 | # Log files 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw* 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-accessibility-widget 2 | Vue plugin for m16y - media accessibility for visually impaired user support. 3 | 4 | Demo: [https://vue-accessibility-demo.netlify.com/](https://vue-accessibility-demo.netlify.com/) 5 | 6 | It will inject a small widget with basic settings to for helping visually impaired user, including: 7 | 1. Low visions (contrast sensitivy, brightness sensitivity, etc) 8 | 2. Color blindness (texture support) 9 | 10 | **_Coming soon_** 11 | - Auto modify all image tags when plugin is enabled. 12 | - Warm lighting mode 13 | 14 | 15 | ## Functionalities 16 | ![screenshot of plugin UI](https://res.cloudinary.com/mayashavin/image/upload/w_250/v1550135241/Screen_Shot_2019-02-14_at_10.05.40.png) 17 | 18 | ### Brightness control 19 | Allow user to change the brightness of the whole app. 20 | 21 | Default: `100%` 22 | 23 | # Contrast control 24 | Allow user to change the contrsat of the whole app. 25 | 26 | Default: `100%` 27 | 28 | ### Dark mode (Night mode) 29 | Allow user to switch the app to dark theme, which is easier to read. 30 | 31 | Default: `false` 32 | 33 | ### Color blind mode 34 | * Allow user to enable color blind mode for images throughout the app. It will add texture to differentiate similar colors (red-green). 35 | 36 | * Currently only works when image is rendered using `image-wrapper` component. 37 | 38 | Default: `false` 39 | 40 | ### Grayscale mode 41 | Allow user to switch the app to grayscale color theme. 42 | 43 | ## How to use 44 | 45 | ### Install the plugin 46 | - Add the plugin to Vue using 47 | 48 | ### Getting started 👩‍💻 49 | - Add the plugin to Vue using 50 | 51 | ``` 52 | import M16yPlugin from 'vue-accessibility-widget'; 53 | 54 | Vue.use(M16yPlugin); 55 | ``` 56 | 57 | - Use Cloudinary plugins to get all other cool accessibility effect: 58 | ``` 59 | import M16yPlugin from 'vue-accessibility-widget'; 60 | 61 | Vue.use(M16yPlugin, { 62 | plugins: { 63 | Cloudinary: { 64 | cloudName: //if you want to use Cloudinary 65 | } 66 | } 67 | }); 68 | ``` 69 | 70 | - In `App.vue` simple add attribute `v-m16y-ctrls` 71 | ``` 72 |
73 | 12 |
13 | 14 | 17 |
18 |
19 | 20 | 23 |
24 |
25 | 26 | 30 |
31 |
32 | 33 | 37 |
38 |
39 | 40 | 44 |
45 | 51 |
52 | 53 | 54 | 55 | 112 | 130 | 199 | -------------------------------------------------------------------------------- /packages/m16y/src/components/ImageWrapper.vue: -------------------------------------------------------------------------------- 1 | 4 | 56 | -------------------------------------------------------------------------------- /packages/m16y/src/index.js: -------------------------------------------------------------------------------- 1 | import 'material-design-icons'; 2 | import AccessibilityControls from './components/AccessibilityControls.vue'; 3 | import ImageWrapper from './components/ImageWrapper.vue'; 4 | import Cloudinary from 'cloudinary-vue'; 5 | /* eslint-disable*/ 6 | 7 | const defaults = { 8 | data: { 9 | colorBlind: false, 10 | nightMode: false, 11 | grayscale: false, 12 | brightness: 100, 13 | contrast: 100, 14 | changeBrightness(percent) { 15 | document.body.style.setProperty('--brightness', percent); 16 | }, 17 | changeContrast(percent) { 18 | document.body.style.setProperty('--contrast', percent); 19 | }, 20 | toogleGrayscaleMode(isGrayscaleOn) { 21 | document.body.style.setProperty('--grayscale', isGrayscaleOn ? 1 : 0); 22 | }, 23 | switchNightMode(isDarkMode) { 24 | this.nightMode = isDarkMode; 25 | document.body.style.setProperty('--invert', isDarkMode ? 1 : 0); 26 | }, 27 | supportColorBlind(isColorBlind) { 28 | this.colorBlind = isColorBlind; 29 | }, 30 | }, 31 | } 32 | 33 | const registerComponents = (components) => { 34 | if (components) { 35 | for (var key in components) { 36 | const component = components[key]; 37 | 38 | // TODO - check if the component is registered 39 | if (component) { 40 | Vue.component(key, component); 41 | return true; 42 | } 43 | } 44 | } 45 | 46 | return false; 47 | } 48 | 49 | /** 50 | * Steps to do: 51 | * 1. Check if there is more than one Vue instance installed 52 | * 2. Remove vuetify!!!! 53 | * 3. Options - what accessibility controls should be included - ✅(partial) 54 | * 4. Element to attached 55 | * 5. Separate CSS 56 | * 6. UI? 57 | * 7. Can we make imageWrapper to directive instead of component also? 58 | */ 59 | const M16yPlugin = { 60 | install: function install(Vue) { 61 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 62 | 63 | if (this.installed) return; 64 | 65 | this.installed = true; 66 | let data = {}; 67 | 68 | if (options.configurations) { 69 | 70 | // TODO - customize the configuration panel of widget 71 | for (var key in options.configurations) { 72 | const configuration = options.configurations[key]; 73 | 74 | if (configuration) { 75 | data = { 76 | ...data, 77 | [key]: defaults.data[key], 78 | }; 79 | } 80 | 81 | } 82 | } 83 | else { 84 | data = defaults.data; 85 | } 86 | 87 | const root = new Vue({ 88 | data: data, 89 | render: createElement => createElement(AccessibilityControls), 90 | }); 91 | 92 | root.$on('brightness', data.changeBrightness); 93 | root.$on('contrast', data.changeContrast); 94 | root.$on('grayscale', data.toogleGrayscaleMode); 95 | root.$on('nightMode', data.switchNightMode); 96 | root.$on('colorBlind', data.supportColorBlind); 97 | 98 | if (options.plugins && options.plugins.Cloudinary) { 99 | Vue.use(Cloudinary, { 100 | ...options.plugins.Cloudinary, 101 | }); 102 | } 103 | 104 | // Only register external components 105 | const registeredComps = registerComponents(options.components); 106 | 107 | if (!registeredComps) { 108 | Vue.component('imageWrapper', ImageWrapper); 109 | } 110 | 111 | const directiveName = options.directiveName || "m16yCtrls"; 112 | 113 | Vue.directive(directiveName, { 114 | bind(el) { 115 | el.classList.add(`m16y-control--enabled`); 116 | root.$mount(el.appendChild(document.createElement('div'))); 117 | } 118 | }); 119 | 120 | Vue.prototype.$m16y = root; 121 | }, 122 | version: '0.0.6', 123 | }; 124 | 125 | let GlobalVue = null; 126 | 127 | if (typeof window !== 'undefined') { 128 | GlobalVue = window.Vue; 129 | } else if (typeof global !== 'undefined') { 130 | GlobalVue = global.Vue; 131 | } 132 | if (GlobalVue) { 133 | GlobalVue.use(M16yPlugin); 134 | } 135 | 136 | export default M16yPlugin; 137 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayashavin/vue-m16y/6c4c6dff27bd7534b281085c99045caf92b74645/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | The M16y Media Accessibility Project 9 | 10 | 11 | 12 | 13 | 16 |
17 | 18 | 19 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mayashavin/vue-m16y/6c4c6dff27bd7534b281085c99045caf92b74645/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | Artboard 46 2 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import './plugins/vuetify'; 3 | import App from './App.vue'; 4 | import M16y from 'vue-accessibility-widget'; 5 | import router from './router' 6 | 7 | Vue.use(M16y, { 8 | plugins: { 9 | Cloudinary: { 10 | configuration: { 11 | cloudName: 'mayashavin', 12 | } 13 | } 14 | } 15 | }); 16 | 17 | Vue.config.productionTip = false 18 | 19 | new Vue({ 20 | router, 21 | render: h => h(App) 22 | }).$mount('#app') 23 | -------------------------------------------------------------------------------- /src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuetify from 'vuetify/lib' 3 | import { VApp, VSlider, VSwitch, VIcon, VBtn, VRadio, VRadioGroup } from 'vuetify/lib/components'; 4 | import 'vuetify/src/stylus/app.styl' 5 | 6 | Vue.use(Vuetify, { 7 | iconfont: 'md', 8 | components: { 9 | VApp, 10 | VSlider, 11 | VSwitch, 12 | VIcon, 13 | VBtn, 14 | VRadio, 15 | VRadioGroup, 16 | }, 17 | }) 18 | -------------------------------------------------------------------------------- /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: '/about', 18 | name: 'about', 19 | // route level code-splitting 20 | // this generates a separate chunk (about.[hash].js) for this route 21 | // which is lazy-loaded when the route is visited. 22 | component: () => import(/* webpackChunkName: "about" */ './views/About.vue') 23 | } 24 | ] 25 | }) 26 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 54 | --------------------------------------------------------------------------------