├── src
├── css
│ ├── _theme.css
│ ├── card.css
│ ├── index.css
│ ├── _variables.css
│ ├── tabs.css
│ ├── navbar.css
│ ├── button.css
│ ├── radio.css
│ ├── _ios.css
│ └── _android.css
├── ui
│ ├── GeoCard.vue
│ ├── ProgressButton.vue
│ ├── MenuButton.vue
│ ├── RangeSlider.vue
│ ├── Toggle.vue
│ └── Avatar.vue
├── view
│ ├── List.vue
│ ├── Nav.vue
│ ├── Tabs.vue
│ ├── Scroll.vue
│ ├── SideMenus.vue
│ └── Overlay.vue
└── index.js
├── .gitignore
├── app.js
├── variables.js
├── README.md
├── package.json
└── webpack.config.js
/src/css/_theme.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/css/card.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ui/GeoCard.vue:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/view/List.vue:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/view/Nav.vue:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/view/Tabs.vue:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | dist/
4 | public/
5 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | var Vue = require('vue')
2 | Vue.use(require('./src/'))
3 | window.Vue = Vue
4 |
--------------------------------------------------------------------------------
/src/css/index.css:
--------------------------------------------------------------------------------
1 | @import "_variables.css";
2 | @import "button.css";
3 | @import "radio.css";
4 | @import "navbar.css";
5 | @import "tabs.css";
6 |
7 | @import "_android.css";
8 | @import "_ios.css";
9 |
--------------------------------------------------------------------------------
/src/css/_variables.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --vui-green: #42b983;
3 | --ios-green: #4cd964;
4 | --ios-blue: #027AFB;
5 | --android-green: #009A57;
6 | --menu-button-color: #666;
7 | --range-slider-color: #027AFB;
8 | }
9 |
--------------------------------------------------------------------------------
/variables.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'vui-green': '#42b983',
3 | 'menu-button-color': '#666',
4 | 'range-slider-color': '#027AFB',
5 | 'ios-green': '#4cd964',
6 | 'ios-blue': '#027AFB',
7 | 'android-green': '#009A57',
8 | }
9 |
--------------------------------------------------------------------------------
/src/css/tabs.css:
--------------------------------------------------------------------------------
1 | .v-tabs {
2 | display: flex;
3 | justify-content: center;
4 | color: #454545;
5 | background: #f7f7f7;
6 | border-top: 1px solid #ddd;
7 | height: 49px;
8 | line-height: 49px;
9 | }
10 | .v-tabs .v-tabs_item {
11 | flex: 1 1 0;
12 | overflow: hidden;
13 | white-space: nowrap;
14 | color: inherit;
15 | font-size: 14px;
16 | font-weight: 400;
17 | text-decoration: none;
18 | text-align: center;
19 | height: 100%;
20 | }
21 |
--------------------------------------------------------------------------------
/src/css/navbar.css:
--------------------------------------------------------------------------------
1 | .v-navbar {
2 | display: flex;
3 | padding: 5px;
4 | border-top: 1px solid transparent;
5 | border-bottom: 1px solid #ddd;
6 | line-height: 43px;
7 | justify-content: space-between;
8 | align-content: center;
9 | }
10 | .v-navbar .v-navbar_title {
11 | flex: 1;
12 | text-align: center;
13 | align-items: center;
14 | font-size: 17px;
15 | font-weight: 500;
16 | }
17 | .v-navbar .v-navbar_left {
18 | align-items: flex-start;
19 | margin: auto;
20 | }
21 | .v-navbar .v-navbar_right {
22 | align-items: flex-end;
23 | margin: auto;
24 | }
25 | .v-navbar .v-button {
26 | padding: 4px 12px;
27 | font-weight: 400;
28 | -webkit-font-smoothing: initial;
29 | }
30 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 |
2 | var Avatar = require('./ui/Avatar.vue')
3 | var Toggle = require('./ui/Toggle.vue')
4 | var MenuButton = require('./ui/MenuButton.vue')
5 | var ProgressButton = require('./ui/ProgressButton.vue')
6 | var RangeSlider = require('./ui/RangeSlider.vue')
7 |
8 | var Scroll = require('./view/Scroll.vue')
9 | var SideMenus = require('./view/SideMenus.vue')
10 |
11 | exports.install = function(Vue) {
12 | // register ui components
13 | Vue.component('vui-avatar', Avatar)
14 | Vue.component('vui-toggle', Toggle)
15 | Vue.component('vui-menu-button', MenuButton)
16 | Vue.component('vui-progress-button', ProgressButton)
17 | Vue.component('vui-range-slider', RangeSlider)
18 |
19 | // register views
20 | Vue.component('vui-scroll', Scroll)
21 | Vue.component('vui-side-menus', SideMenus)
22 | }
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VUI
2 |
3 | A UI framework for mobile, built with Vue.
4 |
5 | [](https://lepture.herokuapp.com/?amount=1000&reason=lepture%2Fvui)
6 |
7 |
8 | ## Design Pattern
9 |
10 | VUI provides components that look like native UI in iOS, Android and
11 | Windows UI. However, VUI will not build those animations as in Material
12 | design.
13 |
14 |
15 | ## Development
16 |
17 | Clone site for preview:
18 |
19 | $ git clone git@github.com:vui/vui.github.io.git public
20 |
21 | Generate site in `public` directory with `jekyll`:
22 |
23 | $ cd public
24 | $ jekyll build -w
25 |
26 | Serve assets with webpack dev server:
27 |
28 | $ npm start
29 |
30 | Now visit `http://localhost:9090/ui/index.html`. **Don't forget /index.html**.
31 |
--------------------------------------------------------------------------------
/src/css/button.css:
--------------------------------------------------------------------------------
1 | .v-button, button.v-button, a.v-button {
2 | display: inline-block;
3 | background-color: white;
4 | border: 1px solid #ddd;
5 | border-radius: 3px;
6 | color: #999;
7 | padding: 6px 12px;
8 | font: 600 normal 13px/24px sans-serif;
9 | cursor: pointer;
10 | text-decoration: none;
11 | outline: none;
12 | box-sizing: border-box;
13 | text-align: center;
14 | }
15 | .v-button {
16 | margin: 4px 2px;
17 | }
18 | .v-button:hover {
19 | opacity: 0.8;
20 | }
21 | .v-button:active {
22 | box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.08) inset, 0 0 4px rgba(0, 0, 0, 0.1) inset;
23 | }
24 | .v-button[disabled],
25 | .v-button[disabled]:hover {
26 | opacity: 0.68;
27 | cursor: not-allowed;
28 | }
29 | .v-button[disabled]:active {
30 | box-shadow: none;
31 | }
32 | .v-button.v-button--primary {
33 | background-color: var(--vui-green);
34 | color: white;
35 | border-color: transparent;
36 | }
37 | .v-button.v-button--block {
38 | display: block;
39 | width: 100%;
40 | margin: 10px 0;
41 | }
42 |
--------------------------------------------------------------------------------
/src/css/radio.css:
--------------------------------------------------------------------------------
1 | label.v-radio,
2 | .v-radio label {
3 | -webkit-tap-highlight-color: transparent;
4 | }
5 | .v-radio {
6 | vertical-align: middle;
7 | }
8 | .v-radio input {
9 | -webkit-appearance: none;
10 | margin: 0 8px 0 2px;
11 | line-height: 1;
12 | outline: none;
13 | border: none;
14 | background: none;
15 | vertical-align: middle;
16 | }
17 | .v-radio input:before {
18 | position: relative;
19 | display: inline-block;
20 | content: '';
21 | width: 1em;
22 | height: 0.4em;
23 | border: 2px solid #666;
24 | border-top: 0;
25 | border-right: 0;
26 | transform: rotate3d(0, 0, 1, -45deg) translate3d(4px, 0, 0);
27 | opacity: 0.18;
28 | }
29 | .v-radio input:checked:before {
30 | opacity: 1;
31 | }
32 |
33 | /* radio group */
34 | ul.v-radio {
35 | list-style-type: none;
36 | padding: 0;
37 | margin: 0;
38 | }
39 | .v-radio li {
40 | border-bottom: 1px solid #ddd;
41 | margin-left: 2em;
42 | padding: 0.6em 0 0.4em;
43 | }
44 | .v-radio li:last-of-type {
45 | border-bottom: 0;
46 | }
47 | .v-radio li input {
48 | margin-left: -2.2em;
49 | }
50 |
--------------------------------------------------------------------------------
/src/ui/ProgressButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
35 |
36 |
50 |
--------------------------------------------------------------------------------
/src/view/Scroll.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
43 |
44 |
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vui",
3 | "version": "0.1.0-alpha1",
4 | "description": "UI framework for mobile built with vue.",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --inline --hot",
8 | "build": "NODE_ENV=production webpack --progress --hide-modules",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git://github.com/lepture/vui.git"
14 | },
15 | "keywords": [
16 | "UI",
17 | "Mobile",
18 | "Vue"
19 | ],
20 | "author": "Hsiaoming Yang ",
21 | "license": "MIT",
22 | "devDependencies": {
23 | "autoprefixer": "^6.3.1",
24 | "babel-core": "^6.9.1",
25 | "babel-loader": "^6.1.0",
26 | "babel-plugin-transform-runtime": "^6.9.0",
27 | "babel-preset-es2015": "^6.9.0",
28 | "babel-runtime": "^6.9.2",
29 | "css-loader": "^0.23.0",
30 | "extract-text-webpack-plugin": "^1.0.1",
31 | "postcss": "^5.0.14",
32 | "postcss-css-variables": "^0.5.1",
33 | "postcss-import": "^8.1.2",
34 | "postcss-loader": "^0.9.1",
35 | "vue": "^1.0.24",
36 | "vue-hot-reload-api": "^1.3.2",
37 | "vue-html-loader": "^1.1.0",
38 | "vue-loader": "^8.5.2",
39 | "vue-style-loader": "^1.0.0",
40 | "webpack": "^1.13.1",
41 | "webpack-dev-server": "^1.14.1"
42 | },
43 | "peerDependencies": {
44 | "vue": "^1.0.7"
45 | },
46 | "dependencies": {
47 | "iscroll": "^5.1.3",
48 | "word-color": "^1.2.0"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/ui/MenuButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
17 |
18 |
72 |
--------------------------------------------------------------------------------
/src/view/SideMenus.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
23 |
24 |
71 |
--------------------------------------------------------------------------------
/src/ui/RangeSlider.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
30 |
31 |
79 |
--------------------------------------------------------------------------------
/src/ui/Toggle.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
21 |
22 |
85 |
--------------------------------------------------------------------------------
/src/css/_ios.css:
--------------------------------------------------------------------------------
1 | /** Toggle */
2 | .ios .v-toggle {
3 | width: 51px;
4 | }
5 | .ios .v-toggle--checked.v-toggle {
6 | background: var(--ios-green);
7 | border-color: var(--ios-green);
8 | }
9 | .ios .v-toggle .v-toggle_handle {
10 | box-shadow: 0 2px 7px rgba(0, 0, 0, 0.35), 0 1px 1px rgba(0, 0, 0, 0.15);
11 | }
12 | .ios .v-toggle--checked .v-toggle_handle {
13 | transform: translate3d(20px, 0, 0);
14 | }
15 | .ios .v-toggle:before {
16 | content: '';
17 | position: absolute;
18 | display: block;
19 | left: 0;
20 | top: 0;
21 | width: 100%;
22 | height: 100%;
23 | border-radius: 20px;
24 | background: #e6e6e6;
25 | transition: opacity 0.3s;
26 | }
27 | .ios .v-toggle--checked:before {
28 | opacity: 0;
29 | }
30 | .ios .v-toggle:after {
31 | content: '';
32 | position: absolute;
33 | display: block;
34 | left: 0;
35 | top: 0;
36 | width: 100%;
37 | height: 100%;
38 | background: white;
39 | border-radius: 20px;
40 | transform: scale3d(1, 1, 1);
41 | transition: transform 0.3s;
42 | }
43 | .ios .v-toggle--checked:after {
44 | transform: scale3d(0, 0, 0);
45 | opacity: 1;
46 | }
47 |
48 |
49 | /** Range Slider */
50 | .ios .v-range-slider input::-webkit-slider-runnable-track {
51 | height: 1px;
52 | }
53 | .ios .v-range-slider input::-webkit-slider-thumb:before {
54 | background: var(--ios-blue);
55 | height: 1px;
56 | }
57 | .ios .v-range-slider input::-webkit-slider-thumb {
58 | box-shadow: 0 0 1px rgba(0,0,0,.3), 0 3px 5px rgba(0,0,0,0.2);
59 | }
60 |
61 | /** Radio Group */
62 | .ios .v-radio input:before {
63 | opacity: 0;
64 | }
65 | .ios .v-radio li {
66 | border-bottom: 1px solid #ddd;
67 | margin-left: 2em;
68 | padding: 0.6em 0 0.4em;
69 | }
70 | .ios .v-radio li:last-of-type {
71 | border-bottom: 0;
72 | }
73 | .ios .v-radio li input {
74 | margin-left: -2.2em;
75 | }
76 | .ios .v-radio input:checked:before {
77 | opacity: 1;
78 | border-color: var(--ios-blue);
79 | }
80 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack')
2 | var ExtractTextPlugin = require('extract-text-webpack-plugin')
3 |
4 | var cssLoader = 'vue-style!css?sourceMap!postcss'
5 | if (process.env.NODE_ENV === 'production') {
6 | cssLoader = ExtractTextPlugin.extract('vue-style', 'css?-reduceTransforms!postcss')
7 | }
8 |
9 | var contentBase = __dirname + '/public'
10 | var pkg = require('./package.json')
11 |
12 | var plugins = [new webpack.DefinePlugin({
13 | 'process.env': {
14 | NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development')
15 | }
16 | })]
17 |
18 |
19 | var options = {
20 | entry: {
21 | app: [
22 | './app.js',
23 | './src/css/index.css'
24 | ],
25 | },
26 |
27 | output: {
28 | path: contentBase + '/assets',
29 | filename: 'app.js',
30 | },
31 |
32 | module: {
33 | loaders: [
34 | {test: /\.vue$/, loader: 'vue'},
35 | {test: /\.css$/, loader: cssLoader},
36 | ]
37 | },
38 |
39 | vue: {
40 | loaders: {
41 | css: cssLoader,
42 | }
43 | },
44 |
45 | postcss: function (pack) {
46 | // use webpack context
47 | return [
48 | require('postcss-import')({path: './src/css', addDependencyTo: pack}),
49 | require('postcss-css-variables'),
50 | require('autoprefixer'),
51 | ]
52 | },
53 |
54 | devServer: {
55 | contentBase: contentBase + '/_site',
56 | publicPath: '/assets/',
57 | host: '0.0.0.0',
58 | },
59 |
60 | plugins: plugins,
61 |
62 | devtool: 'source-map',
63 | }
64 |
65 | if (process.env.NODE_ENV === 'production') {
66 | options.entry.vendor = Object.keys(pkg.dependencies)
67 | plugins.push(new ExtractTextPlugin('app.css', {disable: false}))
68 | plugins.push(new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js"))
69 | plugins.push(new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}}))
70 | plugins.push(new webpack.optimize.OccurenceOrderPlugin())
71 | options.plugins = plugins
72 | }
73 |
74 | module.exports = options
75 |
--------------------------------------------------------------------------------
/src/ui/Avatar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
83 |
84 |
113 |
--------------------------------------------------------------------------------
/src/view/Overlay.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
54 |
55 |
116 |
--------------------------------------------------------------------------------
/src/css/_android.css:
--------------------------------------------------------------------------------
1 | /** Toggle */
2 | .android .v-toggle,
3 | .android .v-toggle.v-toggle--checked {
4 | border: 0;
5 | background: transparent;
6 | width: 44px;
7 | }
8 | .android .v-toggle::before {
9 | content: '';
10 | position: absolute;
11 | left: 0;
12 | top: 4px;
13 | width: 100%;
14 | height: 14px;
15 | border-radius: 10px;
16 | background: rgba(0, 0, 0, 0.2);
17 | opacity: 1;
18 | transition: background 0.15s;
19 | }
20 | .android .v-toggle--checked::before {
21 | background: rgba(0, 154, 87, 0.5);
22 | }
23 | .android .v-toggle::after {
24 | display: none;
25 | }
26 | .android .v-toggle .v-toggle_handle {
27 | width: 22px;
28 | height: 22px;
29 | background: #f0f0f0;
30 | box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.4);
31 | transition-duration: .15s;
32 | }
33 | .android .v-toggle--checked .v-toggle_handle {
34 | transform: translate3d(22px, 0, 0);
35 | background: var(--android-green);
36 | }
37 | .android .v-toggle .v-toggle_handle::before {
38 | display: block;
39 | width: 22px;
40 | height: 22px;
41 | border-radius: 50%;
42 | content: '';
43 | background: rgba(0, 0, 0, 0);
44 | transform: scale3d(1, 1, 1);
45 | transition: transform .15s;
46 | }
47 | .android .v-toggle:active .v-toggle_handle::before {
48 | background: rgba(0, 0, 0, 0.2);
49 | transform: scale3d(2.8, 2.8, 2.8);
50 | }
51 | .android .v-toggle--checked:active .v-toggle_handle::before {
52 | background: rgba(0, 154, 87, 0.2);
53 | }
54 |
55 | /** Range Slider */
56 | .android .v-range-slider input::-webkit-slider-thumb:before {
57 | background: var(--android-green);
58 | top: 9px;
59 | }
60 | .android .v-range-slider input::-webkit-slider-thumb {
61 | background: var(--android-green);
62 | border: 0;
63 | box-shadow: none;
64 | width: 22px;
65 | height: 22px;
66 | margin-top: -9px;
67 | }
68 |
69 | /** Radio */
70 | .android .v-radio input {
71 | position: relative;
72 | }
73 | .android .v-radio input:before {
74 | position: static;
75 | content: '';
76 | width: 10px;
77 | height: 10px;
78 | border-radius: 50%;
79 | background: var(--android-green);
80 | opacity: 1;
81 | transform: scale3d(0, 0, 0);
82 | box-shadow: none;
83 | border: 0;
84 | transition: transform 0.15s;
85 | }
86 | .android .v-radio input:after {
87 | position: absolute;
88 | top: -4px;
89 | left: -4px;
90 | display: block;
91 | content: '';
92 | width: 18px;
93 | height: 18px;
94 | border: 2px solid #5a5a5a;
95 | border-radius: 50%;
96 | box-sizing: border-box;
97 | transition: background 0.1s;
98 | }
99 | .android .v-radio input:checked:before {
100 | transform: scale3d(1, 1, 1);
101 | }
102 | .android .v-radio input:checked:after {
103 | border-color: var(--android-green);
104 | }
105 | .android .v-radio li {
106 | border: none;
107 | }
108 |
--------------------------------------------------------------------------------