Repository: museui/muse-ui
Branch: master
Commit: e799a4ff3854
Files: 315
Total size: 501.6 KB
Directory structure:
gitextract_cw61ox71/
├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .gitignore
├── .postcssrc.js
├── .travis.yml
├── README.md
├── commitlint.config.js
├── demo/
│ ├── App.vue
│ ├── bug-date-input.vue
│ ├── bug-popup.vue
│ ├── components/
│ │ └── Sidebar.js
│ ├── index.html
│ └── main.js
├── jest.config.js
├── package.json
├── rollup.config.js
├── src/
│ ├── Alert/
│ │ ├── Alert.js
│ │ ├── Alert.spec.js
│ │ ├── __snapshots__/
│ │ │ └── Alert.spec.js.snap
│ │ └── index.js
│ ├── AppBar/
│ │ ├── AppBar.js
│ │ ├── AppBar.spec.js
│ │ ├── __snapshots__/
│ │ │ └── AppBar.spec.js.snap
│ │ ├── index.js
│ │ └── theme.js
│ ├── AutoComplete/
│ │ ├── AutoComplete.js
│ │ ├── AutoComplete.spec.js
│ │ ├── __snapshots__/
│ │ │ └── AutoComplete.spec.js.snap
│ │ ├── filter.js
│ │ └── index.js
│ ├── Avatar/
│ │ ├── Avatar.js
│ │ ├── Avatar.spec.js
│ │ ├── __snapshots__/
│ │ │ └── Avatar.spec.js.snap
│ │ ├── index.js
│ │ └── theme.js
│ ├── Badge/
│ │ ├── Badge.js
│ │ ├── Badge.spec.js
│ │ ├── __snapshots__/
│ │ │ └── Badge.spec.js.snap
│ │ ├── index.js
│ │ └── theme.js
│ ├── BottomNav/
│ │ ├── BottomNav.js
│ │ ├── BottomNav.spec.js
│ │ ├── BottomNavItem.js
│ │ ├── __snapshots__/
│ │ │ └── BottomNav.spec.js.snap
│ │ ├── index.js
│ │ └── theme.js
│ ├── BottomSheet/
│ │ ├── BottomSheet.js
│ │ ├── BottomSheet.spec.js
│ │ ├── __snapshots__/
│ │ │ └── BottomSheet.spec.js.snap
│ │ ├── index.js
│ │ └── theme.js
│ ├── Breadcrumbs/
│ │ ├── Breadcrumbs.js
│ │ ├── Breadcrumbs.spec.js
│ │ ├── BreadcrumbsItem.js
│ │ ├── __snapshots__/
│ │ │ └── Breadcrumbs.spec.js.snap
│ │ ├── index.js
│ │ └── theme.js
│ ├── Button/
│ │ ├── Button.js
│ │ ├── Button.spec.js
│ │ ├── __snapshots__/
│ │ │ └── Button.spec.js.snap
│ │ ├── index.js
│ │ └── theme.js
│ ├── Card/
│ │ ├── Card.js
│ │ ├── CardHeader.js
│ │ ├── CardMedia.js
│ │ ├── CardTitle.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Carousel/
│ │ ├── Carousel.js
│ │ ├── CarouselItem.js
│ │ └── index.js
│ ├── Checkbox/
│ │ ├── Checkbox.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Chip/
│ │ ├── Chip.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── DataTable/
│ │ ├── DataTable.js
│ │ ├── index.js
│ │ ├── mixins/
│ │ │ ├── body.js
│ │ │ ├── colgroup.js
│ │ │ ├── footer.js
│ │ │ ├── header.js
│ │ │ └── progress.js
│ │ └── theme.js
│ ├── DateInput/
│ │ ├── Container.js
│ │ ├── DateInput.js
│ │ └── index.js
│ ├── Dialog/
│ │ ├── Dialog.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Divider/
│ │ ├── Divider.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Drawer/
│ │ ├── Drawer.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── ExpansionPanel/
│ │ ├── ExpansionPanel.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Form/
│ │ ├── Form.js
│ │ ├── FormItem.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Grid/
│ │ ├── Col.js
│ │ ├── Container.js
│ │ ├── Flex.js
│ │ ├── Row.js
│ │ ├── index.js
│ │ └── utils.js
│ ├── GridList/
│ │ ├── GridList.js
│ │ ├── GridTile.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Helpers/
│ │ └── index.js
│ ├── Icon/
│ │ ├── Icon.js
│ │ └── index.js
│ ├── List/
│ │ ├── List.js
│ │ ├── ListAction.js
│ │ ├── ListItem.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── LoadMore/
│ │ ├── InfiniteScroll.js
│ │ ├── LoadMore.js
│ │ ├── RefreshControl.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Menu/
│ │ ├── Menu.js
│ │ └── index.js
│ ├── Pagination/
│ │ ├── Pagination.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Paper/
│ │ ├── Paper.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Picker/
│ │ ├── DatePicker/
│ │ │ ├── DateDisplay.js
│ │ │ ├── DatePicker.js
│ │ │ ├── DayButton.js
│ │ │ ├── MonthDayView.js
│ │ │ ├── MonthView.js
│ │ │ ├── Toolbar.js
│ │ │ ├── YearButton.js
│ │ │ ├── YearView.js
│ │ │ ├── dateUtils.js
│ │ │ └── index.js
│ │ ├── DateTimePicker/
│ │ │ ├── DateTimeDisplay.js
│ │ │ ├── DateTimePicker.js
│ │ │ └── index.js
│ │ ├── TimePicker/
│ │ │ ├── Hours.js
│ │ │ ├── ListView.js
│ │ │ ├── Minutes.js
│ │ │ ├── Number.js
│ │ │ ├── Pointer.js
│ │ │ ├── TimeDisplay.js
│ │ │ ├── TimePicker.js
│ │ │ ├── index.js
│ │ │ └── timeUtils.js
│ │ ├── index.js
│ │ ├── mixins/
│ │ │ └── props.js
│ │ └── theme.js
│ ├── Popover/
│ │ ├── Popover.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Progress/
│ │ ├── Circular.js
│ │ ├── CircularProgress.js
│ │ ├── LinearProgress.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Radio/
│ │ ├── Radio.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Select/
│ │ ├── Option.js
│ │ ├── Select.js
│ │ ├── index.js
│ │ ├── mixins/
│ │ │ ├── events.js
│ │ │ ├── keyboard.js
│ │ │ ├── menu.js
│ │ │ └── selection.js
│ │ └── theme.js
│ ├── SlidePicker/
│ │ ├── Picker.js
│ │ ├── PickerSlot.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Slider/
│ │ ├── Slider.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Snackbar/
│ │ ├── Snackbar.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Stepper/
│ │ ├── Step.js
│ │ ├── StepButton.js
│ │ ├── StepConnector.js
│ │ ├── StepContent.js
│ │ ├── StepLabel.js
│ │ ├── Stepper.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── SubHeader/
│ │ ├── SubHeader.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Switch/
│ │ ├── Switch.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Tabs/
│ │ ├── Tab.js
│ │ ├── Tabs.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── TextField/
│ │ ├── TextField.js
│ │ ├── Textarea.js
│ │ ├── index.js
│ │ └── theme.js
│ ├── Tooltip/
│ │ ├── Tooltip.js
│ │ ├── TooltipContent.js
│ │ └── index.js
│ ├── index.js
│ ├── internal/
│ │ ├── AbstractButton.js
│ │ ├── CircleRipple.js
│ │ ├── ExpandTransition.js
│ │ ├── FocusRipple.js
│ │ ├── TouchRipple.js
│ │ ├── directives/
│ │ │ ├── click-outside.js
│ │ │ ├── elevation.js
│ │ │ ├── keyboard-focus.js
│ │ │ ├── mousewheel.js
│ │ │ ├── resize.js
│ │ │ ├── scroll.js
│ │ │ └── swipe.js
│ │ ├── mixins/
│ │ │ ├── button.js
│ │ │ ├── color.js
│ │ │ ├── input.js
│ │ │ ├── popup/
│ │ │ │ ├── Overlay.js
│ │ │ │ ├── index.js
│ │ │ │ ├── manager.js
│ │ │ │ └── utils.js
│ │ │ ├── ripple.js
│ │ │ ├── route.js
│ │ │ └── select.js
│ │ └── transitions.js
│ ├── styles/
│ │ ├── base.less
│ │ ├── colors.less
│ │ ├── components/
│ │ │ ├── alert.less
│ │ │ ├── appbar.less
│ │ │ ├── avatar.less
│ │ │ ├── badge.less
│ │ │ ├── bootstrap-grid.less
│ │ │ ├── bottom-nav.less
│ │ │ ├── bottom-sheet.less
│ │ │ ├── breadcrumbs.less
│ │ │ ├── button.less
│ │ │ ├── card.less
│ │ │ ├── carousel.less
│ │ │ ├── checkbox.less
│ │ │ ├── chip.less
│ │ │ ├── circle-ripple.less
│ │ │ ├── data-table.less
│ │ │ ├── date-input.less
│ │ │ ├── dialog.less
│ │ │ ├── divider.less
│ │ │ ├── drawer.less
│ │ │ ├── elevation.less
│ │ │ ├── expand-transition.less
│ │ │ ├── expansion-panel.less
│ │ │ ├── focus-ripple.less
│ │ │ ├── form.less
│ │ │ ├── grid-list.less
│ │ │ ├── grid.less
│ │ │ ├── input.less
│ │ │ ├── list.less
│ │ │ ├── load-more.less
│ │ │ ├── menu.less
│ │ │ ├── overlay.less
│ │ │ ├── pagination.less
│ │ │ ├── paper.less
│ │ │ ├── picker.less
│ │ │ ├── popover.less
│ │ │ ├── progress.less
│ │ │ ├── radio.less
│ │ │ ├── select.less
│ │ │ ├── slide-picker.less
│ │ │ ├── slider.less
│ │ │ ├── snackbar.less
│ │ │ ├── stepper.less
│ │ │ ├── subheader.less
│ │ │ ├── switch.less
│ │ │ ├── tabs.less
│ │ │ ├── text-field.less
│ │ │ ├── tooltip.less
│ │ │ └── touch-ripple.less
│ │ ├── import.less
│ │ ├── index.less
│ │ ├── mixins.less
│ │ ├── normalize.less
│ │ ├── theme.less
│ │ ├── transitions.less
│ │ └── vars.less
│ ├── theme/
│ │ ├── baseTheme.js
│ │ ├── colorTheme.js
│ │ ├── colors.js
│ │ ├── dark.js
│ │ ├── index.js
│ │ └── light.js
│ └── utils/
│ ├── colorManipulator.js
│ ├── dom.js
│ ├── drag.js
│ ├── index.js
│ ├── resize-event.js
│ ├── testing.js
│ ├── to-have-ben-warned.js
│ └── translate.js
└── types/
├── index.d.ts
├── muse-ui.d.ts
└── theme.d.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"env": {
"normal": {
"presets": [
"env",
"stage-2"
],
"plugins": [
"transform-vue-jsx"
],
"comments": false
},
"production": {
"presets": [
"env",
"stage-2"
],
"plugins": [
"transform-vue-jsx"
],
"comments": false
},
"test": {
"presets": ["env", "stage-2"],
"plugins": [ "istanbul", "transform-runtime", "transform-vue-jsx"]
},
"es5": {
"presets": [
[
"env",
{
"modules": false
}
],
"stage-2"
],
"plugins": [
"transform-vue-jsx"
]
}
}
}
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
================================================
FILE: .eslintignore
================================================
================================================
FILE: .eslintrc
================================================
{
root: true,
parser: "babel-eslint",
parserOptions: {
ecmaVersion: 7,
sourceType: "module",
allowImportExportEverywhere: false,
ecmaFeatures: {
jsx: true,
modules: true
}
},
env: {
es6: true,
node: true,
browser: true
},
extends: "vue",
globals: {
expect: true,
describe: true,
it: true,
jest: true
},
rules: {
quotes: [2, "single", { "allowTemplateLiterals": true }],
linebreak-style: [2, "unix"],
semi: [2, "always"],
eqeqeq: [2, "always"],
strict: [2, "global"],
key-spacing: [2, { "afterColon": true }]
}
}
================================================
FILE: .gitattributes
================================================
*.html linguist-language=Vue
*.js linguist-language=Vue
================================================
FILE: .gitignore
================================================
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
/lib
/es5
.idea
dist
.DS_Store
.cache
converage
.vscode
reports
.cache
================================================
FILE: .postcssrc.js
================================================
module.exports = {
plugins: [
require('autoprefixer')({
browsers: [
'> 1%',
'last 5 versions',
'ios >= 7',
'android > 4.4',
'not ie < 10'
]
}),
require('cssnano')({
safe: true
})
]
};
================================================
FILE: .travis.yml
================================================
---
language: node_js
node_js:
- "8.11.2"
before_script:
- yarn global add codecov
script:
- yarn
- yarn run lint
- yarn run test:coverage && codecov
- yarn run build
cache:
yarn: true
================================================
FILE: README.md
================================================
Muse-UI [不再维护]
Material Design
UI library for Vuejs 2.0
## Installation
Muse-UI is available as an [npm package](https://www.npmjs.com/package/muse-ui)
```bash
npm install muse-ui -S
yarn add muse-ui
```
## Usage
```javascript
import Vue from 'vue'
import MuseUI from 'muse-ui'
import 'muse-ui/dist/muse-ui.css'
Vue.use(MuseUI)
```
For more information, please refer to [Usage](https://muse-ui.org/#/zh-CN/usage) in our documentation.
## Browser Support
Modern browsers and Internet Explorer 10+.
## Contributing
Please make sure to read the contributing guide ([中文](https://muse-ui.org/#/zh-CN/contributing) | [English](https://muse-ui.org/#/en-US/contributing)) before making a pull request.
## Changelog
Detailed changes for each release are documented in the [release notes](https://muse-ui.org/#/zh-CN/changelog).
## Licence
muse-ui is open source and released under the MIT Licence.
Copyright (c) 2016 myron
================================================
FILE: commitlint.config.js
================================================
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'subject-case': [0]
}
};
================================================
FILE: demo/App.vue
================================================
{{date.getDate()}}日
CLICK
FOCUS
================================================
FILE: demo/bug-date-input.vue
================================================
提交
重置
================================================
FILE: demo/bug-popup.vue
================================================
OPEN Bottom Sheet
this is simple Dialog
ok
err
Close
================================================
FILE: demo/components/Sidebar.js
================================================
export default {
};
================================================
FILE: demo/index.html
================================================
MuseUI DEV
================================================
FILE: demo/main.js
================================================
import Vue from 'vue';
import MuseUI from '../src';
import App from './App';
Vue.use(MuseUI);
const app = new Vue({
...App
});
app.$mount('#app');
================================================
FILE: jest.config.js
================================================
module.exports = {
verbose: false,
testURL: 'http://localhost/',
roots: [
'/src'
],
moduleFileExtensions: [
'js',
'vue'
],
moduleDirectories: [
'node_modules'
],
transform: {
'.*\\.(vue)$': '/node_modules/vue-jest',
'\\.(less)$': '/node_modules/jest-css-modules',
'\\.(css)$': '/node_modules/jest-css-modules',
'.*\\.(vue|js)$': '/node_modules/babel-jest'
},
transformIgnorePatterns: [
'node_modules/(?!vue-router)'
],
snapshotSerializers: [
'jest-serializer-html'
]
};
================================================
FILE: package.json
================================================
{
"name": "muse-ui",
"version": "3.0.2",
"description": "material design ui for vue2",
"author": "myronliu347 ",
"repository": "https://github.com/museui/muse-ui.git",
"main": "dist/muse-ui.common.js",
"module": "dist/muse-ui.esm.js",
"unpkg": "dist/muse-ui.js",
"jsdelivr": "dist/muse-ui.js",
"typings": "types/index.d.ts",
"files": [
"dist",
"lib",
"es5",
"src",
"types"
],
"scripts": {
"dev": "NODE_ENV=normal parcel demo/index.html",
"build": "npm run build:dist && npm run build:lib && npm run build:es5",
"build:dist": "rimraf dist && rollup --config rollup.config.js",
"build:lib": "rimraf lib && NODE_ENV=normal babel src -d lib --ignore *.spec.js && mkdir lib/styles && cp -rf src/styles/* lib/styles",
"build:es5": "rimraf es5 && NODE_ENV=es5 babel src -d es5 --ignore *.spec.js && mkdir es5/styles && cp -rf src/styles/* es5/styles",
"lint": "NODE_ENV=normal eslint --ext .js,.vue src",
"test": "NODE_ENV=test jest -i",
"test:coverage": "NODE_ENV=test jest -i --coverage",
"prepush": "yarn run lint",
"commit": "npx git-cz",
"commitmsg": "commitlint -E GIT_PARAMS"
},
"peerDependencies": {
"vue": "^2.5.0"
},
"devDependencies": {
"@commitlint/cli": "^7.0.0",
"@commitlint/config-conventional": "^7.0.1",
"autoprefixer": "^8.0.0",
"avoriaz": "^6.3.0",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-eslint": "^8.2.2",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-jest": "^22.4.3",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-plugin-transform-vue-jsx": "^3.5.1",
"babel-preset-env": "^1.6.1",
"babel-preset-es2015-rollup": "^3.0.0",
"babel-preset-stage-2": "^6.24.1",
"cssnano": "^3.10.0",
"eslint": "^4.18.1",
"eslint-config-vue": "^2.0.2",
"eslint-plugin-vue": "^4.3.0",
"husky": "^0.14.3",
"jest": "^22.4.3",
"jest-cli": "^22.4.3",
"jest-css-modules": "^1.1.0",
"jest-serializer-html": "^5.0.0",
"less": "^3.0.1",
"parcel-bundler": "^1.9.7",
"parcel-plugin-vue": "^1.5.0",
"postcss-import": "^11.0.0",
"postcss-modules": "^1.1.0",
"postcss-url": "^7.2.1",
"rimraf": "^2.6.0",
"rollup": "^0.57.1",
"rollup-plugin-babel": "^3.0.3",
"rollup-plugin-commonjs": "^9.1.0",
"rollup-plugin-node-resolve": "^3.3.0",
"rollup-plugin-postcss": "^1.4.0",
"rollup-plugin-replace": "^2.0.0",
"rollup-plugin-uglify": "^3.0.0",
"typeface-roboto": "^0.0.54",
"vue": "^2.5.13",
"vue-jest": "^2.3.0",
"vue-router": "^3.0.1",
"vue-template-compiler": "^2.5.13"
},
"dependencies": {
"body-scroll-lock": "^2.6.1",
"dayjs": "^1.8.2",
"keycode": "^2.1.9",
"normalize-wheel": "^1.0.1",
"resize-observer-polyfill": "^1.5.0"
}
}
================================================
FILE: rollup.config.js
================================================
import babel from 'rollup-plugin-babel';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import uglify from 'rollup-plugin-uglify';
import postcss from 'rollup-plugin-postcss';
import replace from 'rollup-plugin-replace';
import packageJson from './package.json';
const { name, version } = packageJson;
const banner = `/* ${name} myron.liu version ${version} */`;
const plugins = [
postcss({ extensions: ['.less'], extract: `dist/${name}.css` }),
resolve({ jsnext: true, main: true, browser: true }),
commonjs({
include: 'node_modules/**',
namedExports: {
'node_modules/body-scroll-lock/lib/bodyScrollLock.min.js': ['disableBodyScroll', 'enableBodyScroll', 'clearAllBodyScrollLocks']
}
}),
babel({
babelrc: false,
include: 'src/**/*.js',
runtimeHelpers: true,
presets: [
[
'env',
{
modules: false
}
],
'stage-2',
'es2015-rollup'
]
}),
replace({
__VERSION__: version
})
];
export default [{
input: 'src/index.js',
output: [{
banner,
file: `dist/${name}.common.js`,
format: 'cjs'
}, {
banner,
file: `dist/${name}.esm.js`,
format: 'es'
}],
plugins: plugins,
external: ['vue']
}, {
input: 'src/index.js',
output: {
file: `dist/${name}.js`,
format: 'umd',
name: 'MuseUI',
globals: {
vue: 'Vue'
}
},
plugins: [
...plugins,
uglify()
],
external: ['vue']
}];
================================================
FILE: src/Alert/Alert.js
================================================
import color from '../internal/mixins/color';
import Button from '../Button';
export default {
name: 'mu-alert',
mixins: [color],
props: {
delete: Boolean,
transition: String,
mode: String
},
methods: {
handleDelete (e) {
e.stopPropagation();
this.$emit('delete');
}
},
render (h) {
const deleteIcon = h(Button, {
staticClass: 'mu-alert-delete-btn',
props: {
icon: true
},
on: {
click: this.handleDelete
}
}, [
h('svg', {
staticClass: 'mu-alert-delete-icon',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'
}
}),
h('path', {
attrs: {
d: 'M0 0h24v24H0z',
fill: 'none'
}
})
])
]);
const alert = h('div', {
staticClass: `mu-alert ${this.getColorClass()}`,
style: {
'background-color': this.getColor(this.color)
},
on: this.$listeners
}, [this.$slots.default, this.delete ? deleteIcon : undefined]);
return this.transition ? h('transition', {
props: {
mode: this.mode,
name: this.transition
}
}, [alert]) : alert;
}
};
================================================
FILE: src/Alert/Alert.spec.js
================================================
import test from '../utils/testing';
import Alert from './Alert';
test('Alert', ({ mount }) => {
it('should be show by default', async () => {
const wrapper = mount(Alert, {});
expect(wrapper.html()).toMatchSnapshot();
});
it('should be delete', async () => {
const wrapper = mount(Alert, { propsData: { delete: true }});
const deleteBtn = wrapper.find('.mu-alert-delete-btn')[0];
const handleDelete = jest.fn();
wrapper.vm.$on('delete', handleDelete);
deleteBtn.trigger('click');
expect(handleDelete).toBeCalledWith();
});
it('should be set color', async () => {
const wrapper = mount(Alert, {
propsData: {
color: 'primary'
}
});
expect(wrapper.html()).toMatchSnapshot();
wrapper.setProps({ color: 'error' });
expect(wrapper.html()).toMatchSnapshot();
wrapper.setProps({ color: '#fff' });
expect(wrapper.hasStyle('background-color', '#fff')).toEqual(true);
});
});
================================================
FILE: src/Alert/__snapshots__/Alert.spec.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Alert should be set color 1`] = `
`;
exports[`Alert should be set color 2`] = `
`;
exports[`Alert should be show by default 1`] = `
`;
================================================
FILE: src/Alert/index.js
================================================
import '../styles/components/alert.less';
import Alert from './Alert';
Alert.install = function (Vue) {
Vue.component(Alert.name, Alert);
};
export default Alert;
================================================
FILE: src/AppBar/AppBar.js
================================================
import color from '../internal/mixins/color';
export default {
name: 'mu-appbar',
mixins: [color],
props: {
zDepth: {
type: [Number, String],
default: 4,
validator: (val) => val >= 0 && val <= 24
},
title: {
type: String,
default: ''
},
textColor: String
},
render (h) {
const slots = this.$slots;
const left = slots.left && slots.left.length > 0 ? h('div', { staticClass: 'mu-appbar-left' }, slots.left) : undefined;
const right = slots.right && slots.right.length > 0 ? h('div', { staticClass: 'mu-appbar-right' }, slots.right) : undefined;
const center = h('div', { staticClass: 'mu-appbar-title' }, slots.default && slots.default.length > 0 ? slots.default : this.title);
return h('header', {
staticClass: `mu-appbar ${this.getColorClass()} ${this.getTextColorClass()} mu-elevation-${this.zDepth}`,
style: {
'background-color': this.getColor(this.color),
color: this.getColor(this.textColor)
}
}, [left, center, right]);
}
};
================================================
FILE: src/AppBar/AppBar.spec.js
================================================
import Vue from 'vue';
import * as colors from '../theme/colors';
import test from '../utils/testing';
import AppBar from './AppBar';
import Icon from '../Icon';
test('AppBar', ({ mount }) => {
it('should be default', async () => {
const wrapper = mount(AppBar, {});
expect(wrapper.html()).toMatchSnapshot();
});
it('should be set title', async () => {
const wrapper = mount(AppBar, {
propsData: {
title: 'Muse-UI'
}
});
expect(wrapper.html()).toMatchSnapshot();
});
it('should be slots', async () => {
const menuIcon = Vue.component('test', {
components: {
'mu-icon': Icon
},
render (h) {
return h('mu-icon', {
value: 'menu'
});
}
});
let wrapper = mount(AppBar, { slots: { left: [menuIcon] }});
expect(wrapper.html()).toMatchSnapshot();
wrapper = mount(AppBar, { slots: { right: [menuIcon] }});
expect(wrapper.html()).toMatchSnapshot();
});
it('should be set color and text color', async () => {
const wrapper = mount(AppBar, {
propsData: {
color: 'primary',
textColor: 'secondary'
}
});
expect(wrapper.hasClass('mu-primary-color')).toBe(true);
expect(wrapper.hasClass('mu-inverse')).toBe(true);
expect(wrapper.hasClass('mu-secondary-text-color')).toBe(true);
wrapper.setProps({
color: 'teal',
textColor: 'green'
});
expect(wrapper.hasStyle('background-color', colors.teal)).toBe(true);
expect(wrapper.hasStyle('color', colors.green)).toBe(true);
wrapper.setProps({
color: '#000',
textColor: '#fff'
});
expect(wrapper.hasStyle('background-color', '#000')).toBe(true);
expect(wrapper.hasStyle('color', '#fff')).toBe(true);
});
it('should set zDepth', async () => {
const wrapper = mount(AppBar, {});
expect(wrapper.hasClass('mu-elevation-4')).toBe(true);
wrapper.setProps({
zDepth: 1
});
expect(wrapper.hasClass('mu-elevation-1')).toBe(true);
});
});
================================================
FILE: src/AppBar/__snapshots__/AppBar.spec.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AppBar should be default 1`] = `
`;
exports[`AppBar should be set title 1`] = `
`;
exports[`AppBar should be slots 1`] = `
`;
exports[`AppBar should be slots 2`] = `
`;
================================================
FILE: src/AppBar/index.js
================================================
import '../styles/components/elevation.less';
import '../styles/components/appbar.less';
import theme from '../theme';
import AppBarTheme from './theme';
import AppBar from './AppBar';
AppBar.install = function (Vue) {
Vue.component(AppBar.name, AppBar);
};
theme.addCreateTheme(AppBarTheme);
export default AppBar;
================================================
FILE: src/AppBar/theme.js
================================================
export default (theme, type) => {
return `
.mu-appbar {
background-color: ${type === 'light' ? '#f5f5f5' : '#212121'};
color: ${theme.text.primary};
}
`;
};
================================================
FILE: src/AutoComplete/AutoComplete.js
================================================
import input from '../internal/mixins/input';
import clickOutSide from '../internal/directives/click-outside';
import Popover from '../Popover';
import { List, ListItem } from '../List';
import keycode from 'keycode';
import caseSensitiveFilter from './filter';
import { isPromise } from '../utils';
export default {
name: 'mu-auto-complete',
mixins: [input],
directives: {
'click-outside': clickOutSide
},
props: {
data: {
type: Array,
default: () => []
},
maxHeight: {
type: [String, Number],
default: 300
},
debounce: {
type: Number,
default: 200,
validator (val) {
return val > 0;
}
},
filter: {
type: Function,
default: caseSensitiveFilter
},
maxSearchResults: {
type: Number,
default: 0
},
openOnFocus: Boolean,
dense: {
type: Boolean,
default: true
},
textline: List.props.textline,
popoverClass: String,
placement: {
type: String,
default: 'bottom-start'
},
space: Number,
avatar: Boolean
},
data () {
return {
open: false,
enableData: [],
focusIndex: -1
};
},
methods: {
setValue (value, item, e) {
this.open = false;
this.focusIndex = -1;
if (!item) return;
this.$emit('input', value, e);
this.$emit('select', value, item, e);
this.$emit('change', value, e);
},
onKeydown (e) {
if (this.disabled || this.$attrs.readonly) return;
const code = keycode(e);
const maxIndex = this.enableData.length - 1;
const minIndex = 0;
switch (code) {
case 'enter':
if (this.focusIndex === -1) return;
const { value, item } = this.enableData[this.focusIndex];
this.setValue(value, item, e);
break;
case 'up':
event.preventDefault();
if (!this.open) return;
this.focusIndex--;
if (this.focusIndex < minIndex) this.focusIndex = maxIndex;
break;
case 'down':
event.preventDefault();
if (!this.open) return;
this.focusIndex++;
if (this.focusIndex > maxIndex) this.focusIndex = minIndex;
break;
case 'tab':
this.blur(e);
break;
default:
this.focusIndex = -1;
break;
}
this.$emit('keydown', e);
},
onInput (e) {
const value = e.target.value;
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.filterData(value);
}, this.debounce);
this.$emit('input', value, e);
},
focus (e) {
this.isFocused = true;
if (this.openOnFocus) this.filterData(this.value);
this.$emit('focus', e);
},
filterData (val) {
this.open = true;
const results = this.filter(val, this.data, this.maxSearchResults);
switch (true) {
case Array.isArray(results):
this.enableData = results;
return;
case isPromise(results):
results.then((results) => {
this.enableData = Array.isArray(results) ? results : [];
});
return;
}
this.enableData = [];
},
blur (e) {
this.isFocused = false;
this.focusIndex = -1;
this.open = false;
this.$emit('blur', e);
},
setScollPosition (index) {
if (!this.open) return;
this.$nextTick(() => {
const popoverEl = this.$refs.list.$el;
const optionEl = popoverEl.querySelector('.is-focused');
if (!optionEl) return;
const optionHeight = optionEl.offsetHeight;
let scrollTop = optionEl.offsetTop - optionHeight;
if (scrollTop < optionHeight) scrollTop = 0;
popoverEl.scrollTop = scrollTop;
});
},
createTextField (h) {
const listeners = {
...this.$listeners,
input: this.onInput,
change: (e) => this.$emit('change', e.target.value, e),
keydown: this.onKeydown,
focus: this.focus
};
const placeholder = !this.labelFloat ? this.$attrs.placeholder : '';
return [
h('input', {
staticClass: 'mu-text-field-input',
ref: 'input',
attrs: {
tabindex: 0,
...this.$attrs,
disabled: this.disabled,
placeholder
},
domProps: {
value: this.value
},
on: listeners
})
];
},
createContentList (h) {
return h(List, {
staticClass: 'mu-option-list',
ref: 'list',
props: {
dense: this.dense,
textline: this.textline
},
style: {
'maxHeight': this.maxHeight + 'px'
}
}, this.enableData.map((item, index) => {
return h(ListItem, {
staticClass: 'mu-option',
class: {
'is-focused': this.focusIndex === index
},
props: {
button: true,
avatar: this.avatar
},
on: {
click: (e) => this.setValue(item.value, item.item, e)
}
}, this.$scopedSlots.default ? this.$scopedSlots.default({
...item,
index
}) : [
h('span', {
domProps: {
innerHTML: item.highlight
}
})
]);
}));
}
},
render (h) {
const trigger = this.$refs.input;
return this.createInput(h, {
staticClass: 'mu-text-field',
ref: 'content',
directives: [{
name: 'click-outside',
value: (e) => {
if (this.$refs.popover.$el.contains(e.target)) return;
this.blur(e);
}
}]
}, [
this.createTextField(h),
this.$slots.default,
h(Popover, {
staticClass: [this.popoverClass || ''].join(' '),
props: {
trigger: trigger,
placement: this.placement,
space: this.space,
open: this.open
},
on: {
close: () => (this.open = false)
},
ref: 'popover',
style: {
'visibility': this.enableData.length === 0 ? 'hidden' : '',
'min-width': trigger ? trigger.offsetWidth + 'px' : ''
}
}, [
this.createContentList(h),
this.$slots.popover
])
]);
},
watch: {
focusIndex () {
this.setScollPosition();
}
}
};
================================================
FILE: src/AutoComplete/AutoComplete.spec.js
================================================
import test from '../utils/testing';
import AutoComplete from './AutoComplete';
test('AutoComplete', ({ mount }) => {
const data = [
'Apple', 'Apricot', 'Avocado',
'Banana', 'Bilberry', 'Blackberry', 'Blackcurrant', 'Blueberry',
'Boysenberry', 'Blood Orange',
'Cantaloupe', 'Currant', 'Cherry', 'Cherimoya', 'Cloudberry',
'Coconut', 'Cranberry', 'Clementine',
'Damson', 'Date', 'Dragonfruit', 'Durian',
'Elderberry',
'Feijoa', 'Fig',
'Goji berry', 'Gooseberry', 'Grape', 'Grapefruit', 'Guava',
'Honeydew', 'Huckleberry',
'Jabouticaba', 'Jackfruit', 'Jambul', 'Jujube', 'Juniper berry',
'Kiwi fruit', 'Kumquat',
'Lemon', 'Lime', 'Loquat', 'Lychee',
'Nectarine',
'Mango', 'Marion berry', 'Melon', 'Miracle fruit', 'Mulberry', 'Mandarine',
'Olive', 'Orange',
'Papaya', 'Passionfruit', 'Peach', 'Pear', 'Persimmon', 'Physalis', 'Plum', 'Pineapple',
'Pumpkin', 'Pomegranate', 'Pomelo', 'Purple Mangosteen',
'Quince',
'Raspberry', 'Raisin', 'Rambutan', 'Redcurrant',
'Salal berry', 'Satsuma', 'Star fruit', 'Strawberry', 'Squash', 'Salmonberry',
'Tamarillo', 'Tamarind', 'Tomato', 'Tangerine',
'Ugli fruit',
'Watermelon'
];
it('should default view', async () => {
const wrapper = mount(AutoComplete, {});
expect(wrapper.html()).toMatchSnapshot();
});
it('should set maxHeight', async () => {
const wrapper = mount(AutoComplete, {
propsData: {
maxHeight: 600
}
});
expect(wrapper.find('.mu-option-list')[0].hasStyle('max-height', '600px')).toBe(true);
});
it('should has data list', async () => {
const wrapper = mount(AutoComplete, {
propsData: {
data: data
}
});
const input = jest.fn(value => wrapper.setProps({ value }));
const event = { target: { value: 'ta' }};
wrapper.vm.$on('input', input);
wrapper.vm.onInput(event);
expect(input).toBeCalledWith('ta', event);
return new Promise((reslove) => {
setTimeout(() => {
expect(wrapper.vm.open).toBe(true);
reslove();
}, wrapper.vm.debounce);
});
});
it('should max search result six and open on focus', async () => {
const wrapper = mount(AutoComplete, {
propsData: {
data: data,
openOnFocus: true,
maxSearchResults: 6
}
});
wrapper.find('input')[0].trigger('focus');
expect(wrapper.vm.open).toBe(true);
expect(wrapper.vm.enableData.length).toBe(6);
});
});
================================================
FILE: src/AutoComplete/__snapshots__/AutoComplete.spec.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AutoComplete should default view 1`] = `
`;
================================================
FILE: src/AutoComplete/filter.js
================================================
export default (query = '', data = [], maxSearchResults = 0) => {
const results = [];
for (let i = 0; i < data.length; i++) {
const value = getValueByItem(data[i]);
const index = value.toLowerCase().indexOf(query.toLowerCase());
if (index === -1) continue;
const before = value.substring(0, index);
const highlight = value.substring(index, index + query.length);
const after = value.substring(index + query.length);
results.push({
value,
item: data[i],
highlight: [
before,
`${highlight} `,
after
].join('')
});
if (maxSearchResults > 0 && maxSearchResults === results.length) break;
}
return results;
};
function getValueByItem (item) {
if (!item) return '';
return typeof item === 'string' ? item : item.value;
}
================================================
FILE: src/AutoComplete/index.js
================================================
import '../styles/components/text-field.less';
import '../styles/components/select.less';
import AutoComplete from './AutoComplete';
AutoComplete.install = function (Vue) {
Vue.component(AutoComplete.name, AutoComplete);
};
export default AutoComplete;
================================================
FILE: src/Avatar/Avatar.js
================================================
import { getWidth } from '../utils';
import color from '../internal/mixins/color';
export default {
name: 'mu-avatar',
mixins: [color],
props: {
textColor: String,
size: {
type: [Number, String],
default: 40
}
},
render (h) {
const size = getWidth(this.size);
return h('div', {
staticClass: `mu-avatar ${this.getColorClass()} ${this.getTextColorClass()}`,
style: {
width: size,
height: size,
'font-size': this.size / 2 + 'px',
'background-color': this.getColor(this.color),
'color': this.getColor(this.textColor)
},
on: this.$listeners
}, [h('div', { class: 'mu-avatar-inner' }, this.$slots.default)]);
}
};
================================================
FILE: src/Avatar/Avatar.spec.js
================================================
import * as colors from '../theme/colors';
import test from '../utils/testing';
import Avatar from './Avatar';
test('Avatar', ({ mount }) => {
it('should have an mu-avatar class', () => {
const wrapper = mount(Avatar, {});
expect(wrapper.hasClass('mu-avatar')).toBe(true);
expect(wrapper.html()).toMatchSnapshot();
});
it('should can set color and text-color', () => {
const wrapper = mount(Avatar, {
propsData: {
color: 'primary',
textColor: 'secondary'
}
});
expect(wrapper.hasClass('mu-primary-color')).toBe(true);
expect(wrapper.hasClass('mu-inverse')).toBe(true);
expect(wrapper.hasClass('mu-secondary-text-color')).toBe(true);
wrapper.setProps({
color: 'teal',
textColor: 'green'
});
expect(wrapper.hasStyle('background-color', colors.teal)).toBe(true);
expect(wrapper.hasStyle('color', colors.green)).toBe(true);
wrapper.setProps({
color: '#000',
textColor: '#fff'
});
expect(wrapper.hasStyle('background-color', '#000')).toBe(true);
expect(wrapper.hasStyle('color', '#fff')).toBe(true);
});
it('should be change size', () => {
const wrapper = mount(Avatar, {
propsData: {
size: 56
}
});
expect(wrapper.html()).toMatchSnapshot();
});
});
================================================
FILE: src/Avatar/__snapshots__/Avatar.spec.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Avatar should be change size 1`] = `
`;
exports[`Avatar should have an mu-avatar class 1`] = `
`;
================================================
FILE: src/Avatar/index.js
================================================
import '../styles/components/avatar.less';
import theme from '../theme';
import AvatarTheme from './theme';
import Avatar from './Avatar';
Avatar.install = function (Vue) {
Vue.component(Avatar.name, Avatar);
};
theme.addCreateTheme(AvatarTheme);
export default Avatar;
================================================
FILE: src/Avatar/theme.js
================================================
export default (theme) => {
return `
.mu-avatar {
background-color: ${theme.track};
color: ${theme.text.alternate};
}
`;
};
================================================
FILE: src/Badge/Badge.js
================================================
import { convertClass } from '../utils';
import color from '../internal/mixins/color';
export default {
name: 'mu-badge',
mixins: [color],
props: {
content: {
type: String,
default: ''
},
circle: Boolean,
badgeClass: [String, Object, Array]
},
render (h) {
const slots = this.$slots;
const isFloat = slots.default && slots.default.length > 0;
const badge = h('em', {
staticClass: `mu-badge ${convertClass(this.badgeClass).join(' ')} ${this.getColorClass()}`,
style: {
'background-color': this.getColor(this.color)
},
class: {
'mu-badge-circle': this.circle,
'mu-badge-float': isFloat
}
}, slots.content && slots.content.length > 0 ? slots.content : this.content);
return h('div', {
staticClass: 'mu-badge-container',
on: this.$listeners
}, [slots.default, badge]);
}
};
================================================
FILE: src/Badge/Badge.spec.js
================================================
import test from '../utils/testing';
import Badge from './Badge';
import * as colors from '../theme/colors';
test('Badge', ({ mount }) => {
it('should default view', async () => {
const wrapper = mount(Badge, {});
expect(wrapper.html()).toMatchSnapshot();
});
it('should custom class', async () => {
const wrapper = mount(Badge, {
propsData: {
circle: true
}
});
expect(wrapper.html()).toMatchSnapshot();
wrapper.setProps({
badgeClass: 'custom-badge'
});
expect(wrapper.find('.custom-badge').length).toBe(1);
expect(wrapper.html()).toMatchSnapshot();
});
it('should custom color', async () => {
const wrapper = mount(Badge, {
propsData: {
color: 'primary'
}
});
const badge = wrapper.find('.mu-badge')[0];
expect(badge.hasClass('mu-primary-color')).toBe(true);
expect(badge.hasClass('mu-inverse')).toBe(true);
wrapper.setProps({ color: 'teal' });
expect(badge.hasStyle('background-color', colors.teal)).toBe(true);
wrapper.setProps({ color: '#000' });
expect(badge.hasStyle('background-color', '#000')).toBe(true);
});
});
================================================
FILE: src/Badge/__snapshots__/Badge.spec.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Badge should custom class 1`] = `
`;
exports[`Badge should custom class 2`] = `
`;
exports[`Badge should default view 1`] = `
`;
================================================
FILE: src/Badge/index.js
================================================
import '../styles/components/badge.less';
import theme from '../theme';
import BadgeTheme from './theme';
import Badge from './Badge';
Badge.install = function (Vue) {
Vue.component(Badge.name, Badge);
};
theme.addCreateTheme(BadgeTheme);
export default Badge;
================================================
FILE: src/Badge/theme.js
================================================
export default (theme) => {
return `
.mu-badge{
background-color: ${theme.track};
color: ${theme.text.alternate};
}
`;
};
================================================
FILE: src/BottomNav/BottomNav.js
================================================
import color from '../internal/mixins/color';
import AbstractButton from '../internal/AbstractButton';
export default {
name: 'mu-bottom-nav',
mixins: [color],
provide () {
return {
getBottomNavValue: this.getBottomNavValue,
getBottomNavShift: this.getBottomNavShift,
bottomNavItemClick: this.bottomNavItemClick,
getDefaultVal: this.getDefaultVal,
getActiveColor: this.getActiveColor
};
},
props: {
shift: Boolean,
value: {}
},
data () {
return {
activeValue: this.value || 0
};
},
methods: {
getBottomNavValue () {
return this.activeValue;
},
getBottomNavShift () {
return this.shift;
},
getDefaultVal () {
if (!this.index) this.index = 0;
return this.index++;
},
getActiveColor () {
return {
className: this.getNormalColorClass(this.color, true),
color: this.getColor(this.color)
};
},
bottomNavItemClick (value) {
this.activeValue = value;
}
},
watch: {
value (val) {
this.activeValue = val;
},
activeValue (val) {
if (val === this.value) return;
this.$emit('update:value', val);
this.$emit('change', val);
}
},
render (h) {
return h(AbstractButton, {
class: {
'mu-bottom-nav': true,
'mu-bottom-nav-shift': this.shift,
[this.getColorClass(false)]: this.shift
},
style: {
'background-color': this.shift ? this.getColor(this.color) : ''
},
props: {
ripple: this.shift,
wrapperClass: 'mu-bottom-nav-shift-wrapper',
containerElement: 'div',
rippleOpacity: 0.3
}
}, this.$slots.default);
}
};
================================================
FILE: src/BottomNav/BottomNav.spec.js
================================================
import Vue from 'vue';
import test from '../utils/testing';
import BottomNav from './BottomNav';
import BottomNavItem from './BottomNavItem';
test('Bottom Navigation', ({ mount }) => {
function createNavItem (icon, text, value) {
return Vue.component('nav-item', {
component: {
'mu-bottom-nav-item': BottomNavItem
},
render (h) {
return h('mu-bottom-nav-item', {
props: {
icon: icon,
title: text,
value: value
}
});
}
});
}
it('should default view', async () => {
const wrapper = mount(BottomNav, {
slots: {
default: [
createNavItem('restore', 'Recents'),
createNavItem('favorite', 'Favorites'),
createNavItem('location_on', 'Nearby')
]
}
});
expect(wrapper.html()).toMatchSnapshot();
});
it('should shift mode', async () => {
const wrapper = mount(BottomNav, {
propsData: {
shift: true
},
slots: {
default: [
createNavItem('restore', 'Recents'),
createNavItem('favorite', 'Favorites'),
createNavItem('location_on', 'Nearby')
]
}
});
expect(wrapper.html()).toMatchSnapshot();
});
it('should change value', async () => {
const wrapper = mount(BottomNav, {
propsData: {
value: 'Recents'
},
slots: {
default: [
createNavItem('restore', 'Recents', 'Recents'),
createNavItem('favorite', 'Favorites', 'Favorites'),
createNavItem('location_on', 'Nearby', 'Nearby')
]
}
});
expect(wrapper.find('.mu-bottom-item')[0].hasClass('mu-bottom-item-active')).toBe(true);
wrapper.setProps({
value: 'Nearby'
});
expect(wrapper.vm.activeValue).toBe('Nearby');
});
});
================================================
FILE: src/BottomNav/BottomNavItem.js
================================================
import AbstractButton from '../internal/AbstractButton';
import Icon from '../Icon';
import route from '../internal/mixins/route';
import ripple from '../internal/mixins/ripple';
import { isNotNull } from '../utils';
export default {
name: 'mu-bottom-nav-item',
mixins: [route, ripple],
inject: [
'getBottomNavValue',
'getBottomNavShift',
'getDefaultVal',
'bottomNavItemClick',
'getActiveColor'
],
props: {
icon: String,
title: String,
value: {}
},
data () {
return {
itemVal: null
};
},
created () {
this.itemVal = isNotNull(this.value) ? this.value : this.getDefaultVal();
},
computed: {
active () {
return this.getBottomNavValue() === this.itemVal;
},
activeClassName () {
return this.getActiveColor().className;
},
activeColor () {
return this.getActiveColor().color;
},
shift () {
return this.getBottomNavShift();
}
},
methods: {
handleClick () {
this.bottomNavItemClick(this.itemVal);
}
},
watch: {
value (val) {
this.itemVal = val;
}
},
render (h) {
const icon = this.icon ? h(Icon, { class: 'mu-bottom-item-icon', props: { value: this.icon }}) : undefined;
const title = this.title ? h('span', { staticClass: 'mu-bottom-item-text' }, this.title) : undefined;
return h(AbstractButton, {
staticClass: 'mu-bottom-item',
class: {
'mu-bottom-item-active': this.active,
'is-shift': this.active && this.shift,
[this.activeClassName]: !this.shift && this.active
},
style: {
color: !this.shift && this.active ? this.activeColor : ''
},
props: {
ripple: !this.shift && this.ripple,
containerElement: 'div',
wrapperClass: 'mu-bottom-item-wrapper',
...this.generateRouteProps()
},
on: {
click: this.handleClick
}
}, [icon, title]);
}
};
================================================
FILE: src/BottomNav/__snapshots__/BottomNav.spec.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Bottom Navigation should default view 1`] = `
`;
exports[`Bottom Navigation should shift mode 1`] = `
`;
================================================
FILE: src/BottomNav/index.js
================================================
import '../styles/components/bottom-nav.less';
import theme from '../theme';
import BottomNavTheme from './theme';
import BottomNav from './BottomNav';
import BottomNavItem from './BottomNavItem';
BottomNav.install = function (Vue) {
Vue.component(BottomNav.name, BottomNav);
Vue.component(BottomNavItem.name, BottomNavItem);
};
theme.addCreateTheme(BottomNavTheme);
export { BottomNav, BottomNavItem };
export default BottomNav;
================================================
FILE: src/BottomNav/theme.js
================================================
import { fade } from '../utils/colorManipulator';
export default (theme) => {
return `
.mu-bottom-nav{
background-color: ${theme.background.paper};
}
.mu-bottom-nav-shift{
background-color: ${theme.primary};
}
.mu-bottom-item {
color: ${theme.text.secondary};
}
.mu-bottom-nav-shift .mu-bottom-item {
color: ${fade(theme.text.alternate, 0.7)};
}
.mu-bottom-item-active .mu-bottom-item-icon,
.mu-bottom-item-active .mu-bottom-item-text {
color: ${theme.primary};
}
.mu-bottom-nav-shift .mu-bottom-item-active .mu-bottom-item-icon,
.mu-bottom-nav-shift .mu-bottom-item-active .mu-bottom-item-text {
color: ${theme.text.alternate};
}
`;
};
================================================
FILE: src/BottomSheet/BottomSheet.js
================================================
import popup from '../internal/mixins/popup';
import { BottomSheetTransition } from '../internal/transitions';
export default {
name: 'mu-bottom-sheet',
mixins: [popup],
props: {
lockScroll: {
type: Boolean,
default: true
}
},
render (h) {
return h(BottomSheetTransition, [
this.open ? h('div', {
staticClass: 'mu-bottom-sheet',
style: {
'z-index': this.zIndex
}
}, this.$slots.default) : undefined
]);
}
};
================================================
FILE: src/BottomSheet/BottomSheet.spec.js
================================================
import test from '../utils/testing';
import BottomSheet from './BottomSheet';
test('Bottom Sheet', ({ mount }) => {
it('should default view', async () => {
const wrapper = mount(BottomSheet, {
propsData: {
open: true
}
});
expect(wrapper.html()).toMatchSnapshot();
});
});
================================================
FILE: src/BottomSheet/__snapshots__/BottomSheet.spec.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Bottom Sheet should default view 1`] = `
`;
================================================
FILE: src/BottomSheet/index.js
================================================
import '../styles/components/bottom-sheet.less';
import theme from '../theme';
import BottomSheetTheme from './theme';
import BottomSheet from './BottomSheet';
BottomSheet.install = function (Vue) {
Vue.component(BottomSheet.name, BottomSheet);
};
theme.addCreateTheme(BottomSheetTheme);
export default BottomSheet;
================================================
FILE: src/BottomSheet/theme.js
================================================
export default (theme) => {
return `
.mu-bottom-sheet {
background-color: ${theme.background.paper};
}
`;
};
================================================
FILE: src/Breadcrumbs/Breadcrumbs.js
================================================
export default {
name: 'mu-breadcrumbs',
props: {
divider: {
type: String,
default: '/'
}
},
methods: {
createChildren (h) {
if (!this.$slots.default) return;
const divider = this.$slots.divider ? this.$slots.divider : this.divider;
const children = [];
const length = this.$slots.default.length;
const dividerData = { staticClass: 'mu-breadcrumbs-divider' };
this.$slots.default.forEach((el, i) => {
children.push(el);
if (!el.componentOptions || el.componentOptions.tag !== 'mu-breadcrumbs-item' || i === length - 1) return;
children.push(this.$createElement('li', dividerData, divider));
});
return children;
}
},
render (h) {
return h('ul', {
staticClass: 'mu-breadcrumbs',
on: this.$listeners
}, this.createChildren(h));
}
};
================================================
FILE: src/Breadcrumbs/Breadcrumbs.spec.js
================================================
import Vue from 'vue';
import test from '../utils/testing';
import Breadcrumbs from './Breadcrumbs';
import BreadcrumbsItem from './BreadcrumbsItem';
test('Breadcrumbs Items', ({ mount }) => {
function createBreadcrumbsItem (text) {
return Vue.component('breadcrumbs-item', {
components: {
'mu-breadcrumbs-item': BreadcrumbsItem
},
render (h) {
return h('mu-breadcrumbs-item', {
slots: {
default: [
text
]
}
});
}
});
}
it('should default view', async () => {
const wrapper = mount(Breadcrumbs, {
slots: {
default: [
createBreadcrumbsItem('home'),
createBreadcrumbsItem('dashboard')
]
}
});
expect(wrapper.html()).toMatchSnapshot();
});
});
================================================
FILE: src/Breadcrumbs/BreadcrumbsItem.js
================================================
import route from '../internal/mixins/route';
export default {
name: 'mu-breadcrumbs-item',
mixins: [route],
props: {
disabled: Boolean
},
render (h) {
const props = this.to ? this.generateRouteProps() : {
href: this.href
};
return h('li', {
staticClass: 'mu-breadcrumbs-item',
class: this.disabled ? 'is-disabled' : ''
}, [
h(this.to ? 'router-link' : 'a', {
props
}, this.$slots.default)
]);
}
};
================================================
FILE: src/Breadcrumbs/__snapshots__/Breadcrumbs.spec.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Breadcrumbs Items should default view 1`] = `
`;
================================================
FILE: src/Breadcrumbs/index.js
================================================
import '../styles/components/breadcrumbs.less';
import theme from '../theme';
import BreadcrumbsTheme from './theme';
import Breadcrumbs from './Breadcrumbs';
import BreadcrumbsItem from './BreadcrumbsItem';
Breadcrumbs.install = function (Vue) {
Vue.component(Breadcrumbs.name, Breadcrumbs);
Vue.component(BreadcrumbsItem.name, BreadcrumbsItem);
};
theme.addCreateTheme(BreadcrumbsTheme);
export { Breadcrumbs, BreadcrumbsItem };
export default Breadcrumbs;
================================================
FILE: src/Breadcrumbs/theme.js
================================================
export default (theme) => {
return `
.mu-breadcrumbs-item {
color: ${theme.primary};
}
.mu-breadcrumbs-item.is-disabled {
color: ${theme.text.disabled};
}
.mu-breadcrumbs-divider {
color: ${theme.text.disabled};
}
`;
};
================================================
FILE: src/Button/Button.js
================================================
import route from '../internal/mixins/route';
import ripple from '../internal/mixins/ripple';
import button from '../internal/mixins/button';
import color from '../internal/mixins/color';
import AbstractButton from '../internal/AbstractButton';
export default {
name: 'mu-button',
mixins: [route, ripple, button, color],
props: {
fab: Boolean,
flat: Boolean,
icon: Boolean,
small: Boolean,
large: Boolean,
round: Boolean,
textColor: String,
fullWidth: Boolean
},
computed: {
buttonClass () {
const colorClass = this.getNormalColorClass(this.color, this.icon || this.flat);
const textColorClass = this.getTextColorClass();
return {
'mu-fab-button': this.fab,
'mu-flat-button': this.flat,
'mu-icon-button': this.icon,
'mu-raised-button': !this.icon && !this.flat && !this.fab,
'mu-button-small': this.small,
'mu-button-large': this.large,
'mu-button-full-width': !this.fab && !this.icon && this.fullWidth,
[colorClass]: true,
[textColorClass]: true,
'mu-button-round': this.round,
'is-focus': this.focus
};
}
},
render (h) {
const flat = this.flat || this.icon;
let color = this.getColor(this.textColor);
if (!color && flat) color = this.getColor(this.color);
return h(AbstractButton, {
staticClass: 'mu-button',
class: this.buttonClass,
style: {
'background-color': !flat ? this.getColor(this.color) : '',
color
},
props: {
wrapperClass: 'mu-button-wrapper',
disabled: this.disabled,
keyboardFocused: this.keyboardFocused,
type: this.type,
centerRipple: this.icon,
ripple: this.ripple,
rippleOpacity: this.rippleOpacity,
rippleColor: this.rippleColor,
...this.generateRouteProps()
},
on: this.getListener()
}, this.$slots.default);
}
};
================================================
FILE: src/Button/Button.spec.js
================================================
import test from '../utils/testing';
import Button from './Button';
import * as colors from '../theme/colors';
test('Button', ({ mount }) => {
it('should default view', async () => {
const wrapper = mount(Button, {});
expect(wrapper.html()).toMatchSnapshot();
});
it('should variant style', async () => {
const wrapper = mount(Button, {
propsData: {
fab: true
}
});
expect(wrapper.html()).toMatchSnapshot();
wrapper.setProps({
fab: false,
flat: true
});
expect(wrapper.html()).toMatchSnapshot();
wrapper.setProps({
flat: false,
icon: true
});
expect(wrapper.html()).toMatchSnapshot();
wrapper.setProps({
icon: false,
small: true
});
expect(wrapper.html()).toMatchSnapshot();
wrapper.setProps({
small: false,
large: true
});
expect(wrapper.html()).toMatchSnapshot();
wrapper.setProps({
large: false,
round: true
});
expect(wrapper.html()).toMatchSnapshot();
});
it('should custom color', async () => {
const wrapper = mount(Button, {
propsData: {
color: 'primary',
textColor: 'secondary'
}
});
expect(wrapper.hasClass('mu-primary-color')).toBe(true);
expect(wrapper.hasClass('mu-inverse')).toBe(true);
expect(wrapper.hasClass('mu-secondary-text-color')).toBe(true);
wrapper.setProps({
color: 'teal',
textColor: 'green'
});
expect(wrapper.hasStyle('background-color', colors.teal)).toBe(true);
expect(wrapper.hasStyle('color', colors.green)).toBe(true);
wrapper.setProps({
color: '#000',
textColor: '#fff'
});
expect(wrapper.hasStyle('background-color', '#000')).toBe(true);
expect(wrapper.hasStyle('color', '#fff')).toBe(true);
});
});
================================================
FILE: src/Button/__snapshots__/Button.spec.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Button should default view 1`] = `
`;
exports[`Button should variant style 1`] = `
`;
exports[`Button should variant style 2`] = `
`;
exports[`Button should variant style 3`] = `
`;
exports[`Button should variant style 4`] = `
`;
exports[`Button should variant style 5`] = `
`;
exports[`Button should variant style 6`] = `
`;
================================================
FILE: src/Button/index.js
================================================
import '../styles/components/button.less';
import theme from '../theme';
import ButtonTheme from './theme';
import Button from './Button';
Button.install = function (Vue) {
Vue.component(Button.name, Button);
};
theme.addCreateTheme(ButtonTheme);
export default Button;
================================================
FILE: src/Button/theme.js
================================================
import { fade, darken } from '../utils/colorManipulator';
export default (theme) => {
return `
.mu-raised-button {
background-color: ${theme.background.paper};
color: ${theme.text.primary};
}
.mu-raised-button.disabled{
color: ${fade(theme.text.primary, 0.3)};
background-color: ${darken(theme.text.alternate, 0.1)};
}
.mu-flat-button {
color: ${theme.text.primary};
}
.mu-flat-button.disabled {
color: ${theme.text.disabled};
}
.mu-icon-button {
color: ${theme.text.primary};
}
.mu-icon-button.disabled {
color: ${theme.text.disabled};
}
.mu-fab-button {
background-color: ${theme.primary};
color: ${theme.text.alternate};
}
.mu-fab-button.disabled {
color: ${fade(theme.text.primary, 0.3)};
background-color: ${darken(theme.text.alternate, 0.1)};
}
`;
};
================================================
FILE: src/Card/Card.js
================================================
export default {
name: 'mu-card',
props: {
raised: Boolean
},
render (h) {
return h('div', {
staticClass: 'mu-card',
class: {
'mu-card__raised': this.raised
},
on: this.$listeners
}, this.$slots.default);
}
};
================================================
FILE: src/Card/CardHeader.js
================================================
export default {
name: 'mu-card-header',
functional: true,
props: {
title: String,
subTitle: String
},
render (h, { data, props, slots }) {
slots = slots();
const title = props.title || props.subTitle ? h('div', {
staticClass: 'mu-card-header-title'
}, [
h('div', { staticClass: 'mu-card-title' }, props.title),
h('div', { staticClass: 'mu-card-sub-title' }, props.subTitle)
]) : undefined;
data.staticClass = `${data.staticClass || ''} mu-card-header`;
return h('div', data, [slots.avatar, title, slots.default]);
}
};
================================================
FILE: src/Card/CardMedia.js
================================================
export default {
name: 'mu-card-media',
functional: true,
props: {
title: String,
subTitle: String
},
render (h, { data, props, children }) {
const title = props.title || props.subTitle ? h('div', {
staticClass: 'mu-card-media-title'
}, [
h('div', { staticClass: 'mu-card-title' }, props.title),
h('div', { staticClass: 'mu-card-sub-title' }, props.subTitle)
]) : undefined;
data.staticClass = `${data.staticClass || ''} mu-card-media`;
return h('div', data, [children, title]);
}
};
================================================
FILE: src/Card/CardTitle.js
================================================
export default {
name: 'mu-card-title',
functional: true,
props: {
title: String,
subTitle: String
},
render (h, { data, props }) {
data.staticClass = `${data.staticClass || ''} mu-card-title-container`;
return h('div', data, [
h('div', { staticClass: 'mu-card-title' }, props.title),
h('div', { staticClass: 'mu-card-sub-title' }, props.subTitle)
]);
}
};
================================================
FILE: src/Card/index.js
================================================
import '../styles/components/card.less';
import theme from '../theme';
import CardTheme from './theme';
import Card from './Card';
import CardHeader from './CardHeader';
import CardMedia from './CardMedia';
import CardTitle from './CardTitle';
import { createSimpleFunctional } from '../utils';
export { Card, CardHeader, CardMedia, CardTitle };
export const CardActions = createSimpleFunctional('mu-card-actions', 'div', 'mu-card-actions');
export const CardText = createSimpleFunctional('mu-card-text', 'div', 'mu-card-text');
Card.install = function (Vue) {
Vue.component(Card.name, Card);
Vue.component(CardHeader.name, CardHeader);
Vue.component(CardMedia.name, CardMedia);
Vue.component(CardTitle.name, CardTitle);
Vue.component(CardActions.name, CardActions);
Vue.component(CardText.name, CardText);
};
theme.addCreateTheme(CardTheme);
export default Card;
================================================
FILE: src/Card/theme.js
================================================
import { fade } from '../utils/colorManipulator';
export default (theme) => {
return `
.mu-card {
background-color: ${theme.background.paper};
}
.mu-card-header-title .mu-card-title{
color: ${fade(theme.text.primary, 0.87)};
}
.mu-card-header-title .mu-card-sub-title{
color: ${fade(theme.text.primary, 0.57)};
}
.mu-card-text{
color: ${theme.text.primary};
}
.mu-card-title-container .mu-card-title{
color: ${theme.text.primary};
}
.mu-card-title-container .mu-card-sub-title {
color: ${theme.text.secondary};
}
`;
};
================================================
FILE: src/Carousel/Carousel.js
================================================
import Button from '../Button';
import swipe from '../internal/directives/swipe';
export default {
name: 'mu-carousel',
directives: {
swipe
},
provide () {
return {
addCarouselItem: this.addCarouselItem,
removeCarouselItem: this.removeCarouselItem,
isCarouselActive: this.isCarouselActive,
getCarouselTransition: this.getTransition
};
},
props: {
active: {
type: Number,
default: 0
},
cycle: {
type: Boolean,
default: true
},
interval: {
type: [Number, String],
default: 6000,
validator: value => value > 0
},
transition: {
type: String,
default: 'slide',
validator: (val) => ['slide', 'fade'].indexOf(val) !== -1
},
hideIndicators: Boolean,
hideControls: Boolean
},
data () {
return {
items: [],
inverse: false,
activeIndex: this.active
};
},
mounted () {
this.startAutoNext();
},
beforeDestroy () {
if (this.timer) clearInterval(this.timer);
},
methods: {
getTransition () {
return this.transition;
},
addCarouselItem (item) {
const index = this.$children.indexOf(item);
return index === -1 ? this.items.push(item) : this.items.splice(index, 0, item);
},
removeCarouselItem (item) {
const index = this.items.indexOf(item);
if (index === -1) return;
this.items.splice(index, 1);
},
isCarouselActive (item) {
return this.items.indexOf(item) === this.activeIndex;
},
startAutoNext () {
if (this.timer) clearInterval(this.timer);
if (!this.cycle) return;
this.timer = setInterval(() => this.next(), this.interval > 0 ? this.interval : 6000);
},
next () {
const maxIndex = this.items.length - 1;
let index = this.activeIndex + 1;
if (index > maxIndex) index = 0;
this.activeIndex = index;
this.inverse = false;
},
prev () {
let index = this.activeIndex - 1;
if (index < 0) index = this.items.length - 1;
this.activeIndex = index;
this.inverse = true;
},
changeActiveIndex (index) {
if (index !== this.activeIndex) {
this.inverse = this.activeIndex > index;
this.activeIndex = index;
}
},
createControls (h) {
if (this.hideControls) return [];
return [
h(Button, {
class: 'mu-carousel-button mu-carousel-button__left',
props: {
icon: true
},
on: {
click: this.prev
}
}, [
this.$slots.left && this.$slots.left.length > 0
? this.$slots.left
: h('svg', {
staticClass: 'mu-carousel-svg-icon',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'
}
}),
h('path', {
attrs: {
d: 'M0 0h24v24H0z',
fill: 'none'
}
})
])
]),
h(Button, {
class: 'mu-carousel-button mu-carousel-button__right',
props: {
icon: true
},
on: {
click: this.next
}
}, [
this.$slots.right && this.$slots.right.length > 0
? this.$slots.right
: h('svg', {
staticClass: 'mu-carousel-svg-icon',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'
}
}),
h('path', {
attrs: {
d: 'M0 0h24v24H0z',
fill: 'none'
}
})
])
])
];
},
createIndicators (h) {
if (this.hideIndicators) return;
return h('div', {
staticClass: 'mu-carousel-indicators'
}, this.items.map((item, index) => {
const active = index === this.activeIndex;
return this.$scopedSlots.indicator
? this.$scopedSlots.indicator({ index, active })
: h(Button, {
staticClass: 'mu-carousel-indicator-button',
class: {
'mu-carousel-indicator-button__active': active
},
props: {
icon: true
},
on: {
click: () => this.changeActiveIndex(index)
}
}, [
h('span', {
staticClass: 'mu-carousel-indicator-icon'
})
]);
}));
}
},
render (h) {
return h('div', {
staticClass: 'mu-carousel',
class: {
'mu-carousel__transition_inverse': this.inverse
},
directives: [{
name: 'swipe',
value: {
left: this.next,
right: this.prev
},
modifiers: {
touch: true
}
}]
}, [
...this.createControls(h),
this.createIndicators(h),
this.$slots.default
]);
},
watch: {
activeIndex (val) {
this.startAutoNext();
this.$emit('change', val);
this.$emit('update:active', val);
},
active (val) {
this.changeActiveIndex(val);
},
cycle () {
this.startAutoNext();
},
interval () {
this.startAutoNext();
}
}
};
================================================
FILE: src/Carousel/CarouselItem.js
================================================
export default {
name: 'mu-carousel-item',
inject: [
'addCarouselItem',
'removeCarouselItem',
'isCarouselActive',
'getCarouselTransition'
],
data () {
return {
classes: []
};
},
computed: {
active () {
return this.isCarouselActive(this);
},
transition () {
return this.getCarouselTransition();
}
},
created () {
this.addCarouselItem(this);
},
beforeDestroy () {
this.removeCarouselItem(this);
},
render (h) {
return h('transition', {
props: {
name: 'mu-carousel-' + this.transition
}
}, [
h('div', {
staticClass: 'mu-carousel-item',
class: [...this.classes],
directives: [{
name: 'show',
value: this.active
}]
}, this.$slots.default)
]);
}
};
================================================
FILE: src/Carousel/index.js
================================================
import '../styles/components/carousel.less';
import Carousel from './Carousel';
import CarouselItem from './CarouselItem';
Carousel.install = function (Vue) {
Vue.component(Carousel.name, Carousel);
Vue.component(CarouselItem.name, CarouselItem);
};
export { Carousel, CarouselItem };
export default Carousel;
================================================
FILE: src/Checkbox/Checkbox.js
================================================
import select from '../internal/mixins/select';
import Icon from '../Icon';
export default {
name: 'mu-checkbox',
mixins: [select('checkbox')],
props: {
inputValue: [Boolean, Array]
},
computed: {
checked () {
if (!this.inputValue) return false;
const inputValue = this.inputValue;
const value = this.$attrs.value;
if (inputValue instanceof Array) return inputValue.indexOf(value) !== -1;
return inputValue;
}
},
methods: {
toggle () {
const inputValue = this.inputValue;
const value = this.$attrs.value;
if (!inputValue || typeof inputValue === 'boolean') {
this.$emit('change', !inputValue);
return;
}
if (this.checked) {
inputValue.splice(inputValue.indexOf(value), 1);
this.$emit('change', inputValue);
} else {
this.$emit('change', [...inputValue, value]);
}
}
},
render (h) {
const defaultSvgUnCheckIcon = h('svg', {
staticClass: `mu-checkbox-icon-uncheck mu-checkbox-svg-icon`,
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z'
}
})
]);
const defaultSvgCheckedIcon = h('svg', {
staticClass: `mu-checkbox-icon-checked mu-checkbox-svg-icon`,
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'
}
})
]);
const view = this.createRipple(h, 'mu-checkbox-icon', [
this.uncheckIcon ? h(Icon, {
staticClass: `mu-checkbox-icon-uncheck`,
props: {
value: this.uncheckIcon
}
}) : defaultSvgUnCheckIcon,
this.checkedIcon ? h(Icon, {
staticClass: `mu-checkbox-icon-checked`,
props: {
value: this.checkedIcon
}
}) : defaultSvgCheckedIcon
]);
return this.createSelect(h, view);
}
};
================================================
FILE: src/Checkbox/index.js
================================================
import '../styles/components/checkbox.less';
import theme from '../theme';
import CheckboxTheme from './theme';
import Checkbox from './Checkbox';
Checkbox.install = function (Vue) {
Vue.component(Checkbox.name, Checkbox);
};
theme.addCreateTheme(CheckboxTheme);
export default Checkbox;
================================================
FILE: src/Checkbox/theme.js
================================================
export default (theme) => {
return `
.mu-checkbox {
color: ${theme.text.secondary};
}
.mu-checkbox.disabled {
color: ${theme.text.disabled};
}
.mu-checkbox-checked {
color: ${theme.primary};
}
.mu-checkbox.disabled .mu-checkbox-label {
color: ${theme.text.disabled};
}
.mu-checkbox-label {
color: ${theme.text.primary};
}
`;
};
================================================
FILE: src/Chip/Chip.js
================================================
import color from '../internal/mixins/color';
export default {
name: 'mu-chip',
mixins: [color],
props: {
delete: Boolean,
selected: Boolean,
textColor: String
},
methods: {
handleDelete (e) {
e.stopPropagation();
this.$emit('delete');
}
},
render (h) {
const svgDeleteIcon = h('svg', {
staticClass: 'mu-chip-delete-icon',
attrs: {
viewBox: '0 0 24 24'
},
on: {
click: this.handleDelete
}
}, [
h('path', {
attrs: {
d: 'M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z'
}
})
]);
return h('span', {
staticClass: `mu-chip ${this.getColorClass()} ${this.getTextColorClass()}`,
class: {
'is-deletable': this.selected
},
attrs: {
tabindex: 0
},
style: {
color: this.getColor(this.textColor),
backgroundColor: this.getColor(this.color)
},
on: this.$listeners
}, [this.$slots.default, this.delete ? svgDeleteIcon : undefined]);
}
};
================================================
FILE: src/Chip/index.js
================================================
import '../styles/components/chip.less';
import theme from '../theme';
import ChipTheme from './theme';
import Chip from './Chip';
Chip.install = function (Vue) {
Vue.component(Chip.name, Chip);
};
theme.addCreateTheme(ChipTheme);
export default Chip;
================================================
FILE: src/Chip/theme.js
================================================
import { fade, emphasize } from '../utils/colorManipulator';
export default (theme) => {
return `
.mu-chip {
background-color: ${theme.background.chip};
color: ${theme.text.primary};
}
.mu-chip:hover .mu-chip-delete-icon{
color: ${fade(fade(theme.text.primary, 0.26), 0.4)};
}
.mu-chip-delete-icon{
color: ${fade(theme.text.primary, 0.26)};
}
.mu-chip:active,
.mu-chip:focus,
.mu-chip.is-deletable {
background-color: ${emphasize(theme.background.chip, 0.08)};
}
.mu-chip:hover{
background-color: ${emphasize(theme.background.chip, 0.08)};
}
.mu-chip.mu-primary-color {
background-color: ${theme.primary};
}
.mu-chip.mu-secondary-color {
background-color: ${theme.secondary};
}
.mu-chip.mu-success-color {
background-color: ${theme.success};
}
.mu-chip.mu-warning-color {
background-color: ${theme.warning};
}
.mu-chip.mu-info-color {
background-color: ${theme.info};
}
.mu-chip.mu-error-color {
background-color: ${theme.error};
}
`;
};
================================================
FILE: src/DataTable/DataTable.js
================================================
import header from './mixins/header';
import body from './mixins/body';
import footer from './mixins/footer';
import colgroup from './mixins/colgroup';
import progress from './mixins/progress';
import mousewheel from '../internal/directives/mousewheel';
import { addResizeListener, removeResizeListener } from '../utils/resize-event';
import { getWidth } from '../utils';
export default {
name: 'mu-data-table',
mixins: [header, body, footer, colgroup, progress],
props: {
data: Array,
columns: Array,
noDataText: {
type: String,
default: '暂无数据'
},
height: [String, Number],
maxHeight: [String, Number],
selectAll: Boolean,
selectable: Boolean,
selects: {
type: Array,
default: () => []
},
sort: {
type: Object
},
checkbox: Boolean,
stripe: Boolean,
border: Boolean,
loading: Boolean,
hideHeader: Boolean,
rowClassName: [String, Function],
rowStyle: [Object, Function],
rowKey: {
type: String,
default: 'id'
},
fit: {
type: Boolean,
default: true
},
hover: {
type: Boolean,
default: true
}
},
methods: {
handleHeaderFooterMousewheel (event, data) {
const { pixelX, pixelY } = data;
if (Math.abs(pixelX) >= Math.abs(pixelY)) {
event.preventDefault();
if (!this.$refs.body) return;
this.$refs.body.scrollLeft += data.pixelX / 5;
}
},
resizeListener () {
this.setCols();
}
},
mounted () {
if (this.fit) {
addResizeListener(this.$el, this.resizeListener);
}
},
beforeDestroy () {
if (this.resizeListener) removeResizeListener(this.$el, this.resizeListener);
},
render (h) {
return h('div', {
staticClass: 'mu-table',
class: {
'mu-table-border': this.border,
'mu-table-flex': this.maxHeight || this.height
},
style: {
'max-height': getWidth(this.maxHeight),
'height': getWidth(this.height)
}
}, [
!this.hideHeader ? this.createHeader(h) : undefined,
this.createProgress(h),
this.createBody(h),
this.createFooter(h)
]);
},
directives: {
mousewheel
}
};
================================================
FILE: src/DataTable/index.js
================================================
import '../styles/components/data-table.less';
import theme from '../theme';
import DataTableTheme from './theme';
import DataTable from './DataTable';
DataTable.install = function (Vue) {
Vue.component(DataTable.name, DataTable);
};
theme.addCreateTheme(DataTableTheme);
export default DataTable;
================================================
FILE: src/DataTable/mixins/body.js
================================================
import Checkbox from '../../Checkbox';
import { ExpandTransition } from '../../internal/transitions';
export default {
props: {
expandRowIndex: {
type: Number,
default: -1
},
autoExpand: {
type: Boolean,
default: true
}
},
data () {
return {
hoverIndex: -1,
expandIndex: this.expandRowIndex,
isSelectAll: false
};
},
methods: {
handleScroll (e) {
const scrollLeft = e.target.scrollLeft;
const theader = this.$refs.header;
const tfooter = this.$refs.tfooter;
if (theader) theader.scrollLeft = scrollLeft;
if (tfooter) tfooter.scrollLeft = scrollLeft;
},
isSelected (index) {
return this.selects.indexOf(index) !== -1;
},
toggleSelect (index) {
if (!this.selectable) return;
const selects = [...this.selects];
const selectIndex = selects.indexOf(index);
if (selectIndex !== -1) {
selects.splice(selectIndex, 1);
} else {
selects.push(index);
}
selects.sort((a, b) => a - b);
this.$emit('update:selects', selects);
this.$emit('select-change', index, selects);
},
toggleExpand (index) {
this.expandIndex = this.expandIndex === index ? -1 : index;
},
createEmpty (h) {
return [
this.$slots.empty
? this.$slots.empty
: h('div', { staticClass: 'mu-table-empty' }, this.noDataText)
];
},
createSlotContent (row, index) {
return this.$scopedSlots.default({
row,
$index: index
});
},
createCheckboxTd (h, index) {
return h('td', {
staticClass: 'mu-checkbox-col'
}, [
h(Checkbox, {
props: {
inputValue: this.isSelected(index),
disabled: !this.selectable
},
on: {
change: () => this.toggleSelect(index),
click: (e) => {
e.stopPropagation();
}
}
})
]);
},
createContent (h) {
const contents = [];
for (let index = 0; index < this.data.length; index++) {
const row = this.data[index];
const arr = this.$scopedSlots.default
? this.createSlotContent(row, index)
: this.columns.map((column) => {
const text = column.formatter && typeof column.formatter === 'function'
? column.formatter(row[column.name], row)
: row[column.name];
return h('td', {
class: [
column.align || column.cellAlign ? `is-${column.cellAlign || column.align}` : ''
]
}, text);
}) || [];
if (this.checkbox) arr.unshift(this.createCheckboxTd(h, index));
const rowClassName = typeof this.rowClassName === 'function' ? this.rowClassName(index, row) : this.rowClassName;
contents.push(
h('tr', {
staticClass: rowClassName,
class: {
'is-hover': this.hover && this.hoverIndex === index,
'is-stripe': this.stripe && index % 2 !== 0,
'is-selected': this.isSelected(index)
},
style: typeof this.rowStyle === 'function' ? this.rowStyle(index, row) : this.rowStyle,
on: {
mouseenter: (e) => {
this.hoverIndex = index;
this.$emit('row-mouseenter', index, row, e);
},
mouseleave: (e) => {
this.hoverIndex = -1;
this.$emit('row-mouseleave', index, row, e);
},
contextmenu: (e) => {
this.$emit('row-contextmenu', index, row, e);
},
click: (e) => {
if (!this.checkbox) this.toggleSelect(index);
if (this.autoExpand) this.toggleExpand(index);
this.$emit('row-click', index, row, e);
},
dblclick: (e) => this.$emit('row-dblclick', index, row, e)
},
key: row[this.rowKey]
}, arr)
);
if (this.$scopedSlots.expand) {
contents.push(
h('tr', {
staticClass: 'mu-table-expand-row'
}, [
h('td', {
attrs: {
colspan: this.columns.length + (this.checkbox ? 1 : 0)
},
class: {
'is-expand': this.expandIndex === index
}
}, this.expandIndex === index ? [
h(ExpandTransition, {}, this.$scopedSlots.expand({
row,
$index: index
}))
] : undefined)
])
);
}
}
return contents;
},
createBody (h) {
return this.data && this.data.length > 0 ? h('div', {
staticClass: 'mu-table-body-wrapper',
on: {
scroll: this.handleScroll
},
ref: 'body'
}, [
h('table', {
staticClass: 'mu-table-body',
style: {
width: this.tableWidth
}
}, [
this.createColGroup(h),
h('tbody', {}, this.createContent(h))
])
]) : this.createEmpty(h);
}
},
watch: {
selects (val) {
this.isSelectAll = val && val.length >= this.data.length;
},
expandRowIndex (val) {
if (this.expandIndex === val) return;
this.expandIndex = val;
},
expandIndex (val) {
this.$emit('update:expandRowIndex', val);
this.$emit('change-expand', val);
}
}
};
================================================
FILE: src/DataTable/mixins/colgroup.js
================================================
import resize from '../../internal/directives/resize';
export default {
props: {
minColWidth: {
type: Number,
default: 128
},
checkboxColWidth: {
type: Number,
default: 75
}
},
data () {
return {
cols: [],
tableWidth: ''
};
},
mounted () {
this.setCols();
},
methods: {
setCols () {
let tableElWidth = this.$el.offsetWidth;
const widthArr = this.columns.filter((column) => column.width).map((column) => Number(column.width));
widthArr.forEach((width) => (tableElWidth -= width));
if (this.checkbox) tableElWidth -= this.checkboxColWidth;
let otherWidth = Math.floor(tableElWidth / (this.columns.length - widthArr.length));
if (otherWidth < this.minColWidth) otherWidth = this.minColWidth;
this.cols = this.columns.map((column) => {
return column.width ? column.width : this.fit ? otherWidth : this.minColWidth;
});
if (this.checkbox) this.cols.unshift(this.checkboxColWidth);
let tableWidth = 0;
this.cols.forEach((width) => (tableWidth += Number(width)));
this.tableWidth = tableWidth + 'px';
},
createColGroup (h) {
return h('colgroup', {
}, this.cols.map((width) => {
return h('col', {
attrs: {
width
}
});
}));
}
},
watch: {
columns () {
this.setCols();
}
},
directives: {
resize
}
};
================================================
FILE: src/DataTable/mixins/footer.js
================================================
export default {
methods: {
createFooter (h) {
return this.$scopedSlots.footer ? h('div', {
staticClass: 'mu-table-footer-wrapper',
ref: 'footer'
}, [
h('table', {
staticClass: 'mu-table-footer',
style: {
width: this.tableWidth
}
}, [
this.createColGroup(h),
h('tbody', {}, this.$scopedSlots.footer({
columns: this.columns
}))
])
]) : undefined;
}
}
};
================================================
FILE: src/DataTable/mixins/header.js
================================================
import Checkbox from '../../Checkbox';
import Tooltip from '../../Tooltip';
export default {
methods: {
toggleSelectAll (val) {
this.isSelectAll = val;
const selects = [];
if (this.isSelectAll) {
let i = 0;
while (i < this.data.length) {
selects.push(i++);
}
}
this.$emit('update:selects', selects);
},
handleSortChange (column) {
const sort = {
...this.sort
};
if (this.sort && this.sort.name === column.name) {
sort.order = sort.order === 'desc' ? 'asc' : 'desc';
} else {
sort.name = column.name;
sort.order = 'desc';
}
this.$emit('update:sort', sort);
this.$emit('sort-change', sort);
},
createSlotHeader () {
return this.$scopedSlots.header({
columns: this.columns
});
},
createSlotTh (column) {
return this.$scopedSlots.th({
...column
});
},
createDefaultTh (h, column) {
return [
column.sortable ? h('svg', {
staticClass: 'mu-table-sort-icon',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z'
}
})
]) : undefined,
column.title
];
},
createTh (h, column) {
return this.$scopedSlots.th ? this.createSlotTh(column) : this.createDefaultTh(h, column);
},
createCheckboxTh (h) {
const isEnable = this.selectable && this.selectAll;
return h('th', {
staticClass: 'mu-checkbox-col'
}, [
h(Checkbox, {
props: {
inputValue: this.isSelectAll,
disabled: !isEnable
},
on: {
change: this.toggleSelectAll
}
})
]);
},
createTHeader (h) {
const arr = this.columns.map((column) => {
const th = h('th', {
class: [
column.align ? `is-${column.align}` : '',
column.class || '',
column.sortable ? 'is-sortable' : '',
column.sortable && this.sort && this.sort.name === column.name ? 'is-sorting' : '',
column.sortable && this.sort && this.sort.name === column.name && this.sort.order === 'asc' ? 'sort-asc' : ''
],
on: {
click: () => column.sortable && this.handleSortChange(column)
}
}, this.createTh(h, column));
return column.tooltip ? h(Tooltip, {
props: {
content: column.tooltip
}
}, [th]) : th;
});
if (this.checkbox) arr.unshift(this.createCheckboxTh(h));
return h('tr', {}, arr);
},
createHeader (h) {
return h('div', {
staticClass: 'mu-table-header-wrapper',
ref: 'header',
directives: [{
name: 'mousewheel',
value: this.handleHeaderFooterMousewheel
}]
}, [
h('table', {
staticClass: 'mu-table-header',
style: {
width: this.tableWidth
}
}, [
this.createColGroup(h),
h('thead', {}, [
this.$scopedSlots.header ? this.createSlotHeader() : this.createTHeader(h)
])
])
]);
}
}
};
================================================
FILE: src/DataTable/mixins/progress.js
================================================
import { LinearProgress } from '../../Progress';
import { FadeTransition } from '../../internal/transitions';
export default {
mounted () {
},
methods: {
createProgress (h) {
const headerHeight = this.$refs.header ? this.$refs.header.offsetHeight + 'px' : '';
return h(FadeTransition, {}, [
this.loading ? h(LinearProgress, {
staticClass: 'mu-table-progress',
style: {
top: headerHeight
}
}) : undefined
]);
}
}
};
================================================
FILE: src/DataTable/theme.js
================================================
import * as colors from '../theme/colors';
export default (theme, type) => {
return `
.mu-table {
background-color: ${theme.text.alternate};
}
.mu-table tr {
color: ${theme.text.primary};
}
.mu-table tr.is-stripe {
background-color: ${type === 'dark' ? colors.grey800 : colors.grey50};
}
.mu-table tr.is-hover {
background-color: ${type === 'dark' ? 'rgba(0, 0, 0, .14)' : colors.grey200};
}
.mu-table tr.is-selected {
background-color: ${type === 'dark' ? colors.grey700 : colors.grey100};
}
.mu-table td {
border-bottom-color: ${theme.divider};
}
.mu-table th {
color: ${theme.text.secondary};
border-bottom-color: ${theme.divider};
}
.mu-table th.is-sortable:hover {
color: ${theme.text.primary};
}
.mu-table th.is-sorting {
color: ${theme.text.primary};
}
.mu-table-border {
border-color: ${theme.divider};
}
.mu-table-border th,
.mu-table-border td {
border-right-color: ${theme.divider};
}
.mu-table-empty {
color: ${theme.text.secondary};
}
.mu-table-expand-row td.is-expand {
border-bottom-color: ${theme.divider};
}
`;
};
================================================
FILE: src/DateInput/Container.js
================================================
import BottomSheet from '../BottomSheet';
import Dialog from '../Dialog';
import Popover from '../Popover';
export default {
props: {
container: {
type: String,
default: 'popover', // dialog popover bottomSheet
validator (val) {
return val && ['dialog', 'popover', 'bottomSheet'].indexOf(val) !== -1;
}
},
trigger: {},
open: Boolean
},
methods: {
createWrap (h, children) {
switch (this.container) {
case 'popover':
return h(Popover, {
props: {
open: this.open,
cover: true,
lazy: true,
trigger: this.trigger
},
on: this.$listeners
}, children);
case 'dialog':
return h(Dialog, {
props: {
open: this.open,
dialogClass: 'mu-picker-dialog',
transition: 'slide-top'
},
on: this.$listeners
}, children);
case 'bottomSheet':
return h(BottomSheet, {
props: {
open: this.open
},
on: this.$listeners
}, children);
}
}
},
render (h) {
return this.createWrap(h, this.$slots.default);
}
};
================================================
FILE: src/DateInput/DateInput.js
================================================
import input from '../internal/mixins/input';
import keyboardFocus from '../internal/directives/keyboard-focus';
import { DatePicker, TimePicker, DateTimePicker } from '../Picker';
import PickerMixin from '../Picker/mixins/props';
import Container from './Container';
import dayjs from 'dayjs';
import Button from '../Button/Button';
import keycode from 'keycode';
const DEFAULT_FORMAT = {
date: 'YYYY-MM-DD',
time: 'HH:mm',
year: 'YYYY',
month: 'YYYY-MM',
dateTime: 'YYYY-MM-DD HH:mm'
};
const PickerProps = {
...TimePicker.props,
...DatePicker.props,
...PickerMixin.props
};
delete PickerProps.date;
delete PickerProps.time;
delete PickerProps.type;
delete PickerProps.format;
export default {
name: 'mu-date-input',
mixins: [{
...input
}],
directives: {
keyboardFocus
},
model: {
prop: 'value',
event: 'input'
},
props: {
container: {
type: String,
default: 'popover', // dialog popover bottomSheet
validator (val) {
return val && ['dialog', 'popover', 'bottomSheet'].indexOf(val) !== -1;
}
},
type: {
type: String,
default: 'date' // date, time, year, month, dateTime, dateRange
},
format: {
type: String
},
rangeSeparator: {
type: String,
default: '—'
},
actions: Boolean,
clockType: TimePicker.props.format,
okLabel: {
type: String,
default: '确定'
},
cancelLabel: {
type: String,
default: '取消'
},
value: {},
valueFormat: String,
...PickerProps
},
data () {
return {
open: false,
date: this.value ? dayjs(this.value).toDate() : new Date()
};
},
methods: {
changeValue () {
this.closePicker();
const value = this.valueFormat ? dayjs(this.date).format(this.valueFormat) : this.date;
this.$emit('input', value);
this.$emit('change', value);
if (this.muFormItem) this.muFormItem.onBlur();
},
focus (e) {
this.isFocused = true;
this.$emit('focus', e);
},
blur (e) {
this.isFocused = false;
this.$emit('blur', e);
},
closePicker () {
this.open = false;
},
handleDateChange (date) {
this.date = date;
if (!this.actions) this.changeValue();
},
handleTimeChange (date, mode, finished) {
this.date = date;
if (!finished || mode !== 'minute') return;
if (!this.actions) this.changeValue();
},
generateTextFieldProps () {
return this.generateProps(input.props);
},
generatePickerProps () {
return this.generateProps(PickerMixin.props);
},
generateDatePickerProps () {
return this.generateProps(DatePicker.props);
},
generateTimePickerProps () {
return this.generateProps(TimePicker.props);
},
generateProps (props) {
const obj = {};
Object.keys(props).forEach(key => {
obj[key] = this[key];
});
return obj;
},
createTextField (h) {
const dateStr = this.value ? dayjs(this.value).format(this.format ? this.format : DEFAULT_FORMAT[this.type]) : '';
const listeners = {
...this.$listeners,
keydown: (e) => {
if (keycode(e) === 'tab') {
this.blur(e);
this.open = false;
}
},
click: () => (this.open = true),
focus: this.focus,
blur: this.blur
};
delete listeners.input;
delete listeners.change;
const placeholder = !this.labelFloat ? this.$attrs.placeholder : '';
return [
h('input', {
staticClass: 'mu-text-field-input',
ref: 'input',
attrs: {
tabindex: 0,
...this.$attrs,
disabled: this.disabled,
placeholder,
readonly: true
},
domProps: {
value: dateStr
},
directives: [{
name: 'keyboard-focus',
value: () => (this.open = true)
}],
on: listeners
})
];
},
createActions (h) {
if (!this.actions) return;
return h('div', {
staticClass: `mu-picker-actions`
}, [
h(Button, {
props: {
flat: true,
color: 'primary'
},
on: {
click: this.closePicker
}
}, this.cancelLabel),
h(Button, {
props: {
flat: true,
color: 'primary'
},
on: {
click: this.changeValue
}
}, this.okLabel)
]);
},
createPicker (h) {
switch (this.type) {
case 'date':
case 'year':
case 'month':
return h(DatePicker, {
props: {
...this.generateDatePickerProps(),
...this.generatePickerProps(),
type: this.type === 'month' ? 'month' : this.type === 'year' ? 'year' : 'date',
date: this.date
},
on: {
change: this.handleDateChange
},
style: {
width: this.container === 'bottomSheet' ? 'auto' : ''
},
scopedSlots: {
day: this.$scopedSlots.day
}
}, [this.createActions(h)]);
case 'dateTime':
return h(DateTimePicker, {
props: {
...this.generateDatePickerProps(),
...this.generateTimePickerProps(),
...this.generatePickerProps(),
format: this.clockType,
date: this.date
},
scopedSlots: {
day: this.$scopedSlots.day
},
on: {
change: this.handleTimeChange
},
style: {
width: this.container === 'bottomSheet' ? 'auto' : ''
}
}, [this.createActions(h)]);
case 'time':
return h(TimePicker, {
props: {
...this.generateTimePickerProps(),
...this.generatePickerProps(),
time: this.date,
format: this.clockType
},
on: {
change: this.handleTimeChange
},
style: {
width: this.container === 'bottomSheet' ? 'auto' : ''
}
}, [this.createActions(h)]);
}
}
},
render (h) {
return this.createInput(h, {
staticClass: 'mu-text-field',
ref: 'content'
}, [
this.createTextField(h),
this.$slots.default,
h(Container, {
props: {
container: this.container,
open: this.open,
trigger: this.$el ? this.$el.querySelector('.mu-text-field') : undefined
},
ref: 'popover',
on: {
close: this.closePicker
}
}, [this.createPicker(h)])
]);
},
beforeDestroy () {
this.closePicker();
},
watch: {
value (val) {
this.date = val ? dayjs(val).toDate() : undefined;
}
}
};
================================================
FILE: src/DateInput/index.js
================================================
import DateInput from './DateInput';
import '../styles/components/date-input.less';
DateInput.install = function (Vue) {
Vue.component(DateInput.name, DateInput);
};
export default DateInput;
================================================
FILE: src/Dialog/Dialog.js
================================================
import popup from '../internal/mixins/popup';
import resize from '../internal/directives/resize';
import { convertClass, getWidth } from '../utils';
export default {
name: 'mu-dialog',
mixins: [popup],
directives: {
resize
},
props: {
dialogClass: [String, Array, Object],
title: String,
scrollable: Boolean,
padding: { // 设置scrollable 之后dailog 框距离顶部和底部的值
type: Number,
default: 64
},
fullscreen: Boolean,
width: [String, Number],
maxWidth: [String, Number],
lockScroll: {
type: Boolean,
default: true
},
transition: {
type: String,
default: 'scale',
validator (val) {
return ['slide-top', 'slide-bottom', 'slide-left', 'slide-right', 'fade', 'scale'];
}
}
},
mounted () {
this.setMaxDialogContentHeight();
},
updated () {
this.$nextTick(() => {
this.setMaxDialogContentHeight();
});
},
methods: {
handleWrapperClick (e) {
if (this.$el !== e.target) return;
this.overlayClick(e);
},
setMaxDialogContentHeight () {
const dialogEl = this.$refs.dialog;
if (!dialogEl) return;
if (!this.scrollable) {
dialogEl.style.maxHeight = '';
return;
}
const maxDialogContentHeight = window.innerHeight - 2 * this.padding;
const { footer, title, elBody } = this.$refs;
if (elBody) {
let maxBodyHeight = maxDialogContentHeight;
if (footer) maxBodyHeight -= footer.offsetHeight;
if (title) maxBodyHeight -= title.offsetHeight;
elBody.style.maxHeight = maxBodyHeight + 'px';
}
dialogEl.style.maxHeight = maxDialogContentHeight + 'px';
}
},
watch: {
open (newValue) {
if (!newValue) return;
this.$nextTick(() => {
this.setMaxDialogContentHeight();
const dialogEl = this.$refs.dialog;
if (!dialogEl) return;
dialogEl.focus();
});
}
},
render (h) {
const hasTitleSlots = this.$slots.title && this.$slots.title.length > 0;
const isShowTitle = this.title || hasTitleSlots;
const dialogTitle = isShowTitle ? h('div', {
staticClass: 'mu-dialog-title',
ref: 'title'
}, hasTitleSlots ? this.$slots.title : this.title) : undefined;
const dialogBody = h('div', {
staticClass: 'mu-dialog-body',
ref: 'elBody'
}, this.$slots.default);
const dialogActions = this.$slots.actions && this.$slots.actions.length > 0 ? h('div', {
staticClass: 'mu-dialog-actions',
ref: 'footer'
}, this.$slots.actions) : undefined;
const data = {
staticClass: 'mu-dialog ' + convertClass(this.dialogClass).join(' '),
attrs: {
tabindex: -1
},
class: {
'mu-dialog-fullscreen': this.fullscreen,
'mu-dialog-scrollable': this.scrollable,
[`mu-${this.transition}`]: true
},
ref: 'dialog'
};
if (!this.fullscreen) {
data.style = {
'max-width': this.maxWidth === 'auto' ? undefined : getWidth(this.maxWidth),
'width': this.width === 'auto' ? undefined : getWidth(this.width)
};
}
const dialog = h('div', data, [dialogTitle, dialogBody, dialogActions]);
return this.open ? h('transition', {
props: {
name: `mu-dialog-transition`
}
}, [
h('div', {
staticClass: 'mu-dialog-wrapper',
directives: [{
name: 'resize',
value: () => this.setMaxDialogContentHeight()
}],
style: {
'z-index': this.zIndex
},
on: {
click: this.handleWrapperClick
}
}, [dialog])
]) : null;
}
};
================================================
FILE: src/Dialog/index.js
================================================
import '../styles/components/dialog.less';
import theme from '../theme';
import DialogTheme from './theme';
import Dialog from './Dialog';
Dialog.install = function (Vue) {
Vue.component(Dialog.name, Dialog);
};
theme.addCreateTheme(DialogTheme);
export default Dialog;
================================================
FILE: src/Dialog/theme.js
================================================
import { fade } from '../utils/colorManipulator';
export default (theme) => {
return `
.mu-dialog {
background-color: ${theme.background.paper};
}
.mu-dialog-scrollable .mu-dialog-title {
border-bottom-color: ${theme.divider};
}
.mu-dialog-scrollable .mu-dialog-actions {
border-top-color: ${theme.divider};
}
.mu-dialog-title {
color: ${theme.text.primary};
}
.mu-dialog-body {
color: ${fade(theme.text.primary, 0.6)};
}
`;
};
================================================
FILE: src/Divider/Divider.js
================================================
export default {
name: 'mu-divider',
functional: true,
props: {
inset: Boolean,
shallowInset: Boolean
},
render (h, { data, props }) {
data.staticClass = `${data.staticClass || ''} mu-divider ${props.inset ? 'inset' : ''} ${props.shallowInset ? 'shallow-inset' : ''}`;
return h('hr', data);
}
};
================================================
FILE: src/Divider/index.js
================================================
import '../styles/components/divider.less';
import theme from '../theme';
import DividerTheme from './theme';
import Divider from './Divider';
Divider.install = function (Vue) {
Vue.component(Divider.name, Divider);
};
theme.addCreateTheme(DividerTheme);
export default Divider;
================================================
FILE: src/Divider/theme.js
================================================
export default (theme) => {
return `
.mu-divider {
background-color: ${theme.divider};
}
`;
};
================================================
FILE: src/Drawer/Drawer.js
================================================
import Paper from '../Paper';
import PopupManager from '../internal/mixins/popup/manager';
import { getZIndex } from '../internal/mixins/popup/utils';
import { getWidth } from '../utils';
const transitionEvents = ['msTransitionEnd', 'mozTransitionEnd', 'oTransitionEnd', 'webkitTransitionEnd', 'transitionend'];
export default {
name: 'mu-drawer',
props: {
right: Boolean,
open: Boolean,
docked: {
type: Boolean,
default: true
},
lockScroll: {
type: Boolean,
default: true
},
width: [Number, String],
zDepth: {
type: Number,
default: 16
}
},
data () {
return {
overlayZIndex: getZIndex(),
zIndex: getZIndex()
};
},
computed: {
drawerStyle () {
return {
width: getWidth(this.width),
'z-index': this.docked ? '' : this.zIndex
};
},
overlay () {
return !this.docked;
}
},
mounted () {
if (this.open && !this.docked) PopupManager.open(this);
this.bindTransition();
},
methods: {
overlayClick () {
this.close('overlay');
},
escPress (e) {
if (this.docked) return;
this.close('esc');
},
close (reason) {
this.$emit('update:open', false);
this.$emit('close', reason);
},
bindTransition () {
this.handleTransition = (e) => {
if (e.propertyName !== 'transform') return;
this.$emit(this.open ? 'show' : 'hide');
};
transitionEvents.forEach((eventName) => {
this.$el.addEventListener(eventName, this.handleTransition);
});
},
unBindTransition () {
if (!this.handleTransition) return;
transitionEvents.forEach((eventName) => {
this.$el.removeEventListener(eventName, this.handleTransition);
});
},
resetZIndex () {
this.overlayZIndex = getZIndex();
this.zIndex = getZIndex();
}
},
beforeDestroy () {
PopupManager.close(this);
this.unBindTransition();
},
watch: {
open (val) {
if (val && !this.docked) {
PopupManager.open(this);
} else {
PopupManager.close(this);
}
},
docked (val, oldVal) {
if (val && !oldVal) {
PopupManager.close(this);
}
}
},
render (h) {
return h(Paper, {
class: {
'mu-drawer': true,
'is-open': this.open,
'is-right': this.right
},
style: this.drawerStyle,
props: {
zDepth: this.zDepth
}
}, this.$slots.default);
}
};
================================================
FILE: src/Drawer/index.js
================================================
import '../styles/components/drawer.less';
import theme from '../theme';
import DrawerTheme from './theme';
import Drawer from './Drawer';
Drawer.install = function (Vue) {
Vue.component(Drawer.name, Drawer);
};
theme.addCreateTheme(DrawerTheme);
export default Drawer;
================================================
FILE: src/Drawer/theme.js
================================================
export default (theme) => {
return `
.mu-drawer {
background-color: ${theme.background.paper};
}
`;
};
================================================
FILE: src/ExpansionPanel/ExpansionPanel.js
================================================
import Paper from '../Paper';
import Button from '../Button';
import { ExpandTransition } from '../internal/transitions';
export default {
name: 'mu-expansion-panel',
props: {
expand: Boolean,
zDepth: {
type: Number,
default: 2
}
},
data () {
return {
show: this.expand
};
},
methods: {
createToggleIcon (h) {
return h('svg', {
staticClass: '',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('g', {}, [
h('path', {
attrs: {
d: 'M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z'
}
})
])
]);
},
createHeader (h) {
return h('div', {
staticClass: 'mu-expansion-panel-header',
on: {
click: () => {
this.show = !this.show;
this.$emit('update:expand', this.show);
this.$emit('change', this.show);
}
}
}, [
this.$slots.header,
h(Button, {
staticClass: 'mu-expansion-toggle-btn',
props: {
icon: true
},
attrs: {
tabindex: -1
}
}, [this.createToggleIcon(h)])
]);
},
createContainer (h) {
return h(ExpandTransition, {}, [
h('div', {
staticClass: 'mu-expansion-panel-container',
directives: [{
name: 'show',
value: this.show
}]
}, [
this.createContent(h),
this.createActions(h)
])
]);
},
createContent (h) {
return h('div', {
staticClass: 'mu-expansion-panel-content'
}, this.$slots.default);
},
createActions (h) {
return this.$slots.action && this.$slots.action.length > 0
? h('div', {
staticClass: 'mu-expansion-panel-actions'
}, this.$slots.action)
: undefined;
}
},
render (h) {
return h(Paper, {
staticClass: 'mu-expansion-panel',
class: {
'mu-expansion-panel__expand': this.show
},
props: {
zDepth: this.zDepth,
rounded: false
}
}, [
this.createHeader(h),
this.createContainer(h)
]);
},
watch: {
expand (val) {
this.show = val;
}
}
};
================================================
FILE: src/ExpansionPanel/index.js
================================================
import '../styles/components/expansion-panel.less';
import theme from '../theme';
import ExpansionPanelTheme from './theme';
import ExpansionPanel from './ExpansionPanel';
ExpansionPanel.install = function (Vue) {
Vue.component(ExpansionPanel.name, ExpansionPanel);
};
theme.addCreateTheme(ExpansionPanelTheme);
export default ExpansionPanel;
================================================
FILE: src/ExpansionPanel/theme.js
================================================
export default (theme) => {
return `
.mu-expansion-panel {
color: ${theme.text.primary};
border-top-color: ${theme.divider};
}
.mu-expansion-toggle-btn {
color: ${theme.text.secondary};
}
.mu-expansion-panel-actions {
border-top-color: ${theme.divider};
}
`;
};
================================================
FILE: src/Form/Form.js
================================================
import { isPromise, getObjAttr } from '../utils';
export default {
name: 'mu-form',
provide () {
return {
muForm: this
};
},
props: {
model: {
type: Object,
required: true
},
inline: Boolean,
labelWidth: [String, Number],
labelPosition: {
type: String,
default: 'top',
validator (val) {
return ['left', 'right', 'top'].indexOf(val) !== -1;
}
},
autoValidate: {
type: Boolean,
default: true
}
},
data () {
return {
items: []
};
},
methods: {
getValue (prop) {
return getObjAttr(this.model, prop);
},
addItem (item) {
this.items.push(item);
},
removeItem (item) {
const index = this.items.indexOf(item);
if (index === -1) return;
this.items.splice(index, 1);
},
validate () {
let valid = true;
const promises = [];
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
const result = item.validate();
if (isPromise(result)) {
promises.push(result);
continue;
}
if (!result) {
valid = false;
}
}
if (promises.length > 0 && typeof Promise !== 'undefined') {
return Promise.all([
valid ? Promise.resolve(valid) : Promise.reject(valid),
...promises
]).then(
() => true,
() => false
);
}
return typeof Promise !== 'undefined' ? Promise.resolve(valid) : valid;
},
clear () {
this.items.forEach((item) => (item.errorMessage = ''));
}
},
render (h) {
return h('form', {
staticClass: 'mu-form',
class: {
'mu-form__inline': this.inline
},
on: this.$listeners
}, this.$slots.default);
}
};
================================================
FILE: src/Form/FormItem.js
================================================
import Form from './Form';
import Icon from '../Icon';
import { getWidth, isPromise, isObject } from '../utils';
import { SlideTopTransition } from '../internal/transitions';
export default {
name: 'mu-form-item',
inject: ['muForm'],
provide () {
return {
muFormItem: this
};
},
props: {
label: String,
labelFloat: Boolean,
icon: String,
prop: String,
labelWidth: Form.props.labelWidth,
rules: Array,
helpText: String,
errorText: String,
labelPosition: String
},
data () {
return {
focus: false,
errorMessage: this.errorText
};
},
mounted () {
this.setHelpLeft();
this.muForm.addItem(this);
},
updated () {
setTimeout(() => this.setHelpLeft(), 0);
},
beforeDestroy () {
this.muForm.removeItem(this);
},
methods: {
validate () {
if (!this.rules || this.rules.length === 0) return true;
const promises = [];
const promiseMessages = [];
for (let i = 0; i < this.rules.length; i++) {
const rule = this.rules[i];
const result = rule.validate(this.muForm.getValue(this.prop), this.muForm.model);
if (isPromise(result)) {
promises.push(result);
promiseMessages.push(rule.message);
continue;
}
if (!this.validateResult(result, rule.message)) return false;
}
// promise 处理
if (promises.length > 0 && typeof Promise !== 'undefined') {
return Promise.all(promises).then((results) => {
for (let i = 0; i < results.length; i++) {
const valid = this.validateResult(results[i], promiseMessages[i]);
if (!valid) return Promise.reject(false);
}
this.errorMessage = '';
return true;
});
}
this.errorMessage = '';
return true;
},
validateResult (result, message) {
switch (true) {
case isObject(result) && !result.valid:
this.errorMessage = result.message || message;
return false;
case !result:
this.errorMessage = message;
return false;
}
return true;
},
onFocus () {
this.focus = true;
},
onBlur () {
this.focus = false;
if (this.muForm.autoValidate) this.validate();
},
createIcon (h) {
if (!this.icon) return;
return h(Icon, {
staticClass: 'mu-form-item-icon',
props: {
value: this.icon
}
});
},
createContent (h) {
return h('div', {
staticClass: 'mu-form-item-content',
ref: 'content'
}, this.$slots.default);
},
createLabel (h) {
const labelWidth = getWidth(this.labelWidth || this.muForm.labelWidth);
const value = this.muForm.model && this.prop && this.muForm.model[this.prop];
return h('div', {
staticClass: 'mu-form-item-label',
class: {
'is-float': this.labelFloat && !this.focus && !value && value !== 0
},
style: {
width: labelWidth
}
}, this.$slots.label || this.label);
},
createHelpText (h) {
if (!this.helpText && !this.errorMessage) return;
return h('div', {
staticClass: 'mu-form-item-help',
ref: 'help'
}, this.errorMessage || this.helpText);
},
setHelpLeft () {
if (!this.$refs.help || !this.$refs.content) return;
this.$refs.help.style.left = this.$refs.content.offsetLeft + 'px';
}
},
render (h) {
const labelPosition = this.labelPosition || this.muForm.labelPosition;
return h('div', {
staticClass: 'mu-form-item',
class: {
'mu-form-item__float-label': this.labelFloat,
'mu-form-item__label-left': labelPosition === 'left',
'mu-form-item__label-right': labelPosition === 'right',
'mu-form-item__has-icon': !!this.icon && labelPosition === 'top',
'mu-form-item__has-label': !!this.label || (this.$slots.label && this.$slots.label.length > 0),
'mu-form-item__focus': this.focus,
'mu-form-item__error': !!this.errorMessage
}
}, [
this.createLabel(h),
labelPosition === 'top' ? this.createIcon(h) : undefined,
h(SlideTopTransition, {}, [this.createHelpText(h)]),
this.createContent(h)
]);
},
watch: {
errorText (val) {
this.errorMessage = val;
},
rules () {
if (this.errorMessage) this.validate();
}
}
};
================================================
FILE: src/Form/index.js
================================================
import '../styles/components/form.less';
import theme from '../theme';
import FormTheme from './theme';
import Form from './Form';
import FormItem from './FormItem';
Form.install = function (Vue) {
Vue.component(Form.name, Form);
Vue.component(FormItem.name, FormItem);
};
theme.addCreateTheme(FormTheme);
export { Form, FormItem };
export default Form;
================================================
FILE: src/Form/theme.js
================================================
export default (theme) => {
return `
.mu-form-item {
color: ${theme.text.secondary};
}
.mu-form-item__focus {
color: ${theme.primary};
}
.mu-form-item__error {
color: ${theme.error};
}
.mu-form-item-help {
color: ${theme.text.secondary};
}
.mu-form-item__error .mu-form-item-help {
color: ${theme.error};
}
`;
};
================================================
FILE: src/Grid/Col.js
================================================
import { props, generatePropsClass } from './utils';
function createColClass (props) {
const classNames = [];
if (props.span) classNames.push('col-' + props.span);
if (props.sm) classNames.push('col-sm-' + props.sm);
if (props.md) classNames.push('col-md-' + props.md);
if (props.lg) classNames.push('col-lg-' + props.lg);
if (props.xl) classNames.push('col-xl-' + props.xl);
if (props.order) classNames.push('order-' + props.order);
if (props.offset) classNames.push('offset-' + props.offset);
return classNames.join(' ');
}
export default {
name: 'mu-col',
functional: true,
props: {
tag: String,
alignSelf: props.alignSelf,
fill: Boolean,
span: [String, Number], // auto 1-12
sm: [String, Number], // auto 1-12
md: [String, Number], // auto 1-12
lg: [String, Number], // auto 1-12
xl: [String, Number], // auto 1-12
order: [String, Number], // first last 0-12
offset: [String, Number] // 1-11
},
render (h, { data, props, children }) {
const flex = generatePropsClass(props);
const col = createColClass(props);
data.staticClass = ['col', col, flex, data.staticClass || ''].join(' ');
return h(props.tag || 'div', data, children);
}
};
================================================
FILE: src/Grid/Container.js
================================================
export default {
name: 'mu-container',
functional: true,
props: {
fluid: Boolean
},
render (h, { data, props, children }) {
data.staticClass = `${data.staticClass || ''} ${props.fluid ? 'container-fluid' : 'container'}`;
return h('div', data, children);
}
};
================================================
FILE: src/Grid/Flex.js
================================================
import { props, generatePropsClass } from './utils';
export default {
name: 'mu-flex',
functional: true,
props: {
tag: String,
inline: Boolean,
...props
},
render (h, { data, props, children }) {
const flexClass = generatePropsClass(props);
data.staticClass = `${props.inline ? 'd-inline-flex' : 'd-flex'} ${flexClass} ${data.staticClass || ''}`;
return h(props.tag || 'div', data, children);
}
};
================================================
FILE: src/Grid/Row.js
================================================
import { props, generatePropsClass } from './utils';
export default {
name: 'mu-row',
functional: true,
props: {
...props,
tag: String,
gutter: Boolean
},
render (h, { data, props, children }) {
const gutter = !props.gutter ? 'no-gutters' : '';
const flex = generatePropsClass(props);
data.staticClass = ['row', gutter, flex, data.staticClass || ''].join(' ');
return h(props.tag || 'div', data, children);
}
};
================================================
FILE: src/Grid/index.js
================================================
import '../styles/components/bootstrap-grid.less';
import Container from './Container';
import Row from './Row';
import Col from './Col';
import Flex from './Flex';
export { Container, Row, Col };
export default {
install (Vue) {
Vue.component(Container.name, Container);
Vue.component(Row.name, Row);
Vue.component(Col.name, Col);
Vue.component(Flex.name, Flex);
}
};
================================================
FILE: src/Grid/utils.js
================================================
function createEnumProps (type, def, enums) {
return {
type,
default: def,
validator (val) {
return enums.indexOf(val) !== -1;
}
};
}
export const props = {
direction: createEnumProps(String, 'row', ['row', 'column', 'row-reverse', 'column-reverse']),
wrap: createEnumProps(String, '', ['', 'wrap', 'nowrap', 'wrap-reverse']),
fill: Boolean,
justifyContent: createEnumProps(String, 'start', ['start', 'center', 'end', 'between', 'around']),
alignItems: createEnumProps(String, 'start', ['start', 'center', 'end', 'baseline', 'stretch']),
alignContent: createEnumProps(String, '', ['', 'start', 'center', 'end', 'between', 'around', 'stretch']),
alignSelf: createEnumProps(String, '', ['', 'auto', 'start', 'center', 'end', 'baseline', 'stretch'])
};
export function generatePropsClass (props) {
const classNames = [];
if (props.direction) classNames.push('flex-' + props.direction);
if (props.wrap) classNames.push('flex-' + props.wrap);
if (props.fill) classNames.push('flex-fill');
if (props.justifyContent) classNames.push('justify-content-' + props.justifyContent);
if (props.alignItems) classNames.push('align-items-' + props.alignItems);
if (props.alignContent) classNames.push('align-content-' + props.alignContent);
if (props.alignSelf) classNames.push('align-self-' + props.alignSelf);
return classNames.join(' ');
}
================================================
FILE: src/GridList/GridList.js
================================================
export default {
name: 'mu-grid-list',
provide () {
return {
getGridListCellHeight: this.getGridListCellHeight,
getGridListCols: this.getGridListCols,
getGridListPadding: this.getGridListPadding
};
},
props: {
cellHeight: {
type: Number,
default: 180
},
cols: {
type: Number,
default: 2
},
padding: {
type: Number,
default: 4
}
},
methods: {
getGridListCellHeight () {
return this.cellHeight;
},
getGridListCols () {
return this.cols;
},
getGridListPadding () {
return this.padding;
}
},
render (h) {
return h('div', {
staticClass: 'mu-grid-list',
style: {
margin: -this.padding / this.cols + 'px'
},
on: this.$listeners
}, this.$slots.default);
}
};
================================================
FILE: src/GridList/GridTile.js
================================================
export default {
name: 'mu-grid-tile',
inject: ['getGridListCellHeight', 'getGridListCols', 'getGridListPadding'],
props: {
actionPosition: {
type: String,
default: 'right',
validator (val) {
return ['left', 'right'].indexOf(val) !== -1;
}
},
cols: {
type: Number,
default: 1
},
rows: {
type: Number,
default: 1
},
title: {
type: String
},
subTitle: {
type: String
},
titlePosition: {
type: String,
default: 'bottom',
validator (val) {
return ['top', 'bottom'].indexOf(val) !== -1;
}
}
},
computed: {
tileClass () {
return {
'is-top': this.titlePosition === 'top',
'action-left': this.actionPosition === 'left',
'multiline': this.$slots.title && this.$slots.subTitle && this.$slots.title.length > 0 && this.$slots.subTitle.length > 0
};
},
style () {
return {
width: (this.cols / this.getGridListCols() * 100) + '%',
padding: (this.getGridListPadding() / 2) + 'px',
height: (this.getGridListCellHeight() * this.rows) + 'px'
};
}
},
render (h) {
const title = h('div', {
staticClass: 'mu-grid-tile-title'
}, this.$slots.title && this.$slots.title.length > 0 ? this.$slots.title : this.title);
const subTitle = h('div', {
staticClass: 'mu-grid-tile-subtitle'
}, this.$slots.subTitle && this.$slots.subTitle.length > 0 ? this.$slots.subTitle : this.subTitle);
return h('div', {
staticClass: 'mu-grid-tile-wrapper',
style: this.style,
on: this.$listeners
}, [
h('div', {
staticClass: 'mu-grid-tile',
class: this.tileClass
}, [
this.$slots.default,
h('div', { staticClass: 'mu-grid-tile-titlebar' }, [
h('div', { staticClass: 'mu-grid-tile-title-container' }, [title, subTitle]),
h('div', { staticClass: 'mu-grid-tile-action' }, this.$slots.action)
])
])
]);
}
};
================================================
FILE: src/GridList/index.js
================================================
import '../styles/components/grid-list.less';
import theme from '../theme';
import GridListTheme from './theme';
import GridList from './GridList';
import GridTile from './GridTile';
GridList.install = function (Vue) {
Vue.component(GridList.name, GridList);
Vue.component(GridTile.name, GridTile);
};
theme.addCreateTheme(GridListTheme);
export { GridList, GridTile };
export default GridList;
================================================
FILE: src/GridList/theme.js
================================================
export default (theme) => {
return ``;
};
================================================
FILE: src/Helpers/index.js
================================================
import TouchRipple from '../internal/TouchRipple';
import {
ExpandTransition,
FadeTransition,
SlideTopTransition,
SlideBottomTransition,
SlideLeftTransition,
SlideRightTransition,
ScaleTransition
} from '../internal/transitions';
import clickOutside from '../internal/directives/click-outside';
import resize from '../internal/directives/resize';
import scroll from '../internal/directives/scroll';
import elevation from '../internal/directives/elevation';
export default {
install (Vue) {
Vue.component('mu-ripple', TouchRipple);
[
ExpandTransition,
FadeTransition,
SlideTopTransition,
SlideBottomTransition,
SlideLeftTransition,
SlideRightTransition,
ScaleTransition
].forEach(transition => Vue.component(transition.name, transition));
Vue.directive(clickOutside.name, clickOutside);
Vue.directive(resize.name, resize);
Vue.directive(scroll.name, scroll);
Vue.directive(elevation.name, elevation);
}
};
================================================
FILE: src/Icon/Icon.js
================================================
import color from '../internal/mixins/color';
export default {
name: 'mu-icon',
functional: true,
props: {
value: String,
left: Boolean,
right: Boolean,
size: [Number, String],
color: String
},
render (h, { data, props }) {
if (!props.value) return null;
data.style = data.style || {};
data.style = {
...data.style,
'user-select': 'none',
'font-size': props.size + 'px',
'width': props.size + 'px',
'height': props.size + 'px',
'color': color.methods.getColor(props.color)
};
const isMaterial = props.value.indexOf(':') !== 0;
const text = isMaterial ? props.value : '';
data.staticClass = `${data.staticClass || ''} mu-icon ${color.methods.getNormalColorClass(props.color, true)} ${isMaterial ? 'material-icons' : props.value.substring(1)} ${props.left ? 'mu-icon-left' : ''} ${props.right ? 'mu-icon-right' : ''}`;
return h('i', data, text);
}
};
================================================
FILE: src/Icon/index.js
================================================
import Icon from './Icon';
Icon.install = function (Vue) {
Vue.component(Icon.name, Icon);
};
export default Icon;
================================================
FILE: src/List/List.js
================================================
export default {
name: 'mu-list',
provide () {
return {
listItemClick: this.listItemClick,
getNestedLevel: this.getNestedLevel,
getToggleNested: this.getToggleNested,
getToggleNestedType: this.getToggleNestedType,
getListValue: this.getListValue,
getNestedIndent: this.getNestedIndent
};
},
props: {
nestedLevel: {
type: Number,
default: 0
},
textline: {
type: String,
default: '',
validator (val) {
return ['', 'two-line', 'three-line'].indexOf(val) !== -1;
}
},
nestedIndent: {
type: Boolean,
default: true
},
toggleNested: Boolean,
toggleNestedType: {
type: String,
default: 'expand',
validator (val) {
return ['expand', 'popover'].indexOf !== -1;
}
},
dense: Boolean,
value: {}
},
methods: {
listItemClick (item) {
this.$emit('change', item.value);
this.$emit('item-click', item);
},
getListValue () {
return this.value;
},
getNestedLevel () {
return this.nestedLevel;
},
getNestedIndent () {
return this.nestedIndent;
},
getToggleNested () {
return this.toggleNested;
},
getToggleNestedType () {
return this.toggleNestedType;
}
},
render (h) {
return h('ul', {
staticClass: 'mu-list',
class: {
['mu-list-' + this.textline]: this.textline,
'mu-list-dense': this.dense
},
on: this.$listeners
}, this.$slots.default);
}
};
================================================
FILE: src/List/ListAction.js
================================================
export default {
name: 'mu-list-item-action',
functional: true,
render (h, { data, props, children }) {
data.staticClass = `mu-item-action ${children && children.length > 1 ? 'is-more' : ''} ${data.staticClass || ''}`;
return h('div', data, children);
}
};
================================================
FILE: src/List/ListItem.js
================================================
import AbstractButton from '../internal/AbstractButton';
import route from '../internal/mixins/route';
import ripple from '../internal/mixins/ripple';
import ExpandTransition from '../internal/ExpandTransition';
import List from './List';
import Popover from '../Popover';
import { isNotNull } from '../utils';
export default {
name: 'mu-list-item',
mixins: [route, ripple],
inject: [
'listItemClick',
'getNestedLevel',
'getNestedIndent',
'getListValue',
'getToggleNested',
'getToggleNestedType'
],
props: {
button: Boolean,
nestedListClass: [String, Object, Array],
open: {
type: Boolean,
default: true
},
avatar: Boolean,
nested: Boolean, // 是否允许嵌套
tabIndex: [String, Number],
value: {}
},
data () {
return {
nestedOpen: this.open
};
},
computed: {
nestedLevel () {
return this.getNestedLevel();
},
nestedIndent () {
return this.getNestedIndent();
},
toggleNested () {
return this.getToggleNested();
},
toggleNestedType () {
return this.getToggleNestedType();
}
},
created () {
if (this.toggleNestedType === 'popover' && this.nestedOpen) {
this.nestedOpen = false;
}
},
methods: {
handleClick (e) {
this.$emit('click', e);
this.listItemClick(this);
if (this.toggleNested) this.handleToggleNested();
},
handleKeyboardFocus (isFocus) {
this.$emit('keyboard-focus', isFocus);
},
handleHover (event) {
this.$emit('hover', event);
},
handleHoverExit (event) {
this.$emit('hover-exit', event);
},
handleToggleNested () {
this.nestedOpen = !this.nestedOpen;
this.$emit('toggle-nested', this.nestedOpen);
},
handleNestedClick (item) {
this.listItemClick(item);
},
createItem (h) {
const listValue = this.getListValue();
const nestedPadding = this.nestedIndent && this.toggleNestedType === 'expand' ? 18 * this.nestedLevel : 0;
const itemClass = [
'mu-item',
this.nestedOpen && this.nested ? 'mu-item__open' : '',
this.avatar ? 'has-avatar' : '',
this.textline,
isNotNull(listValue) && isNotNull(this.value) && listValue === this.value ? 'is-selected' : ''
].join(' ');
return h(AbstractButton, {
class: 'mu-item-wrapper',
ref: 'button',
attrs: {
tabindex: this.tabIndex
},
props: {
containerElement: this.button ? 'a' : 'div',
wrapperClass: itemClass,
wrapperStyle: {
'margin-left': nestedPadding ? nestedPadding + 'px' : ''
},
disabled: !this.button,
ripple: this.button && this.ripple,
rippleColor: this.rippleColor,
rippleOpacity: this.rippleOpacity,
centerRipple: false,
...this.generateRouteProps()
},
on: {
click: this.handleClick,
keyboardFocus: this.handleKeyboardFocus,
hover: this.handleHover,
hoverExit: this.handleHoverExit
}
}, this.$slots.default);
},
createNestedList (h) {
if (!this.nested) return null;
const list = h(List, {
class: this.nestedListClass,
props: {
nestedIndent: this.nestedIndent,
toggleNested: this.toggleNested,
toggleNestedType: this.toggleNestedType,
nestedLevel: this.nestedLevel + 1,
value: this.getListValue()
},
on: {
'item-click': this.handleNestedClick
}
}, this.$slots.nested);
switch (this.toggleNestedType) {
case 'expand':
return h(ExpandTransition, {}, [this.nestedOpen ? list : undefined]);
case 'popover':
return h(Popover, {
props: {
open: this.nestedOpen,
trigger: this.$el,
appendBody: false,
placement: 'right-start'
},
on: {
close: this.handleToggleNested
}
}, [list]);
}
return null;
}
},
render (h) {
return h('li', [this.createItem(h), this.createNestedList(h)]);
},
watch: {
open (val) {
this.nestedOpen = val;
},
nestedOpen (val) {
this.$emit('update:open', val);
}
}
};
================================================
FILE: src/List/index.js
================================================
import '../styles/components/list.less';
import theme from '../theme';
import ListTheme from './theme';
import { createSimpleFunctional } from '../utils';
import List from './List';
import ListItem from './ListItem';
import ListAction from './ListAction';
export { List, ListItem, ListAction };
export const ListItemContent = createSimpleFunctional('mu-item-content', 'div', 'mu-list-item-content');
export const ListItemTitle = createSimpleFunctional('mu-item-title', 'div', 'mu-list-item-title');
export const ListItemSubTitle = createSimpleFunctional('mu-item-sub-title', 'div', 'mu-list-item-sub-title');
export const ListItemAfterText = createSimpleFunctional('mu-item-after-text', 'span', 'mu-list-item-after-text');
List.install = function (Vue) {
Vue.component(List.name, List);
Vue.component(ListItem.name, ListItem);
Vue.component(ListAction.name, ListAction);
Vue.component(ListItemContent.name, ListItemContent);
Vue.component(ListItemTitle.name, ListItemTitle);
Vue.component(ListItemSubTitle.name, ListItemSubTitle);
Vue.component(ListItemAfterText.name, ListItemAfterText);
};
theme.addCreateTheme(ListTheme);
export default List;
================================================
FILE: src/List/theme.js
================================================
import { fade } from '../utils/colorManipulator';
export default (theme) => {
return `
.mu-item-wrapper.hover {
background-color: ${fade(theme.text.primary, 0.1)};
}
.mu-item {
color: ${theme.text.primary};
}
.mu-item-action {
color: ${theme.text.secondary};
}
.mu-item.is-selected {
color: ${theme.primary};
}
.mu-item-sub-title {
color: ${theme.text.secondary};
}
.mu-item-after-text {
color: ${theme.text.secondary};
}
`;
};
================================================
FILE: src/LoadMore/InfiniteScroll.js
================================================
import Circular from '../Progress/Circular';
import scroll from '../internal/directives/scroll';
import { getScrollTop } from '../utils/dom';
export default {
name: 'mu-infinite-scroll',
directives: {
scroll
},
props: {
loading: {
type: Boolean,
default: false
},
loadingText: {
type: String,
default: '正在加载...'
}
},
data () {
return {
target: null
};
},
mounted () {
this.target = this.$el;
},
methods: {
onScroll (scroller) {
if (this.loading) return;
const isWindow = scroller === window;
const scrollTop = getScrollTop(scroller);
const scrollHeight = isWindow ? document.documentElement.scrollHeight || document.body.scrollHeight : scroller.scrollHeight;
const h = scrollHeight - scrollTop - 5;
const sh = isWindow ? window.innerHeight : scroller.offsetHeight;
if (h <= sh) {
this.$emit('load');
}
}
},
render (h) {
return h('div', {
staticClass: 'mu-infinite-scroll',
directives: [{
name: 'scroll',
value: {
callback: this.onScroll,
target: this.target
}
}]
}, [
h(Circular, {
props: {
size: 24
},
directives: [{
name: 'show',
value: this.loading
}]
}),
h('span', {
staticClass: 'mu-infinite-scroll-text',
directives: [{
name: 'show',
value: this.loading
}]
}, this.loadingText)
]);
}
};
================================================
FILE: src/LoadMore/LoadMore.js
================================================
import RefreshControl from './RefreshControl';
import InfiniteScroll from './InfiniteScroll';
import { isNotNull } from '../utils';
export default {
name: 'mu-load-more',
props: {
refreshing: Boolean,
...InfiniteScroll.props,
loadedAll: Boolean
},
data () {
return {
trigger: null
};
},
mounted () {
this.trigger = this.$el;
},
render (h) {
return h('div', {
staticClass: 'mu-load-more'
}, [
isNotNull(this.$listeners.refresh) ? h(RefreshControl, {
props: {
refreshing: this.refreshing,
trigger: this.trigger
},
on: {
refresh: () => this.$emit('refresh')
}
}) : undefined,
this.$slots.default,
isNotNull(this.$listeners.load) && !this.loadedAll ? h(InfiniteScroll, {
props: {
loading: this.loading,
loadingText: this.loadingText
},
on: {
load: () => this.$emit('load')
}
}) : undefined
]);
}
};
================================================
FILE: src/LoadMore/RefreshControl.js
================================================
import Circular from '../Progress/Circular';
import { transitionEnd, getScrollEventTarget } from '../utils/dom';
import Drag from '../utils/drag';
const LENGTH = 130; // 下拉最大长度
const INITY = -68; // 初始化Y轴位置
export default {
name: 'mu-refresh-control',
props: {
refreshing: Boolean,
trigger: {}
},
data () {
return {
y: 0,
draging: false,
state: 'ready'
};
},
computed: {
refreshStyle () {
const style = {};
if (!this.refreshing && this.draging) {
const translate3d = 'translate3d(0, ' + (this.y + INITY) + 'px, 0) ';
style['-webkit-transform'] = style['transform'] = translate3d;
}
return style;
},
circularStyle () {
const style = {};
if (!this.refreshing && this.draging) {
const percentage = this.y / LENGTH;
const rotate = 'rotate(' + (360 * percentage) + 'deg)';
const opacity = this.y / Math.abs(INITY);
style['-webkit-transform'] = style['transform'] = rotate;
style['opacity'] = opacity;
}
return style;
},
refreshClass () {
const classNames = [];
switch (this.state) {
case 'ready':
classNames.push('mu-refresh-control-noshow');
break;
case 'dragStart':
classNames.push('mu-refresh-control-hide');
break;
case 'dragAnimate':
classNames.push('mu-refresh-control-animate');
classNames.push('mu-refresh-control-hide');
break;
case 'refreshAnimate':
classNames.push('mu-refresh-control-animate');
classNames.push('mu-refresh-control-noshow');
break;
}
if (this.refreshing) classNames.push('mu-refresh-control-refreshing');
return classNames;
}
},
mounted () {
this.bindDrag();
},
beforeDestory () {
this.unbindDrag();
},
methods: {
clearState () {
this.state = 'ready';
this.draging = false;
this.y = 0;
},
getScrollTop () {
const scroller = getScrollEventTarget(this.$el);
if (scroller === window) {
return Math.max(window.pageYOffset || 0, document.documentElement.scrollTop);
} else {
return scroller.scrollTop;
}
},
bindDrag () {
if (!this.trigger) return;
const drager = this.drager = new Drag(this.trigger);
this.state = 'ready';
drager.start(() => {
if (this.refreshing) return;
this.state = 'dragStart';
const scrollTop = this.getScrollTop();
if (scrollTop === 0) this.draging = true;
}).drag((pos, event) => {
const scrollTop = this.getScrollTop();
if (pos.y < 5 || this.refreshing || scrollTop !== 0) return; // 消除误差
if (scrollTop === 0 && !this.draging) {
this.draging = true;
drager.reset(event);
}
this.y = pos.y / 2;
if (this.y < 0) this.y = 1;
if (this.y > LENGTH) this.y = LENGTH;
}).end((pos, event) => {
if (!pos.y || pos.y < 5) {
this.clearState();
return; // 消除误差
}
const canRefresh = this.y + INITY > 0 && this.draging;
this.state = 'dragAnimate';
if (canRefresh) {
this.draging = false;
this.$emit('refresh');
} else {
this.y = 0;
transitionEnd(this.$el, this.clearState.bind(this));
}
});
// fix ios
this.handlePrevent = event => {
if (this.draging && this.y > 0) event.preventDefault();
};
this.handleTouchEnd = () => true;
this.trigger.addEventListener('touchmove', this.handlePrevent, { passive: false });
this.trigger.addEventListener('touchend', this.handleTouchEnd, { passive: false });
},
unbindDrag () {
if (!this.drager) return;
if (this.handlePrevent) {
this.trigger.removeEventListener('touchmove', this.handlePrevent);
this.trigger.removeEventListener('touchend', this.handleTouchEnd);
}
this.drager.destory();
this.drager = null;
},
createRefreshIcon (h) {
return this.refreshing
? h(Circular, {
props: {
size: 24,
borderWidth: 2
}
})
: this.draging
? h('svg', {
staticClass: 'mu-refresh-svg-icon',
style: this.circularStyle,
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z'
}
})
]) : undefined;
}
},
render (h) {
return h('div', {
staticClass: 'mu-refresh-control',
style: this.refreshStyle,
class: this.refreshClass
}, [
this.createRefreshIcon(h)
]);
},
watch: {
refreshing (val) {
if (!val) {
transitionEnd(this.$el, this.clearState.bind(this));
} else {
this.state = 'refreshAnimate';
}
},
trigger (trigger, oldTrigger) {
if (trigger === oldTrigger) return;
this.unbindDrag();
this.bindDrag();
}
}
};
================================================
FILE: src/LoadMore/index.js
================================================
import '../styles/components/progress.less';
import '../styles/components/load-more.less';
import theme from '../theme';
import LoadMoreTheme from './theme';
import InfiniteScroll from './InfiniteScroll';
import RefreshControl from './RefreshControl';
import LoadMore from './LoadMore';
LoadMore.install = function (Vue) {
Vue.component(LoadMore.name, LoadMore);
Vue.component(RefreshControl.name, RefreshControl);
Vue.component(InfiniteScroll.name, InfiniteScroll);
};
theme.addCreateTheme(LoadMoreTheme);
export { LoadMore, InfiniteScroll, RefreshControl };
export default LoadMore;
================================================
FILE: src/LoadMore/theme.js
================================================
export default theme => {
return `
.mu-refresh-control{
color: ${theme.primary};
}
`;
};
================================================
FILE: src/Menu/Menu.js
================================================
import Popover from '../Popover';
export default {
name: 'mu-menu',
props: {
popoverClass: [String, Object, Array],
cover: Popover.props.cover,
placement: Popover.props.placement,
space: Popover.props.space,
open: Boolean,
openOnHover: Boolean
},
data () {
return {
active: this.open,
trigger: null
};
},
mounted () {
this.trigger = this.$el;
},
methods: {
handleMouseEnter () {
if (!this.openOnHover) return;
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(() => this.show(), 100);
},
handleMouseLeave () {
if (!this.openOnHover) return;
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(() => this.hide(), 100);
},
show () {
this.active = true;
this.$emit('open');
},
hide () {
this.active = false;
this.$emit('close');
},
createPopover (h) {
return h(Popover, {
class: this.popoverClass,
style: {
'min-width': this.trigger ? this.trigger.offsetWidth + 'px' : ''
},
props: {
cover: this.cover,
placement: this.placement,
open: this.active,
space: this.space,
trigger: this.trigger
},
on: {
close: this.hide,
mouseenter: this.handleMouseEnter,
mouseleave: this.handleMouseLeave
}
}, this.$slots.content);
}
},
render (h) {
return h('div', {
staticClass: 'mu-menu',
class: {
'mu-menu__open': this.active
}
}, [
h('div', {
staticClass: 'mu-menu-activator',
on: {
click: () => this.openOnHover ? null : this.active ? this.hide() : this.show(),
mouseenter: this.handleMouseEnter,
mouseleave: this.handleMouseLeave
}
}, this.$slots.default),
this.createPopover(h)
]);
},
beforeDestroy () {
this.hide();
},
watch: {
active (val) {
this.$emit('update:open', val);
},
open (val) {
this.active = val;
}
}
};
================================================
FILE: src/Menu/index.js
================================================
import '../styles/components/menu.less';
import Menu from './Menu';
Menu.install = function (Vue) {
Vue.component(Menu.name, Menu);
};
export default Menu;
================================================
FILE: src/Pagination/Pagination.js
================================================
import Button from '../Button';
export default {
name: 'mu-pagination',
props: {
total: {
type: Number,
default: 0,
validator: (val) => val >= 0
},
current: {
type: Number,
default: 1,
validator: (val) => val >= 1
},
pageCount: {
type: Number,
default: 7,
validator (val) {
return val >= 5 && val <= 21 && val % 2 !== 0;
}
},
pageSize: {
type: Number,
default: 10
},
raised: Boolean,
circle: Boolean
},
computed: {
showPageCount () {
return this.pageCount - 2;
},
totalPage () {
return Math.ceil(this.total / this.pageSize);
},
items () {
if (this.total === 0) return [];
const showPageCount = this.showPageCount;
const arr = [];
const start = 1;
const end = this.totalPage;
if (end <= showPageCount + 2) {
for (let i = start; i <= end; i++) {
arr.push({ text: i, value: i });
}
return arr;
}
arr.push({ text: start, value: start });
if (this.current - start >= showPageCount - 1) {
const go = this.current - showPageCount;
arr.push({
text: '...',
value: go < 1 ? 1 : go
});
}
let listStart = this.current - Math.floor(showPageCount / 2);
if (listStart <= 1) listStart = 2;
let listEnd = listStart + showPageCount - 1;
if (listEnd >= end) listEnd = end - 1;
listStart = listEnd - showPageCount + 1;
for (let i = listStart; i <= listEnd; i++) {
arr.push({ text: i, value: i });
}
if (end - this.current >= showPageCount - 1) {
const go = this.current + showPageCount;
arr.push({
text: '...',
value: go > end ? end : go
});
}
arr.push({ text: end, value: end });
return arr;
}
},
methods: {
changePage (page) {
this.$emit('update:current', page);
this.$emit('change', page);
},
createPrevBtn (h) {
return h(Button, {
staticClass: 'mu-pagination-btn',
props: {
flat: true,
disabled: this.current <= 1
},
on: {
click: () => this.changePage(this.current - 1)
}
}, [
h('svg', {
staticClass: 'mu-pagination-svg-icon',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'
}
})
])
]);
},
creatNextBtn (h) {
return h(Button, {
staticClass: 'mu-pagination-btn',
props: {
flat: true,
disabled: this.current >= this.totalPage
},
on: {
click: () => this.changePage(this.current + 1)
}
}, [
h('svg', {
staticClass: 'mu-pagination-svg-icon',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'
}
})
])
]);
},
createPageList (h) {
return h('ul', {}, this.items.map(item => {
const btn = h(Button, {
staticClass: 'mu-pagination-item',
class: {
'is-current': this.current === item.value
},
props: {
flat: true
},
on: {
click: () => this.changePage(item.value)
}
}, [
item.text === '...'
? h('svg', {
staticClass: 'mu-pagination-svg-icon',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M6 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z'
}
})
])
: item.text
]);
return h('li', {}, [btn]);
}));
}
},
render (h) {
return h('div', {
staticClass: 'mu-pagination',
class: {
'mu-pagination__raised': this.raised,
'mu-pagination__circle': this.circle
}
}, [
this.createPrevBtn(h),
this.createPageList(h),
this.creatNextBtn(h)
]);
}
};
================================================
FILE: src/Pagination/index.js
================================================
import '../styles/components/pagination.less';
import theme from '../theme';
import PaginationTheme from './theme';
import Pagination from './Pagination';
Pagination.install = function (Vue) {
Vue.component(Pagination.name, Pagination);
};
theme.addCreateTheme(PaginationTheme);
export default Pagination;
================================================
FILE: src/Pagination/theme.js
================================================
export default (theme) => {
return `
.mu-pagination {
color: ${theme.text.primary};
font-size: 14px;
}
.mu-pagination__raised .mu-pagination-item.mu-button,
.mu-pagination__raised .mu-pagination-btn.mu-button{
background-color: ${theme.text.alternate};
}
.mu-pagination-item.mu-button.is-current {
background-color: ${theme.primary};
}
`;
};
================================================
FILE: src/Paper/Paper.js
================================================
import { convertClass } from '../utils';
export default {
name: 'mu-paper',
functional: true,
props: {
circle: Boolean,
rounded: {
type: Boolean,
default: true
},
zDepth: {
type: Number,
default: 0,
validator (val) {
return val >= 0 && val < 25;
}
}
},
render (h, { data, props, children }) {
const classObj = {
'mu-paper-circle': props.circle,
'mu-paper-round': props.rounded,
['mu-elevation-' + props.zDepth]: !!props.zDepth
};
data.staticClass = `mu-paper ${data.staticClass || ''} ${convertClass(classObj).join(' ')}`;
return h('div', data, children);
}
};
================================================
FILE: src/Paper/index.js
================================================
import '../styles/components/paper.less';
import '../styles/components/elevation.less';
import theme from '../theme';
import PaperTheme from './theme';
import Paper from './Paper';
Paper.install = function (Vue) {
Vue.component(Paper.name, Paper);
};
theme.addCreateTheme(PaperTheme);
export default Paper;
================================================
FILE: src/Paper/theme.js
================================================
export default (theme) => {
return `
.mu-paper {
color: ${theme.text.primary};
background-color: ${theme.background.paper};
}
`;
};
================================================
FILE: src/Picker/DatePicker/DateDisplay.js
================================================
import color from '../../internal/mixins/color';
export default {
mixins: [color],
props: {
type: String,
dateTimeFormat: Object,
monthDaySelected: {
type: Boolean,
default: true
},
displayDate: Date
},
data () {
return {
displayDates: [this.displayDate],
slideType: 'next'
};
},
methods: {
replaceSelected (date) {
const oldDate = this.displayDates[0];
this.slideType = date.getTime() > oldDate.getTime() ? 'next' : 'prev';
this.displayDates.push(date);
this.displayDates.splice(0, 1);
},
createYearSlide (h) {
return this.displayDates.map((displayDate, index) => {
const fullYear = displayDate.getFullYear();
return h('transition', {
props: {
name: `mu-date-display-${this.slideType}`
},
key: index
}, [
h('div', {
staticClass: 'mu-date-display-slideIn-wrapper',
key: fullYear
}, [
h('div', { staticClass: 'mu-date-display-year-title' }, fullYear)
])
]);
});
},
createMonthSlide (h) {
return this.displayDates.map((displayDate, index) => {
const displayMonthDay = this.type === 'date' ? this.dateTimeFormat.formatDisplay(displayDate) : this.dateTimeFormat.getMonthList()[displayDate.getMonth()];
return h('transition', {
props: {
name: `mu-date-display-${this.slideType}`
},
key: index
}, [
h('div', {
staticClass: 'mu-date-display-slideIn-wrapper',
key: displayMonthDay
}, [
h('div', { staticClass: 'mu-date-display-monthday-title' }, displayMonthDay)
])
]);
});
}
},
render (h) {
const displayYear = h('div', {
staticClass: 'mu-date-display-year',
on: {
click: () => this.$emit('changeView', 'year')
}
}, this.createYearSlide(h));
const displayMonthDay = this.type !== 'year' ? h('div', {
staticClass: 'mu-date-display-monthday',
on: {
click: () => this.$emit('changeView', this.type === 'date' ? 'monthDay' : 'month')
}
}, this.createMonthSlide(h)) : undefined;
return h('div', {
staticClass: 'mu-picker-display mu-date-display ' + this.getColorClass(false),
style: {
'background-color': this.getColor(this.color)
},
class: {
'selected-year': !this.monthDaySelected
}
}, [displayYear, displayMonthDay]);
},
watch: {
displayDate (val) {
this.replaceSelected(val);
}
}
};
================================================
FILE: src/Picker/DatePicker/DatePicker.js
================================================
import color from '../../internal/mixins/color';
import pickerProps from '../mixins/props';
import DateDisplay from './DateDisplay';
import MonthDayView from './MonthDayView';
import YearView from './YearView';
import MonthView from './MonthView';
import * as dateUtils from './dateUtils';
export default {
name: 'mu-date-picker',
mixins: [color, pickerProps],
provide () {
return {
getDayButtonSlots: this.getDayButtonSlots,
getMonthButtonSlots: this.getMonthButtonSlots,
getYearButtonSlots: this.getYearButtonSlots
};
},
props: {
dateTimeFormat: {
type: Object,
default () {
return dateUtils.dateTimeFormat;
}
},
firstDayOfWeek: {
type: Number,
default: 0
},
date: {
type: Date,
default () {
return new Date();
}
},
type: {
type: String,
default: 'date' // date, year, month
},
maxDate: {
type: Date,
default () {
return dateUtils.addYears(new Date(), 100);
}
},
minDate: {
type: Date,
default () {
return dateUtils.addYears(new Date(), -100);
}
},
shouldDisableDate: Function
},
data () {
return {
displayDate: this.date,
view: this.type === 'date' ? 'monthDay' : this.type === 'year' ? 'year' : 'month'
};
},
methods: {
getDayButtonSlots () {
return this.$scopedSlots.day;
},
getMonthButtonSlots () {
return this.$scopedSlots.month;
},
getYearButtonSlots () {
return this.$scopedSlots.year;
},
handleYearChange (year) {
const date = dateUtils.cloneAsDate(this.displayDate);
date.setDate(1);
date.setFullYear(year);
this.changeDisplayDate(date);
if (this.type === 'year') return this.changeDate(date);
this.changeView(this.type === 'month' ? 'month' : 'monthDay');
},
handleMonthChange (date) {
this.changeDisplayDate(date);
if (this.type === 'month') return this.changeDate(date);
this.changeView('monthDay');
},
handleSelect (date) {
if (date.getTime() > this.maxDate.getTime()) date = new Date(this.maxDate.getTime());
if (date.getTime() < this.minDate.getTime()) date = new Date(this.minDate.getTime());
this.changeDisplayDate(date);
this.changeDate(date);
},
changeDate (date) {
this.$emit('change', date);
this.$emit('update:date', date);
},
changeDisplayDate (date) {
this.displayDate = date;
},
changeView (view) {
this.view = view;
}
},
render (h) {
const colorClass = this.getNormalColorClass(this.color, true);
const color = this.getColor(this.color);
const monthdayView = h(MonthDayView, {
props: {
dateTimeFormat: this.dateTimeFormat,
firstDayOfWeek: this.firstDayOfWeek,
maxDate: this.maxDate,
minDate: this.minDate,
displayDate: this.displayDate,
selectedDate: this.date,
shouldDisableDate: this.shouldDisableDate
},
on: {
changeView: this.changeView,
select: this.handleSelect
}
});
const yearView = h(YearView, {
props: {
displayDate: this.displayDate,
maxDate: this.maxDate,
minDate: this.minDate
},
on: {
change: this.handleYearChange
}
});
const monthView = h(MonthView, {
props: {
dateTimeFormat: this.dateTimeFormat,
maxDate: this.maxDate,
minDate: this.minDate,
displayDate: this.displayDate
},
on: {
changeView: this.changeView,
change: this.handleMonthChange
}
});
return h(
'div',
{
staticClass: `mu-picker mu-datepicker ${colorClass}`,
class: {
'mu-picker-landspace': this.landscape
},
style: {
color
}
},
[
!this.noDisplay ? h(DateDisplay, {
props: {
type: this.type,
monthDaySelected: this.view !== 'year',
color: this.displayColor,
displayDate: this.displayDate,
dateTimeFormat: this.dateTimeFormat
},
on: {
changeView: this.changeView
}
}) : undefined,
h(
'div',
{
staticClass: 'mu-picker-container'
},
[
this.view === 'monthDay'
? monthdayView
: this.view === 'month' ? monthView : yearView,
this.$slots.default
]
)
]
);
},
watch: {
date (val) {
this.displayDate = val;
}
}
};
================================================
FILE: src/Picker/DatePicker/DayButton.js
================================================
export default {
inject: [
'getDayButtonSlots'
],
props: {
selected: Boolean,
date: Date,
disabled: Boolean
},
data () {
return {
hover: false
};
},
computed: {
isNow () {
const now = new Date();
return this.date && this.date.getYear() === now.getYear() && this.date.getMonth() === now.getMonth() && this.date.getDate() === now.getDate();
},
dayButtonClass () {
return {
selected: this.selected,
disabled: this.disabled,
now: this.isNow
};
}
},
render (h) {
const scopedSlot = this.getDayButtonSlots();
return this.date ? h('button', {
staticClass: 'mu-day-button',
class: this.dayButtonClass,
on: this.$listeners,
domProps: {
disabled: this.disabled
}
}, scopedSlot ? scopedSlot({
selected: this.selected,
date: this.date,
disabled: this.disabled,
now: this.isNow
}) : [
h('div', { class: 'mu-day-button-bg' }),
h('span', {
class: 'mu-day-button-text',
domProps: {
innerHTML: this.date.getDate()
}
})
]) : h('span', { class: 'mu-day-empty' });
}
};
================================================
FILE: src/Picker/DatePicker/MonthDayView.js
================================================
import DayButton from './DayButton';
import Toolbar from './Toolbar';
import * as dateUtils from './dateUtils';
export default {
props: {
dateTimeFormat: Object,
firstDayOfWeek: {
type: Number,
default: 1
},
maxDate: Date,
minDate: Date,
displayDate: Date,
selectedDate: Date,
shouldDisableDate: Function
},
data () {
const displayDate = dateUtils.cloneDate(this.displayDate);
displayDate.setDate(1);
return {
weekTexts: this.dateTimeFormat.getWeekDayArray(this.firstDayOfWeek),
displayDates: [displayDate],
slideType: 'next'
};
},
computed: {
prevMonth () {
return this.displayDates && dateUtils.monthDiff(this.displayDates[0], this.minDate) > 0;
},
nextMonth () {
return this.displayDates && dateUtils.monthDiff(this.displayDates[0], this.maxDate) < 0;
}
},
methods: {
equalsDate (date) {
return dateUtils.isEqualDate(date, this.selectedDate);
},
isDisableDate (day) {
if (day === null) return false;
let disabled = false;
if (this.maxDate && this.minDate) disabled = !dateUtils.isBetweenDates(day, this.minDate, this.maxDate);
if (!disabled && this.shouldDisableDate) disabled = this.shouldDisableDate(day);
return disabled;
},
handleClick (date) {
if (date) this.$emit('select', date);
},
handleChange (val) {
const displayDate = dateUtils.addMonths(this.displayDates[0], val);
this.changeDisplayDate(displayDate);
},
changeDisplayDate (date) {
const oldDate = this.displayDates[0];
if (date.getFullYear() === oldDate.getFullYear() && date.getMonth() === oldDate.getMonth()) return;
this.slideType = date.getTime() > oldDate.getTime() ? 'next' : 'prev';
const displayDate = dateUtils.cloneDate(date);
displayDate.setDate(1);
this.displayDates.push(displayDate);
this.displayDates.splice(0, 1);
},
createWeek (h) {
return h('div', {
staticClass: 'mu-datepicker-week'
}, this.weekTexts.map((weekText, index) => {
return h('span', {
staticClass: 'mu-datepicker-week-day',
key: index
}, weekText);
}));
},
createMonthDay (h) {
return h('div', {
staticClass: 'mu-datepicker-monthday'
}, this.displayDates.map((displayDate, index) => {
return h('transition', {
props: {
name: `mu-datepicker-slide-${this.slideType}`
},
key: index
}, [
h('div', {
staticClass: 'mu-datepicker-monthday-slide',
key: displayDate.getTime()
}, [this.createContent(h, displayDate)])
]);
}));
},
createContent (h, displayDate) {
const weeksArray = dateUtils.getWeekArray(displayDate || new Date(), this.firstDayOfWeek);
return h('div', {
staticClass: 'mu-datepicker-monthday-content'
}, weeksArray.map((week, i) => {
return h('div', {
staticClass: 'mu-datepicker-monthday-row',
key: i
}, week.map((date, j) => {
return h(DayButton, {
props: {
disabled: this.isDisableDate(date),
selected: this.equalsDate(date),
date: date
},
on: {
click: () => this.handleClick(date)
},
key: `dayButton${i}${j}`
});
}));
}));
}
},
render (h) {
return h('div', {
staticClass: 'mu-datepicker-monthday-container'
}, [
h(Toolbar, {
props: {
slideType: this.slideType,
nextMonth: this.nextMonth,
prevMonth: this.prevMonth,
displayDates: this.displayDates,
dateTimeFormat: this.dateTimeFormat
},
on: {
click: () => this.$emit('changeView', 'month'),
change: this.handleChange
}
}),
this.createWeek(h),
this.createMonthDay(h)
]);
},
watch: {
displayDate (val) {
this.changeDisplayDate(val);
}
}
};
================================================
FILE: src/Picker/DatePicker/MonthView.js
================================================
import Toolbar from './Toolbar';
import * as dateUtils from './dateUtils';
export default {
props: {
dateTimeFormat: Object,
maxDate: Date,
minDate: Date,
displayDate: Date
},
data () {
const displayDate = dateUtils.cloneDate(this.displayDate);
displayDate.setDate(1);
return {
displayDates: [displayDate],
slideType: 'next'
};
},
methods: {
changeDisplayDate (date) {
const oldDate = this.displayDates[0];
if (date.getFullYear() === oldDate.getFullYear() && date.getMonth() === oldDate.getMonth()) return;
this.slideType = date.getTime() > oldDate.getTime() ? 'next' : 'prev';
const displayDate = dateUtils.cloneDate(date);
displayDate.setDate(1);
this.displayDates.push(displayDate);
this.displayDates.splice(0, 1);
},
handleChange (val) {
const displayDate = dateUtils.cloneDate(this.displayDates[0]);
displayDate.setFullYear(displayDate.getFullYear() + val);
this.changeDisplayDate(displayDate);
},
createMonth (h) {
return h('div', {
staticClass: 'mu-datepicker-month'
}, this.displayDates.map((displayDate, index) => {
return h('transition', {
props: {
name: `mu-datepicker-slide-${this.slideType}`
},
key: index
}, [
h('div', {
staticClass: 'mu-datepicker-month-slide',
key: displayDate.getTime()
}, [this.createContent(h, displayDate)])
]);
}));
},
createContent (h, displayDate) {
const monthArray = dateUtils.getMonthArray(displayDate);
return h('div', {
staticClass: 'mu-datepicker-month-content'
}, monthArray.map((month, i) => {
return h('div', {
staticClass: 'mu-datepicker-month-row',
key: i
}, month.map((date) => this.createMonthButton(h, date)));
}));
},
createMonthButton (h, date) {
const monthText = this.dateTimeFormat.getMonthList()[date.getMonth()];
const maxDate = new Date(this.maxDate.getFullYear(), this.maxDate.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
const minDate = new Date(this.minDate.getFullYear(), this.minDate.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
const disabled = date.getTime() > maxDate.getTime() || date.getTime() < minDate.getTime();
return h(
'button',
{
staticClass: 'mu-month-button',
attrs: {
disabled
},
class: {
selected:
date.getFullYear() === this.displayDate.getFullYear() &&
date.getMonth() === this.displayDate.getMonth()
},
on: {
click: () => !disabled && this.$emit('change', date)
}
},
[
h('div', { staticClass: 'mu-month-button-bg' }),
h('span', { staticClass: 'mu-month-button-text' }, monthText)
]
);
}
},
render (h) {
return h(
'div',
{
staticClass: 'mu-datepicker-month-container'
},
[
h(Toolbar, {
props: {
slideType: this.slideType,
type: 'year',
displayDates: this.displayDates,
dateTimeFormat: this.dateTimeFormat
},
on: {
click: () => this.$emit('changeView', 'year'),
change: this.handleChange
}
}),
this.createMonth(h)
]
);
}
};
================================================
FILE: src/Picker/DatePicker/Toolbar.js
================================================
import Button from '../../Button';
export default {
props: {
dateTimeFormat: Object,
displayDates: Array,
type: {
type: String,
default: 'month'
}, // month, year
nextMonth: {
type: Boolean,
default: true
},
prevMonth: {
type: Boolean,
default: true
},
slideType: String
},
methods: {
createTitleSlide (h) {
return this.displayDates.map((displayDate, index) => {
const title = this.type === 'month' ? this.dateTimeFormat.formatMonth(displayDate) : displayDate.getFullYear();
return h('transition', {
props: {
name: `mu-datepicker-slide-${this.slideType}`
},
key: index
}, [
h('div', {
staticClass: 'mu-datepicker-toolbar-title',
class: {
'clickable': true
},
key: title,
on: {
click: (e) => this.$emit('click', e)
}
}, title)
]);
});
},
createPrevIcon (h) {
return h('svg', {
staticClass: 'mu-datepicker-svg-icon',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'
}
})
]);
},
createNextIcon (h) {
return h('svg', {
staticClass: 'mu-datepicker-svg-icon',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'
}
})
]);
}
},
render (h) {
return h('div', {
staticClass: 'mu-datepicker-toolbar'
}, [
h(Button, {
staticClass: 'mu-datepicker-tool-btn',
props: {
icon: true,
disabled: !this.prevMonth
},
on: {
click: () => this.$emit('change', -1)
}
}, [this.createPrevIcon(h)]),
h('div', {
staticClass: 'mu-datepicker-toolbar-title-wrapper',
on: {
click: () => this.$emit('changeView', 'month')
}
}, [this.createTitleSlide(h)]),
h(Button, {
staticClass: 'mu-datepicker-tool-btn',
props: {
icon: true,
disabled: !this.nextMonth
},
on: {
click: () => this.$emit('change', 1)
}
}, [this.createNextIcon(h)])
]);
}
};
================================================
FILE: src/Picker/DatePicker/YearButton.js
================================================
export default {
props: {
year: [String, Number],
selected: Boolean
},
mounted () {
if (this.selected) this.$parent.scrollToSelectedYear(this.$el);
},
render (h) {
return h('button', {
staticClass: 'mu-year-button',
class: {
selected: this.selected
},
on: this.$listeners
}, [
h('span', {
staticClass: 'mu-year-button-text'
}, this.year)
]);
},
watch: {
selected (val) {
if (val) this.$parent.scrollToSelectedYear(this.$el);
}
}
};
================================================
FILE: src/Picker/DatePicker/YearView.js
================================================
import YearButton from './YearButton';
export default {
props: {
maxDate: Date,
minDate: Date,
displayDate: Date
},
computed: {
years () {
const minYear = this.minDate.getFullYear();
const maxYear = this.maxDate.getFullYear();
const years = [];
for (let year = minYear; year <= maxYear; year++) {
years.push(year);
}
return years;
}
},
methods: {
scrollToSelectedYear (yearButtonNode) {
const container = this.$refs.container;
const containerHeight = container.clientHeight;
const yearButtonNodeHeight = yearButtonNode.clientHeight || 32;
const scrollYOffset = (yearButtonNode.offsetTop + yearButtonNodeHeight / 2) - containerHeight / 2;
setTimeout(() => (container.scrollTop = scrollYOffset), 0);
},
createYearButtons (h) {
return this.years.map((year) => {
return h(YearButton, {
props: {
year,
selected: year === this.displayDate.getFullYear()
},
on: {
click: (e) => {
this.$emit('change', year);
}
}
});
});
}
},
render (h) {
return h('div', {
staticClass: 'mu-datepicker-year-container'
}, [
h('div', {
staticClass: 'mu-datepicker-year',
ref: 'container'
}, [
h('div', {
staticClass: 'mu-datepicker-year-list'
}, this.createYearButtons(h))
])
]);
}
};
================================================
FILE: src/Picker/DatePicker/dateUtils.js
================================================
/**
* material-ui dateUtils.js
* https://github.com/callemall/material-ui/blob/master/src/DatePicker/dateUtils.js
*/
const localConfig = {
dayAbbreviation: ['日', '一', '二', '三', '四', '五', '六'],
dayList: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
monthList: ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'],
monthLongList: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
};
export const dateTimeFormat = {
formatDisplay (date) {
var day = date.getDate();
return `${localConfig.monthList[date.getMonth()]}-${day > 9 ? day : '0' + day} ${localConfig.dayList[date.getDay()]}`;
},
formatDateDisplay (date) {
var day = date.getDate();
return `${localConfig.monthList[date.getMonth()]}-${day > 9 ? day : '0' + day}`;
},
formatMonth (date) {
return `${date.getFullYear()} ${localConfig.monthLongList[date.getMonth()]}`;
},
getWeekDayArray (firstDayOfWeek) {
const beforeArray = [];
const afterArray = [];
const dayAbbreviation = localConfig.dayAbbreviation;
for (let i = 0; i < dayAbbreviation.length; i++) {
if (i < firstDayOfWeek) {
afterArray.push(dayAbbreviation[i]);
} else {
beforeArray.push(dayAbbreviation[i]);
}
}
return beforeArray.concat(afterArray);
},
getMonthList () {
return localConfig.monthLongList;
}
};
export function getDaysInMonth (d) {
const resultDate = getFirstDayOfMonth(d);
resultDate.setMonth(resultDate.getMonth() + 1);
resultDate.setDate(resultDate.getDate() - 1);
return resultDate.getDate();
}
export function getFirstDayOfMonth (d) {
return new Date(d.getFullYear(), d.getMonth(), 1);
}
export function getMonthArray (d) {
const length = 3;
const months = [];
let month = [];
for (let i = 0; i < 12; i++) {
month.push(new Date(d.getFullYear(), i, 1, d.getHours(), d.getMinutes()));
if (month.length === length) {
months.push(month);
month = [];
}
}
return months;
}
export function getWeekArray (d, firstDayOfWeek) {
const dayArray = [];
const daysInMonth = getDaysInMonth(d);
const weekArray = [];
let week = [];
for (let i = 1; i <= daysInMonth; i++) {
dayArray.push(new Date(d.getFullYear(), d.getMonth(), i, d.getHours(), d.getMinutes()));
}
const addWeek = (week) => {
const emptyDays = 7 - week.length;
for (let i = 0; i < emptyDays; ++i) {
week[weekArray.length ? 'push' : 'unshift'](null);
}
weekArray.push(week);
};
dayArray.forEach((day) => {
if (week.length > 0 && day.getDay() === firstDayOfWeek) {
addWeek(week);
week = [];
}
week.push(day);
if (dayArray.indexOf(day) === dayArray.length - 1) {
addWeek(week);
}
});
return weekArray;
}
export function addDays (d, days) {
const newDate = cloneDate(d);
newDate.setDate(d.getDate() + days);
return newDate;
}
export function addMonths (d, months) {
const newDate = cloneDate(d);
newDate.setMonth(d.getMonth() + months);
return newDate;
}
export function addYears (d, years) {
const newDate = cloneDate(d);
newDate.setFullYear(d.getFullYear() + years);
return newDate;
}
export function cloneDate (d) {
return new Date(d.getTime());
}
export function cloneAsDate (d) {
const clonedDate = cloneDate(d);
clonedDate.setHours(0, 0, 0, 0);
return clonedDate;
}
export function isBeforeDate (d1, d2) {
const date1 = cloneAsDate(d1);
const date2 = cloneAsDate(d2);
return (date1.getTime() < date2.getTime());
}
export function isAfterDate (d1, d2) {
const date1 = cloneAsDate(d1);
const date2 = cloneAsDate(d2);
return (date1.getTime() > date2.getTime());
}
export function isBetweenDates (dateToCheck, startDate, endDate) {
return (!(isBeforeDate(dateToCheck, startDate)) &&
!(isAfterDate(dateToCheck, endDate)));
}
export function isEqualDate (d1, d2) {
return d1 && d2 &&
(d1.getFullYear() === d2.getFullYear()) &&
(d1.getMonth() === d2.getMonth()) &&
(d1.getDate() === d2.getDate());
}
export function monthDiff (d1, d2) {
let m;
m = (d1.getFullYear() - d2.getFullYear()) * 12;
m += d1.getMonth();
m -= d2.getMonth();
return m;
}
export function yearDiff (d1, d2) {
return ~~(monthDiff(d1, d2) / 12);
}
================================================
FILE: src/Picker/DatePicker/index.js
================================================
export { default } from './DatePicker';
================================================
FILE: src/Picker/DateTimePicker/DateTimeDisplay.js
================================================
import color from '../../internal/mixins/color';
export default {
mixins: [color],
props: {
affix: String,
dateTimeFormat: Object,
view: String,
format: String,
viewType: String,
displayDate: Date
},
computed: {
sanitizeTime () {
let hour = this.displayDate.getHours();
let min = this.displayDate.getMinutes().toString();
if (this.format === 'ampm') {
hour %= 12;
hour = hour || 12;
}
hour = hour.toString();
if (hour.length < 2) hour = `0${hour}`;
if (min.length < 2) min = `0${min}`;
return [hour, min];
}
},
methods: {
createDateDisplay (h) {
const fullYear = this.displayDate.getFullYear();
const displayMonthDay = this.dateTimeFormat.formatDateDisplay(this.displayDate);
return h('div', {
staticClass: 'mu-date-display'
}, [
h('div', {
staticClass: 'mu-date-display-year',
class: {
active: this.view === 'year'
},
on: {
click: () => this.$emit('changeView', 'year')
}
}, [
h('div', {
staticClass: 'mu-date-display-year-title'
}, fullYear)
]),
h('div', {
staticClass: 'mu-date-display-monthday',
class: {
active: this.view === 'monthDay' || this.view === 'month'
},
on: {
click: () => this.$emit('changeView', 'monthDay')
}
}, [
h('div', {
staticClass: 'mu-date-display-monthday-title'
}, displayMonthDay)
])
]);
},
createTimeDisplay (h) {
const displayTime = h('div', {
staticClass: 'mu-time-display-time'
}, [
h('span', {
staticClass: 'mu-time-display-clickable',
class: {
'active': this.view === 'hour' || (this.view === 'minute' && this.viewType === 'list')
},
on: {
click: this.viewType === 'list' ? () => {} : () => this.$emit('changeView', 'hour')
}
}, this.sanitizeTime[0]),
h('span', {}, ':'),
h('span', {
staticClass: 'mu-time-display-clickable',
class: {
'active': this.view === 'minute' || (this.view === 'hour' && this.viewType === 'list')
},
on: {
click: this.viewType === 'list' ? () => {} : () => this.$emit('changeView', 'minute')
}
}, this.sanitizeTime[1])
]);
const displayAffix = this.format === 'ampm' ? h('div', {
staticClass: 'mu-time-display-affix'
}, [
h('div', {
staticClass: 'mu-time-display-clickable',
class: {
'active': this.affix === 'pm'
},
on: {
click: () => this.$emit('selectAffix', 'pm')
}
}, 'PM'),
h('div', {
staticClass: 'mu-time-display-clickable',
class: {
'active': this.affix === 'am'
},
on: {
click: () => this.$emit('selectAffix', 'am')
}
}, 'AM')
]) : undefined;
return h('div', {
staticClass: ' mu-time-display'
}, [
h('div', {
staticClass: 'mu-time-display-text'
}, [
this.format === 'ampm' ? h('div', {
staticClass: 'mu-time-display-affix'
}) : undefined,
displayTime,
displayAffix
])
]);
}
},
render (h) {
return h('div', {
staticClass: 'mu-picker-display mu-date-time-display ' + this.getColorClass(false),
style: {
'background-color': this.getColor(this.color)
}
}, [
this.createDateDisplay(h),
this.createTimeDisplay(h)
]);
}
};
================================================
FILE: src/Picker/DateTimePicker/DateTimePicker.js
================================================
import pickerProps from '../mixins/props';
import color from '../../internal/mixins/color';
import DatePicker from '../DatePicker';
import TimePicker from '../TimePicker';
import DateTimeDisplay from './DateTimeDisplay';
import MonthDayView from '../DatePicker/MonthDayView';
import YearView from '../DatePicker/YearView';
import MonthView from '../DatePicker/MonthView';
import ClockHours from '../TimePicker/Hours';
import ClockMinutes from '../TimePicker/Minutes';
import ListView from '../TimePicker/ListView';
import { Tabs, Tab } from '../../Tabs';
import { FadeTransition } from '../../internal/transitions';
import * as dateUtils from '../DatePicker/dateUtils';
const props = {
...DatePicker.props,
...TimePicker.props
};
delete props.time;
delete props.type;
delete props.landscape;
export default {
name: 'mu-date-time-picker',
provide () {
return {
getColorObject: this.getColorObject,
getDayButtonSlots: this.getDayButtonSlots,
getMonthButtonSlots: this.getMonthButtonSlots,
getYearButtonSlots: this.getYearButtonSlots
};
},
mixins: [pickerProps, color],
props,
data () {
return {
displayDate: this.date,
view: 'monthDay',
type: 'date' // date, time
};
},
methods: {
getDayButtonSlots () {
return this.$scopedSlots.day;
},
getMonthButtonSlots () {
return this.$scopedSlots.month;
},
getYearButtonSlots () {
return this.$scopedSlots.year;
},
getColorObject () {
return {
color: this.getColor(this.color),
colorClass: this.getNormalColorClass(this.color, true),
bgColorClass: this.getNormalColorClass(this.color)
};
},
getAffix () {
if (this.format !== 'ampm') return '';
const hours = this.date.getHours();
if (hours < 12) {
return 'am';
}
return 'pm';
},
handleYearChange (year) {
const date = dateUtils.cloneAsDate(this.displayDate);
date.setDate(1);
date.setFullYear(year);
this.changeDisplayDate(date);
this.changeView('monthDay');
},
handleMonthChange (date) {
this.changeDisplayDate(date);
this.changeView('monthDay');
},
handleSelect (date) {
if (date.getTime() > this.maxDate.getTime()) date = new Date(this.maxDate.getTime());
if (date.getTime() < this.minDate.getTime()) date = new Date(this.minDate.getTime());
this.changeDisplayDate(date);
this.changeTime(date, 'monthDay', false);
this.changeType('time');
},
changeDisplayDate (date) {
this.displayDate = date;
},
changeType (type) {
this.type = type;
if (type === 'date' && ['hour', 'minute'].indexOf(this.view) !== -1) {
this.changeView('monthDay');
} else if (type === 'time' && ['monthDay', 'month', 'year'].indexOf(this.view) !== -1) {
this.changeView('hour');
}
},
changeView (view) {
this.view = view;
if (['hour', 'minute'].indexOf(view) !== -1 && this.type === 'date') {
this.changeType('time');
} else if (['monthDay', 'month', 'year'].indexOf(view) !== -1 && this.type === 'time') {
this.changeType('date');
}
},
handleSelectAffix (affix) {
if (affix === this.getAffix()) return;
const hours = this.date.getHours();
if (affix === 'am') {
this.handleChangeHours(hours - 12, affix);
return;
}
this.handleChangeHours(hours + 12, affix);
},
handleChangeHours (hours, finished) {
const time = new Date(this.date);
let affix;
if (typeof finished === 'string') {
affix = finished;
finished = undefined;
}
if (!affix) {
affix = this.getAffix();
}
if (affix === 'pm' && hours < 12) {
hours += 12;
}
time.setHours(hours);
this.changeTime(time, 'hour', finished);
if (finished) this.view = 'minute';
},
handleChangeMinutes (minutes, finished) {
const time = new Date(this.date);
time.setMinutes(minutes);
this.changeTime(time, 'minute', finished);
},
changeTime (time, view, finished) {
this.$emit('change', time, view, finished);
this.$emit('update:date', time);
},
createDisplay (h) {
if (this.noDisplay) return;
return h(DateTimeDisplay, {
props: {
affix: this.getAffix(),
dateTimeFormat: this.dateTimeFormat,
view: this.view,
format: this.format,
viewType: this.viewType,
color: this.displayColor,
displayDate: this.displayDate
},
on: {
changeView: this.changeView,
selectAffix: this.handleSelectAffix
}
});
},
createClock (h) {
return h('div', {
staticClass: 'mu-timepicker-clock'
}, [
h('div', { staticClass: 'mu-timepicker-circle' }),
this.view === 'hour' ? h(ClockHours, {
props: {
format: this.format,
initialHours: this.date.getHours()
},
on: {
change: this.handleChangeHours
}
}) : undefined,
this.view === 'minute' ? h(ClockMinutes, {
props: {
initialMinutes: this.date.getMinutes()
},
on: {
change: this.handleChangeMinutes
}
}) : undefined
]);
},
createList (h) {
return h(ListView, {
props: {
format: this.format,
time: this.date
},
on: {
changeHours: (val) => this.handleChangeHours(val, true),
changeMinutes: (val) => this.handleChangeMinutes(val, true)
}
});
},
createTabs (h) {
return h(Tabs, {
props: {
value: this.type,
color: this.displayColor || this.color,
fullWidth: true
},
on: {
'update:value': this.changeType
}
}, [
h(Tab, {
props: {
value: 'date'
}
}, [
h('svg', {
staticClass: 'mu-datetime-picker-svg',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M9 11H7v2h2v-2zm4 0h-2v2h2v-2zm4 0h-2v2h2v-2zm2-7h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V9h14v11z'
}
}),
h('path', {
attrs: {
d: 'M0 0h24v24H0z',
fill: 'none'
}
})
])
]),
h(Tab, {
props: {
value: 'time'
}
}, [
h('svg', {
staticClass: 'mu-datetime-picker-svg',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z'
}
}),
h('path', {
attrs: {
d: 'M0 0h24v24H0z',
fill: 'none'
}
}),
h('path', {
attrs: {
d: 'M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z'
}
})
])
])
]);
},
createContent (h) {
switch (this.view) {
case 'monthDay':
return h(MonthDayView, {
props: {
dateTimeFormat: this.dateTimeFormat,
firstDayOfWeek: this.firstDayOfWeek,
maxDate: this.maxDate,
minDate: this.minDate,
displayDate: this.displayDate,
selectedDate: this.date,
shouldDisableDate: this.shouldDisableDate
},
on: {
changeView: this.changeView,
select: this.handleSelect
}
});
case 'month':
return h(MonthView, {
props: {
dateTimeFormat: this.dateTimeFormat,
maxDate: this.maxDate,
minDate: this.minDate,
displayDate: this.displayDate
},
on: {
changeView: this.changeView,
change: this.handleMonthChange
}
});
case 'year':
return h(YearView, {
props: {
displayDate: this.displayDate,
maxDate: this.maxDate,
minDate: this.minDate
},
on: {
change: this.handleYearChange
}
});
}
return this.viewType === 'clock' ? this.createClock(h) : this.createList(h);
}
},
render (h) {
const { color, colorClass } = this.getColorObject();
return h('div', {
staticClass: `mu-picker mu-datetime-picker ${colorClass}`,
style: {
color
}
}, [
this.createDisplay(h),
h('div', {
staticClass: 'mu-picker-container'
}, [
this.createTabs(h),
h(FadeTransition, [this.createContent(h)]),
this.$slots.default
])
]);
},
watch: {
date (val) {
this.displayDate = val;
}
}
};
================================================
FILE: src/Picker/DateTimePicker/index.js
================================================
export { default } from './DateTimePicker';
================================================
FILE: src/Picker/TimePicker/Hours.js
================================================
import ClockNumber from './Number';
import ClockPointer from './Pointer';
import { getTouchEventOffsetValues, rad2deg } from './timeUtils';
export default {
props: {
format: {
type: String,
default: 'ampm',
validator (val) {
return ['ampm', '24hr'].indexOf(val) !== -1;
}
},
initialHours: {
type: Number,
default: new Date().getHours()
}
},
computed: {
hours () {
const hourSize = this.format === 'ampm' ? 12 : 24;
const hours = [];
for (let i = 1; i <= hourSize; i++) {
hours.push(i % 24);
}
return hours;
}
},
mounted () {
const clockElement = this.$refs.mask;
this.center = {
x: clockElement.offsetWidth / 2,
y: clockElement.offsetHeight / 2
};
this.basePoint = {
x: this.center.x,
y: 0
};
},
methods: {
getSelected () {
let hour = this.initialHours;
if (this.format === 'ampm') {
hour %= 12;
hour = hour || 12;
}
return hour;
},
isMousePressed (event) {
if (typeof event.buttons === 'undefined') {
return event.nativeEvent.which;
}
return event.buttons;
},
handleDown (event) {
this.isMouseDown = true;
},
handleUp (event) {
if (!this.isMouseDown) return;
event.preventDefault();
this.isMouseDown = false;
this.setClock(event, true);
},
handleMove (event) {
event.preventDefault();
if (this.isMousePressed(event) !== 1) return;
this.setClock(event, false);
},
handleTouchMove (event) {
event.preventDefault();
this.setClock(event.changedTouches[0], false);
},
handleTouchEnd (event) {
event.preventDefault();
this.setClock(event.changedTouches[0], true);
},
setClock (event, finish) {
if (typeof event.offsetX === 'undefined') {
const offset = getTouchEventOffsetValues(event);
event.offsetX = offset.offsetX;
event.offsetY = offset.offsetY;
}
const hours = this.getHours(event.offsetX, event.offsetY);
this.$emit('change', hours, finish);
},
getHours (offsetX, offsetY) {
const step = 30;
const x = offsetX - this.center.x;
const y = offsetY - this.center.y;
const cx = this.basePoint.x - this.center.x;
const cy = this.basePoint.y - this.center.y;
const atan = Math.atan2(cx, cy) - Math.atan2(x, y);
let deg = rad2deg(atan);
deg = Math.round(deg / step) * step;
deg %= 360;
let value = Math.floor(deg / step) || 0;
const delta = Math.pow(x, 2) + Math.pow(y, 2);
const distance = Math.sqrt(delta);
value = value || 12;
if (this.format === '24hr') {
if (distance < 90) {
value += 12;
value %= 24;
}
} else {
value %= 12;
}
return value;
}
},
render (h) {
return h('div', {
staticClass: 'mu-timepicker-hours'
}, [
h(ClockPointer, {
props: {
type: 'hour',
hasSelected: true,
value: this.getSelected()
}
}),
this.hours.map((hour) => {
return h(ClockNumber, {
props: {
selected: this.getSelected() === hour,
type: 'hour',
value: hour
},
key: hour
});
}),
h('div', {
staticClass: 'mu-timepicker-hours-mask',
on: {
mousedown: this.handleDown,
mouseup: this.handleUp,
mousemove: this.handleMove,
touchmove: this.handleTouchMove,
touchend: this.handleTouchEnd
},
ref: 'mask'
})
]);
}
};
================================================
FILE: src/Picker/TimePicker/ListView.js
================================================
export default {
props: {
format: {
type: String,
default: 'ampm',
validator (val) {
return ['ampm', '24hr'].indexOf(val) !== -1;
}
},
time: {
type: Date,
default () {
return new Date();
}
}
},
computed: {
hours () {
const hourSize = this.format === 'ampm' ? 12 : 24;
const hours = [];
for (let i = 1; i <= hourSize; i++) {
const num = i % 24;
num === 0 ? hours.unshift('00') : hours.push(num > 9 ? num : '0' + num);
}
return hours;
},
minutes () {
const minutes = [];
for (let i = 1; i <= 60; i++) {
const num = i % 60;
num === 0 ? minutes.unshift('00') : minutes.push(num > 9 ? num : '0' + num);
}
return minutes;
}
},
mounted () {
this.scrollToSelected(this.$refs.hours);
this.scrollToSelected(this.$refs.minutes);
},
methods: {
scrollToSelected (container) {
const buttonNode = container.querySelector('.is-active');
const btnTop = buttonNode.offsetTop;
const btnHeight = buttonNode.offsetHeight;
const containerTop = container.offsetTop;
const containerHeight = container.offsetHeight;
const top = containerTop + containerHeight / 2;
const maxScrollTop = container.scrollHeight - containerHeight;
let scrollTop = btnTop + btnHeight / 2 - top;
scrollTop = Math.min(maxScrollTop, scrollTop);
scrollTop = Math.max(0, scrollTop);
setTimeout(() => (container.scrollTop = scrollTop), 0);
},
createHoursList (h) {
const buttons = this.hours.map((hour) => {
const val = Number(hour);
let curHour = this.time.getHours();
if (this.format === 'ampm') {
curHour %= 12;
curHour = curHour || 12;
}
const isActive = curHour === val;
return h('button', {
staticClass: 'mu-timepicker-hour-button',
class: {
'is-active': isActive
},
on: {
click: () => this.$emit('changeHours', val)
}
}, hour);
});
return h('div', {
staticClass: 'mu-timepicker-list-hours',
ref: 'hours'
}, buttons);
},
createMinutesList (h) {
const buttons = this.minutes.map((minute) => {
const val = Number(minute);
return h('button', {
staticClass: 'mu-timepicker-minute-button',
class: {
'is-active': this.time.getMinutes() === val
},
on: {
click: () => this.$emit('changeMinutes', val)
}
}, minute);
});
return h('div', {
staticClass: 'mu-timepicker-list-minutes',
ref: 'minutes'
}, buttons);
}
},
render (h) {
return h('div', {
staticClass: 'mu-timepicker-list'
}, [
this.createHoursList(h),
this.createMinutesList(h)
]);
},
watch: {
time () {
if (this.$isServer) return;
this.$nextTick(() => {
this.scrollToSelected(this.$refs.hours);
this.scrollToSelected(this.$refs.minutes);
});
}
}
};
================================================
FILE: src/Picker/TimePicker/Minutes.js
================================================
import ClockNumber from './Number';
import ClockPointer from './Pointer';
import { getTouchEventOffsetValues, rad2deg } from './timeUtils';
export default {
props: {
initialMinutes: {
type: Number,
default () {
return new Date().getMinutes();
}
}
},
mounted () {
const clockElement = this.$refs.mask;
this.center = {
x: clockElement.offsetWidth / 2,
y: clockElement.offsetHeight / 2
};
this.basePoint = {
x: this.center.x,
y: 0
};
},
data () {
return {
minutes: null
};
},
created () {
this.minutes = this.getMinuteNumbers();
},
methods: {
getMinuteNumbers () {
const minutes = [];
for (let i = 0; i < 12; i++) {
minutes.push(i * 5);
}
const selectedMinutes = this.initialMinutes;
let hasSelected = false;
const numbers = minutes.map((minute) => {
const isSelected = selectedMinutes === minute;
if (isSelected) {
hasSelected = true;
}
return {
minute,
isSelected
};
});
return {
numbers: numbers,
hasSelected: hasSelected,
selected: selectedMinutes
};
},
isMousePressed (event) {
if (typeof event.buttons === 'undefined') {
return event.nativeEvent.which;
}
return event.buttons;
},
handleDown (event) {
this.isMouseDown = true;
},
handleUp (event) {
if (!this.isMouseDown) return;
event.preventDefault();
this.isMouseDown = false;
this.setClock(event, true);
},
handleMove (event) {
event.preventDefault();
if (this.isMousePressed(event) !== 1) {
return;
}
this.setClock(event, false);
},
handleTouch (event) {
event.preventDefault();
this.setClock(event.changedTouches[0], event.type.toLowerCase() === 'touchend');
},
setClock (event, finish) {
if (typeof event.offsetX === 'undefined') {
const offset = getTouchEventOffsetValues(event);
event.offsetX = offset.offsetX;
event.offsetY = offset.offsetY;
}
const minutes = this.getMinutes(event.offsetX, event.offsetY);
this.$emit('change', minutes, finish);
},
getMinutes (offsetX, offsetY) {
const step = 6;
const x = offsetX - this.center.x;
const y = offsetY - this.center.y;
const cx = this.basePoint.x - this.center.x;
const cy = this.basePoint.y - this.center.y;
const atan = Math.atan2(cx, cy) - Math.atan2(x, y);
let deg = rad2deg(atan);
deg = Math.round(deg / step) * step;
deg %= 360;
const value = Math.floor(deg / step) || 0;
return value;
}
},
render (h) {
return h('div', {
staticClass: 'mu-timepicker-minutes'
}, [
h(ClockPointer, {
props: {
hasSelected: this.minutes.hasSelected,
value: this.minutes.selected,
type: 'minute'
}
}),
this.minutes.numbers.map((minute) => {
return h(ClockNumber, {
props: {
selected: minute.isSelected,
type: 'minute',
value: minute.minute
},
key: minute.minute
});
}),
h('div', {
staticClass: 'mu-timepicker-minutes-mask',
on: {
mousedown: this.handleDown,
mouseup: this.handleUp,
mousemove: this.handleMove,
touchmove: this.handleTouch,
touchend: this.handleTouch
},
ref: 'mask'
})
]);
},
watch: {
initialMinutes (val) {
this.minutes = this.getMinuteNumbers();
}
}
};
================================================
FILE: src/Picker/TimePicker/Number.js
================================================
import { isInner } from './timeUtils';
const positions = [
[0, 5],
[54.5, 16.6],
[94.4, 59.5],
[109, 114],
[94.4, 168.5],
[54.5, 208.4],
[0, 223],
[-54.5, 208.4],
[-94.4, 168.5],
[-109, 114],
[-94.4, 59.5],
[-54.5, 19.6]
];
const innerPositions = [
[0, 40],
[36.9, 49.9],
[64, 77],
[74, 114],
[64, 151],
[37, 178],
[0, 188],
[-37, 178],
[-64, 151],
[-74, 114],
[-64, 77],
[-37, 50]
];
export default {
inject: ['getColorObject'],
props: {
value: {
type: Number,
default: 0
},
type: {
type: String,
default: 'minute',
validator (val) {
return ['hour', 'minute'].indexOf(val) !== -1;
}
},
selected: {
type: Boolean,
default: false
}
},
computed: {
isInner () {
return isInner(this);
},
numberClass () {
return {
'mu-timepicker-number__selected': this.selected,
'mu-timepicker-number__inner': this.isInner
};
},
numberStyle () {
let pos = this.value;
if (this.type === 'hour') {
pos %= 12;
} else {
pos = pos / 5;
}
let transformPos = positions[pos];
if (this.isInner) transformPos = innerPositions[pos];
const [x, y] = transformPos;
return {
transform: `translate(${x}px, ${y}px)`,
left: this.isInner ? 'calc(50% - 14px)' : 'calc(50% - 16px)'
};
}
},
render (h) {
const { color, bgColorClass } = this.getColorObject();
return h('span', {
staticClass: 'mu-timepicker-number ' + (this.selected ? bgColorClass : ''),
class: this.numberClass,
style: {
'background-color': this.selected ? color : '',
...this.numberStyle
}
}, this.value === 0 ? '00' : this.value);
}
};
================================================
FILE: src/Picker/TimePicker/Pointer.js
================================================
import { isInner } from './timeUtils';
export default {
props: {
hasSelected: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'minute',
validator (val) {
return ['hour', 'minute'].indexOf(val) !== -1;
}
},
value: {
type: Number
}
},
computed: {
isInner () {
return isInner(this);
},
pointerStyle () {
const { type, value, calcAngle } = this;
const angle = type === 'hour' ? calcAngle(value, 12) : calcAngle(value, 60);
return {
transform: `rotateZ(${angle}deg)`
};
}
},
methods: {
calcAngle (value, base) {
value %= base;
const angle = 360 / base * value;
return angle;
}
},
render (h) {
if (this.value === undefined || this.value === null) return h('span', {});
return h('div', {
staticClass: 'mu-timepicker-pointer',
class: {
'inner': this.isInner
},
style: this.pointerStyle
}, [
h('div', {
staticClass: 'mu-timepicker-pointer-mark',
class: {
'has-selected': this.hasSelected
}
})
]);
}
};
================================================
FILE: src/Picker/TimePicker/TimeDisplay.js
================================================
import color from '../../internal/mixins/color';
export default {
mixins: [color],
props: {
affix: {
type: String,
default: '',
validator (val) {
return ['', 'pm', 'am'].indexOf(val) !== -1;
}
},
format: {
type: String,
validator (val) {
return val && ['ampm', '24hr'].indexOf(val) !== -1;
}
},
mode: {
type: String,
default: 'hour',
validator (val) {
return ['hour', 'minute'].indexOf(val) !== -1;
}
},
selectedTime: {
type: Date,
default () {
return new Date();
},
required: true
},
viewType: String
},
computed: {
sanitizeTime () {
let hour = this.selectedTime.getHours();
let min = this.selectedTime.getMinutes().toString();
if (this.format === 'ampm') {
hour %= 12;
hour = hour || 12;
}
hour = hour.toString();
if (hour.length < 2) hour = `0${hour}`;
if (min.length < 2) min = `0${min}`;
return [hour, min];
}
},
methods: {
handleSelectAffix (affix) {
this.$emit('selectAffix', affix);
},
handleSelectHour () {
this.$emit('changeView', 'hour');
},
handleSelectMin () {
this.$emit('changeView', 'minute');
}
},
render (h) {
const displayTime = h('div', {
staticClass: 'mu-time-display-time'
}, [
h('span', {
staticClass: 'mu-time-display-clickable',
class: {
'inactive': this.viewType === 'clock' && this.mode === 'minute'
},
on: {
click: this.viewType === 'list' ? () => {} : this.handleSelectHour
}
}, this.sanitizeTime[0]),
h('span', {}, ':'),
h('span', {
staticClass: 'mu-time-display-clickable',
class: {
'inactive': this.viewType === 'clock' && this.mode === 'hour'
},
on: {
click: this.viewType === 'list' ? () => {} : this.handleSelectMin
}
}, this.sanitizeTime[1])
]);
const displayAffix = this.format === 'ampm' ? h('div', {
staticClass: 'mu-time-display-affix'
}, [
h('div', {
staticClass: 'mu-time-display-clickable',
class: {
'inactive': this.affix === 'am'
},
on: {
click: () => this.handleSelectAffix('pm')
}
}, 'PM'),
h('div', {
staticClass: 'mu-time-display-clickable',
class: {
'inactive': this.affix === 'pm'
},
on: {
click: () => this.handleSelectAffix('am')
}
}, 'AM')
]) : undefined;
return h('div', {
staticClass: 'mu-picker-display mu-time-display ' + this.getColorClass(false),
style: {
'background-color': this.getColor(this.color)
}
}, [
h('div', {
staticClass: 'mu-time-display-text'
}, [
this.format === 'ampm' ? h('div', {
staticClass: 'mu-time-display-affix'
}) : undefined,
displayTime,
displayAffix
])
]);
}
};
================================================
FILE: src/Picker/TimePicker/TimePicker.js
================================================
import TimeDisplay from './TimeDisplay';
import ClockHours from './Hours';
import ClockMinutes from './Minutes';
import ListView from './ListView';
import color from '../../internal/mixins/color';
import pickerProps from '../mixins/props';
export default {
name: 'mu-time-picker',
provide () {
return {
getColorObject: this.getColorObject
};
},
mixins: [color, pickerProps],
props: {
viewType: {
type: String,
default: 'clock',
validator (val) {
return ['clock', 'list'].indexOf(val) !== -1;
}
},
format: {
type: String,
default: 'ampm',
validator (val) {
return ['ampm', '24hr'].indexOf(val) !== -1;
}
},
time: {
type: Date,
default () {
return new Date();
}
}
},
data () {
return {
view: 'hour'
};
},
methods: {
getColorObject () {
return {
color: this.getColor(this.color),
colorClass: this.getNormalColorClass(this.color, true),
bgColorClass: this.getNormalColorClass(this.color)
};
},
getAffix () {
if (this.format !== 'ampm') return '';
const hours = this.time.getHours();
if (hours < 12) {
return 'am';
}
return 'pm';
},
handleSelectAffix (affix) {
if (affix === this.getAffix()) return;
const hours = this.time.getHours();
if (affix === 'am') {
this.handleChangeHours(hours - 12, affix);
return;
}
this.handleChangeHours(hours + 12, affix);
},
handleChangeHours (hours, finished) {
const time = new Date(this.time);
let affix;
if (typeof finished === 'string') {
affix = finished;
finished = undefined;
}
if (!affix) {
affix = this.getAffix();
}
if (affix === 'pm' && hours < 12) {
hours += 12;
}
time.setHours(hours);
this.changeTime(time, 'hour', finished);
if (finished) this.view = 'minute';
},
handleChangeMinutes (minutes, finished) {
const time = new Date(this.time);
time.setMinutes(minutes);
this.changeTime(time, 'minute', finished);
if (finished) this.view = 'hour';
},
changeTime (time, view, finished) {
this.$emit('change', time, view, finished);
this.$emit('update:time', time);
},
changeView (view) {
this.view = view;
},
createTimeDisplay (h) {
if (this.noDisplay) return;
return h(TimeDisplay, {
props: {
selectedTime: this.time,
format: this.format,
mode: this.view,
color: this.displayColor,
viewType: this.viewType,
affix: this.getAffix()
},
on: {
changeView: this.changeView,
selectAffix: this.handleSelectAffix
}
});
},
createClock (h) {
return h('div', {
staticClass: 'mu-timepicker-clock'
}, [
h('div', { staticClass: 'mu-timepicker-circle' }),
this.view === 'hour' ? h(ClockHours, {
props: {
format: this.format,
initialHours: this.time.getHours()
},
on: {
change: this.handleChangeHours
}
}) : undefined,
this.view === 'minute' ? h(ClockMinutes, {
props: {
initialMinutes: this.time.getMinutes()
},
on: {
change: this.handleChangeMinutes
}
}) : undefined
]);
},
createList (h) {
return h(ListView, {
props: {
format: this.format,
time: this.time
},
on: {
changeHours: (val) => this.handleChangeHours(val, true),
changeMinutes: (val) => this.handleChangeMinutes(val, true)
}
});
}
},
render (h) {
const { color, colorClass } = this.getColorObject();
return h('div', {
staticClass: 'mu-picker mu-timepicker ' + colorClass,
style: {
color
},
class: {
'mu-picker-landspace': this.landscape
}
}, [
this.createTimeDisplay(h),
h('div', {
staticClass: 'mu-picker-container'
}, [
this.viewType === 'list' ? this.createList(h) : this.createClock(h),
this.$slots.default
])
]);
}
};
================================================
FILE: src/Picker/TimePicker/index.js
================================================
export { default } from './TimePicker';
================================================
FILE: src/Picker/TimePicker/timeUtils.js
================================================
/**
* material-ui timeUtils.js
* https://github.com/callemall/material-ui/blob/master/src/TimePicker/timeUtils.js
*/
export function addHours (d, hours) {
const newDate = clone(d);
newDate.setHours(d.getHours() + hours);
return newDate;
}
export function addMinutes (d, minutes) {
const newDate = clone(d);
newDate.setMinutes(d.getMinutes() + minutes);
return newDate;
}
export function addSeconds (d, seconds) {
const newDate = clone(d);
newDate.setSeconds(d.getMinutes() + seconds);
return newDate;
}
function clone (d) {
return new Date(d.getTime());
}
/**
* @param date [Date] A Date object.
* @param format [String] One of 'ampm', '24hr', defaults to 'ampm'.
* @param pedantic [Boolean] Check time-picker/time-picker.jsx file.
*
* @return String A string representing the formatted time.
*/
export function formatTime (date, format = 'ampm', pedantic = false) {
if (!date) return '';
let hours = date.getHours();
let mins = date.getMinutes().toString();
if (format === 'ampm') {
const isAM = hours < 12;
hours = hours % 12;
const additional = isAM ? ' am' : ' pm';
hours = (hours || 12).toString();
if (mins.length < 2) mins = `0${mins}`;
if (pedantic) {
// Treat midday/midnight specially http://www.nist.gov/pml/div688/times.cfm
if (hours === '12' && mins === '00') {
return additional === ' pm' ? '12 noon' : '12 midnight';
}
}
return hours + (mins === '00' ? '' : `:${mins}`) + additional;
}
hours = hours.toString();
if (hours.length < 2) hours = `0${hours}`;
if (mins.length < 2) mins = `0${mins}`;
return `${hours}:${mins}`;
}
export function strToTime (str, format = 'ampm', pedantic = false) {
const date = new Date();
if (!str) return date;
let mtype = '';
let index = -1;
if (format === 'ampm') {
index = str.indexOf('am');
if (index === -1) index = str.indexOf('midnight');
if (index !== -1) {
mtype = 'am';
} else {
mtype = 'pm';
index = str.indexOf('pm');
if (index === -1) index = str.indexOf('noon');
}
}
if (index !== -1) str = str.substring(0, index).trim();
const times = str.split(':');
let hour = Number(times[0].trim());
if (mtype === 'pm') {
hour += 12;
}
if (hour >= 24) hour = 0;
const minute = times.length > 1 ? Number(times[1]) : 0;
date.setMinutes(minute);
date.setHours(hour);
return date;
}
export function rad2deg (rad) {
return rad * 57.29577951308232;
}
export function getTouchEventOffsetValues (event) {
const el = event.target;
const boundingRect = el.getBoundingClientRect();
return {
offsetX: event.clientX - boundingRect.left,
offsetY: event.clientY - boundingRect.top
};
}
export function isInner (props) {
if (props.type !== 'hour') {
return false;
}
return props.value < 1 || props.value > 12;
}
================================================
FILE: src/Picker/index.js
================================================
import '../styles/components/picker.less';
import theme from '../theme';
import PickerTheme from './theme';
import DatePicker from './DatePicker';
import TimePicker from './TimePicker';
import DateTimePicker from './DateTimePicker';
theme.addCreateTheme(PickerTheme);
export { DatePicker, TimePicker, DateTimePicker };
export default {
install (Vue) {
Vue.component(DatePicker.name, DatePicker);
Vue.component(TimePicker.name, TimePicker);
Vue.component(DateTimePicker.name, DateTimePicker);
}
};
================================================
FILE: src/Picker/mixins/props.js
================================================
export default {
props: {
landscape: Boolean,
noDisplay: Boolean,
displayColor: String
}
};
================================================
FILE: src/Picker/theme.js
================================================
import { fade } from '../utils/colorManipulator';
export default (theme, type) => {
return `
.mu-picker {
color: ${theme.primary};
background-color: ${theme.background.paper};
}
.mu-picker-display {
background-color: ${type === 'dark' ? '#555555' : 'currentColor'};
}
.mu-datepicker-week,
.mu-datepicker-toolbar-title,
.mu-datepicker-tool-btn,
.mu-datepicker-svg-icon,
.mu-day-button-text,
.mu-month-button-text,
.mu-year-button-text,
.mu-timepicker-number {
color: ${theme.text.primary};
}
.mu-day-button:hover:not(:disabled) .mu-day-button-text,
.mu-day-button.selected .mu-day-button-text{
color: ${theme.text.alternate};
}
.mu-month-button:hover .mu-month-button-text,
.mu-month-button.selected .mu-month-button-text {
color: ${theme.text.alternate};
}
.mu-month-button:disabled .mu-month-button-text {
color: ${theme.text.disabled};
}
.mu-timepicker-number__selected {
background-color: ${theme.primary};
color: ${theme.text.alternate};
}
.mu-timepicker-pointer-mark {
background-color: ${theme.text.alternate};
}
.mu-timepicker-list-hours {
border-right-color: ${theme.divider};
}
.mu-timepicker-hour-button,
.mu-timepicker-minute-button {
color: ${theme.text.primary};
}
.mu-timepicker-hour-button:hover,
.mu-timepicker-minute-button:hover,
.mu-year-button:hover {
background-color: ${fade(theme.text.primary, 0.1)};
}
.mu-datetime-picker .mu-tabs {
background-color: ${type === 'dark' ? '#555555' : ''};
color: ${type === 'dark' ? theme.text.secondary : ''}
}
.mu-datetime-picker .mu-tab-active {
color: ${type === 'dark' ? theme.text.primary : ''}
}
`;
};
================================================
FILE: src/Popover/Popover.js
================================================
import popup from '../internal/mixins/popup';
import scroll from '../internal/directives/scroll';
import resize from '../internal/directives/resize';
import clickOutSide from '../internal/directives/click-outside';
import { PopoverTransiton } from '../internal/transitions';
const SPACE = 8;
export default {
name: 'mu-popover',
mixins: [popup],
directives: {
scroll,
resize,
'click-outside': clickOutSide
},
props: {
overlay: {
default: false
},
lazy: Boolean,
cover: Boolean,
space: {
type: Number,
default: 0
}, // 距离trigger 的间隔, 在cover false的情况下完成
trigger: {},
placement: {
type: String,
default: 'bottom-start',
validator (val) {
return [
'top', 'top-start', 'top-end',
'bottom', 'bottom-start', 'bottom-end',
'left', 'left-start', 'left-end',
'right', 'right-start', 'right-end'
].indexOf(val) !== -1;
}
}
},
methods: {
getLeftPosition (width, react) {
let left = 0;
const maxLeft = window.innerWidth - SPACE - width;
const minLeft = SPACE;
switch (this.placement) {
case 'left':
case 'left-start':
case 'left-end':
left = react.left - width - this.space;
if (this.cover) {
left += react.width;
} else if (left < minLeft) {
left = react.left + react.width + this.space;
};
break;
case 'right':
case 'right-start':
case 'right-end':
left = this.cover
? react.left
: react.left + react.width > maxLeft
? react.left - width - this.space
: react.left + react.width + this.space;
break;
case 'top':
case 'bottom':
left = react.left + react.width / 2 - width / 2;
break;
case 'bottom-start':
case 'top-start':
left = react.left;
break;
case 'bottom-end':
case 'top-end':
left = react.left + react.width - width;
break;
}
left = Math.min(maxLeft, left);
left = Math.max(minLeft, left);
return left;
},
getTopPosition (height, react) {
let top = 0;
const maxTop = window.innerHeight - SPACE - height;
const minTop = SPACE;
switch (this.placement) {
case 'top':
case 'top-start':
case 'top-end':
top = react.top - height;
if (!this.cover) {
top += this.space;
if (top < minTop) top = react.top + react.height + this.space;
} else {
top += react.height;
}
break;
case 'bottom':
case 'bottom-start':
case 'bottom-end':
top = this.cover ? react.top
: react.top + react.height + this.space > maxTop
? react.top - height - this.space
: react.top + react.height + this.space;
break;
case 'left':
case 'right':
top = react.top + react.height / 2 - height / 2;
break;
case 'left-start':
case 'right-start':
top = react.top;
break;
case 'left-end':
case 'right-end':
top = react.top + react.height - height;
break;
}
top = Math.min(maxTop, top);
top = Math.max(minTop, top);
return top;
},
setStyle () {
if (!this.open) return;
const el = this.$el;
const triggerEl = this.trigger;
if (!el || !triggerEl) return;
const react = triggerEl.getBoundingClientRect();
if (react.top < -react.height || react.top > window.innerHeight) this.close('overflow');
el.style.top = this.getTopPosition(el.offsetHeight, react) + 'px';
el.style.left = this.getLeftPosition(el.offsetWidth, react) + 'px';
},
close (reason) {
if (!this.open) return;
this.$emit('update:open', false);
this.$emit('close', reason);
},
clickOutSide (e) {
if (this.trigger && this.trigger.contains(e.target)) return;
this.close('clickOutSide');
},
getTransitionName () {
if (this.cover) return `transition-${this.placement}`;
return this.placement.indexOf('top') !== -1 ||
['left-end', 'right-end'].indexOf(this.placement) !== -1
? 'transition-top' : 'transition-bottom';
}
},
mounted () {
this.setStyle();
},
updated () {
setTimeout(() => {
this.setStyle();
}, 0);
},
render (h) {
const directives = [{
name: 'resize',
value: this.setStyle
}, {
name: 'scroll',
value: {
target: this.trigger,
callback: this.setStyle
}
}, {
name: 'click-outside',
value: this.clickOutSide
}];
if (!this.lazy) {
directives.push({
name: 'show',
value: this.open
});
}
const transition = this.getTransitionName();
return h(PopoverTransiton, [
!this.lazy || this.open ? h('div', {
staticClass: `mu-popover ${transition}`,
style: {
'z-index': this.zIndex
},
on: this.$listeners,
directives
}, this.$slots.default) : undefined
]);
}
};
================================================
FILE: src/Popover/index.js
================================================
import '../styles/components/popover.less';
import theme from '../theme';
import PopoverTheme from './theme';
import Popover from './Popover';
Popover.install = function (Vue) {
Vue.component(Popover.name, Popover);
};
theme.addCreateTheme(PopoverTheme);
export default Popover;
================================================
FILE: src/Popover/theme.js
================================================
export default (theme) => {
return `
.mu-popover{
background: ${theme.background.paper};
}
`;
};
================================================
FILE: src/Progress/Circular.js
================================================
import color from '../internal/mixins/color';
export default {
mixins: [color],
props: {
size: {
type: Number,
default: 24
},
color: {
type: String,
default: ''
},
borderWidth: {
type: Number,
default: 3
}
},
render (h) {
return h('div', {
staticClass: 'mu-circle-wrapper active',
style: {
width: this.size + 'px',
height: this.size + 'px'
}
}, [
h('div', {
staticClass: `mu-circle-spinner active ${this.getColorClass()}`,
style: {
'border-color': this.getColor(this.color)
}
}, [
h('div', { staticClass: 'mu-circle-clipper left' }, [
h('div', { staticClass: 'mu-circle', style: { 'border-width': this.borderWidth + 'px' }})
]),
h('div', { staticClass: 'mu-circle-gap-patch' }, [
h('div', { staticClass: 'mu-circle' })
]),
h('div', { staticClass: 'mu-circle-clipper right' }, [
h('div', { staticClass: 'mu-circle', style: { 'border-width': this.borderWidth + 'px' }})
])
])
]);
}
};
================================================
FILE: src/Progress/CircularProgress.js
================================================
import Circular from './Circular';
import color from '../internal/mixins/color';
export default {
name: 'mu-circular-progress',
mixins: [color],
props: {
max: {
type: Number,
default: 100
},
min: {
type: Number,
default: 0
},
mode: {
type: String,
default: 'indeterminate',
validator (val) {
return ['indeterminate', 'determinate'].indexOf(val) !== -1;
}
},
value: {
type: Number,
default: 0
},
size: {
type: Number,
default: 24
},
strokeWidth: {
type: Number,
default: 3
}
},
computed: {
radius () {
return (this.size - this.strokeWidth) / 2;
},
circularSvgStyle () {
return {
width: this.size,
height: this.size
};
},
circularPathStyle () {
const relVal = this.getRelativeValue();
return {
stroke: this.getColor(this.color),
'stroke-dasharray': `${this.getArcLength(relVal)}, ${this.getArcLength(1)}`
};
}
},
methods: {
getArcLength (fraction) {
return fraction * Math.PI * (this.size - this.strokeWidth);
},
getRelativeValue () {
const { value, min, max } = this;
const clampedValue = Math.min(Math.max(min, value), max);
return clampedValue / (max - min);
},
createDeterminateCircular (h) {
if (this.mode !== 'determinate') return;
return h('svg', {
staticClass: 'mu-circular-progress-determinate',
style: this.circularSvgStyle,
attrs: {
viewBox: '0 0 ' + this.size + ' ' + this.size
}
}, [
h('circle', {
staticClass: 'mu-circular-progress-determinate-path',
style: this.circularPathStyle,
attrs: {
r: this.radius,
cx: this.size / 2,
cy: this.size / 2,
fill: 'none',
'stroke-miterlimit': '20',
'stroke-width': this.strokeWidth
}
})
]);
}
},
render (h) {
const circular = this.mode === 'indeterminate'
? h(Circular, {
props: {
size: this.size,
color: this.color,
borderWidth: this.strokeWidth
}
}) : undefined;
return h('div', {
staticClass: `mu-circular-progress ${this.getColorClass()}`,
style: {
width: this.size + 'px',
height: this.size + 'px'
}
}, [
circular,
this.createDeterminateCircular(h)
]);
}
};
================================================
FILE: src/Progress/LinearProgress.js
================================================
import color from '../internal/mixins/color';
export default {
name: 'mu-linear-progress',
mixins: [color],
props: {
max: {
type: Number,
default: 100
},
min: {
type: Number,
default: 0
},
mode: {
type: String,
default: 'indeterminate',
validator (val) {
return ['indeterminate', 'determinate'].indexOf(val) !== -1;
}
},
value: {
type: Number,
default: 0
},
size: [Number, String]
},
computed: {
percent () {
return (this.value - this.min) / (this.max - this.min) * 100;
},
linearStyle () {
const { color, mode, percent } = this;
return {
'background-color': this.getColor(color),
width: mode === 'determinate' ? percent + '%' : ''
};
},
linearClass () {
return 'mu-linear-progress-' + this.mode;
}
},
render (h) {
return h('div', {
staticClass: `mu-linear-progress ${this.getColorClass()}`,
style: {
height: this.size + 'px'
}
}, [
h('div', {
staticClass: 'mu-linear-progress-background',
style: {
'background-color': this.getColor(this.color)
}
}),
h('div', {
style: this.linearStyle,
class: this.linearClass
})
]);
}
};
================================================
FILE: src/Progress/index.js
================================================
import '../styles/components/progress.less';
import theme from '../theme';
import ProgressTheme from './theme';
import LinearProgress from './LinearProgress';
import CircularProgress from './CircularProgress';
theme.addCreateTheme(ProgressTheme);
export { LinearProgress, CircularProgress };
export default {
install (Vue) {
Vue.component(LinearProgress.name, LinearProgress);
Vue.component(CircularProgress.name, CircularProgress);
}
};
================================================
FILE: src/Progress/theme.js
================================================
export default (theme) => {
return `
.mu-linear-progress.mu-secondary-color .mu-linear-progress-background,
.mu-linear-progress.mu-secondary-color .mu-linear-progress-indeterminate,
.mu-linear-progress.mu-secondary-color .mu-linear-progress-determinate {
background-color: ${theme.secondary};
}
.mu-linear-progress.mu-success-color .mu-linear-progress-background,
.mu-linear-progress.mu-success-color .mu-linear-progress-indeterminate,
.mu-linear-progress.mu-success-color .mu-linear-progress-determinate {
background-color: ${theme.success};
}
.mu-linear-progress.mu-warning-color .mu-linear-progress-background,
.mu-linear-progress.mu-warning-color .mu-linear-progress-indeterminate,
.mu-linear-progress.mu-warning-color .mu-linear-progress-determinate {
background-color: ${theme.warning};
}
.mu-linear-progress.mu-info-color .mu-linear-progress-background,
.mu-linear-progress.mu-info-color .mu-linear-progress-indeterminate,
.mu-linear-progress.mu-info-color .mu-linear-progress-determinate {
background-color: ${theme.info};
}
.mu-linear-progress.mu-error-color .mu-linear-progress-background,
.mu-linear-progress.mu-error-color .mu-linear-progress-indeterminate,
.mu-linear-progress.mu-error-color .mu-linear-progress-determinate {
background-color: ${theme.error};
}
.mu-linear-progress-background {
background-color: ${theme.primary};
}
.mu-linear-progress-indeterminate{
background-color: ${theme.primary};
}
.mu-linear-progress-determinate{
background-color: ${theme.primary};
}
.mu-circle-spinner {
border-color: ${theme.primary};
}
.mu-circle-spinner.mu-secondary-color {
border-color: ${theme.secondary};
}
.mu-circular-progress.mu-secondary-color .mu-circular-progress-determinate-path {
stroke: ${theme.secondary};
}
.mu-circle-spinner.mu-success-color {
border-color: ${theme.success};
}
.mu-circular-progress.mu-success-color .mu-circular-progress-determinate-path {
stroke: ${theme.success};
}
.mu-circle-spinner.mu-warning-color {
border-color: ${theme.warning};
}
.mu-circular-progress.mu-warning-color .mu-circular-progress-determinate-path {
stroke: ${theme.warning};
}
.mu-circle-spinner.mu-info-color {
border-color: ${theme.info};
}
.mu-circular-progress.mu-info-color .mu-circular-progress-determinate-path {
stroke: ${theme.info};
}
.mu-circle-spinner.mu-error-color {
border-color: ${theme.error};
}
.mu-circular-progress.mu-error-color .mu-circular-progress-determinate-path {
stroke: ${theme.error};
}
.mu-circular-progress-determinate-path{
stroke: ${theme.primary};
}
`;
};
================================================
FILE: src/Radio/Radio.js
================================================
import select from '../internal/mixins/select';
import Icon from '../Icon';
import { isNull } from '../utils';
export default {
name: 'mu-radio',
mixins: [select('radio')],
props: {
inputValue: {}
},
computed: {
checked () {
const inputValue = this.inputValue;
const value = this.$attrs.value;
if (isNull(inputValue)) return false;
return inputValue === value;
}
},
methods: {
toggle () {
this.$emit('change', this.$attrs.value);
}
},
render (h) {
const defaultSvgUnCheckIcon = h('svg', {
staticClass: 'mu-radio-icon-uncheck mu-radio-svg-icon',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z'
}
})
]);
const defaultSvgCheckedIcon = h('svg', {
staticClass: 'mu-radio-icon-checked mu-radio-svg-icon',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zm0-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z'
}
})
]);
const view = this.createRipple(h, 'mu-radio-icon', [
this.uncheckIcon ? h(Icon, {
staticClass: 'mu-radio-icon-uncheck',
props: {
value: this.uncheckIcon
}
}) : defaultSvgUnCheckIcon,
this.checkedIcon ? h(Icon, {
staticClass: 'mu-radio-icon-checked',
props: {
value: this.checkedIcon
}
}) : defaultSvgCheckedIcon
]);
return this.createSelect(h, view);
}
};
================================================
FILE: src/Radio/index.js
================================================
import '../styles/components/radio.less';
import theme from '../theme';
import RadioTheme from './theme';
import Radio from './Radio';
Radio.install = function (Vue) {
Vue.component(Radio.name, Radio);
};
theme.addCreateTheme(RadioTheme);
export default Radio;
================================================
FILE: src/Radio/theme.js
================================================
export default (theme) => {
return `
.mu-radio.disabled {
color: ${theme.text.disabled};
}
.mu-radio-checked {
color: ${theme.primary};
}
.mu-radio-label {
color: ${theme.text.primary};
}
.mu-radio.disabled .mu-radio-label {
color: ${theme.text.disabled};
}
`;
};
================================================
FILE: src/Select/Option.js
================================================
import { ListItem, ListItemContent, ListItemTitle, ListAction } from '../List';
import Checkbox from '../Checkbox';
export default {
name: 'mu-option',
inject: ['addOption', 'removeOption', 'optionClick', 'isOptionSelected', 'isMultiple'],
props: {
label: {
required: true,
type: String
},
value: {
required: true
},
disabled: Boolean,
ripple: {
type: Boolean,
default: true
},
searchText: String, // 用户搜索的文本,如果设置此值,会根据这个字段来搜搜,否则使用label属性
avatar: Boolean
},
data () {
return {
visible: true,
focused: false
};
},
computed: {
selected () {
return this.isOptionSelected(this.value);
}
},
created () {
this.addOption(this);
},
destroyed () {
this.removeOption(this);
},
render (h) {
const defaultItem = [
this.isMultiple() ? h(ListAction, [
h(Checkbox, {
props: {
inputValue: this.selected,
color: 'secondary',
disabled: this.disabled,
tabIndex: -1
}
})
]) : undefined,
h(ListItemContent, [
h(ListItemTitle, {}, this.label)
])
];
return h(ListItem, {
staticClass: 'mu-option',
ref: 'listItem',
class: {
'is-selected': this.selected,
'is-disabled': this.disabled,
'is-focused': this.focused
},
props: {
ripple: this.ripple,
open: this.open,
avatar: this.avatar,
button: !this.disabled,
tabIndex: -1
},
directives: [{
name: 'show',
value: this.visible
}],
on: {
click: (e) => this.optionClick(this.value)
}
}, this.$slots.default && this.$slots.default.length > 0 ? this.$slots.default : defaultItem);
}
};
================================================
FILE: src/Select/Select.js
================================================
import input from '../internal/mixins/input.js';
import menu from './mixins/menu';
import selection from './mixins/selection';
import events from './mixins/events';
import keyboard from './mixins/keyboard';
export default {
name: 'mu-select',
mixins: [input, menu, selection, events, keyboard],
props: {
popoverClass: [String, Object, Array],
multiple: Boolean,
maxHeight: {
type: [String, Number],
default: 300
},
readonly: Boolean,
chips: Boolean,
tags: Boolean, // 可创建条目
placeholder: String,
separator: {
type: String,
default: ','
},
filterable: Boolean // enable search option
},
computed: {
autoComplete () {
return this.filterable || this.tags;
}
},
render (h) {
const { data, children, defaultActionIcon } = this.createSelection(h);
return this.createInput(h, data, [
...children,
this.createMenu(h)
], defaultActionIcon);
}
};
================================================
FILE: src/Select/index.js
================================================
import '../styles/components/select.less';
import theme from '../theme';
import SelectTheme from './theme';
import Select from './Select';
import Option from './Option';
Select.install = function (Vue) {
Vue.component(Select.name, Select);
Vue.component(Option.name, Option);
};
theme.addCreateTheme(SelectTheme);
export { Select, Option };
export default Select;
================================================
FILE: src/Select/mixins/events.js
================================================
export default {
methods: {
blur () {
this.deactivateInput();
this.closeMenu();
this.$emit('blur');
},
focus () {
this.activateInput();
this.openMenu();
this.$emit('focus');
},
focusInput () {
this.$refs.input.focus();
},
createListeners () {
const listeners = Object.assign({}, this.$listeners);
delete listeners.input;
delete listeners.change;
return {
...listeners,
click: (e) => {
if (this.disabled || this.readonly || !this.autoComplete) return;
if (this.isFocused && !this.open) {
this.openMenu();
return;
}
this.focus();
},
focus: (e) => {
if (this.disabled || this.readonly || this.isFocused) {
return;
}
this.activateInput();
this.$nextTick(this.focusInput);
},
keydown: this.onKeydown // mixins/keyboard.js
};
}
}
};
================================================
FILE: src/Select/mixins/keyboard.js
================================================
import keycode from 'keycode';
export default {
data () {
return {
focusIndex: -1
};
},
computed: {
enableOptions () {
return this.options.filter((option) => {
return option.visible && !option.disabled;
});
}
},
methods: {
onKeydown (e) {
if (this.disabled || this.readonly) return;
const code = keycode(e);
if (!this.open && ['enter', 'space', 'up', 'down'].indexOf(code) !== -1) {
e.preventDefault();
return this.openMenu();
}
const options = this.enableOptions;
switch (code) {
case 'enter':
const option = options[this.focusIndex];
if (option) {
this.optionClick(option.value);
} else if (this.tags && this.multiple && this.searchValue) {
this.optionClick(this.searchValue, true);
}
break;
case 'up':
case 'down':
e.preventDefault();
this.resetSelectedIndex();
code === 'up' ? this.decrementFocusIndex() : this.incrementFocusIndex();
break;
case 'tab':
this.blur();
if (this.multiple) this.searchValue = '';
break;
case 'left':
case 'right':
case 'delete':
case 'backspace':
if (!this.searchValue && this.autoComplete && this.multiple) this.changeSelectedIndex(code);
break;
default:
this.resetSelectedIndex();
break;
}
},
decrementFocusIndex () {
let index = this.focusIndex;
const maxIndex = this.getOptionCount() - 1;
index--;
if (index < 0) index = maxIndex;
this.setFocusIndex(index);
},
incrementFocusIndex () {
let index = this.focusIndex;
const maxIndex = this.getOptionCount() - 1;
index++;
if (index > maxIndex) index = 0;
this.setFocusIndex(index);
},
getOptionCount () {
return this.enableOptions.length;
},
resetFocusIndex () {
this.focusIndex = -1;
},
setFocusIndex (index) {
this.focusIndex = index;
},
getSelectedIndex () {
for (let i = 0; i < this.enableOptions.length; i++) {
if (this.enableOptions[i].selected) return i;
}
return -1;
},
setScollPosition (index) {
if (index === -1 || !this.open) return;
this.$nextTick(() => {
const option = this.enableOptions[index];
if (!option) return;
const optionEl = option.$el;
const optionHeight = optionEl.offsetHeight;
let scrollTop = optionEl.offsetTop - optionHeight;
if (scrollTop < optionHeight) scrollTop = 0;
this.$refs.popover.$el.scrollTop = scrollTop;
});
}
},
watch: {
focusIndex (val) {
this.enableOptions.forEach((option, index) => {
option.focused = index === val;
});
this.setScollPosition(val);
}
}
};
================================================
FILE: src/Select/mixins/menu.js
================================================
import List from '../../List';
import Popover from '../../Popover';
export default {
provide () {
return {
optionClick: this.optionClick,
addOption: this.addOption,
removeOption: this.removeOption,
isOptionSelected: this.isOptionSelected,
isMultiple: this.isMultiple
};
},
props: {
textline: List.props.textline,
space: Popover.props.space,
placement: Popover.props.placement,
dense: {
...List.props.dense,
default: true
},
noDataText: {
type: String,
default: '暂无数据显示'
}
},
data () {
return {
options: [],
open: false
};
},
computed: {
selects () {
if (!this.multiple) {
const option = this.getOption(this.value);
return option ? [{
label: option.label,
value: this.value,
index: 0
}] : [];
}
const selects = Array.isArray(this.value) ? this.value : [];
const selectItems = [];
for (let i = 0; i < selects.length; i++) {
const value = selects[i];
const option = this.getOption(value);
if (option) {
selectItems.push({
label: option.label,
value: option.value,
index: selectItems.length
});
continue;
}
if (this.tags) {
selectItems.push({
label: value,
value,
index: selectItems.length
});
}
}
return selectItems;
}
},
beforeDestroy () {
this.closeMenu();
},
methods: {
activateInput () {
this.isFocused = true;
},
deactivateInput () {
this.isFocused = false;
this.selectedIndex = -1;
this.setSeachValue();
},
openMenu () {
this.open = true;
this.resetOptionVisable();
const selectedIndex = this.getSelectedIndex();
this.setFocusIndex(selectedIndex);
setTimeout(() => this.setScollPosition(selectedIndex), 0);
if (this.autoComplete) {
this.$nextTick(() => {
this.$refs.input.select();
});
}
},
closeMenu () {
this.open = false;
this.resetFocusIndex();
},
toggleMenu () {
if (this.open) return this.closeMenu();
this.openMenu();
this.focusInput();
},
resetOptionVisable () {
this.options.forEach((option) => (option.visible = true));
},
isMultiple () {
return this.multiple;
},
isOptionSelected (value) {
return value === this.value || (
this.multiple &&
this.value &&
this.value.indexOf(value) !== -1
);
},
addOption (option) {
this.options.push(option);
},
removeOption (option) {
const index = this.options.indexOf(option);
if (index !== -1) this.options.splice(index, 1);
},
getOption (value) {
const option = this.options.filter((option) => option.value === value)[0];
if (option) return option;
return {
label: value,
value
};
},
insertValue (selectedValue, value) {
let index = 0;
for (let i = 0; i < this.options.length; i++) {
const item = this.options[i];
if (item.selected) {
index = selectedValue.indexOf(item.value) + 1;
continue;
}
if (item.value === value) {
return selectedValue.splice(index, 0, value);
}
}
return selectedValue.push(value);
},
optionClick (value, notRemove = false) {
let selectedValue = this.multiple ? this.value ? [...this.value] : [] : this.value;
if (this.multiple) {
const index = selectedValue.indexOf(value);
if (index === -1) {
this.insertValue(selectedValue, value);
} else {
if (!notRemove) selectedValue.splice(index, 1);
}
} else {
selectedValue = value;
}
this.$emit('input', selectedValue);
this.$emit('change', selectedValue);
if (this.multiple && this.autoComplete) this.searchValue = '';
this.$nextTick(() => {
this.focusInput();
if (!this.multiple) this.closeMenu();
});
},
createMenu (h) {
const trigger = this.$refs.select;
return h(Popover, {
staticClass: 'mu-option-list',
class: this.popoverClass,
style: {
'maxHeight': this.maxHeight + 'px',
'visibility': this.tags && this.enableOptions.length === 0 ? 'hidden' : '',
'min-width': trigger ? trigger.offsetWidth + 'px' : ''
},
ref: 'popover',
props: {
trigger: trigger,
open: this.open,
space: this.space,
cover: !this.autoComplete,
placement: this.placement
},
on: {
close: () => this.closeMenu()
}
}, [
h(List, {
props: {
textline: this.textline,
dense: this.dense
}
}, [
!this.tags && this.filterable && this.enableOptions.length === 0 ? h('div', { staticClass: 'mu-select-no-data' }, this.noDataText) : null,
this.$slots.default
])
]);
}
}
};
================================================
FILE: src/Select/mixins/selection.js
================================================
import Chip from '../../Chip';
import clickOutSide from '../../internal/directives/click-outside';
export default {
directives: {
'click-outside': clickOutSide
},
data () {
return {
searchValue: '',
shouldBreak: false,
selectedIndex: -1
};
},
created () {
this.setSeachValue();
},
methods: {
setSeachValue () {
if (!this.multiple) this.searchValue = this.selects.map(item => item.label).join(',');
},
changeSelectedIndex (keycode) {
const maxIndex = this.selects.length - 1;
if (keycode === 'left') {
this.selectedIndex = this.selectedIndex === -1 ? maxIndex : this.selectedIndex - 1;
} else if (keycode === 'right') {
this.selectedIndex = this.selectedIndex >= maxIndex ? -1 : this.selectedIndex + 1;
} else if (this.selectedIndex === -1) {
this.selectedIndex = maxIndex;
return;
}
if (['backspace', 'delete'].indexOf(keycode) !== -1) {
const newIndex = this.selectedIndex === maxIndex
? this.selectedIndex - 1
: this.selects[this.selectedIndex + 1] ? this.selectedIndex : -1;
if (this.selectedIndex !== -1) this.removeSelection(this.selectedIndex);
this.selectedIndex = newIndex;
}
},
resetSelectedIndex () {
this.selectedIndex = -1;
},
removeSelection (index) {
const value = [...this.value];
value.splice(index, 1);
this.$emit('input', value);
this.$emit('change', value);
},
createSlotSelection (item) {
return this.$scopedSlots.selection({
...item,
disabled: this.disabled || this.readonly
});
},
createChipSelection (h, { selected, index, label }) {
return h(Chip, {
attrs: {
tabindex: -1
},
props: {
delete: true,
selected
},
on: {
delete: () => {
if (this.disabled || this.readonly) return;
this.removeSelection(index);
}
}
}, label);
},
createTextSelection (h, { selected, label }, isLast) {
return h('span', {
staticClass: 'mu-selection-text',
class: {
'is-active': selected
}
}, isLast ? label : label + this.separator);
},
createSelectedItems (h) {
return this.selects.map((item, index) => {
const selected = this.selectedIndex === index;
switch (true) {
case !!this.$scopedSlots.selection:
return this.createSlotSelection({ ...item, selected });
case this.chips:
return this.createChipSelection(h, { ...item, selected });
default:
return this.createTextSelection(h, { ...item, selected }, index === this.selects.length - 1);
}
});
},
createInputElement (h) {
const enable = this.autoComplete && !this.readonly;
return [
h('input', {
staticClass: 'mu-select-input',
ref: 'input',
class: {
'is-enable': enable,
'is-break': this.shouldBreak
},
attrs: {
tabindex: 0,
readonly: !enable,
disabled: this.disabled,
placeholder: !this.value && this.value !== 0 ? this.placeholder : ''
},
domProps: {
value: this.searchValue
},
on: {
...this.createListeners(),
input: (e) => { this.searchValue = e.target.value; }
}
}),
h('input', {
attrs: {
...this.$attrs,
type: 'hidden'
},
domProps: {
value: this.value
}
})
];
},
createSelection (h) {
const content = h('div', {
staticClass: 'mu-select-content'
}, this.multiple ? [
...this.createSelectedItems(h),
...this.createInputElement(h)
] : this.createInputElement(h));
return {
data: {
staticClass: 'mu-select',
class: {
'is-open': this.open,
'is-multi': this.multiple,
'is-filterable': this.autoComplete,
'is-readonly': this.readonly,
'is-disabled': this.disabled
},
on: {
click: (e) => {
if (this.disabled || this.readonly || (this.autoComplete && e.target === this.$refs.input)) return;
this.toggleMenu();
}
},
directives: [{
name: 'click-outside',
value: (e) => {
if (this.open && this.$refs.popover.$el.contains(e.target)) return;
this.blur();
}
}],
ref: 'select'
},
children: [
content
],
defaultActionIcon: h('div', {
staticClass: 'mu-select-action'
}, [
h('svg', {
staticClass: 'mu-select-icon',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M7 10l5 5 5-5z'
}
})
])
])
};
}
},
watch: {
searchValue (val) {
if (this.$refs.input) {
if (this.$refs.input.scrollWidth > this.$refs.input.clientWidth) {
this.shouldBreak = true;
} else if (val === '') {
this.shouldBreak = false;
}
}
this.options.forEach(option => {
const searchText = option.searchText || option.label;
option.visible = !this.autoComplete || !val || searchText.toLowerCase().indexOf(val.toLowerCase()) !== -1;
});
this.resetFocusIndex();
if (this.isFocused && !this.open) this.open = true;
},
selects () {
this.setSeachValue();
}
}
};
================================================
FILE: src/Select/theme.js
================================================
import { fade } from '../utils/colorManipulator';
export default (theme) => {
return `
.mu-select-content {
color: ${theme.text.primary};
}
.mu-select-input {
color: ${theme.text.primary};
}
.mu-selection-text.is-active {
color: ${theme.primary};
}
.mu-select-no-data {
color: ${theme.text.disabled};
}
.mu-option.is-selected .mu-item {
color: ${theme.secondary};
}
.mu-option.is-focused {
color: ${theme.secondary};
background-color: ${fade(theme.text.primary, 0.1)};
}
.mu-option.is-disabled .mu-item {
color: ${theme.text.disabled};
}
`;
};
================================================
FILE: src/SlidePicker/Picker.js
================================================
import PickerSlot from './PickerSlot';
export default {
name: 'mu-slide-picker',
props: {
visibleItemCount: {
type: Number,
default: 5
},
values: {
type: Array,
default () {
return [];
}
},
slots: {
type: Array,
default () {
return [];
}
}
},
methods: {
change (index, value) {
this.$emit('change', value, index);
}
},
render (h) {
return h('div', {
staticClass: 'mu-slide-picker'
}, [
...this.slots.map((slot, index) => {
return h(PickerSlot, {
props: {
divider: slot.divider,
content: slot.content,
textAlign: slot.textAlign,
width: slot.width,
value: this.values[index],
values: slot.values,
visibleItemCount: this.visibleItemCount
},
key: 'picker-slot-item-' + index,
on: {
change: (value) => {
this.change(index, value);
}
}
});
}),
h('div', {
staticClass: 'mu-slide-picker-center-highlight'
})
]);
}
};
================================================
FILE: src/SlidePicker/PickerSlot.js
================================================
import swipe from '../internal/directives/swipe';
import translateUtil from '../utils/translate';
import { transitionEnd } from '../utils/dom';
export default {
name: 'mu-slide-picker-slot',
directives: {
swipe
},
props: {
divider: {
type: Boolean,
default: false
},
content: {
type: String,
default: ''
},
values: {
type: Array,
default () {
return [];
}
},
itemHeight: {
type: Number,
default: 36
},
value: {},
textAlign: {
type: String,
default: ''
},
width: {
type: String,
default: ''
},
visibleItemCount: {
type: Number,
default: 5
}
},
data () {
return {
animate: false,
startTop: 0,
velocityTranslate: 0,
prevTranslate: 0
};
},
computed: {
contentHeight () {
return this.itemHeight * this.visibleItemCount;
},
valueIndex () {
return this.values.indexOf(this.value);
},
dragRange () {
const values = this.values;
const visibleItemCount = this.visibleItemCount;
return [-this.itemHeight * (values.length - Math.ceil(visibleItemCount / 2)), this.itemHeight * Math.floor(visibleItemCount / 2)];
}
},
mounted () {
if (!this.divider) {
this.doOnValueChange();
}
},
methods: {
value2Translate (value) {
const values = this.values;
const valueIndex = values.indexOf(value);
const offset = Math.floor(this.visibleItemCount / 2);
if (valueIndex !== -1) {
return (valueIndex - offset) * -this.itemHeight;
}
},
translate2Value (translate) {
translate = Math.round(translate / this.itemHeight) * this.itemHeight;
const index = -(translate - Math.floor(this.visibleItemCount / 2) * this.itemHeight) / this.itemHeight;
return this.values[index];
},
doOnValueChange () {
const value = this.value;
const wrapper = this.$refs.wrapper;
translateUtil.translateElement(wrapper, null, this.value2Translate(value));
},
doOnValuesChange () {
const el = this.$el;
const items = el.querySelectorAll('.mu-slide-picker-item');
Array.prototype.forEach.call(items, (item, index) => {
translateUtil.translateElement(item, null, this.itemHeight * index);
});
},
handleStart () {
this.startTop = translateUtil.getElementTranslate(this.$refs.wrapper).top;
},
handleMove (pos, drag, event) {
const el = this.$refs.wrapper;
const translate = this.startTop + pos.y;
translateUtil.translateElement(el, 0, translate);
this.velocityTranslate = translate - this.prevTranslate || translate;
this.prevTranslate = translate;
},
handleEnd (pos, drag, event) {
const el = this.$refs.wrapper;
const momentumRatio = 7;
const currentTranslate = translateUtil.getElementTranslate(el).top;
let momentumTranslate;
if (pos.time < 300) {
momentumTranslate = currentTranslate + this.velocityTranslate * momentumRatio;
}
const dragRange = this.dragRange;
this.animate = true;
transitionEnd(el, () => {
this.animate = false;
});
this.$nextTick(() => {
let translate;
if (momentumTranslate) {
translate = Math.round(momentumTranslate / this.itemHeight) * this.itemHeight;
} else {
translate = Math.round(currentTranslate / this.itemHeight) * this.itemHeight;
}
translate = Math.max(Math.min(translate, dragRange[1]), dragRange[0]);
translateUtil.translateElement(el, null, translate);
this.$emit('change', this.translate2Value(translate));
});
}
},
render (h) {
return h('div', {
staticClass: 'mu-slide-picker-slot',
class: {
'mu-slide-picker-slot-divider': this.divider
},
style: {
width: this.width
},
on: {
touchmove: (e) => {
e.preventDefault();
}
},
directives: this.divider ? [] : [{
name: 'swipe',
value: {
start: this.handleStart,
move: this.handleMove,
end: this.handleEnd
}
}]
}, [
this.divider
? h('div', {}, this.content)
: h('div', {
staticClass: 'mu-slide-picker-slot-wrapper',
class: {
animate: this.animate
},
style: {
height: this.contentHeight + 'px'
},
ref: 'wrapper'
}, this.values.map((item, index) => {
return h('div', {
staticClass: 'mu-slide-picker-item',
style: {
'text-align': this.textAlign
},
class: {
selected: item === this.value
},
key: 'pick-slot-' + index
}, item.text || item);
}))
]);
},
watch: {
values (newVal) {
if (this.valueIndex === -1) {
this.value = (newVal || [])[0];
}
},
value () {
this.doOnValueChange();
}
}
};
================================================
FILE: src/SlidePicker/index.js
================================================
import '../styles/components/slide-picker.less';
import theme from '../theme';
import PickerTheme from './theme';
import Picker from './Picker';
Picker.install = function (Vue) {
Vue.component(Picker.name, Picker);
};
theme.addCreateTheme(PickerTheme);
export default Picker;
================================================
FILE: src/SlidePicker/theme.js
================================================
export default (theme) => {
return `
.mu-slide-picker{
background: ${theme.background.paper};
}
.mu-slide-picker-center-highlight {
border-top-color: ${theme.divider};
border-bottom-color: ${theme.divider};
}
.mu-slide-picker-slot.mu-slide-picker-slot-divider{
color: ${theme.text.primary};
}
.mu-slide-picker-item{
color: ${theme.text.secondary};
}
.mu-slide-picker-item.selected {
color: ${theme.text.primary};
}
`;
};
================================================
FILE: src/Slider/Slider.js
================================================
import keycode from 'keycode';
import FocusRipple from '../internal/FocusRipple';
import color from '../internal/mixins/color';
export default {
name: 'mu-slider',
mixins: [color],
model: {
prop: 'value',
event: 'change'
},
props: {
value: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100
},
min: {
type: Number,
default: 0
},
step: {
type: Number,
default: 0.1
},
thumbColor: String,
trackColor: String,
disabled: Boolean,
displayValue: {
type: Boolean,
default: true
}
},
data () {
return {
active: false,
hover: false,
focused: false,
dragging: false
};
},
computed: {
percent () {
const percentNum = (this.value - this.min) / (this.max - this.min) * 100;
return percentNum > 100 ? 100 : percentNum < 0 ? 0 : percentNum;
}
},
created () {
this.handleDragMouseMove = this.handleDragMouseMove.bind(this);
this.handleMouseEnd = this.handleMouseEnd.bind(this);
this.handleTouchMove = this.handleTouchMove.bind(this);
this.handleTouchEnd = this.handleTouchEnd.bind(this);
},
methods: {
handleKeydown (e) {
const { min, max, step } = this;
let action;
switch (keycode(e)) {
case 'page down':
case 'down':
action = 'decrease';
break;
case 'left':
action = 'decrease';
break;
case 'page up':
case 'up':
action = 'increase';
break;
case 'right':
action = 'increase';
break;
case 'home':
action = 'min';
break;
case 'end':
action = 'max';
break;
}
let value = this.value;
if (action) {
e.preventDefault();
switch (action) {
case 'decrease':
value -= step;
break;
case 'increase':
value += step;
break;
case 'min':
value = min;
break;
case 'max':
value = max;
break;
}
value = parseFloat(value.toFixed(5));
if (value > max) {
value = max;
} else if (value < min) {
value = min;
}
}
this.$emit('change', value);
},
handleMouseDown (e) {
if (this.disabled) return;
this.setValue(e);
e.preventDefault();
document.addEventListener('mousemove', this.handleDragMouseMove);
document.addEventListener('mouseup', this.handleMouseEnd);
this.$el.focus();
this.onDragStart(e);
},
handleMouseUp () {
if (this.disabled) return;
this.active = false;
},
handleTouchStart (e) {
if (this.disabled) return;
this.setValue(e.touches[0]);
document.addEventListener('touchmove', this.handleTouchMove);
document.addEventListener('touchup', this.handleTouchEnd);
document.addEventListener('touchend', this.handleTouchEnd);
document.addEventListener('touchcancel', this.handleTouchEnd);
e.preventDefault();
this.onDragStart(e);
},
handleTouchEnd (e) {
if (this.disabled) return;
document.removeEventListener('touchmove', this.handleTouchMove);
document.removeEventListener('touchup', this.handleTouchEnd);
document.removeEventListener('touchend', this.handleTouchEnd);
document.removeEventListener('touchcancel', this.handleTouchEnd);
this.onDragStop(e);
},
handleFocus () {
if (this.disabled) return;
this.focused = true;
},
handleBlur () {
if (this.disabled) return;
this.focused = false;
},
handleMouseEnter () {
if (this.disabled) return;
this.hover = true;
},
handleMouseLeave () {
if (this.disabled) return;
this.hover = false;
},
// 从点击位置更新 value
setValue (e) {
const { $el, max, min, step } = this;
let value = (e.clientX - $el.getBoundingClientRect().left) / $el.offsetWidth * (max - min);
value = Math.round(value / step) * step + min;
value = parseFloat(value.toFixed(5));
if (value > max) {
value = max;
} else if (value < min) {
value = min;
}
this.$emit('change', value);
},
// 拖拽控制
onDragStart (e) {
this.dragging = true;
this.active = true;
this.$emit('drag-start', e);
},
onDragUpdate (e) {
if (this.dragRunning) return;
this.dragRunning = true;
window.requestAnimationFrame(() => {
this.dragRunning = false;
if (!this.disabled) this.setValue(e);
});
},
onDragStop (e) {
this.dragging = false;
this.active = false;
this.$emit('drag-stop', e);
},
handleDragMouseMove (e) {
this.onDragUpdate(e);
},
handleTouchMove (e) {
this.onDragUpdate(e.touches[0]);
},
handleMouseEnd (e) {
document.removeEventListener('mousemove', this.handleDragMouseMove);
document.removeEventListener('mouseup', this.handleMouseEnd);
this.onDragStop(e);
}
},
render (h) {
const colorClass = this.getNormalColorClass(this.color, true);
const color = this.getColor(this.color);
const thumbColorClass = this.getNormalColorClass(this.thumbColor);
const thumbColor = this.getColor(this.thumbColor);
const thumbTextColorClass = this.getNormalColorClass(this.thumbColor, true);
const trackColorClass = this.getNormalColorClass(this.trackColor);
const trackColor = this.getColor(this.trackColor);
const percent = this.percent + '%';
const input = h('input', {
attrs: {
...this.$attrs,
type: 'hidden',
value: this.value
}
});
const displayValue = this.displayValue ? h('div', {
staticClass: 'mu-slider-display-value ' + thumbColorClass,
style: {
left: percent,
'background-color': thumbColor
}
}, [
h('span', {
staticClass: 'display-value-text'
}, this.value)
]) : undefined;
const thumb = h('div', {
staticClass: ['mu-slider-thumb', thumbColorClass, thumbTextColorClass].join(' '),
style: {
left: this.percent + '%',
color: thumbColor,
'background-color': thumbColor
}
}, [
(this.focused || this.hover) && !this.active ? h(FocusRipple) : undefined
]);
return h('div', {
staticClass: 'mu-slider ' + colorClass,
class: {
zero: this.value <= this.min,
active: this.active,
'display-value': this.displayValue && this.active,
disabled: this.disabled
},
style: { color },
attrs: {
tabindex: this.disabled ? -1 : 0
},
on: {
...this.$listeners,
focus: this.handleFocus,
blur: this.handleBlur,
keydown: this.handleKeydown,
touchstart: this.handleTouchStart,
touchend: this.handleTouchEnd,
touchcancel: this.handleTouchEnd,
mousedown: this.handleMouseDown,
mouseup: this.handleMouseUp,
mouseenter: this.handleMouseEnter,
mouseleave: this.handleMouseLeave
}
}, [
input,
displayValue,
h('div', {
staticClass: `mu-slider-track ${trackColorClass}`,
style: {
'background-color': trackColor
}
}),
h('div', { staticClass: 'mu-slider-fill', style: { width: percent }}),
thumb
]);
}
};
================================================
FILE: src/Slider/index.js
================================================
import '../styles/components/slider.less';
import theme from '../theme';
import SliderTheme from './theme';
import Slider from './Slider';
Slider.install = function (Vue) {
Vue.component(Slider.name, Slider);
};
theme.addCreateTheme(SliderTheme);
export default Slider;
================================================
FILE: src/Slider/theme.js
================================================
export default (theme) => {
return `
.mu-slider {
color: ${theme.primary};
}
.mu-slider-track{
background-color: ${theme.track};
}
.mu-slider.disabled .mu-slider-fill{
background-color: ${theme.track};
}
.mu-slider.zero .mu-slider-thumb,
.mu-slider.disabled .mu-slider-thumb{
border-color: ${theme.track};
color: ${theme.track};
background-color: ${theme.text.alternate};
}
`;
};
================================================
FILE: src/Snackbar/Snackbar.js
================================================
import popup from '../internal/mixins/popup';
import { SlideTopTransition, SlideBottomTransition } from '../internal/transitions';
import color from '../internal/mixins/color';
export default {
name: 'mu-snackbar',
mixins: [popup, color],
props: {
overlay: {
default: false
},
escPressClose: {
default: false
},
textColor: String,
message: String,
position: {
type: String,
default: 'bottom',
validator (val) {
return ['top-start', 'top', 'top-end', 'bottom-start', 'bottom', 'bottom-end'].indexOf(val) !== -1;
}
}
},
render (h) {
const message = h('div', {
staticClass: 'mu-snackbar-message'
}, this.$slots.default && this.$slots.default.length > 0 ? this.$slots.default : this.message);
const action = this.$slots.action ? h('div', {
staticClass: 'mu-snackbar-action'
}, this.$slots.action) : undefined;
return h(this.position.indexOf('top') !== -1 ? SlideTopTransition : SlideBottomTransition, [
this.open ? h('div', {
staticClass: `mu-snackbar ${this.getColorClass()} ${this.getTextColorClass()}`,
style: {
'z-index': this.zIndex,
'background-color': this.getColor(this.color),
'color': this.getColor(this.textColor)
},
class: {
['mu-snackbar-' + this.position]: !!this.position
},
on: this.$listeners
}, [message, action]) : undefined
]);
}
};
================================================
FILE: src/Snackbar/index.js
================================================
import '../styles/components/snackbar.less';
import theme from '../theme';
import SnackbarTheme from './theme';
import Snackbar from './Snackbar';
Snackbar.install = function (Vue) {
Vue.component(Snackbar.name, Snackbar);
};
theme.addCreateTheme(SnackbarTheme);
export default Snackbar;
================================================
FILE: src/Snackbar/theme.js
================================================
export default (theme) => {
return `
.mu-snackbar {
color: ${theme.text.alternate};
background-color: ${theme.text.primary};
}
`;
};
================================================
FILE: src/Stepper/Step.js
================================================
export default {
name: 'mu-step',
props: {
active: {
type: Boolean,
default: false
},
completed: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
index: {
type: Number
},
last: {
type: Boolean,
default: false
}
},
render (h) {
const { active, completed, disabled, index, last } = this;
const children = [];
const slots = this.$slots;
if (slots.default && slots.default.length > 0) {
slots.default.forEach((vNode) => {
if (!vNode.componentOptions || !vNode.componentOptions.propsData) return;
const num = index + 1;
vNode.componentOptions.propsData = { active, completed, disabled, last, num, ...vNode.componentOptions.propsData };
children.push(vNode);
});
}
return h('div', { staticClass: 'mu-step', on: this.$listeners }, children);
}
};
================================================
FILE: src/Stepper/StepButton.js
================================================
import AbstractButton from '../internal/AbstractButton';
import StepLabel from './StepLabel';
export default {
name: 'mu-step-button',
props: {
active: Boolean,
completed: Boolean,
disabled: Boolean,
ripple: {
type: Boolean,
default: true
},
num: [String, Number],
last: Boolean,
childrenInLabel: {
type: Boolean,
default: true
}
},
render (h) {
const slots = this.$slots;
const stepLabel = h(StepLabel, {
props: {
active: this.active,
completed: this.completed,
num: this.num,
disabled: this.disabled
}
}, [
slots.default,
slots.icon && slots.icon.map(vNode => {
if (!vNode.tag) return vNode;
vNode.data = vNode.data || {};
vNode.data.slot = 'icon';
})
]);
return h(AbstractButton, {
staticClass: 'mu-step-button',
props: {
disabled: this.disabled,
ripple: this.ripple
},
on: this.$listeners
}, [this.childrenInLabel ? stepLabel : slots.default]);
}
};
================================================
FILE: src/Stepper/StepConnector.js
================================================
export default {
name: 'mu-step-connector',
functional: true,
render (h, { data, children }) {
data.staticClass = `mu-step-connector ${data.staticClass || ''}`;
return h('div', data, [h('span', { staticClass: 'mu-step-connector-line' })]);
}
};
================================================
FILE: src/Stepper/StepContent.js
================================================
import ExpandTransition from '../internal/ExpandTransition';
export default {
name: 'mu-step-content',
props: {
active: Boolean,
last: Boolean
},
render (h) {
return h('div', {
staticClass: 'mu-step-content',
class: {
last: this.last
},
on: this.$listeners
}, [
h('div', {
style: {
position: 'relative',
overflow: 'hidden',
height: '100%'
}
}, [
h(ExpandTransition, [
this.active ? h('div', { staticClass: 'mu-step-content-inner', ref: 'inner' }, this.$slots.default) : undefined
])
])
]);
}
};
================================================
FILE: src/Stepper/StepLabel.js
================================================
export default {
name: 'mu-step-label',
props: {
active: Boolean,
completed: Boolean,
disabled: Boolean,
num: [String, Number]
},
render (h) {
const slots = this.$slots;
const isSlotsIcon = slots.icon && slots.icon.length > 0;
const icon = this.completed ? h('svg', {
staticClass: 'mu-step-label-icon',
attrs: {
viewBox: '0 0 24 24'
}
}, [
h('path', {
attrs: {
d: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z'
}
})
]) : h('div', { staticClass: 'mu-step-label-circle' }, this.num);
return h('span', {
staticClass: 'mu-step-label',
class: {
active: this.active,
completed: this.completed,
disabled: this.disabled
},
on: this.$listeners
}, [
this.num || isSlotsIcon
? h('span', { staticClass: 'mu-step-label-icon-container' }, [isSlotsIcon ? slots.icon : icon]) : undefined,
slots.default
]);
}
};
================================================
FILE: src/Stepper/Stepper.js
================================================
import StepConnector from './StepConnector';
export default {
name: 'mu-stepper',
props: {
activeStep: {
type: Number,
default: 0
},
linear: {
type: Boolean,
default: true
},
orientation: {
type: String,
default: 'horizontal',
validator (val) {
return ['horizontal', 'vertical'].indexOf(val) !== -1;
}
}
},
render (h) {
const { activeStep, linear, orientation } = this;
const children = [];
const slots = this.$slots;
if (slots.default && slots.default.length > 0) {
let index = 0;
slots.default.forEach((vNode) => {
if (!vNode.componentOptions) return;
if (index > 0) {
children.push(h(StepConnector, {}));
}
const propsData = vNode.componentOptions.propsData;
if (activeStep === index) {
propsData.active = true;
} else if (linear && activeStep > index) {
propsData.completed = true;
} else if (linear && activeStep < index) {
propsData.disabled = true;
}
propsData.index = index++;
children.push(vNode);
});
if (children.length > 0) children[children.length - 1].componentOptions.propsData.last = true;
}
return h('div', {
staticClass: `mu-stepper ${orientation === 'vertical' ? 'mu-stepper-vertical' : ''}`
}, children);
}
};
================================================
FILE: src/Stepper/index.js
================================================
import '../styles/components/stepper.less';
import theme from '../theme';
import StepperTheme from './theme';
import Stepper from './Stepper';
import Step from './Step';
import StepLabel from './StepLabel';
import StepButton from './StepButton';
import StepConnector from './StepConnector';
import StepContent from './StepContent';
Stepper.install = function (Vue) {
Vue.component(Stepper.name, Stepper);
Vue.component(Step.name, Step);
Vue.component(StepLabel.name, StepLabel);
Vue.component(StepButton.name, StepButton);
Vue.component(StepConnector.name, StepConnector);
Vue.component(StepContent.name, StepContent);
};
theme.addCreateTheme(StepperTheme);
export { Stepper, Step, StepLabel, StepButton, StepConnector, StepContent };
export default Stepper;
================================================
FILE: src/Stepper/theme.js
================================================
export default (theme) => {
return `
.mu-step-label {
color: ${theme.text.primary};
}
.mu-step-label.disabled {
color: ${theme.text.disabled};
}
.mu-step-label.completed .mu-step-label-icon,
.mu-step-label.active .mu-step-label-icon {
color: ${theme.primary};
}
.mu-step-label-circle {
color: ${theme.text.alternate};
}
.mu-step-label.completed .mu-step-label-circle,
.mu-step-label.active .mu-step-label-circle {
background-color: ${theme.primary};
}
`;
};
================================================
FILE: src/SubHeader/SubHeader.js
================================================
export default {
name: 'mu-sub-header',
functional: true,
props: {
inset: Boolean
},
render (h, { data, props, children }) {
data.staticClass = `${data.staticClass || ''} mu-sub-header ${props.inset ? 'inset' : ''}`;
return h('div', data, children);
}
};
================================================
FILE: src/SubHeader/index.js
================================================
import '../styles/components/subheader.less';
import theme from '../theme';
import SubHeaderTheme from './theme';
import SubHeader from './SubHeader';
SubHeader.install = function (Vue) {
Vue.component(SubHeader.name, SubHeader);
};
theme.addCreateTheme(SubHeaderTheme);
export default SubHeader;
================================================
FILE: src/SubHeader/theme.js
================================================
export default (theme) => {
return `
.mu-sub-header {
color: ${theme.text.secondary};
}
`;
};
================================================
FILE: src/Switch/Switch.js
================================================
import select from '../internal/mixins/select';
export default {
name: 'mu-switch',
mixins: [select('switch')],
props: {
inputValue: Boolean
},
computed: {
checked () {
return this.inputValue;
}
},
methods: {
toggle () {
this.$emit('change', !this.inputValue);
}
},
render (h) {
const view = h('div', {
staticClass: 'mu-switch-container'
}, [
h('div', { staticClass: 'mu-switch-track' }),
this.createRipple(h, 'mu-switch-thumb')
]);
return this.createSelect(h, view);
}
};
================================================
FILE: src/Switch/index.js
================================================
import '../styles/components/switch.less';
import theme from '../theme';
import SwitchTheme from './theme';
import Switch from './Switch';
Switch.install = function (Vue) {
Vue.component(Switch.name, Switch);
};
theme.addCreateTheme(SwitchTheme);
export default Switch;
================================================
FILE: src/Switch/theme.js
================================================
export default (theme) => {
return `
.mu-switch.disabled input[type="checkbox"]:checked+.mu-switch-wrapper .mu-switch-track{
background-color: ${theme.track};
}
.mu-switch-checked {
color: ${theme.primary};
}
.mu-switch.disabled .mu-switch-label {
color: ${theme.text.disabled};
}
.mu-switch-label {
color: ${theme.text.primary};
}
.mu-switch.disabled .mu-switch-track {
background-color: ${theme.track};
}
.mu-switch-track {
background-color: ${theme.track};
}
.mu-switch-thumb {
background-color: ${theme.background.paper};
}
`;
};
================================================
FILE: src/Tabs/Tab.js
================================================
import route from '../internal/mixins/route';
import ripple from '../internal/mixins/ripple';
import { isNotNull } from '../utils';
import AbstractButton from '../internal/AbstractButton';
export default {
name: 'mu-tab',
mixins: [route, ripple],
inject: [
'tabClick',
'getActiveValue',
'getDefaultVal',
'addTab',
'removeTab',
'setTabHighLineStyle',
'getActiveColor',
'getTabsInverse'
],
props: {
disabled: Boolean,
value: {}
},
data () {
return {
tabVal: 0
};
},
computed: {
active () {
return !this.disabled && this.getActiveValue() === this.tabVal;
},
activeColor () {
return this.getActiveColor();
}
},
created () {
this.tabVal = isNotNull(this.value) ? this.value : this.getDefaultVal();
this.addTab(this);
},
methods: {
handleClick (e) {
this.tabClick(this.tabVal, this);
this.$emit('click', e);
}
},
beforeDestory () {
this.removeTab(this);
},
watch: {
active (val, oldVal) {
if (val) this.$emit('active');
},
value (val) {
this.tabVal = val;
this.setTabHighLineStyle();
}
},
render (h) {
return h(AbstractButton, {
staticClass: 'mu-tab',
props: {
...this.generateRouteProps(),
containerElement: 'div',
wrapperClass: 'mu-tab-wrapper',
disabled: this.disabled,
ripple: this.ripple,
rippleOpacity: this.rippleOpacity,
rippleColor: this.rippleColor
},
style: {
color: this.active ? this.activeColor.color : ''
},
class: {
'mu-tab-active': this.active,
'is-inverse': this.active &&
this.getTabsInverse() &&
!this.activeColor.className &&
!this.activeColor.color,
[this.activeColor.className]: this.active
},
on: {
click: this.handleClick
}
}, this.$slots.default);
}
};
================================================
FILE: src/Tabs/Tabs.js
================================================
import { isNotNull } from '../utils';
import resize from '../internal/directives/resize';
import color from '../internal/mixins/color';
import translateUtils from '../utils/translate';
export default {
name: 'mu-tabs',
mixins: [color],
provide () {
return {
tabClick: this.handleTabClick,
getDefaultVal: this.getDefaultVal,
addTab: this.addTab,
removeTab: this.removeTab,
setTabHighLineStyle: this.setTabHighLineStyle,
getActiveValue: this.getActiveValue,
getActiveColor: this.getActiveColor,
getTabsInverse: this.getInverse
};
},
props: {
inverse: Boolean,
indicatorColor: String,
fullWidth: Boolean,
center: Boolean,
value: {}
},
data () {
return {
tabs: [],
activeValue: isNotNull(this.value) ? this.value : 0
};
},
created () {
this.tabIndex = 0;
},
mounted () {
this.setTabHighLineStyle();
},
updated () {
this.setTabHighLineStyle();
},
methods: {
handleTabClick (value, tab) {
if (this.activeValue !== value) {
this.activeValue = value;
this.$emit('update:value', value);
this.$emit('change', value);
}
},
getActiveValue () {
return this.activeValue;
},
getDefaultVal () {
return this.tabIndex++;
},
getActiveColor () {
return this.inverse ? {
className: this.getNormalColorClass(this.color, true),
color: this.getColor(this.color)
} : { className: '', color: '' };
},
getInverse () {
return this.inverse;
},
addTab (tab) {
const index = this.$children.indexOf(tab);
return index === -1 ? this.tabs.push(tab) : this.tabs.splice(index, 0, tab);
},
removeTab (tab) {
const index = this.tabs.indexOf(tab);
if (index === -1) return;
this.tabs.splice(index, 1);
},
getActiveTab () {
return this.tabs.filter((tab) => tab.active)[0];
},
setTabHighLineStyle () {
const activeTab = this.getActiveTab();
if (!activeTab || !this.$refs.line || !activeTab.$el) return;
const el = activeTab.$el;
const lineEl = this.$refs.line;
const rect = el.getBoundingClientRect();
const tabsRect = this.$el.getBoundingClientRect();
lineEl.style.width = rect.width + 'px';
translateUtils.translateElement(lineEl, rect.left - tabsRect.left, 0);
}
},
watch: {
value (val) {
this.activeValue = val;
},
activeValue () {
this.setTabHighLineStyle();
}
},
directives: {
resize
},
render (h) {
return h('div', {
staticClass: `mu-tabs ${!this.inverse ? this.getColorClass(false) : ''}`,
class: {
'mu-tabs-full-width': this.fullWidth,
'mu-tabs-center': this.center,
'mu-tabs-inverse': this.inverse
},
style: {
'background-color': !this.inverse ? this.getColor(this.color) : ''
},
directives: [{
name: 'resize',
value: this.setTabHighLineStyle
}]
}, [
this.$slots.default,
h('span', {
staticClass: `mu-tab-link-highlight ${this.getNormalColorClass(this.indicatorColor, false, false)}`,
style: {
'background-color': this.getColor(this.indicatorColor)
},
ref: 'line'
})
]);
}
};
================================================
FILE: src/Tabs/index.js
================================================
import '../styles/components/tabs.less';
import theme from '../theme';
import TabsTheme from './theme';
import Tabs from './Tabs';
import Tab from './Tab';
Tabs.install = function (Vue) {
Vue.component(Tabs.name, Tabs);
Vue.component(Tab.name, Tab);
};
theme.addCreateTheme(TabsTheme);
export { Tabs, Tab };
export default Tabs;
================================================
FILE: src/Tabs/theme.js
================================================
import { fade } from '../utils/colorManipulator';
export default (theme) => {
return `
.mu-tabs{
background-color: ${theme.primary};
color: ${fade(theme.text.alternate, 0.7)};
}
.mu-tabs-inverse {
background-color: ${theme.background.default};
color: ${theme.text.secondary};
}
.mu-tab-link-highlight{
background-color: ${theme.secondary};
}
.mu-tab-active {
color: ${theme.text.alternate};
}
.mu-tab-active.is-inverse {
color: ${theme.text.primary};
}
`;
};
================================================
FILE: src/TextField/TextField.js
================================================
import input from '../internal/mixins/input';
import Textarea from './Textarea';
export default {
name: 'mu-text-field',
mixins: [input],
props: {
rows: {
type: Number,
default: 1
},
rowsMax: {
type: Number
},
multiLine: Boolean,
maxLength: [String, Number]
},
methods: {
handleFocus (e) {
this.isFocused = true;
this.$emit('focus', e);
},
handleBlur (e) {
this.isFocused = false;
this.$emit('blur', e);
},
focus () {
if (this.disabled) return;
if (this.$refs.input) {
this.$refs.input.focus();
} else if (this.$refs.textarea) {
this.$refs.textarea.$refs.textarea.focus();
}
},
createTextField (h) {
const listeners = {
...this.$listeners,
input: (e) => this.$emit('input', e.target.value, e),
change: (e) => this.$emit('change', e.target.value, e),
focus: this.handleFocus,
blur: this.handleBlur
};
const placeholder = !this.labelFloat ? this.$attrs.placeholder : '';
return [
this.multiLine ? h(Textarea, {
attrs: {
...this.$attrs,
maxlength: this.maxLength,
placeholder
},
props: {
disabled: this.disabled,
rows: this.rows,
rowsMax: this.rowsMax,
value: String(this.value || '')
},
ref: 'textarea',
on: listeners
}) : h('input', {
staticClass: 'mu-text-field-input',
attrs: {
tabindex: 0,
...this.$attrs,
maxlength: this.maxLength,
disabled: this.disabled,
placeholder
},
domProps: {
value: this.value
},
ref: 'input',
on: listeners
})
];
}
},
render (h) {
return this.createInput(h, {
staticClass: 'mu-text-field'
}, [
this.createTextField(h),
this.$slots.default
]);
}
};
================================================
FILE: src/TextField/Textarea.js
================================================
export default {
inheritAttrs: false,
props: {
disabled: Boolean,
rows: {
type: Number,
default: 1
},
rowsMax: {
type: Number
},
value: {
type: String,
default: ''
}
},
mounted () {
this.resizeTextarea();
},
watch: {
value (val, oldVal) {
this.$nextTick(() => {
this.resizeTextarea();
});
}
},
methods: {
resizeTextarea () {
const element = this.$refs.textarea;
if (!element) return;
const hiddenEl = this.$refs.textareaHidden;
let lineHeight = window.getComputedStyle(element, null).getPropertyValue('line-height');
lineHeight = Number(lineHeight.substring(0, lineHeight.indexOf('px')));
let pt = window.getComputedStyle(element, null).getPropertyValue('padding-top');
pt = Number(pt.substring(0, pt.indexOf('px')));
let pd = window.getComputedStyle(element, null).getPropertyValue('padding-bottom');
pd = Number(pd.substring(0, pd.indexOf('px')));
const minHeight = pd + pt + lineHeight * this.rows;
const maxHeight = pd + pt + lineHeight * (this.rowsMax || this.rows);
const height = hiddenEl.scrollHeight;
element.style.height = (height < minHeight ? minHeight : height > maxHeight && maxHeight > 0 ? maxHeight : height) + 'px';
}
},
render (h) {
return h('div', {
staticClass: 'mu-text-field-multiline'
}, [
h('textarea', {
staticClass: 'mu-text-field-textarea-hide mu-text-field-input',
ref: 'textareaHidden',
attrs: {
rows: 1
},
domProps: {
value: this.value || ' '
}
}),
h('textarea', {
staticClass: 'mu-text-field-input mu-text-field-textarea',
ref: 'textarea',
attrs: {
tabindex: 0,
...this.$attrs,
disabled: this.disabled
},
domProps: {
value: this.value || ''
},
on: this.$listeners
})
]);
}
};
================================================
FILE: src/TextField/index.js
================================================
import '../styles/components/text-field.less';
import theme from '../theme';
import TextFieldTheme from './theme';
import TextField from './TextField';
TextField.install = function (Vue) {
Vue.component(TextField.name, TextField);
};
theme.addCreateTheme(TextFieldTheme);
export default TextField;
================================================
FILE: src/TextField/theme.js
================================================
export default (theme) => {
return `
.mu-input {
color: ${theme.text.secondary};
}
.mu-input__focus {
color: ${theme.primary};
}
.mu-input__error {
color: ${theme.error};
}
.mu-input.disabled .mu-input-content {
color: ${theme.text.disabled};
}
.mu-input-help {
color: ${theme.text.secondary};
}
.mu-input__error .mu-input-help {
color: ${theme.error};
}
.mu-input.has-label .mu-input-label.float {
color: ${theme.text.disabled};
}
.mu-input-line {
background-color: ${theme.divider};
}
.mu-input-line.disabled{
border-bottom-color: ${theme.text.disabled};
}
.mu-input-suffix-text,
.mu-input-prefix-text {
color: ${theme.text.secondary};
}
.mu-text-field-input {
color: ${theme.text.primary};
}
.mu-text-field-suffix {
color: ${theme.text.secondary};
}
`;
};
================================================
FILE: src/Tooltip/Tooltip.js
================================================
import Vue from 'vue';
import TooltipContent from './TooltipContent';
import { getFirstComponentChild } from '../utils';
export default {
name: 'mu-tooltip',
props: {
content: String,
placement: TooltipContent.props.placement,
open: Boolean,
tooltipClass: [String, Object, Array]
},
data () {
return {
active: this.open,
trigger: null
};
},
beforeCreate () {
if (this.$isServer) return;
this.tooltipVM = new Vue({
data: { node: '' },
render (h) {
return this.node;
}
}).$mount();
},
mounted () {
this.trigger = this.$el;
},
methods: {
addEventHandle (old, fn) {
if (!old) {
return fn;
} else if (Array.isArray(old)) {
return old.indexOf(fn) > -1 ? old : old.concat(fn);
} else {
return old === fn ? old : [old, fn];
}
},
show () {
if (this.timer) clearTimeout(this.timer);
this.active = true;
},
hide () {
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.active = false;
}, 200);
}
},
watch: {
active (val) {
this.$emit('update:open', val);
},
open (val) {
this.active = val;
}
},
render (h) {
const content = (this.$slots.content && this.$slots.content.length > 0 ? this.$slots.content : this.content) || '';
if (this.tooltipVM) {
this.tooltipVM.node = h(TooltipContent, {
class: this.tooltipClass,
props: {
placement: this.placement,
open: this.active,
trigger: this.trigger
},
nativeOn: {
mouseenter: () => this.show(),
mouseleave: () => this.hide()
}
}, content);
}
const vnode = getFirstComponentChild(this.$slots.default);
if (!vnode) return vnode;
vnode.data = vnode.data || {};
const on = vnode.data.on = vnode.data.on || {};
const nativeOn = vnode.data.nativeOn = vnode.data.nativeOn || {};
nativeOn.mouseenter = on.mouseenter = this.addEventHandle(on.mouseenter, this.show);
nativeOn.mouseleave = on.mouseleave = this.addEventHandle(on.mouseleave, this.hide);
return vnode;
}
};
================================================
FILE: src/Tooltip/TooltipContent.js
================================================
import popup from '../internal/mixins/popup';
import resize from '../internal/directives/resize';
import scroll from '../internal/directives/scroll';
const SPACE = 8;
export default {
name: 'mu-tooltip-content',
mixins: [popup],
directives: {
resize,
scroll
},
props: {
overlay: {
default: false
},
escPressClose: {
default: false
},
placement: {
type: String,
default: 'bottom',
validator (val) {
return [
'top', 'top-start', 'top-end',
'bottom', 'bottom-start', 'bottom-end',
'left', 'left-start', 'left-end',
'right', 'right-start', 'right-end'
].indexOf(val) !== -1;
}
},
trigger: {}
},
mounted () {
this.setStyle();
},
updated () {
setTimeout(() => this.setStyle(), 0);
},
methods: {
getLeftPosition (width, react) {
switch (this.placement) {
case 'left':
case 'left-start':
case 'left-end':
return react.left - width - SPACE;
case 'right':
case 'right-start':
case 'right-end':
return react.left + react.width + SPACE;
case 'top':
case 'bottom':
return react.left + react.width / 2 - width / 2;
case 'bottom-start':
case 'top-start':
return react.left;
case 'bottom-end':
case 'top-end':
return react.left + react.width - width;
}
},
getTopPosition (height, react) {
switch (this.placement) {
case 'top':
case 'top-start':
case 'top-end':
return react.top - height - SPACE;
case 'bottom':
case 'bottom-start':
case 'bottom-end':
return react.top + react.height + SPACE;
case 'left':
case 'right':
return react.top + react.height / 2 - height / 2;
case 'left-start':
case 'right-start':
return react.top;
case 'left-end':
case 'right-end':
return react.top + react.height - height;
}
},
setStyle () {
if (!this.open) return;
const el = this.$el;
const triggerEl = this.trigger;
if (!el || !triggerEl) return;
const elReact = el.getBoundingClientRect();
const react = triggerEl.getBoundingClientRect();
el.style.top = this.getTopPosition(elReact.height, react) + 'px';
el.style.left = this.getLeftPosition(elReact.width, react) + 'px';
}
},
render (h) {
return h('transition', {
props: {
name: 'mu-tooltip-' + this.placement.split('-')[0]
}
}, [
this.open ? h('div', {
staticClass: 'mu-tooltip',
style: {
'z-index': this.zIndex
},
directives: [{
name: 'resize',
value: this.setStyle
}, {
name: 'scroll',
value: this.setStyle
}]
}, this.$slots.default) : undefined
]);
}
};
================================================
FILE: src/Tooltip/index.js
================================================
import '../styles/components/tooltip.less';
import Tooltip from './Tooltip';
Tooltip.install = function (Vue) {
Vue.component(Tooltip.name, Tooltip);
};
export default Tooltip;
================================================
FILE: src/index.js
================================================
import './styles/base.less';
import Alert from './Alert';
import AppBar from './AppBar';
import AutoComplete from './AutoComplete';
import Avatar from './Avatar';
import Badge from './Badge';
import BottomNav from './BottomNav';
import BottomSheet from './BottomSheet';
import Breadcrumbs from './Breadcrumbs';
import Button from './Button';
import Card from './Card';
import Carousel from './Carousel';
import Checkbox from './Checkbox';
import Chip from './Chip';
import DateInput from './DateInput';
import DataTable from './DataTable';
import Dialog from './Dialog';
import Divider from './Divider';
import Drawer from './Drawer';
import ExpansionPanel from './ExpansionPanel';
import Form from './Form';
import Grid from './Grid';
import GridList from './GridList';
import Helpers from './Helpers';
import Icon from './Icon';
import List from './List';
import LoadMore from './LoadMore';
import Menu from './Menu';
import Pagination from './Pagination';
import Paper from './Paper';
import Picker from './Picker';
import Popover from './Popover';
import Progress from './Progress';
import Radio from './Radio';
import Select from './Select';
import SlidePicker from './SlidePicker';
import Slider from './Slider';
import Snackbar from './Snackbar';
import Stepper from './Stepper';
import SubHeader from './SubHeader';
import Switch from './Switch';
import Tabs from './Tabs';
import TextField from './TextField';
import Tooltip from './Tooltip';
import './styles/theme.less';
import theme from './theme';
import * as Colors from './theme/colors';
const version = '__VERSION__';
const components = {
Alert, AppBar, AutoComplete, Avatar,
Badge, BottomNav, BottomSheet, Breadcrumbs, Button,
Card, Carousel, Checkbox, Chip,
DataTable, DateInput, Dialog, Divider, Drawer,
ExpansionPanel, Form, Grid, GridList, Helpers, Icon,
LoadMore, List, Menu,
Pagination, Paper, Picker, Popover, Progress, Radio,
Select, SlidePicker, Slider, Snackbar, Stepper, SubHeader, Switch,
Tabs, TextField, Tooltip
};
function install (Vue) {
Object.keys(components).forEach((key) => {
Vue.use(components[key]);
});
}
if (typeof window !== 'undefined' && window.Vue) install(window.Vue);
export {
version,
Colors,
Alert,
AppBar,
AutoComplete,
Avatar,
Badge,
BottomNav,
BottomSheet,
Breadcrumbs,
Button,
Card,
Carousel,
Checkbox,
Chip,
DateInput,
DataTable,
Dialog,
Divider,
Drawer,
ExpansionPanel,
Form,
Grid,
GridList,
Helpers,
Icon,
List,
LoadMore,
Menu,
Pagination,
Paper,
Picker,
Popover,
Progress,
Radio,
Select,
SlidePicker,
Slider,
Snackbar,
Stepper,
SubHeader,
Switch,
Tabs,
TextField,
Tooltip,
theme,
install
};
export default {
version,
install,
theme,
Colors,
...components
};
================================================
FILE: src/internal/AbstractButton.js
================================================
import TouchRipple from './TouchRipple';
import FocusRipple from './FocusRipple';
import route from './mixins/route';
import ripple from './mixins/ripple';
import keycode from 'keycode';
import { isPc } from '../utils';
let tabPressed = false;
let listening = false;
function listenForTabPresses () {
if (!listening) {
typeof window !== 'undefined' && window.addEventListener('keydown', (event) => {
tabPressed = keycode(event) === 'tab';
});
listening = true;
}
}
export default {
mixins: [route, ripple],
props: {
disabled: Boolean,
centerRipple: Boolean,
containerElement: String,
disableKeyboardFocus: Boolean,
wrapperClass: String,
wrapperStyle: [String, Object],
type: {
type: String,
default: 'button'
},
keyboardFocused: Boolean
},
data () {
return {
hover: false,
isKeyboardFocused: false
};
},
computed: {
buttonClass () {
const classNames = [];
if (this.disabled) classNames.push('disabled');
if (!this.disabled && (this.hover || this.isKeyboardFocused)) classNames.push('hover');
return classNames.join(' ');
}
},
beforeMount () {
const { disabled, disableKeyboardFocus, keyboardFocused } = this;
if (!disabled && keyboardFocused && !disableKeyboardFocus) {
this.isKeyboardFocused = true;
}
},
mounted () {
listenForTabPresses();
if (this.isKeyboardFocused) {
this.$el.focus();
this.$emit('keyboardFocus', true);
}
},
beforeUpdate () {
if ((this.disabled || this.disableKeyboardFocus) && this.isKeyboardFocused) {
this.isKeyboardFocused = false;
this.$emit('keyboardFocus', false);
}
},
beforeDestory () {
this.cancelFocusTimeout();
},
methods: {
handleHover (event) {
if (!this.disabled && isPc()) {
this.hover = true;
this.$emit('hover', event);
this.$emit('mouseenter', event);
}
},
handleOut (event) {
if (!this.disabled && isPc()) {
this.hover = false;
this.$emit('hoverExit', event);
this.$emit(event.type, event);
}
},
removeKeyboardFocus (event) {
if (this.isKeyboardFocused) {
this.isKeyboardFocused = false;
this.$emit('KeyboardFocus', false);
}
},
setKeyboardFocus (event) {
if (!this.isKeyboardFocused) {
this.isKeyboardFocused = true;
this.$emit('KeyboardFocus', true);
}
},
cancelFocusTimeout () {
if (this.focusTimeout) {
clearTimeout(this.focusTimeout);
this.focusTimeout = null;
}
},
handleKeydown (event) {
if (!this.disabled && !this.disableKeyboardFocus) {
if (keycode(event) === 'enter' && this.isKeyboardFocused) {
this.$el.click();
event.preventDefault();
}
if (keycode(event) === 'esc' && this.isKeyboardFocused) {
this.removeKeyboardFocus(event);
}
}
this.$emit('keydown', event);
},
handleFocus (event) {
if (!this.disabled && !this.disableKeyboardFocus) {
this.focusTimeout = setTimeout(() => {
if (tabPressed) {
this.setKeyboardFocus(event);
tabPressed = false;
}
}, 150);
this.$emit('focus', event);
}
},
handleBlur (event) {
this.cancelFocusTimeout();
this.removeKeyboardFocus(event);
this.$emit('blur', event);
},
handleClick (event) {
if (!this.disabled) {
tabPressed = false;
// this.$el.blur(); // 点击之后失去焦点
this.removeKeyboardFocus(event);
this.$emit('click', event);
}
},
getTagName () {
const defaultTag = 'button';
switch (true) {
case !!this.to:
return 'router-link';
case !!this.href:
return 'a';
case !!this.containerElement:
return this.containerElement;
default:
return defaultTag;
}
},
createButtonChildren (h) {
const {
isKeyboardFocused,
disabled,
ripple,
disableKeyboardFocus,
rippleColor,
rippleOpacity
} = this;
let children = [];
children = children.concat(this.$slots.default);
const FocusRippleEL = isKeyboardFocused && !disableKeyboardFocus && !disabled && ripple
? h(FocusRipple, {
color: rippleColor,
opacity: rippleOpacity
}) : undefined;
if (!disabled && ripple) {
children = [h(TouchRipple, {
class: this.wrapperClass,
style: this.wrapperStyle,
ref: 'ripple',
props: {
autoBind: false,
color: this.rippleColor,
centerRipple: this.centerRipple,
opacity: this.rippleOpacity
}
}, this.$slots.default)];
} else {
children = [h('div', {
class: this.wrapperClass,
style: this.wrapperStyle
}, this.$slots.default)];
}
children.unshift(FocusRippleEL);
return children;
}
},
watch: {
disabled (val) {
if (!val) this.hover = false;
}
},
render (h) {
const tagName = this.getTagName();
const attrs = {
target: this.target,
tabindex: !this.disabled ? (this.$attrs.tabindex || 0) : -1
};
if (tagName === 'button') {
attrs.disabled = this.disabled;
attrs.type = this.type;
}
if (this.href && !this.disabled) {
attrs.href = this.href;
}
const props = this.to ? {
to: this.to,
tag: this.tag,
activeClass: this.activeClass,
event: this.event,
exact: this.exact,
append: this.append,
replace: this.replace,
exactActiveClass: this.exactActiveClass
} : {};
return h(tagName, {
class: this.buttonClass,
attrs,
props,
style: tagName === 'button' ? {
'user-select': this.disabled ? '' : 'none',
'-webkit-user-select': this.disabled ? '' : 'none',
'outline': 'none',
'appearance': 'none'
} : {},
[tagName === 'router-link' ? 'nativeOn' : 'on']: {
...this.$listeners,
mouseup: (e) => {
this.$refs.ripple && this.$refs.ripple.end(e);
this.$emit('mouseup', e);
},
mousedown: (e) => {
this.$refs.ripple && this.$refs.ripple.handleMouseDown(e);
this.$emit('mousedown', e);
},
mouseenter: this.handleHover,
mouseleave: (e) => {
this.$refs.ripple && this.$refs.ripple.end(e);
this.handleOut(e);
},
touchstart: (e) => {
this.$refs.ripple && this.$refs.ripple.handleTouchStart(e);
this.$emit('touchstart', e);
},
touchend: (e) => {
this.$refs.ripple && this.$refs.ripple.end(e);
this.handleOut(e);
},
touchcancel: (e) => {
this.$refs.ripple && this.$refs.ripple.end(e);
this.handleOut(e);
},
click: this.handleClick,
focus: this.handleFocus,
blur: this.handleBlur,
keydown: this.handleKeydown
}
}, this.createButtonChildren(h));
}
};
================================================
FILE: src/internal/CircleRipple.js
================================================
import '../styles/components/circle-ripple.less';
export default {
props: {
mergeStyle: {
type: Object,
default () {
return {};
}
},
color: {
type: String,
default: ''
},
opacity: {
type: Number
}
},
computed: {
styles () {
return {
color: this.color,
opacity: this.opacity,
...this.mergeStyle
};
}
},
render (h) {
return h('transition', {
props: {
name: 'mu-ripple',
appear: true
}
}, [
h('div', {
class: 'mu-circle-ripple',
style: this.styles
})
]);
}
};
================================================
FILE: src/internal/ExpandTransition.js
================================================
import '../styles/components/expand-transition.less';
function getSize (size) {
if (!size) return 0;
const index = size.indexOf('px');
if (index === -1) return 0;
return Number(size.substring(0, index));
}
export default {
name: 'mu-expand-transition',
methods: {
beforeEnter (el) {
el.dataset.oldPaddingTop = el.style.paddingTop;
el.dataset.oldPaddingBottom = el.style.paddingBottom;
el.dataset.oldOverflow = el.style.overflow;
el.style.paddingTop = '0';
el.style.paddingBottom = '0';
el.style.height = '0';
},
enter (el) {
el.style.display = 'block';
el.style.overflow = 'hidden';
el.style.height = el.scrollHeight + getSize(el.dataset.oldPaddingTop) + getSize(el.dataset.oldPaddingBottom) + 'px';
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
},
afterEnter (el) {
el.style.display = '';
// uc浏览器上设置height会闪屏
el.style.height = '';
el.style.overflow = el.dataset.oldOverflow;
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
},
beforeLeave (el) {
el.dataset.oldPaddingTop = el.style.paddingTop;
el.dataset.oldPaddingBottom = el.style.paddingBottom;
el.dataset.oldOverflow = el.style.overflow;
el.style.display = 'block';
if (el.scrollHeight !== 0) {
el.style.height = el.scrollHeight + 'px';
}
el.style.overflow = 'hidden';
},
leave (el) {
if (el.scrollHeight !== 0) {
setTimeout(() => {
el.style.height = 0;
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
});
}
},
afterLeave (el) {
el.style.display = 'none';
el.style.height = '';
el.style.overflow = el.dataset.oldOverflow;
el.style.paddingTop = el.dataset.oldPaddingTop;
el.style.paddingBottom = el.dataset.oldPaddingBottom;
}
},
render (h) {
return h('transition', {
props: {
name: 'mu-expand'
},
on: {
'before-enter': this.beforeEnter,
enter: this.enter,
'after-enter': this.afterEnter,
'before-leave': this.beforeLeave,
leave: this.leave,
'after-leave': this.afterLeave
}
}, this.$slots.default);
}
};
================================================
FILE: src/internal/FocusRipple.js
================================================
import '../styles/components/focus-ripple.less';
export default {
props: {
color: {
type: String,
default: ''
},
opacity: {
type: Number
}
},
computed: {
style () {
return {
color: this.color,
opacity: this.opacity
};
}
},
methods: {
setRippleSize () {
const el = this.$refs.innerCircle;
const height = el.offsetHeight;
const width = el.offsetWidth;
const size = Math.max(height, width);
let oldTop = 0;
if (el.style.top.indexOf('px', el.style.top.length - 2) !== -1) {
oldTop = parseInt(el.style.top);
}
el.style.height = `${size}px`;
el.style.top = `${(height / 2) - (size / 2) + oldTop}px`;
}
},
mounted () {
this.setRippleSize();
},
updated () {
this.setRippleSize();
},
render (h) {
return h('div', {
class: 'mu-focus-ripple-wrapper'
}, [h('div', {
ref: 'innerCircle',
style: this.style,
class: 'mu-focus-ripple'
})]);
}
};
================================================
FILE: src/internal/TouchRipple.js
================================================
import '../styles/components/touch-ripple.less';
import CircleRipple from './CircleRipple';
import { getOffset } from '../utils/dom';
export default {
props: {
centerRipple: {
type: Boolean,
default: false
},
rippleWrapperClass: {},
tag: {
type: String,
default: 'div'
},
autoBind: {
type: Boolean,
default: true
},
color: {
type: String,
default: ''
},
opacity: Number
},
data () {
return {
nextKey: 0,
ripples: []
};
},
methods: {
start (event, isRippleTouchGenerated) {
if (this.ignoreNextMouseDown && !isRippleTouchGenerated) {
this.ignoreNextMouseDown = false;
return;
}
this.ripples.push({
key: this.nextKey++,
color: this.color,
opacity: this.opacity,
style: this.centerRipple ? {} : this.getRippleStyle(event)
});
this.ignoreNextMouseDown = isRippleTouchGenerated;
},
end () {
if (this.ripples.length === 0) return;
this.ripples.splice(0, 1);
this.stopListeningForScrollAbort();
},
stopListeningForScrollAbort () {
if (!this.handleMove) this.handleMove = this.handleTouchMove.bind(this);
document.body.removeEventListener('touchmove', this.handleMove, false);
},
startListeningForScrollAbort (event) {
this.firstTouchY = event.touches[0].clientY;
this.firstTouchX = event.touches[0].clientX;
document.body.addEventListener('touchmove', this.handleMove, false);
},
handleMouseDown (event) {
if (event.button === 0) {
this.start(event, false);
}
},
handleTouchStart (event) {
if (event.touches) {
this.startListeningForScrollAbort(event);
this.startTime = Date.now();
}
this.start(event.touches[0], true);
},
handleTouchMove (event) {
const deltaY = Math.abs(event.touches[0].clientY - this.firstTouchY);
const deltaX = Math.abs(event.touches[0].clientX - this.firstTouchX);
// 判断滚动 6px
if (deltaY > 6 || deltaX > 6) this.end();
// const timeSinceStart = Math.abs(Date.now() - this.startTime)
// if (timeSinceStart > 300) {
// this.stopListeningForScrollAbort()
// return
// }
},
getRippleStyle (event) {
const el = this.$refs.holder;
if (!el) return;
const offset = getOffset(el);
const elHeight = el.offsetHeight;
const elWidth = el.offsetWidth;
const isTouchEvent = event.touches && event.touches.length;
const pageX = isTouchEvent ? event.touches[0].pageX : event.pageX;
const pageY = isTouchEvent ? event.touches[0].pageY : event.pageY;
const pointerX = pageX - offset.left;
const pointerY = pageY - offset.top;
const topLeftDiag = this.calcDiag(pointerX, pointerY);
const topRightDiag = this.calcDiag(elWidth - pointerX, pointerY);
const botRightDiag = this.calcDiag(elWidth - pointerX, elHeight - pointerY);
const botLeftDiag = this.calcDiag(pointerX, elHeight - pointerY);
const rippleRadius = Math.max(
topLeftDiag, topRightDiag, botRightDiag, botLeftDiag
);
const rippleSize = rippleRadius * 2;
const left = pointerX - rippleRadius;
const top = pointerY - rippleRadius;
return {
directionInvariant: true,
height: rippleSize + 'px',
width: rippleSize + 'px',
top: top + 'px',
left: left + 'px'
};
},
calcDiag (a, b) {
return Math.sqrt((a * a) + (b * b));
},
createCircleRipple (h) {
return this.ripples.map((ripple) => {
return h(CircleRipple, {
props: {
color: ripple.color,
opacity: ripple.opacity,
mergeStyle: ripple.style
},
key: ripple.key
});
});
}
},
render (h) {
const listeners = this.autoBind ? {
...this.$listeners,
mousedown: this.handleMouseDown,
mouseup: this.end,
mouseleave: this.end,
touchstart: this.handleTouchStart,
touchend: this.end,
touchcancel: this.end
} : {
...this.$listeners
};
return h(this.tag, {
on: listeners
}, [h('div', {
class: this.rippleWrapperClass,
attrs: {
class: 'mu-ripple-wrapper'
},
ref: 'holder'
}, this.createCircleRipple(h)), this.$slots.default]);
}
};
================================================
FILE: src/internal/directives/click-outside.js
================================================
const clickoutsideContext = '@@clickoutsideContext';
export default {
name: 'click-outside',
bind (el, binding, vnode) {
const documentHandler = function (e) {
if (!vnode.context || el.contains(e.target)) return;
if (binding.expression) {
vnode.context[el[clickoutsideContext].methodName](e);
} else {
el[clickoutsideContext].bindingFn(e);
}
};
el[clickoutsideContext] = {
documentHandler,
methodName: binding.expression,
bindingFn: binding.value
};
setTimeout(() => {
document.addEventListener('click', documentHandler);
}, 0);
},
update (el, binding) {
el[clickoutsideContext].methodName = binding.expression;
el[clickoutsideContext].bindingFn = binding.value;
},
unbind (el) {
document.removeEventListener('click', el[clickoutsideContext].documentHandler);
}
};
================================================
FILE: src/internal/directives/elevation.js
================================================
import '../../styles/components/elevation.less';
import { hasClass, addClass, removeClass } from '../../utils/dom';
function getElevationClass (depth) {
return 'mu-elevation-' + depth;
}
export default {
name: 'elevation',
inserted (el, { value }, vnode) {
addClass(el, getElevationClass(value));
},
update (el, { value, oldValue }) {
if (value === oldValue && hasClass(el, getElevationClass(oldValue))) return;
removeClass(el, getElevationClass(oldValue));
addClass(el, getElevationClass(value));
},
unbind (el, { value }) {
removeClass(el, getElevationClass(value));
}
};
================================================
FILE: src/internal/directives/keyboard-focus.js
================================================
import keycode from 'keycode';
let tabPressed = false;
let listening = false;
function listenForTabPresses () {
if (!listening) {
typeof window !== 'undefined' && window.addEventListener('keydown', (event) => {
tabPressed = keycode(event) === 'tab';
});
listening = true;
}
}
const keyboardcontext = '@@keyboardcontext';
export default {
name: 'keyboard-focus',
bind (el, binding, vnode) {
listenForTabPresses();
let timer;
const handleFocus = (e) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
if (tabPressed) {
if (binding.expression) {
vnode.context[el[keyboardcontext].methodName](e);
} else {
el[keyboardcontext].bindingFn(e);
}
tabPressed = false;
}
}, 150);
};
el[keyboardcontext] = {
handleFocus,
methodName: binding.expression,
bindingFn: binding.value
};
el.addEventListener('focus', handleFocus);
el.addEventListener('blur', () => {
if (timer) clearTimeout(timer);
});
},
update (el, binding) {
el[keyboardcontext].methodName = binding.expression;
el[keyboardcontext].bindingFn = binding.value;
},
unbind (el) {
el.removeEventListener('focus', el[keyboardcontext].handleFocus);
}
};
================================================
FILE: src/internal/directives/mousewheel.js
================================================
import normalizeWheel from 'normalize-wheel';
const isFirefox =
typeof navigator !== 'undefined' &&
navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
const mousewheel = function (element, callback) {
if (element && element.addEventListener) {
element.addEventListener(
isFirefox ? 'DOMMouseScroll' : 'mousewheel',
function (event) {
const normalized = normalizeWheel(event);
callback && callback.apply(this, [event, normalized]);
}
);
}
};
export default {
bind (el, binding) {
mousewheel(el, binding.value);
}
};
================================================
FILE: src/internal/directives/resize.js
================================================
export default {
name: 'resize',
inserted (el, binding) {
let cb = binding.value;
let debounce = 200;
let callOnLoad = true;
if (typeof binding.value !== 'function') {
cb = binding.value.value;
debounce = binding.value.debounce || debounce;
callOnLoad = binding.value.quiet !== null ? false : callOnLoad;
}
let debounceTimeout = null;
const onResize = () => {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(cb, debounce);
};
window.addEventListener('resize', onResize, { passive: true });
el._onResize = onResize;
callOnLoad && onResize();
},
unbind (el, binding) {
window.removeEventListener('resize', el._onResize);
}
};
================================================
FILE: src/internal/directives/scroll.js
================================================
import { getScrollEventTarget } from '../../utils/dom';
function bindScroll (el, binding) {
const callback = typeof binding.value === 'function'
? binding.value
: binding.value.callback;
const options = binding.value.options || { passive: true };
let target = binding.value.target || window;
if (target === 'undefined') return;
if (target instanceof Element) {
target = getScrollEventTarget(target);
} else if (target !== window) {
target = document.querySelector(target);
}
const handleScroll = function (e) {
callback(target, e);
};
if (el._onScroll && target !== el._onScroll.target) unbind(el, binding);
target.addEventListener('scroll', handleScroll, options);
el._onScroll = {
callback: handleScroll,
options,
target
};
}
function unbind (el, binding) {
const { callback, options, target } = el._onScroll;
if (!target) return;
target.removeEventListener('scroll', callback, options);
}
export default {
name: 'scroll',
inserted: bindScroll,
update: bindScroll,
unbind: unbind
};
================================================
FILE: src/internal/directives/swipe.js
================================================
import Drag from '../../utils/drag';
function inserted (el, { value, modifiers }) {
const drag = new Drag(el, modifiers.touch);
el._drag = drag;
drag.start((pos, e) => {
value.start && value.start(pos, drag, e);
});
drag.drag((pos, e) => {
value.move && value.move(pos, drag, e);
});
drag.end((pos, e) => {
value.end && value.end(pos, drag, e);
const dirRatio = 0.5;
const minDistance = 16;
if (Math.abs(pos.y) < dirRatio * Math.abs(pos.x)) {
value.left && pos.x < -minDistance && value.left(pos, drag, e);
value.right && pos.x > minDistance && value.right(pos, e);
}
if (Math.abs(pos.x) < dirRatio * Math.abs(pos.y)) {
value.up && pos.y < -minDistance && value.up(pos, drag, e);
value.down && pos.y > minDistance && value.down(pos, drag, e);
}
});
}
function unbind (el) {
if (el._drag) el._drag.destory();
el._drag = null;
}
export default {
name: 'swipe',
inserted,
unbind
};
================================================
FILE: src/internal/mixins/button.js
================================================
export default {
props: {
disabled: Boolean,
type: {
type: String,
default: 'button'
},
keyboardFocused: Boolean
},
data () {
return {
focus: this.focus
};
},
methods: {
handleClick (e) {
this.$emit('click', e);
},
handleKeyboardFocus (isFocus) {
this.focus = isFocus;
this.$emit('keyboard-focus', isFocus);
},
handleHover (e) {
this.$emit('hover', e);
},
handleHoverExit (e) {
this.$emit('hover-exit', e);
},
getListener () {
return {
...this.$listeners,
click: this.handleClick,
keyboardFocus: this.handleKeyboardFocus,
hover: this.handleHover,
hoverExit: this.handleHoverExit
};
}
}
};
================================================
FILE: src/internal/mixins/color.js
================================================
import { convertClass, getColor } from '../../utils';
export default {
props: {
color: String
},
methods: {
getColorClass (addInverse = true) {
return this.getNormalColorClass(this.color, false, addInverse);
},
getTextColorClass () {
return this.getNormalColorClass(this.textColor, true, true);
},
getColor (color, disabled) {
if (disabled || this.disabled) return;
return getColor(color);
},
getNormalColorClass (color, text = false, addInverse = true) {
const classObj = {};
const themes = ['primary', 'secondary', 'success', 'warning', 'info', 'error'];
themes.forEach((theme) => {
classObj[`mu-${theme}${text ? '-text' : ''}-color`] = color === theme;
});
if (!text && addInverse) classObj['mu-inverse'] = !!color;
return convertClass(classObj).join(' ');
}
}
};
================================================
FILE: src/internal/mixins/input.js
================================================
import '../../styles/components/input.less';
import Icon from '../../Icon';
import color from './color';
export default {
inheritAttrs: false,
mixins: [color],
model: {
prop: 'value',
event: 'input'
},
inject: {
muFormItem: {
default: ''
}
},
props: {
icon: String,
label: String,
labelFloat: Boolean,
actionIcon: String,
actionClick: Function,
suffix: String,
prefix: String,
errorText: String,
helpText: String,
fullWidth: Boolean,
disabled: Boolean,
solo: Boolean,
underlineColor: String,
value: {}
},
data () {
return {
isFocused: false
};
},
computed: {
error () {
return !!this.errorText || (this.muFormItem && this.muFormItem.errorMessage);
},
inputClass () {
return {
'mu-input__focus': this.isFocused,
'has-label': this.label,
'no-empty-state': this.value,
'has-icon': this.icon,
'mu-input__error': this.error,
'multi-line': this.multiLine,
'disabled': this.disabled,
'full-width': this.fullWidth,
'is-solo': this.solo
};
},
float () {
return this.labelFloat && !this.isFocused && !this.value && this.value !== 0;
}
},
methods: {
createIcon (h) {
if (!this.icon) return;
return h(Icon, {
staticClass: 'mu-input-icon',
props: {
value: this.icon
}
});
},
createLabel (h) {
return !this.solo && this.label ? h('div', {
staticClass: 'mu-input-label',
class: {
float: this.float
}
}, this.label) : undefined;
},
createUnderline (h) {
if (this.solo) return;
return h('div', [
h('div', {
staticClass: 'mu-input-line',
class: {
disabled: this.disabled
}
}),
this.disabled ? undefined : h('div', {
staticClass: [
'mu-input-focus-line',
this.getNormalColorClass(this.underlineColor, false, false)
].join(' '),
class: {
focus: this.isFocused
},
style: {
'background-color': this.getColor(this.underlineColor)
}
})
]);
},
createHelpText (h) {
if (!this.errorText && !this.helpText && !this.maxLength) return;
return h('div', {
staticClass: 'mu-input-help'
}, [
h('div', {}, (this.errorText ? this.errorText : this.helpText) || ''),
this.maxLength ? h('div', {}, `${this.value ? String(this.value).length : 0} / ${this.maxLength}`) : undefined
]);
},
createActionIcon (h) {
return this.actionIcon ? h(Icon, {
staticClass: 'mu-input-action-icon',
props: {
value: this.actionIcon
},
on: {
click: () => !this.disabled && this.actionClick && this.actionClick()
}
}) : undefined;
},
createInput (h, data, children, defaultAction) {
data.staticClass = `${data.staticClass || ''} mu-input-content`;
const isFocus = !this.disabled && !this.errorText && this.isFocused;
const colorClass = isFocus ? this.getNormalColorClass(this.color, true) : '';
const color = isFocus ? this.getColor(this.color) : '';
return h('div', {
staticClass: `mu-input ${colorClass}`,
class: this.inputClass,
style: {
color
}
}, [
this.createIcon(h),
this.createLabel(h),
h('div', data, [
this.$slots.prepend,
this.prefix && !this.float ? h('span', { staticClass: 'mu-input-prefix-text' }, this.prefix) : undefined,
...children,
this.suffix && !this.float ? h('span', { staticClass: 'mu-input-suffix-text' }, this.suffix) : undefined,
defaultAction || this.createActionIcon(h),
this.$slots.append,
this.createUnderline(h),
this.createHelpText(h)
])
]);
}
},
watch: {
isFocused (val) {
if (!this.muFormItem) return;
val ? this.muFormItem.onFocus() : this.muFormItem.onBlur();
}
}
};
================================================
FILE: src/internal/mixins/popup/Overlay.js
================================================
import '../../../styles/components/overlay.less';
import { FadeTransition } from '../../transitions';
export default {
name: 'mu-overlay',
props: {
show: Boolean,
fixed: Boolean,
onClick: Function,
opacity: {
type: Number,
default: 0.4
},
color: String,
zIndex: Number
},
computed: {
overlayStyle () {
return {
'opacity': this.opacity,
'background-color': this.color,
'position': this.fixed ? 'fixed' : '',
'z-index': this.zIndex
};
}
},
methods: {
prevent (event) {
event.preventDefault();
event.stopPropagation();
},
handleClick () {
if (this.onClick) {
this.onClick();
}
}
},
render (h) {
return h(FadeTransition, [
h('div', {
staticClass: 'mu-overlay',
style: this.overlayStyle,
directives: [{
name: 'show',
value: this.show
}],
on: {
click: this.handleClick,
touchmove: this.prevent
}
})
]);
}
};
================================================
FILE: src/internal/mixins/popup/index.js
================================================
import PopupManager from './manager';
import { getZIndex } from './utils';
export default {
props: {
open: Boolean,
overlay: {
type: Boolean,
default: true
},
overlayClose: {
type: Boolean,
default: true
},
overlayOpacity: {
type: Number,
default: 0.4
},
overlayColor: {
type: String,
default: '#000'
},
escPressClose: { // 按退出键是否触发关闭事件
type: Boolean,
default: true
},
lockScroll: { // 是否锁定全局滚动
type: Boolean,
default: false
},
appendBody: { // 是否添加到 body 元素后, 内部使用
type: Boolean,
default: true
}
},
data () {
return {
overlayZIndex: getZIndex(),
zIndex: getZIndex()
};
},
methods: {
overlayClick (e) {
if (!this.overlay || !this.overlayClose || !this.open) return;
this.$emit('update:open', false);
this.$emit('close', 'overlay');
},
escPress (e) {
if (!this.escPressClose || !this.open) return;
this.$emit('update:open', false);
this.$emit('close', 'esc');
},
resetZIndex () {
this.overlayZIndex = getZIndex();
this.zIndex = getZIndex();
},
popupEl () {
return this.$el;
},
appendPopupElToBody () {
if (!this.appendBody || this.appened) return;
this.$nextTick(() => {
document.body.appendChild(this.$el);
this.appened = true;
});
}
},
mounted () {
if (this.open) {
PopupManager.open(this);
this.appendPopupElToBody();
}
},
beforeDestroy () {
PopupManager.close(this);
if (this.appendBody) {
if (!this.$el) return;
if (this.$el.parentNode) this.$el.parentNode.removeChild(this.$el);
}
},
watch: {
open (val, oldVal) {
if (val) {
this.resetZIndex();
PopupManager.open(this);
this.appendPopupElToBody();
} else {
PopupManager.close(this);
}
}
}
};
================================================
FILE: src/internal/mixins/popup/manager.js
================================================
import Vue from 'vue';
import keycode from 'keycode';
import { disableBodyScroll, enableBodyScroll, clearAllBodyScrollLocks } from 'body-scroll-lock';
import overlayOpt from './Overlay';
const Overlay = Vue.extend(overlayOpt);
const PopupManager = {
instances: [],
overlay: false,
open (instance) {
if (!instance || this.instances.indexOf(instance) !== -1) return;
if (!this.overlay && instance.overlay) {
this.showOverlay(instance);
}
this.instances.push(instance);
this.changeOverlayStyle();
},
close (instance) {
const index = this.instances.indexOf(instance);
if (index === -1) return;
this.instances.splice(index, 1);
this.changeOverlayStyle();
},
showOverlay (instance) {
const overlay = this.overlay = new Overlay({
el: document.createElement('div')
});
overlay.fixed = true;
overlay.color = instance.overlayColor;
overlay.opacity = instance.overlayOpacity;
overlay.zIndex = instance.overlayZIndex;
overlay.onClick = this.handleOverlayClick.bind(this);
document.body.appendChild(overlay.$el);
if (instance.lockScroll) this.preventScrolling();
Vue.nextTick(() => {
overlay.show = true;
});
},
// 禁止滚动
preventScrolling () {
if (this.locked) return;
// body 操作
const body = document.getElementsByTagName('body')[0];
disableBodyScroll(body, {
reserveScrollBarGap: true,
allowTouchMove: (el) => {
for (let i = 0; i < this.instances.length; i++) {
if (
this.instances[i] &&
this.instances[i].$el &&
this.instances[i].$el.contains(el)
) {
return true;
}
}
return false;
}
});
this.locked = true;
},
// 还原滚动设置
allowScrolling () {
const body = document.getElementsByTagName('body')[0];
enableBodyScroll(body, {
reserveScrollBarGap: true
});
clearAllBodyScrollLocks();
this.locked = false;
},
closeOverlay () {
if (!this.overlay) return;
this.allowScrolling();
const overlay = this.overlay;
overlay.show = false;
this.overlay = null;
setTimeout(() => {
document.body.removeChild(overlay.$el);
overlay.$destroy();
}, 450);
},
changeOverlayStyle () {
if (!this.overlay) return;
let instance;
for (let i = 1; i <= this.instances.length; i++) {
instance = this.instances[this.instances.length - i];
if (instance && instance.overlay) {
break;
}
instance = null;
}
if (!instance) return this.closeOverlay();
if (instance && instance.overlay) {
this.overlay.color = instance.overlayColor;
this.overlay.opacity = instance.overlayOpacity;
this.overlay.zIndex = instance.overlayZIndex;
}
},
handleOverlayClick () {
if (this.instances.length === 0) return;
const instance = this.instances[this.instances.length - 1];
if (instance.overlayClick) {
instance.overlayClick();
}
}
};
typeof window !== 'undefined' && window.addEventListener('keydown', (e) => {
if (PopupManager.instances.length === 0 || keycode(e) !== 'esc') return;
const instance = PopupManager.instances[PopupManager.instances.length - 1];
if (instance.escPress) {
instance.escPress();
}
});
export default PopupManager;
================================================
FILE: src/internal/mixins/popup/utils.js
================================================
let zIndex = 20141223;
export const getZIndex = () => zIndex++;
export const getDOM = function (dom) {
if (dom.nodeType === 3) {
dom = dom.nextElementSibling || dom.nextSibling;
getDOM(dom);
}
return dom;
};
================================================
FILE: src/internal/mixins/ripple.js
================================================
export default {
props: {
ripple: {
type: Boolean,
default: true
},
rippleColor: {
type: String,
default: ''
},
rippleOpacity: {
type: Number
}
}
};
================================================
FILE: src/internal/mixins/route.js
================================================
export default {
props: {
href: String,
target: String,
to: {
type: [String, Object]
},
tag: {
type: String,
default: 'a'
},
activeClass: String,
event: {
type: [String, Array],
default: 'click'
},
exact: Boolean,
exactActiveClass: String,
append: Boolean,
replace: Boolean
},
methods: {
generateRouteProps () {
return {
href: this.href,
target: this.target,
to: this.to,
tag: this.tag,
activeClass: this.activeClass,
event: this.event,
exact: this.exact,
exactActiveClass: this.exactActiveClass,
append: this.append,
replace: this.replace
};
}
}
};
================================================
FILE: src/internal/mixins/select.js
================================================
import TouchRipple from '../TouchRipple';
import keycode from 'keycode';
import color from './color';
import ripple from './ripple';
export default function (type = 'checkbox') { // checkbox
const iconProps = type === 'switch' ? {} : { uncheckIcon: String, checkedIcon: String };
return {
mixins: [color, ripple],
inheritAttrs: false,
inject: {
muFormItem: {
default: ''
}
},
model: {
prop: 'inputValue',
event: 'change'
},
props: {
label: String,
labelLeft: Boolean,
readonly: Boolean,
...iconProps,
disabled: Boolean,
tabIndex: [Number, String]
},
methods: {
start (event) {
if (this.disabled) return;
if (this.ripple && (event.type !== 'mousedown' || event.button === 0)) {
this.$refs.ripple.start(event);
}
this.$emit(event.type, event);
},
end (event) {
if (this.disabled) return;
if (this.ripple) this.$refs.ripple.end();
if (event) this.$emit(event.type, event);
},
handleClick (e) {
if (this.disabled || this.readonly) return;
this.end();
this.toggle();
if (!this) return; // #1136
this.muFormItem && this.muFormItem.onBlur();
this.$emit('click', e);
},
handleKeydown (e) {
if (this.disabled) return;
this.end(e);
if (keycode(e) === 'enter' && !this.readonly) this.handleClick(e);
},
createRipple (h, staticClass, children) {
return this.disabled || !this.ripple ? h('div', {
staticClass
}, children) : h(TouchRipple, {
staticClass,
props: {
rippleWrapperClass: `mu-${type}-ripple-wrapper`,
centerRipple: true,
color: this.rippleColor,
opacity: this.rippleOpacity
},
ref: 'ripple'
}, children);
},
createInputElement (h) {
return h('input', {
attrs: {
...this.$attrs,
type: type === 'switch' ? 'checkbox' : type,
disabled: this.disabled,
checked: this.checked,
readonly: this.readonly,
tabindex: -1
}
});
},
createSelect (h, view) {
const colorClass = this.getNormalColorClass(this.color, true);
const label = this.label ? h('div', { staticClass: `mu-${type}-label` }, this.label) : undefined;
const wrapper = h('div', {
staticClass: `mu-${type}-wrapper`
}, this.labelLeft ? [label, view] : [view, label]);
return h('div', {
staticClass: `mu-${type} ${this.checked ? colorClass : ''}`,
attrs: {
tabindex: this.disabled ? -1 : this.tabIndex ? this.tabIndex : 0
},
style: {
color: this.checked && !this.disabled ? this.getColor(this.color) : ''
},
class: {
'label-left': this.labelLeft,
'disabled': this.disabled,
[`mu-${type}-checked`]: this.checked,
'no-label': !this.label
},
on: {
...this.$listeners,
click: this.handleClick,
keydown: this.handleKeydown,
mousedown: this.start,
mouseleave: this.end,
mouseup: this.end,
touchstart: this.start,
touchend: this.end,
touchcancel: this.end,
focus: this.start,
blur: this.end
}
}, [
this.createInputElement(h),
wrapper
]);
}
}
};
}
================================================
FILE: src/internal/transitions.js
================================================
import '../styles/transitions.less';
export { default as ExpandTransition } from './ExpandTransition';
function createTransition (name, mode) {
return {
name,
functional: true,
render (h, context) {
context.data = context.data || {};
context.data.props = { name };
context.data.on = context.data.on || {};
if (!Object.isExtensible(context.data.on)) {
context.data.on = { ...context.data.on };
}
if (mode) context.data.props.mode = mode;
return h('transition', context.data, context.children);
}
};
}
export const FadeTransition = createTransition('mu-fade-transition');
export const SlideTopTransition = createTransition('mu-slide-top-transition');
export const SlideBottomTransition = createTransition('mu-slide-bottom-transition');
export const SlideLeftTransition = createTransition('mu-slide-left-transition');
export const SlideRightTransition = createTransition('mu-slide-right-transition');
export const PopoverTransiton = createTransition('mu-popover-transition');
export const BottomSheetTransition = createTransition('mu-bottom-sheet-transition');
export const ScaleTransition = createTransition('mu-scale-transition');
================================================
FILE: src/styles/base.less
================================================
@import "./normalize.less";
@import "./vars.less";
*,
*:before,
*:after {
box-sizing: border-box;
}
// html{
// font-size: 62.5%;
// }
body{
font-family: @fontFamily;
line-height: 1.5;
font-size: 14px;
font-weight: 400;
width: 100%;
-webkit-tap-highlight-color:rgba(0, 0, 0, 0);
background-color: @backgroundColor;
color: @textColor;
}
pre{
white-space: pre-wrap;
word-break: break-all;
margin: 0;
}
a{
text-decoration: none;
color: @secondaryColor;
user-select: none;
-webkit-user-select: none;
}
================================================
FILE: src/styles/colors.less
================================================
@red50: #ffebee;
@red100: #ffcdd2;
@red200: #ef9a9a;
@red300: #e57373;
@red400: #ef5350;
@red500: #f44336;
@red600: #e53935;
@red700: #d32f2f;
@red800: #c62828;
@red900: #b71c1c;
@redA100: #ff8a80;
@redA200: #ff5252;
@redA400: #ff1744;
@redA700: #d50000;
@red: @red500;
@pink50: #fce4ec;
@pink100: #f8bbd0;
@pink200: #f48fb1;
@pink300: #f06292;
@pink400: #ec407a;
@pink500: #e91e63;
@pink600: #d81b60;
@pink700: #c2185b;
@pink800: #ad1457;
@pink900: #880e4f;
@pinkA100: #ff80ab;
@pinkA200: #ff4081;
@pinkA400: #f50057;
@pinkA700: #c51162;
@pink: @pink500;
@purple50: #f3e5f5;
@purple100: #e1bee7;
@purple200: #ce93d8;
@purple300: #ba68c8;
@purple400: #ab47bc;
@purple500: #9c27b0;
@purple600: #8e24aa;
@purple700: #7b1fa2;
@purple800: #6a1b9a;
@purple900: #4a148c;
@purpleA100: #ea80fc;
@purpleA200: #e040fb;
@purpleA400: #d500f9;
@purpleA700: #aa00ff;
@purple: @purple500;
@deepPurple50: #ede7f6;
@deepPurple100: #d1c4e9;
@deepPurple200: #b39ddb;
@deepPurple300: #9575cd;
@deepPurple400: #7e57c2;
@deepPurple500: #673ab7;
@deepPurple600: #5e35b1;
@deepPurple700: #512da8;
@deepPurple800: #4527a0;
@deepPurple900: #311b92;
@deepPurpleA100: #b388ff;
@deepPurpleA200: #7c4dff;
@deepPurpleA400: #651fff;
@deepPurpleA700: #6200ea;
@deepPurple: @deepPurple500;
@indigo50: #e8eaf6;
@indigo100: #c5cae9;
@indigo200: #9fa8da;
@indigo300: #7986cb;
@indigo400: #5c6bc0;
@indigo500: #3f51b5;
@indigo600: #3949ab;
@indigo700: #303f9f;
@indigo800: #283593;
@indigo900: #1a237e;
@indigoA100: #8c9eff;
@indigoA200: #536dfe;
@indigoA400: #3d5afe;
@indigoA700: #304ffe;
@indigo: @indigo500;
@blue50: #e3f2fd;
@blue100: #bbdefb;
@blue200: #90caf9;
@blue300: #64b5f6;
@blue400: #42a5f5;
@blue500: #2196f3;
@blue600: #1e88e5;
@blue700: #1976d2;
@blue800: #1565c0;
@blue900: #0d47a1;
@blueA100: #82b1ff;
@blueA200: #448aff;
@blueA400: #2979ff;
@blueA700: #2962ff;
@blue: @blue500;
@lightBlue50: #e1f5fe;
@lightBlue100: #b3e5fc;
@lightBlue200: #81d4fa;
@lightBlue300: #4fc3f7;
@lightBlue400: #29b6f6;
@lightBlue500: #03a9f4;
@lightBlue600: #039be5;
@lightBlue700: #0288d1;
@lightBlue800: #0277bd;
@lightBlue900: #01579b;
@lightBlueA100: #80d8ff;
@lightBlueA200: #40c4ff;
@lightBlueA400: #00b0ff;
@lightBlueA700: #0091ea;
@lightBlue: @lightBlue500;
@cyan50: #e0f7fa;
@cyan100: #b2ebf2;
@cyan200: #80deea;
@cyan300: #4dd0e1;
@cyan400: #26c6da;
@cyan500: #00bcd4;
@cyan600: #00acc1;
@cyan700: #0097a7;
@cyan800: #00838f;
@cyan900: #006064;
@cyanA100: #84ffff;
@cyanA200: #18ffff;
@cyanA400: #00e5ff;
@cyanA700: #00b8d4;
@cyan: @cyan500;
@teal50: #e0f2f1;
@teal100: #b2dfdb;
@teal200: #80cbc4;
@teal300: #4db6ac;
@teal400: #26a69a;
@teal500: #009688;
@teal600: #00897b;
@teal700: #00796b;
@teal800: #00695c;
@teal900: #004d40;
@tealA100: #a7ffeb;
@tealA200: #64ffda;
@tealA400: #1de9b6;
@tealA700: #00bfa5;
@teal: @teal500;
@green50: #e8f5e9;
@green100: #c8e6c9;
@green200: #a5d6a7;
@green300: #81c784;
@green400: #66bb6a;
@green500: #4caf50;
@green600: #43a047;
@green700: #388e3c;
@green800: #2e7d32;
@green900: #1b5e20;
@greenA100: #b9f6ca;
@greenA200: #69f0ae;
@greenA400: #00e676;
@greenA700: #00c853;
@green: @green500;
@lightGreen50: #f1f8e9;
@lightGreen100: #dcedc8;
@lightGreen200: #c5e1a5;
@lightGreen300: #aed581;
@lightGreen400: #9ccc65;
@lightGreen500: #8bc34a;
@lightGreen600: #7cb342;
@lightGreen700: #689f38;
@lightGreen800: #558b2f;
@lightGreen900: #33691e;
@lightGreenA100: #ccff90;
@lightGreenA200: #b2ff59;
@lightGreenA400: #76ff03;
@lightGreenA700: #64dd17;
@lightGreen: @lightGreen500;
@lime50: #f9fbe7;
@lime100: #f0f4c3;
@lime200: #e6ee9c;
@lime300: #dce775;
@lime400: #d4e157;
@lime500: #cddc39;
@lime600: #c0ca33;
@lime700: #afb42b;
@lime800: #9e9d24;
@lime900: #827717;
@limeA100: #f4ff81;
@limeA200: #eeff41;
@limeA400: #c6ff00;
@limeA700: #aeea00;
@lime: @lime500;
@yellow50: #fffde7;
@yellow100: #fff9c4;
@yellow200: #fff59d;
@yellow300: #fff176;
@yellow400: #ffee58;
@yellow500: #ffeb3b;
@yellow600: #fdd835;
@yellow700: #fbc02d;
@yellow800: #f9a825;
@yellow900: #f57f17;
@yellowA100: #ffff8d;
@yellowA200: #ffff00;
@yellowA400: #ffea00;
@yellowA700: #ffd600;
@yellow: @yellow500;
@amber50: #fff8e1;
@amber100: #ffecb3;
@amber200: #ffe082;
@amber300: #ffd54f;
@amber400: #ffca28;
@amber500: #ffc107;
@amber600: #ffb300;
@amber700: #ffa000;
@amber800: #ff8f00;
@amber900: #ff6f00;
@amberA100: #ffe57f;
@amberA200: #ffd740;
@amberA400: #ffc400;
@amberA700: #ffab00;
@amber: @amber500;
@orange50: #fff3e0;
@orange100: #ffe0b2;
@orange200: #ffcc80;
@orange300: #ffb74d;
@orange400: #ffa726;
@orange500: #ff9800;
@orange600: #fb8c00;
@orange700: #f57c00;
@orange800: #ef6c00;
@orange900: #e65100;
@orangeA100: #ffd180;
@orangeA200: #ffab40;
@orangeA400: #ff9100;
@orangeA700: #ff6d00;
@orange: @orange500;
@deepOrange50: #fbe9e7;
@deepOrange100: #ffccbc;
@deepOrange200: #ffab91;
@deepOrange300: #ff8a65;
@deepOrange400: #ff7043;
@deepOrange500: #ff5722;
@deepOrange600: #f4511e;
@deepOrange700: #e64a19;
@deepOrange800: #d84315;
@deepOrange900: #bf360c;
@deepOrangeA100: #ff9e80;
@deepOrangeA200: #ff6e40;
@deepOrangeA400: #ff3d00;
@deepOrangeA700: #dd2c00;
@deepOrange: @deepOrange500;
@brown50: #efebe9;
@brown100: #d7ccc8;
@brown200: #bcaaa4;
@brown300: #a1887f;
@brown400: #8d6e63;
@brown500: #795548;
@brown600: #6d4c41;
@brown700: #5d4037;
@brown800: #4e342e;
@brown900: #3e2723;
@brown: @brown500;
@blueGrey50: #eceff1;
@blueGrey100: #cfd8dc;
@blueGrey200: #b0bec5;
@blueGrey300: #90a4ae;
@blueGrey400: #78909c;
@blueGrey500: #607d8b;
@blueGrey600: #546e7a;
@blueGrey700: #455a64;
@blueGrey800: #37474f;
@blueGrey900: #263238;
@blueGrey: @blueGrey500;
@grey50: #fafafa;
@grey100: #f5f5f5;
@grey200: #eeeeee;
@grey300: #e0e0e0;
@grey400: #bdbdbd;
@grey500: #9e9e9e;
@grey600: #757575;
@grey700: #616161;
@grey800: #424242;
@grey900: #212121;
@grey: @grey500;
@black: #000000;
@white: #ffffff;
@transparent: rgba(0, 0, 0, 0);
@fullBlack: rgba(0, 0, 0, 1);
@darkBlack: rgba(0, 0, 0, 0.87);
@lightBlack: rgba(0, 0, 0, 0.54);
@minBlack: rgba(0, 0, 0, 0.26);
@faintBlack: rgba(0, 0, 0, 0.12);
@fullWhite: rgba(255, 255, 255, 1);
@darkWhite: rgba(255, 255, 255, 0.87);
@lightWhite: rgba(255, 255, 255, 0.54);
================================================
FILE: src/styles/components/alert.less
================================================
@import '../import.less';
.mu-alert {
min-height: 56px;
padding: 24px 16px;
display: flex;
width: 100%;
align-items: center;
justify-content: flex;
font-size: 14px;
line-height: 16px;
color: @alternateTextColor;
border-radius: 4px;
.mu-icon-left {
color: fade(@textColor, 26%);
margin-right: 16px;
.flex-shrink(0);
}
.mu-alert-delete-btn {
margin-left: auto;
transition: all 450ms @easeOutFunction;
cursor: pointer;
width: 24px;
height: 24px;
padding: 0;
color: @alternateTextColor;
.mu-circle-ripple{
opacity: .3;
}
}
}
.mu-alert-delete-icon{
display: inline-block;
fill: currentColor;
height: 14px;
width: 14px;
}
================================================
FILE: src/styles/components/appbar.less
================================================
@import '../import.less';
.mu-appbar {
display: flex;
align-self: flex-start;
justify-content: flex-start;
align-items: center;
color: @textColor;
background-color: @grey100;
height: 56px;
padding: 0 4px;
z-index: 100;
.mu-icon-button {
color: inherit;
}
.mu-flat-button {
color: inherit;
height: 100%;
line-height: 100%;
min-width: auto;
}
.mu-menu {
height: 100%;
line-height: 100%;
min-width: auto;
}
}
.mu-appbar-left,
.mu-appbar-right {
.flex-shrink(0);
display: flex;
justify-content: flex-start;
align-items: center;
height: 100%;
}
.mu-appbar-left {
padding-right: 8px;
}
.mu-appbar-title {
flex: 1;
padding-left: 12px;
padding-right: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
overflow: hidden;
font-size: 20px;
font-weight: 400;
line-height: 56px;
}
@media only screen and (min-width: 600px) {
.mu-appbar-title {
line-height: 64px;
}
.mu-appbar {
height: 64px;
}
.mu-appbar-title {
font-size: 24px;
}
}
================================================
FILE: src/styles/components/avatar.less
================================================
@import "../import.less";
.mu-avatar{
display: inline-block;
height: 40px;
width: 40px;
font-size: 20px;
color: @alternateTextColor;
background-color: @trackColor;
text-align: center;
border-radius: 50%;
img {
border-radius: 50%;
width: 100%;
height: 100%;
display: block;
}
}
.mu-avatar-inner{
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
}
================================================
FILE: src/styles/components/badge.less
================================================
@import "../import.less";
.mu-badge-container{
display: inline-block;
position: relative;
}
.mu-badge{
font-size: 10px;
display: flex;
justify-content: center;
align-items: center;
padding: 0 6px;
line-height: 1.5;
font-size: 12px;
font-style: normal;
background-color: @trackColor;
color: @alternateTextColor;
border-radius: 3px;
overflow: hidden;
}
.mu-badge-float {
position: absolute;
top: -12px;
right: -12px;
}
.mu-badge-circle {
border-radius: 50%;
padding: 0;
width: 24px;
height: 24px;
overflow: hidden;
}
================================================
FILE: src/styles/components/bootstrap-grid.less
================================================
/*https://github.com/twbs/bootstrap/blob/v4-dev/dist/css/bootstrap-grid.css*/
.container {
width: 100%;
padding-right: 8px;
padding-left: 8px;
margin-right: auto;
margin-left: auto;
}
@media (min-width: 576px) {
.container {
max-width: 540px;
}
}
@media (min-width: 768px) {
.container {
max-width: 720px;
}
}
@media (min-width: 992px) {
.container {
max-width: 960px;
}
}
@media (min-width: 1200px) {
.container {
max-width: 1140px;
}
}
.container-fluid {
width: 100%;
padding-right: 8px;
padding-left: 8px;
margin-right: auto;
margin-left: auto;
}
.row {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -8px;
margin-left: -8px;
}
.no-gutters {
margin-right: 0;
margin-left: 0;
}
.no-gutters > .col,
.no-gutters > [class*="col-"] {
padding-right: 0;
padding-left: 0;
}
.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,
.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,
.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,
.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,
.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,
.col-xl-auto {
position: relative;
width: 100%;
min-height: 1px;
padding-right: 8px;
padding-left: 8px;
}
.col {
-ms-flex-preferred-size: 0;
flex-basis: 0;
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 100%;
}
.col-auto {
-webkit-box-flex: 0;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
width: auto;
max-width: none;
}
.col-1 {
-webkit-box-flex: 0;
-ms-flex: 0 0 8.333333%;
flex: 0 0 8.333333%;
max-width: 8.333333%;
}
.col-2 {
-webkit-box-flex: 0;
-ms-flex: 0 0 16.666667%;
flex: 0 0 16.666667%;
max-width: 16.666667%;
}
.col-3 {
-webkit-box-flex: 0;
-ms-flex: 0 0 25%;
flex: 0 0 25%;
max-width: 25%;
}
.col-4 {
-webkit-box-flex: 0;
-ms-flex: 0 0 33.333333%;
flex: 0 0 33.333333%;
max-width: 33.333333%;
}
.col-5 {
-webkit-box-flex: 0;
-ms-flex: 0 0 41.666667%;
flex: 0 0 41.666667%;
max-width: 41.666667%;
}
.col-6 {
-webkit-box-flex: 0;
-ms-flex: 0 0 50%;
flex: 0 0 50%;
max-width: 50%;
}
.col-7 {
-webkit-box-flex: 0;
-ms-flex: 0 0 58.333333%;
flex: 0 0 58.333333%;
max-width: 58.333333%;
}
.col-8 {
-webkit-box-flex: 0;
-ms-flex: 0 0 66.666667%;
flex: 0 0 66.666667%;
max-width: 66.666667%;
}
.col-9 {
-webkit-box-flex: 0;
-ms-flex: 0 0 75%;
flex: 0 0 75%;
max-width: 75%;
}
.col-10 {
-webkit-box-flex: 0;
-ms-flex: 0 0 83.333333%;
flex: 0 0 83.333333%;
max-width: 83.333333%;
}
.col-11 {
-webkit-box-flex: 0;
-ms-flex: 0 0 91.666667%;
flex: 0 0 91.666667%;
max-width: 91.666667%;
}
.col-12 {
-webkit-box-flex: 0;
-ms-flex: 0 0 100%;
flex: 0 0 100%;
max-width: 100%;
}
.order-first {
-webkit-box-ordinal-group: 0;
-ms-flex-order: -1;
order: -1;
}
.order-last {
-webkit-box-ordinal-group: 14;
-ms-flex-order: 13;
order: 13;
}
.order-0 {
-webkit-box-ordinal-group: 1;
-ms-flex-order: 0;
order: 0;
}
.order-1 {
-webkit-box-ordinal-group: 2;
-ms-flex-order: 1;
order: 1;
}
.order-2 {
-webkit-box-ordinal-group: 3;
-ms-flex-order: 2;
order: 2;
}
.order-3 {
-webkit-box-ordinal-group: 4;
-ms-flex-order: 3;
order: 3;
}
.order-4 {
-webkit-box-ordinal-group: 5;
-ms-flex-order: 4;
order: 4;
}
.order-5 {
-webkit-box-ordinal-group: 6;
-ms-flex-order: 5;
order: 5;
}
.order-6 {
-webkit-box-ordinal-group: 7;
-ms-flex-order: 6;
order: 6;
}
.order-7 {
-webkit-box-ordinal-group: 8;
-ms-flex-order: 7;
order: 7;
}
.order-8 {
-webkit-box-ordinal-group: 9;
-ms-flex-order: 8;
order: 8;
}
.order-9 {
-webkit-box-ordinal-group: 10;
-ms-flex-order: 9;
order: 9;
}
.order-10 {
-webkit-box-ordinal-group: 11;
-ms-flex-order: 10;
order: 10;
}
.order-11 {
-webkit-box-ordinal-group: 12;
-ms-flex-order: 11;
order: 11;
}
.order-12 {
-webkit-box-ordinal-group: 13;
-ms-flex-order: 12;
order: 12;
}
.offset-1 {
margin-left: 8.333333%;
}
.offset-2 {
margin-left: 16.666667%;
}
.offset-3 {
margin-left: 25%;
}
.offset-4 {
margin-left: 33.333333%;
}
.offset-5 {
margin-left: 41.666667%;
}
.offset-6 {
margin-left: 50%;
}
.offset-7 {
margin-left: 58.333333%;
}
.offset-8 {
margin-left: 66.666667%;
}
.offset-9 {
margin-left: 75%;
}
.offset-10 {
margin-left: 83.333333%;
}
.offset-11 {
margin-left: 91.666667%;
}
@media (min-width: 576px) {
.col-sm {
-ms-flex-preferred-size: 0;
flex-basis: 0;
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 100%;
}
.col-sm-auto {
-webkit-box-flex: 0;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
width: auto;
max-width: none;
}
.col-sm-1 {
-webkit-box-flex: 0;
-ms-flex: 0 0 8.333333%;
flex: 0 0 8.333333%;
max-width: 8.333333%;
}
.col-sm-2 {
-webkit-box-flex: 0;
-ms-flex: 0 0 16.666667%;
flex: 0 0 16.666667%;
max-width: 16.666667%;
}
.col-sm-3 {
-webkit-box-flex: 0;
-ms-flex: 0 0 25%;
flex: 0 0 25%;
max-width: 25%;
}
.col-sm-4 {
-webkit-box-flex: 0;
-ms-flex: 0 0 33.333333%;
flex: 0 0 33.333333%;
max-width: 33.333333%;
}
.col-sm-5 {
-webkit-box-flex: 0;
-ms-flex: 0 0 41.666667%;
flex: 0 0 41.666667%;
max-width: 41.666667%;
}
.col-sm-6 {
-webkit-box-flex: 0;
-ms-flex: 0 0 50%;
flex: 0 0 50%;
max-width: 50%;
}
.col-sm-7 {
-webkit-box-flex: 0;
-ms-flex: 0 0 58.333333%;
flex: 0 0 58.333333%;
max-width: 58.333333%;
}
.col-sm-8 {
-webkit-box-flex: 0;
-ms-flex: 0 0 66.666667%;
flex: 0 0 66.666667%;
max-width: 66.666667%;
}
.col-sm-9 {
-webkit-box-flex: 0;
-ms-flex: 0 0 75%;
flex: 0 0 75%;
max-width: 75%;
}
.col-sm-10 {
-webkit-box-flex: 0;
-ms-flex: 0 0 83.333333%;
flex: 0 0 83.333333%;
max-width: 83.333333%;
}
.col-sm-11 {
-webkit-box-flex: 0;
-ms-flex: 0 0 91.666667%;
flex: 0 0 91.666667%;
max-width: 91.666667%;
}
.col-sm-12 {
-webkit-box-flex: 0;
-ms-flex: 0 0 100%;
flex: 0 0 100%;
max-width: 100%;
}
.order-sm-first {
-webkit-box-ordinal-group: 0;
-ms-flex-order: -1;
order: -1;
}
.order-sm-last {
-webkit-box-ordinal-group: 14;
-ms-flex-order: 13;
order: 13;
}
.order-sm-0 {
-webkit-box-ordinal-group: 1;
-ms-flex-order: 0;
order: 0;
}
.order-sm-1 {
-webkit-box-ordinal-group: 2;
-ms-flex-order: 1;
order: 1;
}
.order-sm-2 {
-webkit-box-ordinal-group: 3;
-ms-flex-order: 2;
order: 2;
}
.order-sm-3 {
-webkit-box-ordinal-group: 4;
-ms-flex-order: 3;
order: 3;
}
.order-sm-4 {
-webkit-box-ordinal-group: 5;
-ms-flex-order: 4;
order: 4;
}
.order-sm-5 {
-webkit-box-ordinal-group: 6;
-ms-flex-order: 5;
order: 5;
}
.order-sm-6 {
-webkit-box-ordinal-group: 7;
-ms-flex-order: 6;
order: 6;
}
.order-sm-7 {
-webkit-box-ordinal-group: 8;
-ms-flex-order: 7;
order: 7;
}
.order-sm-8 {
-webkit-box-ordinal-group: 9;
-ms-flex-order: 8;
order: 8;
}
.order-sm-9 {
-webkit-box-ordinal-group: 10;
-ms-flex-order: 9;
order: 9;
}
.order-sm-10 {
-webkit-box-ordinal-group: 11;
-ms-flex-order: 10;
order: 10;
}
.order-sm-11 {
-webkit-box-ordinal-group: 12;
-ms-flex-order: 11;
order: 11;
}
.order-sm-12 {
-webkit-box-ordinal-group: 13;
-ms-flex-order: 12;
order: 12;
}
.offset-sm-0 {
margin-left: 0;
}
.offset-sm-1 {
margin-left: 8.333333%;
}
.offset-sm-2 {
margin-left: 16.666667%;
}
.offset-sm-3 {
margin-left: 25%;
}
.offset-sm-4 {
margin-left: 33.333333%;
}
.offset-sm-5 {
margin-left: 41.666667%;
}
.offset-sm-6 {
margin-left: 50%;
}
.offset-sm-7 {
margin-left: 58.333333%;
}
.offset-sm-8 {
margin-left: 66.666667%;
}
.offset-sm-9 {
margin-left: 75%;
}
.offset-sm-10 {
margin-left: 83.333333%;
}
.offset-sm-11 {
margin-left: 91.666667%;
}
}
@media (min-width: 768px) {
.col-md {
-ms-flex-preferred-size: 0;
flex-basis: 0;
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 100%;
}
.col-md-auto {
-webkit-box-flex: 0;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
width: auto;
max-width: none;
}
.col-md-1 {
-webkit-box-flex: 0;
-ms-flex: 0 0 8.333333%;
flex: 0 0 8.333333%;
max-width: 8.333333%;
}
.col-md-2 {
-webkit-box-flex: 0;
-ms-flex: 0 0 16.666667%;
flex: 0 0 16.666667%;
max-width: 16.666667%;
}
.col-md-3 {
-webkit-box-flex: 0;
-ms-flex: 0 0 25%;
flex: 0 0 25%;
max-width: 25%;
}
.col-md-4 {
-webkit-box-flex: 0;
-ms-flex: 0 0 33.333333%;
flex: 0 0 33.333333%;
max-width: 33.333333%;
}
.col-md-5 {
-webkit-box-flex: 0;
-ms-flex: 0 0 41.666667%;
flex: 0 0 41.666667%;
max-width: 41.666667%;
}
.col-md-6 {
-webkit-box-flex: 0;
-ms-flex: 0 0 50%;
flex: 0 0 50%;
max-width: 50%;
}
.col-md-7 {
-webkit-box-flex: 0;
-ms-flex: 0 0 58.333333%;
flex: 0 0 58.333333%;
max-width: 58.333333%;
}
.col-md-8 {
-webkit-box-flex: 0;
-ms-flex: 0 0 66.666667%;
flex: 0 0 66.666667%;
max-width: 66.666667%;
}
.col-md-9 {
-webkit-box-flex: 0;
-ms-flex: 0 0 75%;
flex: 0 0 75%;
max-width: 75%;
}
.col-md-10 {
-webkit-box-flex: 0;
-ms-flex: 0 0 83.333333%;
flex: 0 0 83.333333%;
max-width: 83.333333%;
}
.col-md-11 {
-webkit-box-flex: 0;
-ms-flex: 0 0 91.666667%;
flex: 0 0 91.666667%;
max-width: 91.666667%;
}
.col-md-12 {
-webkit-box-flex: 0;
-ms-flex: 0 0 100%;
flex: 0 0 100%;
max-width: 100%;
}
.order-md-first {
-webkit-box-ordinal-group: 0;
-ms-flex-order: -1;
order: -1;
}
.order-md-last {
-webkit-box-ordinal-group: 14;
-ms-flex-order: 13;
order: 13;
}
.order-md-0 {
-webkit-box-ordinal-group: 1;
-ms-flex-order: 0;
order: 0;
}
.order-md-1 {
-webkit-box-ordinal-group: 2;
-ms-flex-order: 1;
order: 1;
}
.order-md-2 {
-webkit-box-ordinal-group: 3;
-ms-flex-order: 2;
order: 2;
}
.order-md-3 {
-webkit-box-ordinal-group: 4;
-ms-flex-order: 3;
order: 3;
}
.order-md-4 {
-webkit-box-ordinal-group: 5;
-ms-flex-order: 4;
order: 4;
}
.order-md-5 {
-webkit-box-ordinal-group: 6;
-ms-flex-order: 5;
order: 5;
}
.order-md-6 {
-webkit-box-ordinal-group: 7;
-ms-flex-order: 6;
order: 6;
}
.order-md-7 {
-webkit-box-ordinal-group: 8;
-ms-flex-order: 7;
order: 7;
}
.order-md-8 {
-webkit-box-ordinal-group: 9;
-ms-flex-order: 8;
order: 8;
}
.order-md-9 {
-webkit-box-ordinal-group: 10;
-ms-flex-order: 9;
order: 9;
}
.order-md-10 {
-webkit-box-ordinal-group: 11;
-ms-flex-order: 10;
order: 10;
}
.order-md-11 {
-webkit-box-ordinal-group: 12;
-ms-flex-order: 11;
order: 11;
}
.order-md-12 {
-webkit-box-ordinal-group: 13;
-ms-flex-order: 12;
order: 12;
}
.offset-md-0 {
margin-left: 0;
}
.offset-md-1 {
margin-left: 8.333333%;
}
.offset-md-2 {
margin-left: 16.666667%;
}
.offset-md-3 {
margin-left: 25%;
}
.offset-md-4 {
margin-left: 33.333333%;
}
.offset-md-5 {
margin-left: 41.666667%;
}
.offset-md-6 {
margin-left: 50%;
}
.offset-md-7 {
margin-left: 58.333333%;
}
.offset-md-8 {
margin-left: 66.666667%;
}
.offset-md-9 {
margin-left: 75%;
}
.offset-md-10 {
margin-left: 83.333333%;
}
.offset-md-11 {
margin-left: 91.666667%;
}
}
@media (min-width: 992px) {
.col-lg {
-ms-flex-preferred-size: 0;
flex-basis: 0;
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 100%;
}
.col-lg-auto {
-webkit-box-flex: 0;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
width: auto;
max-width: none;
}
.col-lg-1 {
-webkit-box-flex: 0;
-ms-flex: 0 0 8.333333%;
flex: 0 0 8.333333%;
max-width: 8.333333%;
}
.col-lg-2 {
-webkit-box-flex: 0;
-ms-flex: 0 0 16.666667%;
flex: 0 0 16.666667%;
max-width: 16.666667%;
}
.col-lg-3 {
-webkit-box-flex: 0;
-ms-flex: 0 0 25%;
flex: 0 0 25%;
max-width: 25%;
}
.col-lg-4 {
-webkit-box-flex: 0;
-ms-flex: 0 0 33.333333%;
flex: 0 0 33.333333%;
max-width: 33.333333%;
}
.col-lg-5 {
-webkit-box-flex: 0;
-ms-flex: 0 0 41.666667%;
flex: 0 0 41.666667%;
max-width: 41.666667%;
}
.col-lg-6 {
-webkit-box-flex: 0;
-ms-flex: 0 0 50%;
flex: 0 0 50%;
max-width: 50%;
}
.col-lg-7 {
-webkit-box-flex: 0;
-ms-flex: 0 0 58.333333%;
flex: 0 0 58.333333%;
max-width: 58.333333%;
}
.col-lg-8 {
-webkit-box-flex: 0;
-ms-flex: 0 0 66.666667%;
flex: 0 0 66.666667%;
max-width: 66.666667%;
}
.col-lg-9 {
-webkit-box-flex: 0;
-ms-flex: 0 0 75%;
flex: 0 0 75%;
max-width: 75%;
}
.col-lg-10 {
-webkit-box-flex: 0;
-ms-flex: 0 0 83.333333%;
flex: 0 0 83.333333%;
max-width: 83.333333%;
}
.col-lg-11 {
-webkit-box-flex: 0;
-ms-flex: 0 0 91.666667%;
flex: 0 0 91.666667%;
max-width: 91.666667%;
}
.col-lg-12 {
-webkit-box-flex: 0;
-ms-flex: 0 0 100%;
flex: 0 0 100%;
max-width: 100%;
}
.order-lg-first {
-webkit-box-ordinal-group: 0;
-ms-flex-order: -1;
order: -1;
}
.order-lg-last {
-webkit-box-ordinal-group: 14;
-ms-flex-order: 13;
order: 13;
}
.order-lg-0 {
-webkit-box-ordinal-group: 1;
-ms-flex-order: 0;
order: 0;
}
.order-lg-1 {
-webkit-box-ordinal-group: 2;
-ms-flex-order: 1;
order: 1;
}
.order-lg-2 {
-webkit-box-ordinal-group: 3;
-ms-flex-order: 2;
order: 2;
}
.order-lg-3 {
-webkit-box-ordinal-group: 4;
-ms-flex-order: 3;
order: 3;
}
.order-lg-4 {
-webkit-box-ordinal-group: 5;
-ms-flex-order: 4;
order: 4;
}
.order-lg-5 {
-webkit-box-ordinal-group: 6;
-ms-flex-order: 5;
order: 5;
}
.order-lg-6 {
-webkit-box-ordinal-group: 7;
-ms-flex-order: 6;
order: 6;
}
.order-lg-7 {
-webkit-box-ordinal-group: 8;
-ms-flex-order: 7;
order: 7;
}
.order-lg-8 {
-webkit-box-ordinal-group: 9;
-ms-flex-order: 8;
order: 8;
}
.order-lg-9 {
-webkit-box-ordinal-group: 10;
-ms-flex-order: 9;
order: 9;
}
.order-lg-10 {
-webkit-box-ordinal-group: 11;
-ms-flex-order: 10;
order: 10;
}
.order-lg-11 {
-webkit-box-ordinal-group: 12;
-ms-flex-order: 11;
order: 11;
}
.order-lg-12 {
-webkit-box-ordinal-group: 13;
-ms-flex-order: 12;
order: 12;
}
.offset-lg-0 {
margin-left: 0;
}
.offset-lg-1 {
margin-left: 8.333333%;
}
.offset-lg-2 {
margin-left: 16.666667%;
}
.offset-lg-3 {
margin-left: 25%;
}
.offset-lg-4 {
margin-left: 33.333333%;
}
.offset-lg-5 {
margin-left: 41.666667%;
}
.offset-lg-6 {
margin-left: 50%;
}
.offset-lg-7 {
margin-left: 58.333333%;
}
.offset-lg-8 {
margin-left: 66.666667%;
}
.offset-lg-9 {
margin-left: 75%;
}
.offset-lg-10 {
margin-left: 83.333333%;
}
.offset-lg-11 {
margin-left: 91.666667%;
}
}
@media (min-width: 1200px) {
.col-xl {
-ms-flex-preferred-size: 0;
flex-basis: 0;
-webkit-box-flex: 1;
-ms-flex-positive: 1;
flex-grow: 1;
max-width: 100%;
}
.col-xl-auto {
-webkit-box-flex: 0;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
width: auto;
max-width: none;
}
.col-xl-1 {
-webkit-box-flex: 0;
-ms-flex: 0 0 8.333333%;
flex: 0 0 8.333333%;
max-width: 8.333333%;
}
.col-xl-2 {
-webkit-box-flex: 0;
-ms-flex: 0 0 16.666667%;
flex: 0 0 16.666667%;
max-width: 16.666667%;
}
.col-xl-3 {
-webkit-box-flex: 0;
-ms-flex: 0 0 25%;
flex: 0 0 25%;
max-width: 25%;
}
.col-xl-4 {
-webkit-box-flex: 0;
-ms-flex: 0 0 33.333333%;
flex: 0 0 33.333333%;
max-width: 33.333333%;
}
.col-xl-5 {
-webkit-box-flex: 0;
-ms-flex: 0 0 41.666667%;
flex: 0 0 41.666667%;
max-width: 41.666667%;
}
.col-xl-6 {
-webkit-box-flex: 0;
-ms-flex: 0 0 50%;
flex: 0 0 50%;
max-width: 50%;
}
.col-xl-7 {
-webkit-box-flex: 0;
-ms-flex: 0 0 58.333333%;
flex: 0 0 58.333333%;
max-width: 58.333333%;
}
.col-xl-8 {
-webkit-box-flex: 0;
-ms-flex: 0 0 66.666667%;
flex: 0 0 66.666667%;
max-width: 66.666667%;
}
.col-xl-9 {
-webkit-box-flex: 0;
-ms-flex: 0 0 75%;
flex: 0 0 75%;
max-width: 75%;
}
.col-xl-10 {
-webkit-box-flex: 0;
-ms-flex: 0 0 83.333333%;
flex: 0 0 83.333333%;
max-width: 83.333333%;
}
.col-xl-11 {
-webkit-box-flex: 0;
-ms-flex: 0 0 91.666667%;
flex: 0 0 91.666667%;
max-width: 91.666667%;
}
.col-xl-12 {
-webkit-box-flex: 0;
-ms-flex: 0 0 100%;
flex: 0 0 100%;
max-width: 100%;
}
.order-xl-first {
-webkit-box-ordinal-group: 0;
-ms-flex-order: -1;
order: -1;
}
.order-xl-last {
-webkit-box-ordinal-group: 14;
-ms-flex-order: 13;
order: 13;
}
.order-xl-0 {
-webkit-box-ordinal-group: 1;
-ms-flex-order: 0;
order: 0;
}
.order-xl-1 {
-webkit-box-ordinal-group: 2;
-ms-flex-order: 1;
order: 1;
}
.order-xl-2 {
-webkit-box-ordinal-group: 3;
-ms-flex-order: 2;
order: 2;
}
.order-xl-3 {
-webkit-box-ordinal-group: 4;
-ms-flex-order: 3;
order: 3;
}
.order-xl-4 {
-webkit-box-ordinal-group: 5;
-ms-flex-order: 4;
order: 4;
}
.order-xl-5 {
-webkit-box-ordinal-group: 6;
-ms-flex-order: 5;
order: 5;
}
.order-xl-6 {
-webkit-box-ordinal-group: 7;
-ms-flex-order: 6;
order: 6;
}
.order-xl-7 {
-webkit-box-ordinal-group: 8;
-ms-flex-order: 7;
order: 7;
}
.order-xl-8 {
-webkit-box-ordinal-group: 9;
-ms-flex-order: 8;
order: 8;
}
.order-xl-9 {
-webkit-box-ordinal-group: 10;
-ms-flex-order: 9;
order: 9;
}
.order-xl-10 {
-webkit-box-ordinal-group: 11;
-ms-flex-order: 10;
order: 10;
}
.order-xl-11 {
-webkit-box-ordinal-group: 12;
-ms-flex-order: 11;
order: 11;
}
.order-xl-12 {
-webkit-box-ordinal-group: 13;
-ms-flex-order: 12;
order: 12;
}
.offset-xl-0 {
margin-left: 0;
}
.offset-xl-1 {
margin-left: 8.333333%;
}
.offset-xl-2 {
margin-left: 16.666667%;
}
.offset-xl-3 {
margin-left: 25%;
}
.offset-xl-4 {
margin-left: 33.333333%;
}
.offset-xl-5 {
margin-left: 41.666667%;
}
.offset-xl-6 {
margin-left: 50%;
}
.offset-xl-7 {
margin-left: 58.333333%;
}
.offset-xl-8 {
margin-left: 66.666667%;
}
.offset-xl-9 {
margin-left: 75%;
}
.offset-xl-10 {
margin-left: 83.333333%;
}
.offset-xl-11 {
margin-left: 91.666667%;
}
}
.d-flex {
display: -webkit-box !important;
display: -ms-flexbox !important;
display: flex !important;
}
.d-inline-flex {
display: -webkit-inline-box !important;
display: -ms-inline-flexbox !important;
display: inline-flex !important;
}
@media print {
.d-print-none {
display: none !important;
}
.d-print-inline {
display: inline !important;
}
.d-print-inline-block {
display: inline-block !important;
}
.d-print-block {
display: block !important;
}
.d-print-table {
display: table !important;
}
.d-print-table-row {
display: table-row !important;
}
.d-print-table-cell {
display: table-cell !important;
}
.d-print-flex {
display: -webkit-box !important;
display: -ms-flexbox !important;
display: flex !important;
}
.d-print-inline-flex {
display: -webkit-inline-box !important;
display: -ms-inline-flexbox !important;
display: inline-flex !important;
}
}
.flex-row {
-webkit-box-orient: horizontal !important;
-webkit-box-direction: normal !important;
-ms-flex-direction: row !important;
flex-direction: row !important;
}
.flex-column {
-webkit-box-orient: vertical !important;
-webkit-box-direction: normal !important;
-ms-flex-direction: column !important;
flex-direction: column !important;
}
.flex-row-reverse {
-webkit-box-orient: horizontal !important;
-webkit-box-direction: reverse !important;
-ms-flex-direction: row-reverse !important;
flex-direction: row-reverse !important;
}
.flex-column-reverse {
-webkit-box-orient: vertical !important;
-webkit-box-direction: reverse !important;
-ms-flex-direction: column-reverse !important;
flex-direction: column-reverse !important;
}
.flex-wrap {
-ms-flex-wrap: wrap !important;
flex-wrap: wrap !important;
}
.flex-nowrap {
-ms-flex-wrap: nowrap !important;
flex-wrap: nowrap !important;
}
.flex-wrap-reverse {
-ms-flex-wrap: wrap-reverse !important;
flex-wrap: wrap-reverse !important;
}
.flex-fill {
-webkit-box-flex: 1 !important;
-ms-flex: 1 1 auto !important;
flex: 1 1 auto !important;
}
.justify-content-start {
-webkit-box-pack: start !important;
-ms-flex-pack: start !important;
justify-content: flex-start !important;
}
.justify-content-end {
-webkit-box-pack: end !important;
-ms-flex-pack: end !important;
justify-content: flex-end !important;
}
.justify-content-center {
-webkit-box-pack: center !important;
-ms-flex-pack: center !important;
justify-content: center !important;
}
.justify-content-between {
-webkit-box-pack: justify !important;
-ms-flex-pack: justify !important;
justify-content: space-between !important;
}
.justify-content-around {
-ms-flex-pack: distribute !important;
justify-content: space-around !important;
}
.align-items-start {
-webkit-box-align: start !important;
-ms-flex-align: start !important;
align-items: flex-start !important;
}
.align-items-end {
-webkit-box-align: end !important;
-ms-flex-align: end !important;
align-items: flex-end !important;
}
.align-items-center {
-webkit-box-align: center !important;
-ms-flex-align: center !important;
align-items: center !important;
}
.align-items-baseline {
-webkit-box-align: baseline !important;
-ms-flex-align: baseline !important;
align-items: baseline !important;
}
.align-items-stretch {
-webkit-box-align: stretch !important;
-ms-flex-align: stretch !important;
align-items: stretch !important;
}
.align-content-start {
-ms-flex-line-pack: start !important;
align-content: flex-start !important;
}
.align-content-end {
-ms-flex-line-pack: end !important;
align-content: flex-end !important;
}
.align-content-center {
-ms-flex-line-pack: center !important;
align-content: center !important;
}
.align-content-between {
-ms-flex-line-pack: justify !important;
align-content: space-between !important;
}
.align-content-around {
-ms-flex-line-pack: distribute !important;
align-content: space-around !important;
}
.align-content-stretch {
-ms-flex-line-pack: stretch !important;
align-content: stretch !important;
}
.align-self-auto {
-ms-flex-item-align: auto !important;
align-self: auto !important;
}
.align-self-start {
-ms-flex-item-align: start !important;
align-self: flex-start !important;
}
.align-self-end {
-ms-flex-item-align: end !important;
align-self: flex-end !important;
}
.align-self-center {
-ms-flex-item-align: center !important;
align-self: center !important;
}
.align-self-baseline {
-ms-flex-item-align: baseline !important;
align-self: baseline !important;
}
.align-self-stretch {
-ms-flex-item-align: stretch !important;
align-self: stretch !important;
}
================================================
FILE: src/styles/components/bottom-nav.less
================================================
@import '../import.less';
.mu-bottom-nav{
height: 56px;
display: flex;
justify-content: center;
align-items: center;
background-color: @dialogBackgroundColor;
text-align: center;
outline: none;
position: relative;
color: @secondaryTextColor;
}
.mu-bottom-nav-shift{
background-color: @primaryColor;
color: fade(@alternateTextColor, 70%);
}
.mu-bottom-nav-shift-wrapper{
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
.mu-bottom-item {
flex: 1;
min-width: 80px;
max-width: 168px;
position: relative;
height: 100%;
padding: 0;
background: none;
appearance: none;
text-decoration: none;
border: none;
outline: none;
transition: all .4s @easeInOutFunction;
user-select: none;
padding: 6px;
cursor: pointer;
color: inherit;
.mu-bottom-nav-shift & {
padding: 8px 12px 10px;
min-width: 56px;
max-width: 96px;
}
}
.mu-bottom-item-wrapper {
display: block;
height: 100%;
}
.mu-bottom-item-active {
padding-top: 6px;
padding-bottom: 5px;
color: @primaryColor;
&.is-shift {
color: @alternateTextColor;
}
.mu-bottom-item-text{
font-size: 14px;
}
.mu-bottom-nav-shift & {
flex: 1.7;
min-width: 96px;
max-width: 168px;
padding-top: 6px;
padding-bottom: 5px;
}
}
.mu-bottom-item-text{
display: block;
text-align: center;
font-size: 12px;
transition: transform .4s @easeOutFunction, opacity .4s @easeOutFunction, font-size 0.3s @easeOutFunction;
backface-visibility: hidden;
.mu-bottom-nav-shift & {
opacity: 0;
transform: scale(1) translate3d(0, 6px, 0);
}
.mu-bottom-nav-shift .mu-bottom-item-active & {
transform: scale(1) translate3d(0, 2px, 0);
opacity: 1;
}
}
.mu-bottom-item-icon {
display: block;
margin: auto;
transition: transform .45s @easeOutFunction;
backface-visibility: hidden;
width: 24px;
.mu-bottom-nav-shift & {
transform: translate3d(0, 8px, 0);
}
.mu-bottom-nav-shift .mu-bottom-item-active & {
transform: scale(1) translateZ(0);
}
}
================================================
FILE: src/styles/components/bottom-sheet.less
================================================
@import '../import.less';
.mu-bottom-sheet {
background-color: @dialogBackgroundColor;
position: fixed;
left: 0;
right: 0;
bottom: 0;
}
================================================
FILE: src/styles/components/breadcrumbs.less
================================================
@import '../import.less';
.mu-breadcrumbs {
display: flex;
padding: 18px 12px;
margin: 0;
align-items: center;
> li {
font-size: 14px;
line-height: 1;
display: flex;
align-items: center;
.mu-icon {
font-size: 16px;
}
}
}
.mu-breadcrumbs-item {
display: flex;
align-items: center;
color: @primaryColor;
display: block;
transition: .3s @easeOutFunction;
> a {
display: block;
text-decoration: none;
cursor: pointer;
color: inherit;
}
&.is-disabled {
color: @disabledColor;
> a {
cursor: default;
}
}
}
.mu-breadcrumbs-divider {
padding: 0 12px;
color: @disabledColor;
}
================================================
FILE: src/styles/components/button.less
================================================
@import "../import.less";
.mu-button {
display: inline-block;
overflow: hidden;
position: relative;
transition-duration: 300ms;
transition-timing-function: @easeOutFunction;
text-decoration: none;
text-align: center;
border: none;
appearance: none;
outline: none;
text-transform: uppercase;
margin: 0;
padding: 0;
cursor: pointer;
.flex-shrink(0);
.mu-icon-left {
margin-right: 8px;
}
.mu-icon-right {
margin-left: 8px;
}
&.hover::before{
content: '';
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: currentColor;
opacity: .12;
}
}
.mu-button-wrapper {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.mu-raised-button {
font-size: 14px;
min-width: 88px;
height: 36px;
line-height: 36px;
border-radius: 2px;
background-color: @dialogBackgroundColor;
color: @textColor;
.depth(2);
&.mu-inverse {
.mu-circle-ripple {
opacity: 0.3;
}
}
&.disabled{
color: fade(@textColor, 30%);
cursor: not-allowed;
background-color: darken(@alternateTextColor, 10%);
.depth(0);
&.hover,
&:active,
&:hover {
box-shadow: none;
}
}
&.focus {
.depth(6);
}
&:active {
.depth(8);
}
.mu-button-wrapper {
padding: 0 16px;
}
&.mu-button-round {
border-radius: 36px;
}
&.mu-button-full-width {
width: 100%;
}
&.mu-button-small {
font-size: 13px;
height: 28px;
&.mu-button-round {
border-radius: 28px;
}
.mu-button-wrapper {
padding: 0 8px;
}
.mu-icon {
font-size: 20px;
}
}
&.mu-button-large {
font-size: 15px;
height: 44px;
&.mu-button-round {
border-radius: 44px;
}
.mu-button-wrapper {
padding: 0 32px;
}
.mu-icon {
font-size: 28px;
}
}
}
.mu-flat-button {
border-radius: 2px;
height: 36px;
line-height: 36px;
min-width: 88px;
font-size: 14px;
color: @textColor;
background: transparent;
&.disabled{
color: @disabledColor;
cursor: not-allowed;
background: none;
}
.mu-button-wrapper {
padding: 0 16px;
}
&.mu-button-small {
font-size: 13px;
height: 28px;
.mu-button-wrapper {
padding: 0 8px;
}
.mu-icon {
font-size: 20px;
}
}
&.mu-button-large {
font-size: 15px;
height: 44px;
.mu-button-wrapper {
padding: 0 32px;
}
.mu-icon {
font-size: 28px;
}
}
}
.mu-icon-button {
line-height: 1;
width: 48px;
height: 48px;
border-radius: 50%;
font-size: 24px;
padding: 12px;
border: none;
appearance: none;
background: none;
color: inherit;
background-color: transparent;
&.disabled{
color: @disabledColor;
cursor: not-allowed;
}
&.mu-button-small {
width: 32px;
height: 32px;
.mu-icon {
font-size: 20px;
}
}
&.mu-button-large {
width: 56px;
height: 56px;
.mu-icon {
font-size: 28px;
}
}
}
.mu-fab-button {
line-height: 1;
width: 56px;
height: 56px;
border-radius: 50%;
border: none;
appearance: none;
background-color: @primaryColor;
color: @alternateTextColor;
.depth(6);
&.hover,
&:active{
.depth(12);
}
&.disabled{
color: fade(@textColor, 30%);
cursor: not-allowed;
background-color: darken(@alternateTextColor, 10%);
box-shadow: none;
&.hover,
&:active,
&:hover {
box-shadow: none;
}
}
.mu-circle-ripple{
opacity: .3;
}
&.mu-button-small {
width: 40px;
height: 40px;
.mu-icon {
font-size: 18px;
}
}
&.mu-button-large {
width: 72px;
height: 72px;
.mu-icon {
font-size: 30px;
}
}
}
================================================
FILE: src/styles/components/card.less
================================================
@import "../import.less";
.mu-card {
background-color: @dialogBackgroundColor;
position: relative;
border-radius: 2px;
.depth(1);
}
.mu-card__raised {
.depth(8);
}
.mu-card-actions {
padding: 8px;
position: relative;
}
.mu-card-header {
padding: 16px;
font-weight: 500;
position: relative;
white-space: nowrap;
.mu-avatar {
margin-right: 16px;
}
}
.mu-card-header-title{
display: inline-block;
vertical-align: top;
white-space: normal;
padding-right: 90px;
.mu-card-title{
font-size: 15px;
color: fade(@textColor, 87%);
}
.mu-card-sub-title{
font-size: 14px;
color: fade(@textColor, 57%);
}
}
.mu-card-media {
position: relative;
> img {
width: 100%;
max-width: 100%;
min-width: 100%;
display: block;
vertical-align: top;
}
}
.mu-card-media-title {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 16px;
background-color: @lightBlack;
.mu-card-title {
font-size: 24px;
color: @darkWhite;
line-height: 36px;
}
.mu-card-sub-title {
color: @lightWhite;
font-size: 14px;
}
}
.mu-card-text{
padding: 16px;
font-size: 14px;
color: @textColor;
}
.mu-card-title-container{
padding: 16px;
position: relative;
.mu-card-title{
font-size: 24px;
color: @textColor;
line-height: 36px;
}
.mu-card-sub-title{
font-size: 14px;
color: @secondaryTextColor;
display: block;
}
}
================================================
FILE: src/styles/components/carousel.less
================================================
@import '../import.less';
.mu-carousel {
height: 500px;
width: 100%;
position: relative;
overflow: hidden;
}
.mu-carousel-button.mu-icon-button {
color: @alternateTextColor;
width: 48px;
height: 48px;
z-index: 3;
position: absolute;
top: 50%;
margin-top: -24px;
font-size: 36px;
padding: 0;
.mu-circle-ripple {
opacity: .2;
}
}
.mu-carousel-button__left {
left: 8px;
}
.mu-carousel-button__right {
right: 8px;
}
.mu-carousel-svg-icon {
width: 1em;
height: 1em;
fill: currentColor;
display: inline-block;
}
.mu-carousel-indicators {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
z-index: 3;
}
.mu-carousel-indicator-button {
width: 28px;
height: 28px;
padding: 0;
margin: 0 8px;
}
.mu-carousel-indicator-icon {
display: inline-block;
width: 12px;
height: 12px;
background-color: @alternateTextColor;
border-radius: 50%;
opacity: .5;
transition: opacity .3s @easeOutFunction;
.mu-carousel-indicator-button__active & {
opacity: 1;
}
}
.mu-carousel-item {
width: 100%;
height: 100%;
overflow: hidden;
flex-shrink: 0;
position: absolute;
left: 0;
right: 0;
transition: .4s cubic-bezier(.25,.8,.5,1);
> img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
min-width: 100%;
transition: inherit;
will-change: transform;
max-width: none;
}
}
.mu-carousel-slide-enter {
transform: translate3d(100%, 0, 0);
.mu-carousel__transition_inverse & {
transform: translate3d(-100%, 0, 0);
}
}
.mu-carousel-slide-leave-active {
transform: translate3d(-100%, 0, 0);
.mu-carousel__transition_inverse & {
transform: translate3d(100%, 0, 0);
}
}
.mu-carousel-fade-enter,
.mu-carousel-fade-leave-active {
opacity: 0;
}
================================================
FILE: src/styles/components/checkbox.less
================================================
@import "../import.less";
.mu-checkbox {
position: relative;
display: inline-block;
height: 24px;
line-height: 24px;
cursor: pointer;
user-select: none;
outline: none;
color: @secondaryTextColor;
input[type="checkbox"] {
display: none;
}
&.disabled {
cursor: not-allowed;
color: @disabledColor;
}
}
.mu-checkbox-checked {
color: @primaryColor;
.mu-checkbox-icon-uncheck {
opacity: 0;
transition: opacity 650ms @easeOutFunction 150ms;
}
.mu-checkbox-icon-checked {
opacity: 1;
transform: scale(1);
transition: opacity 0ms @easeOutFunction, transform 800ms @easeOutFunction;
}
}
.mu-checkbox-wrapper {
display: flex;
width: 100%;
height: 24px;
align-items: center;
justify-content: space-between;
}
.mu-checkbox-icon {
width: 24px;
height: 24px;
vertical-align: middle;
position: relative;
margin-right: 8px;
.mu-checkbox.label-left & {
margin-right: 0;
margin-left: 8px;
}
.mu-checkbox.no-label & {
margin-left: 0;
margin-right: 0;
}
}
.mu-checkbox-label {
color: @textColor;
.mu-checkbox.disabled & {
color: @disabledColor;
}
}
.mu-checkbox-svg-icon {
display: inline-block;
fill: currentColor;
height: 24px;
width: 24px;
user-select: none;
}
.mu-checkbox-icon-uncheck {
position: absolute;
left: 0;
top: 0;
opacity: 1;
transition: opacity 1s @easeOutFunction .2s;
}
.mu-checkbox-icon-checked {
position: absolute;
left: 0;
top: 0;
opacity: 0;
transform: scale(0);
transition: opacity 450ms @easeOutFunction, transform 0ms @easeOutFunction 450ms;
}
.mu-checkbox-ripple-wrapper {
width: 48px;
height: 48px;
top: -12px;
left: -12px;
position: absolute;
.mu-checkbox.label-left & {
right: -12px;
left: auto;
}
}
================================================
FILE: src/styles/components/chip.less
================================================
@import '../import.less';
.mu-chip {
border-radius: 16px;
line-height: 32px;
white-space: nowrap;
display: inline-flex;
align-items: center;
background-color: @chipBackgroundColor;
color: @textColor;
font-size: 13px;
padding: 0 12px;
outline: none;
cursor: default;
.mu-avatar:first-child{
margin-left: -12px;
margin-right: 4px;
}
&:active,
&:focus,
&.is-deletable {
background-color: darken(@chipBackgroundColor, 8%);
.depth(1);
}
&:hover{
.mu-chip-delete-icon{
color: fade(fade(@textColor, 26%), 40%);
}
background-color: darken(@chipBackgroundColor, 8%);
cursor: pointer;
}
&.mu-primary-color {
background-color: @primaryColor;
}
&.mu-secondary-color {
background-color: @secondaryColor;
}
&.mu-success-color {
background-color: @successColor;
}
&.mu-warning-color {
background-color: @warningColor;
}
&.mu-info-color {
background-color: @infoColor;
}
&.mu-error-color {
background-color: @errorColor;
}
}
.mu-chip-delete-icon{
display: inline-block;
margin-right: -8px;
margin-left: 4px;
color: fade(@textColor, 26%);
fill: currentColor;
height: 24px;
width: 24px;
user-select: none;
transition: all 450ms @easeOutFunction;
}
================================================
FILE: src/styles/components/circle-ripple.less
================================================
@import "../import.less";
.mu-circle-ripple{
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
pointer-events: none;
user-select: none;
border-radius: 50%;
background-color: currentColor;
background-clip: padding-box;
opacity: 0.1;
}
.mu-ripple-enter-active, .mu-ripple-leave-active{
transition: opacity 2s @easeOutFunction, transform .45s @easeOutFunction;
}
.mu-ripple-enter {
transform: scale(0);
}
.mu-ripple-leave-active{
opacity: 0 !important;
}
================================================
FILE: src/styles/components/data-table.less
================================================
@import "../import.less";
.mu-table {
background-color: @alternateTextColor;
position: relative;
overflow: hidden;
table {
border-collapse: collapse;
border-spacing: 0;
table-layout: fixed;
}
tr {
color: @textColor;
height: 48px;
&.is-stripe {
background-color: @grey50;
}
&.is-hover {
background-color: @grey200;
}
&.is-selected {
background-color: @grey100;
}
}
td {
padding-left: 24px;
padding-right: 24px;
min-height: 48px;
text-align: left;
font-size: 13px;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
border-bottom: 1px solid @borderColor;
&.is-left {
text-align: left;
}
&.is-center {
text-align: center;
}
&.is-right {
text-align: right;
}
}
th {
font-weight: normal;
font-size: 12px;
padding-left: 24px;
padding-right: 24px;
height: 56px;
text-align: left;
color: @secondaryTextColor;
position: relative;
border-bottom: 1px solid @borderColor;
white-space: nowrap;
&.is-left {
text-align: left;
}
&.is-center {
text-align: center;
}
&.is-right {
text-align: right;
}
&.is-sortable {
cursor: pointer;
user-select: none;
&:hover {
color: @textColor;
.mu-table-sort-icon {
opacity: .6;
}
}
}
&.is-sorting {
color: @textColor;
.mu-table-sort-icon,
&:hover .mu-table-sort-icon {
opacity: 1;
}
}
&.sort-asc {
.mu-table-sort-icon {
transform: rotate(180deg);
}
}
}
}
.mu-table-flex {
display: flex;
flex-direction: column;
}
.mu-table-border {
border: 1px solid @borderColor;
th, td {
border-right: 1px solid @borderColor;
&:last-child {
border-right: none;
}
}
}
.mu-table-header-wrapper,
.mu-table-footer-wrapper {
overflow: hidden;
}
.mu-table-empty {
height: 300px;
display: flex;
align-items: center;
justify-content: center;
color: @secondaryTextColor;
font-size: 14px;
}
.mu-table-progress.mu-linear-progress {
position: absolute;
left: 0;
right: 0;
z-index: 10;
}
.mu-table-body-wrapper {
flex: 1;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
.mu-table-sort-icon {
display: inline-block;
vertical-align: sub;
width: 16px;
height: 16px;
font-size: 16px;
fill: currentColor;
opacity: 0;
transition: .3s @easeOutFunction;
}
.mu-checkbox-col {
.mu-checkbox {
vertical-align: middle;
}
}
.mu-table-expand-row {
tr& {
height: 0;
}
td {
padding: 0;
height: 0;
border: none;
min-height: 0;
&.is-expand {
border-bottom: 1px solid @borderColor;
}
}
}
================================================
FILE: src/styles/components/date-input.less
================================================
@import "../import.less";
.mu-picker-dialog {
max-width: 100%;
.mu-dialog-body {
padding: 0;
}
}
================================================
FILE: src/styles/components/dialog.less
================================================
@import "../import.less";
.mu-dialog-wrapper {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
}
.mu-dialog {
padding: 0;
max-width: 75%;
background-color: @dialogBackgroundColor;
border-radius: 2px;
font-size: 16px;
outline: none;
.depth(8);
}
.mu-dialog-scrollable {
.mu-dialog-body {
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
}
.mu-dialog-fullscreen {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
max-width: 100% !important;
width: 100% !important;
height: 100% !important;
max-height: 100% !important;
border-radius: 0;
.mu-dialog-body {
padding: 0;
}
}
.mu-dialog-title {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24px 24px 20px;
margin: 0;
font-size: 22px;
font-weight: normal;
line-height: 32px;
color: @textColor;
+ .mu-dialog-body{
padding-top: 0;
}
}
.mu-dialog-body {
padding: 24px 24px 20px;
color: fade(@textColor, 60%);
}
.mu-dialog-actions {
min-height: 48px;
padding: 8px;
display: flex;
align-items: center;
justify-content: flex-end;
.mu-raised-button + .mu-raised-button{
margin-left: 10px;
}
}
.mu-dialog-transition-enter-active,
.mu-dialog-transition-leave-active {
transition: opacity .45s @easeOutFunction;
.mu-dialog {
&.mu-slide-top,
&.mu-slide-bottom,
&.mu-slide-left,
&.mu-slide-right,
&.mu-scale{
transition: transform .45s @easeOutFunction
}
}
}
.mu-dialog-transition-enter,
.mu-dialog-transition-leave-active {
opacity: 0;
}
.mu-dialog-transition-enter,
.mu-dialog-transition-leave-active {
.mu-dialog {
backface-visibility: hidden;
&.mu-slide-top {
transform: translate3d(0, -100%, 0);
}
&.mu-slide-bottom {
transform: translate3d(0, 100%, 0);
}
&.mu-slide-right {
transform: translate3d(100%, 0, 0);
}
&.mu-slide-left {
transform: translate3d(-100%, 0, 0);
}
&.mu-scale {
transform: scale(0.6);
}
}
}
================================================
FILE: src/styles/components/divider.less
================================================
@import "../import.less";
.mu-divider {
margin: 0;
height: 1px;
border: none;
background-color: @borderColor;
width: 100%;
&.inset{
margin-left: 72px;
}
&.shallow-inset{
margin-left: 16px;
}
html.pixel-ratio-2 & {
.transform(scaleY(0.5));
}
html.pixel-ratio-3 & {
.transform(scaleY(0.33));
}
}
================================================
FILE: src/styles/components/drawer.less
================================================
@import "../import.less";
.mu-drawer {
width: 256px;
position: fixed;
top: 0;
bottom: 0;
background-color: @dialogBackgroundColor;
.scrollable();
.no-scrollbar();
transition-property: transform, visibility;
transition-duration: 0.45s;
transform: translate3d(-100%, 0, 0);
border-radius: 0;
left: 0;
visibility: hidden;
z-index: 200;
&.is-right {
right: 0;
left: auto;
transform: translate3d(100%, 0, 0);
}
&.is-open {
transform: translate3d(0, 0, 0);
visibility: visible;
}
}
================================================
FILE: src/styles/components/elevation.less
================================================
@import '../import.less';
.mu-elevation-0 {
.depth(0);
}
.mu-elevation-1 {
.depth(1);
}
.mu-elevation-2 {
.depth(2);
}
.mu-elevation-3 {
.depth(3);
}
.mu-elevation-4 {
.depth(4);
}
.mu-elevation-5 {
.depth(5);
}
.mu-elevation-6 {
.depth(6);
}
.mu-elevation-7 {
.depth(7);
}
.mu-elevation-8 {
.depth(8);
}
.mu-elevation-9 {
.depth(9);
}
.mu-elevation-10 {
.depth(10);
}
.mu-elevation-11 {
.depth(11);
}
.mu-elevation-12 {
.depth(12);
}
.mu-elevation-13 {
.depth(13);
}
.mu-elevation-14 {
.depth(14);
}
.mu-elevation-15 {
.depth(15);
}
.mu-elevation-16 {
.depth(16);
}
.mu-elevation-17 {
.depth(17);
}
.mu-elevation-18 {
.depth(18);
}
.mu-elevation-19 {
.depth(19);
}
.mu-elevation-20 {
.depth(20);
}
.mu-elevation-21 {
.depth(21);
}
.mu-elevation-22 {
.depth(22);
}
.mu-elevation-23 {
.depth(23);
}
.mu-elevation-24 {
.depth(24);
}
================================================
FILE: src/styles/components/expand-transition.less
================================================
@import '../import.less';
.mu-expand-enter-active,
.mu-expand-leave-active {
transition: all .45s @easeOutFunction;
backface-visibility: hidden;
transform: translate3d(0, 0, 0);
}
================================================
FILE: src/styles/components/expansion-panel.less
================================================
@import "../import.less";
.mu-expansion-panel {
color: @textColor;
&:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
&:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
border-top: 1px solid @borderColor;
&:first-child {
border-top: none;
}
}
.mu-expansion-panel__expand {
margin: 16px 0;
border-top: none;
+ .mu-expansion-panel {
border-top: none;
}
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
.mu-expansion-panel-header {
display: flex;
align-items: center;
min-height: 48px;
padding: 0 24px;
font-size: 15px;
cursor: pointer;
transition: min-height 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,background-color 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
.mu-expansion-panel__expand & {
min-height: 64px;
}
}
.mu-expansion-toggle-btn.mu-button {
margin-left: auto;
margin-right: -12px;
color: @secondaryTextColor;
transform: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
svg {
width: 24px;
height: 24px;
fill: currentColor;
flex-shrink: 0;
}
.mu-expansion-panel__expand & {
transform: rotate(180deg);
}
}
.mu-expansion-panel-content {
padding: 8px 24px 24px;
}
.mu-expansion-panel-actions {
display: flex;
justify-content: flex-end;
padding: 16px 8px;
border-top: 1px solid @borderColor;
.mu-button + .mu-button{
margin-left: 8px;
}
}
================================================
FILE: src/styles/components/focus-ripple.less
================================================
@import "../import.less";
.mu-focus-ripple-wrapper{
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
}
.mu-focus-ripple{
position: absolute;
height: 100%;
width: 100%;
border-radius: 50%;
opacity: 0.16;
background-color: currentColor;
animation: mu-pulsate 750ms @easeInOutFunction;
animation-iteration-count: infinite;
animation-direction: alternate;
}
@keyframes mu-pulsate {
0% {
transform: scale(0.72);
}
100% {
transform: scale(0.85);
}
}
================================================
FILE: src/styles/components/form.less
================================================
@import "../import.less";
.mu-form {
width: 100%;
}
.mu-form__inline {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
.mu-form-item {
min-width: 256px;
margin-right: 16px;
}
}
.mu-form-item {
display: flex;
flex-direction: column;
min-height: 48px;
position: relative;
color: @secondaryTextColor;
margin-bottom: 16px;
padding-bottom: 12px;
position: relative;
.mu-input {
padding: 0;
margin-bottom: 0;
width: 100%;
}
.mu-input-content {
padding-top: 0;
}
.mu-input {
min-height: auto;
}
.mu-slider {
margin-bottom: 0;
}
.mu-checkbox,
.mu-radio,
.mu-switch {
margin-right: 16px;
&:last-child {
margin-right: 0;
}
}
.mu-button {
margin: 6px 8px;
}
}
.mu-form-item__focus {
color: @primaryColor;
}
.mu-form-item__error {
color: @errorColor;
}
.mu-form-item__has-label {
min-height: 72px;
}
.mu-form-item__has-icon {
padding-left: 56px;
}
.mu-form-item__float-label {
padding-top: 28px;
.mu-form-item-label {
transition: all .45s @easeOutFunction;
position: absolute;
top: 2px;
transform: translate3d(0, 0, 0);
&.is-float {
transform: translate3d(0, 28px,0);
font-size: 16px;
}
}
}
.mu-form-item__label-left,
.mu-form-item__label-right {
flex-direction: row;
min-height: 48px;
padding-top: 4px;
.mu-form-item-label {
line-height: 32px;
padding-right: 16px;
flex-shrink: 0;
}
.mu-form-item-content {
flex: 1;
align-items: flex-start;
> *:not(.mu-input) {
margin-top: 4px;
}
}
}
.mu-form-item__label-right .mu-form-item-label {
text-align: right;
}
.mu-form-item-label {
font-size: 14px;
line-height: 28px;
}
.mu-form-item-icon {
position: absolute;
left: 16px;
top: 8px;
.mu-form-item__has-label & {
top: 32px;
}
}
.mu-form-item-content {
min-height: 32px;
display: flex;
align-items: center;
flex-wrap: wrap;
}
.mu-form-item-help {
position: absolute;
font-size: 12px;
line-height: 12px;
bottom: -4px;
left: 0;
color: @secondaryTextColor;
.mu-form-item__error & {
color: @errorColor;
}
}
================================================
FILE: src/styles/components/grid-list.less
================================================
@import '../import.less';
.mu-grid-list {
display: flex;
flex-wrap: wrap;
}
.mu-grid-tile-wrapper {
flex-shrink: 0;
}
.mu-grid-tile {
position: relative;
display: block;
height: 100%;
overflow: hidden;
> img {
height: 100%;
transform: translateX(-50%);
position: relative;
left: 50%;
}
}
.mu-grid-tile-titlebar{
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 48px;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
.mu-grid-tile.multiline & {
height: 68px;
}
.mu-grid-tile.is-top & {
bottom: auto;
top: 0;
}
}
.mu-grid-tile-title-container{
margin-left: 16px;
margin-right: 0;
color: @alternateTextColor;
flex: 1;
overflow: hidden;
.mu-grid-tile.action-left & {
margin-right: 16px;
margin-left: 0;
}
}
.mu-grid-tile-action {
order: 1;
.mu-grid-tile.action-left & {
order: -1;
}
.mu-icon {
color: @alternateTextColor;
}
}
.mu-grid-tile-title {
font-size: 16px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
word-wrap: break-word;
}
.mu-grid-tile-subtitle {
font-size: 12px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
word-wrap: break-word;
}
================================================
FILE: src/styles/components/grid.less
================================================
.row {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
align-items: flex-start;
> [class*='col-'] {
box-sizing: border-box;
}
}
@cols: 5, 10, 15, 20, 25, 30, 100/3, 35, 40, 45, 50, 55, 60, 65, 100 * (2/3), 70,
75, 80, 85, 90, 95, 100;
@media all and (max-width: 600px) {
.row {
.col-auto {
width: 100%;
}
.-(@i: length(@cols)) when (@i > 0) {
@divider: e(extract(@cols, @i));
@className: `Math.floor(@{divider}) `;
@n: `100/parseFloat(@{divider}) `;
@n-1: @n - 1;
.col-@{className} {
width: ~'@{divider}%';
width: ~'-webkit-calc((100% - 16px*@{n-1}) / @{n})';
width: ~'calc((100% - 16px*@{n-1}) / @{n})';
}
&.no-gutter {
.col-@{className} {
width: ~'@{divider}%';
}
}
.-((@i - 1));
}
.-;
.--(@j: 1) when (@j < length(@cols)) {
@divider: e(extract(@cols, @j));
@className: `Math.floor(@{divider}) `;
.col-auto:nth-last-child(@{j}),
.col-auto:nth-last-child(@{j}) ~ .col-auto {
@j-1: @j - 1;
width: 100% / @j;
width: ~'-webkit-calc((100% - 16px*@{j-1}) / @{j})';
width: ~'calc((100% - 16px*@{j-1}) / @{j})';
}
&.no-gutter {
.col-auto:nth-last-child(@{j}),
.col-auto:nth-last-child(@{j}) ~ .col-auto {
width: 100% / @j;
}
}
.--((@j + 1));
}
.--;
}
}
@media all and (max-width: 992px) and (min-width: 601px) {
.row {
.-(@i: length(@cols)) when (@i > 0) {
@divider: e(extract(@cols, @i));
@className: `Math.floor(@{divider}) `;
@n: `100/parseFloat(@{divider}) `;
@n-1: @n - 1;
.tablet-@{className} {
width: ~'@{divider}%';
width: ~'-webkit-calc((100% - 16px*@{n-1}) / @{n})';
width: ~'calc((100% - 16px*@{n-1}) / @{n})';
}
&.no-gutter {
.tablet-@{className} {
width: ~'@{divider}%';
}
}
.-((@i - 1));
}
.-;
.--(@j: 1) when (@j < length(@cols)) {
.tablet-auto:nth-last-child(@{j}),
.tablet-auto:nth-last-child(@{j}) ~ .col-auto {
@j-1: @j - 1;
width: 100% / @j;
width: ~'-webkit-calc((100% - 16px*@{j-1}) / @{j})';
width: ~'calc((100% - 16px*@{j-1}) / @{j})';
}
&.no-gutter {
.tablet-auto:nth-last-child(@{j}),
.tablet-auto:nth-last-child(@{j}) ~ .tablet-auto {
width: 100% / @j;
}
}
.--((@j + 1));
}
.--;
}
}
@media all and (min-width: 993px) {
.row {
.-(@i: length(@cols)) when (@i > 0) {
@divider: e(extract(@cols, @i));
@className: `Math.floor(@{divider}) `;
@n: `100/parseFloat(@{divider}) `;
@n-1: @n - 1;
.desktop-@{className} {
width: ~'@{divider}%';
width: ~'-webkit-calc((100% - 16px*@{n-1}) / @{n})';
width: ~'calc((100% - 16px*@{n-1}) / @{n})';
}
&.no-gutter {
.desktop-@{className} {
width: ~'@{divider}%';
}
}
.-((@i - 1));
}
.-;
.--(@j: 1) when (@j < length(@cols)) {
.desktop-auto:nth-last-child(@{j}),
.desktop-auto:nth-last-child(@{j}) ~ .col-auto {
@j-1: @j - 1;
width: 100% / @j;
width: ~'-webkit-calc((100% - 16px*@{j-1}) / @{j})';
width: ~'calc((100% - 16px*@{j-1}) / @{j})';
}
&.no-gutter {
.desktop-auto:nth-last-child(@{j}),
.desktop-auto:nth-last-child(@{j}) ~ .desktop-auto {
width: 100% / @j;
}
}
.--((@j + 1));
}
.--;
}
}
================================================
FILE: src/styles/components/input.less
================================================
@import "../import.less";
.mu-input {
font-size: 16px;
width: 256px;
min-height: 48px;
display: inline-block;
position: relative;
color: @secondaryTextColor;
margin-bottom: 16px;
padding-bottom: 12px;
padding-top: 4px;
&.has-label {
padding-top: 28px;
padding-bottom: 12px;
}
&.is-solo {
padding-top: 8px;
padding-bottom: 8px;
}
&.full-width {
width: 100%;
}
&.has-icon {
padding-left: 56px;
}
&.has-label{
min-height: 72px;
}
&.is-solo {
margin-bottom: 0;
}
}
.mu-input__focus {
color: @primaryColor;
}
.mu-input__error {
color: @errorColor;
}
.mu-input-icon {
position: absolute;
left: 16px;
top: 8px;
.mu-input.has-label & {
top: 32px;
}
.mu-input.is-solo & {
top: 12px;
}
}
.mu-input-content{
height: 100%;
position: relative;
.mu-input.disabled &{
color: @disabledColor;
cursor: not-allowed;
}
}
.mu-input-help {
position: absolute;
font-size: 12px;
line-height: 12px;
bottom: -16px;
color: @secondaryTextColor;
display: flex;
justify-content: space-between;
left: 0;
right: 0;
.mu-input__error & {
color: @errorColor;
}
.mu-input.disabled &{
color: inherit;
}
}
.mu-input-action-icon {
.flex-shrink(0);
padding: 0 6px;
cursor: pointer;
}
.mu-input-suffix-text {
padding-left: 4px;
}
.mu-input-prefix-text {
padding-right: 4px;
}
.mu-input-suffix-text,
.mu-input-prefix-text {
color: @secondaryTextColor;
white-space: nowrap;
.flex-shrink(0);
}
.mu-input-label {
line-height: 20px;
transition: all .45s @easeOutFunction;
z-index: 1;
cursor: text;
transform: translate3d(0, 0, 0) scale(0.75);
transform-origin: left top;
user-select: none;
pointer-events: none;
backface-visibility: hidden;
.mu-input.has-label & {
top: 8px;
position: absolute;
}
.mu-input.has-label &.float {
transform: translate3d(0, 28px, 0) scale(1);
color: @disabledColor;
}
}
.mu-input-line {
margin: 0;
height: 1px;
border: none;
background-color: @borderColor;
left: 0;
right: 0;
bottom: -1px;
position: absolute;
&.disabled {
height: auto;
background-color: transparent;
border-bottom: 2px dotted @disabledColor;
}
.mu-input__error & {
background-color: currentColor;
}
}
.mu-input-focus-line{
margin: 0;
height: 2px;
border: none;
background-color: currentColor;
position: absolute;
left: 0;
right: 0;
bottom: -1px;
transform: scaleX(0);
transition: transform .45s @easeOutFunction;
&.focus {
transform: scaleX(1);
}
}
================================================
FILE: src/styles/components/list.less
================================================
@import '../import.less';
.mu-list {
padding: 8px 0;
width: 100%;
position: relative;
overflow-x: hidden;
overflow-y: visible;
margin: 0;
display: block;
> li {
display: block;
}
.mu-sub-header:first-child{
margin-top: -8px;
}
.mu-list {
padding: 0;
}
}
.mu-item-wrapper {
display: block;
color: inherit;
position: relative;
outline: none;
cursor: pointer;
&.hover {
background-color: fade(@textColor, 10%);
}
&.disabled{
cursor: default;
}
}
.mu-list-dense {
.mu-item {
height: 36px;
}
.mu-icon {
font-size: 22px;
}
.mu-item-title {
font-size: 14px;
}
}
.mu-item {
height: 48px;
display: flex;
align-items: center;
padding: 0 16px;
color: @textColor;
&.has-avatar {
height: 56px;
}
.mu-list-two-line & {
height: 72px;
}
.mu-list-three-line & {
height: 88px;
}
&.is-selected {
color: @primaryColor;
}
&.mu-icon-left {
margin-right: 16px;
}
}
.mu-item-action {
min-width: 56px;
display: flex;
height: 100%;
align-items: center;
color: @secondaryTextColor;
&:first-child .mu-icon-button {
margin-left: -12px;
}
&:last-child .mu-icon-button {
margin-right: -12px;
}
&.is-more {
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
padding-top: 8px;
padding-bottom: 8px;
}
}
.mu-list-three-line .mu-item-action .mu-avatar {
margin-top: -18px;
}
.mu-item-title,
.mu-item-content {
flex: 1 1 auto;
text-align: left;
min-width: 1px;
+ .mu-item-action:not(.is-more) {
justify-content: flex-end;
}
}
.mu-item-title {
font-size: 16px;
height: 24px;
line-height: 24px;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
word-wrap: break-word;
}
.mu-item-sub-title {
width: 100%;
font-size: 14px;
line-height: 1.5;
color: @secondaryTextColor;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
word-wrap: break-word;
.mu-list.mu-list-three-line & {
white-space: normal;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
display: -webkit-box;
}
}
.mu-item-after-text {
color: @secondaryTextColor;
font-size: 12px;
}
================================================
FILE: src/styles/components/load-more.less
================================================
@import '../import.less';
.mu-load-more {
position: relative;
overflow: hidden;
user-select: none;
}
.mu-refresh-control{
display: flex;
margin: 0 auto;
width: 40px;
height: 40px;
color: @primaryColor;
align-items: center;
justify-content: center;
background-color: #FFF;
border-radius: 50%;
.depth(2);
position: absolute;
left: 50%;
margin-left: -18px;
margin-top: 24px;
z-index: 90;
.mu-icon {
display: inline-block;
vertical-align: middle;
}
}
.mu-refresh-svg-icon {
display: inline-block;
width: 28px;
height: 28px;
fill: currentColor;
user-select: none;
}
.mu-refresh-control-animate{
transition: all 0.45s ease;
}
.mu-refresh-control-hide{
opacity: 1;
transform: translate3d(0, -68px, 0);
}
.mu-refresh-control-noshow{
opacity: 0;
transform: scale(0.01);
}
.mu-refresh-control-refreshing {
transform: scale(1);
opacity: 1;
}
.mu-infinite-scroll{
display: flex;
justify-content: center;
align-items: center;
height: 48px;
width: 100%;
}
.mu-infinite-scroll-text{
margin-left: 16px;
font-size: 16px;
}
================================================
FILE: src/styles/components/menu.less
================================================
@import "../import.less";
.mu-menu {
display: inline-block;
position: relative;
vertical-align: middle;
}
.mu-menu-toggle-icon {
transition: transform .3s cubic-bezier(.23,1,.32,1);
.mu-menu__open & {
transform: rotate(180deg);
}
}
.mu-menu-activator {
align-items: center;
cursor: pointer;
height: 100%;
position: relative;
input[readonly] {
cursor: pointer;
}
.mu-menu.is-disabled & {
cursor: default;
pointer-events: none;
}
}
================================================
FILE: src/styles/components/overlay.less
================================================
.mu-overlay {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: #000;
opacity: .4;
z-index: 1000;
}
================================================
FILE: src/styles/components/pagination.less
================================================
@import "../import.less";
.mu-pagination {
display: flex;
justify-content: flex-start;
align-items: center;
color: @textColor;
font-size: 14px;
> ul {
display: flex;
list-style: 0;
margin: 0;
padding: 0;
li {
display: inline-block;
margin: 0 4px;
}
}
}
.mu-pagination-svg-icon {
width: 20px;
height: 20px;
fill: currentColor;
}
.mu-pagination-btn.mu-button {
height: 28px;
padding: 0;
width: 28px;
min-width: auto;
.mu-pagination__raised & {
background-color: @alternateTextColor;
.depth(2);
}
.mu-pagination__circle & {
width: 32px;
height: 32px;
border-radius: 50%;
}
&:first-child {
margin-right: 4px;
}
&:last-child {
margin-left: 4px;
}
.mu-button-wrapper {
padding: 0;
}
}
.mu-pagination-item.mu-button {
min-width: 32px;
height: 32px;
padding: 0 8px;
.mu-pagination__raised & {
background-color: @alternateTextColor;
.depth(2);
}
.mu-pagination__circle & {
width: 32px;
border-radius: 50%;
}
.mu-button-wrapper {
padding: 0;
}
&.is-current {
background-color: @primaryColor;
color: @alternateTextColor;
}
}
================================================
FILE: src/styles/components/paper.less
================================================
@import '../import.less';
.mu-paper {
transition: all .45s @easeOutFunction;
color: @textColor;
background-color: @dialogBackgroundColor;
}
.mu-paper-round {
border-radius: 2px;
}
.mu-paper-circle {
border-radius: 50%;
}
================================================
FILE: src/styles/components/picker.less
================================================
@import "../import.less";
.mu-picker {
color: @primaryColor;
background-color: @dialogBackgroundColor;
user-select: none;
width: 310px;
}
.mu-timepicker {
width: 280px;
}
.mu-datetime-picker {
.mu-tabs {
.depth(1);
}
.mu-picker-container {
position: relative;
}
.mu-fade-transition-leave-active {
position: absolute;
left: 0;
right: 0;
}
}
.mu-picker-landspace {
width: 479px;
display: flex;
justify-content: space-between;
}
.mu-picker-container {
padding-bottom: 8px;
flex: 1;
}
.mu-picker-display {
display: flex;
align-items: flex-start;
flex-direction: column;
justify-content: center;
height: 100px;
background-color: currentColor;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
border-bottom-left-radius: 0;
padding-left: 16px;
padding-right: 16px;
.mu-picker-landspace & {
width: 165px;
height: auto;
padding-top: 16px;
border-top-right-radius: 0;
border-bottom-left-radius: 2px;
position: relative;
}
}
.mu-date-display {
font-weight: 700;
}
@media (min-width: 600px){
.mu-picker-display {
padding-left: 24px;
padding-right: 24px;
}
.mu-picker-landspace .mu-picker-display {
padding-top: 24px;
}
}
.mu-time-display {
align-items: center;
}
.mu-date-time-display {
flex-direction: row;
justify-content: space-around;
align-items: center;
.mu-time-display-text {
font-size: 45px;
line-height: 45px;
}
.mu-date-display-monthday {
font-size: 34px;
line-height: 41px;
height: 41px;
}
.mu-time-display-time {
margin: 0 8px;
}
.mu-date-display,
.mu-time-display {
height: 65px;
}
.mu-time-display-text {
height: 100%;
align-items: flex-end;
margin: 0;
}
.mu-time-display-affix {
height: 45px;
padding-top: 7px;
}
.mu-date-display-year,
.mu-date-display-monthday,
.mu-time-display-clickable {
opacity: .7;
&.active {
opacity: 1;
}
}
}
.mu-date-display-year {
position: relative;
overflow: hidden;
margin: 0;
width: 100%;
font-size: 16px;
font-weight: 500;
line-height: 16px;
height: 16px;
opacity: 0.7;
transition: all .45s @easeOutFunction;
margin-bottom: 10px;
color: @alternateTextColor;
.mu-date-display.selected-year & {
opacity: 1;
}
}
.mu-date-display-year-title {
cursor: pointer;
.mu-date-display-year.disabled & {
cursor: not-allowed;
}
.mu-date-display.selected-year {
cursor: default;
}
}
.mu-date-display-monthday {
position: relative;
display: block;
overflow: hidden;
font-size: 36px;
line-height: 36px;
height: 38px;
transition: all .45s @easeOutFunction;
width: 100%;
font-weight: 500;
color: @alternateTextColor;
.mu-date-display.selected-year & {
opacity: 0.7;
}
.mu-picker-landspace & {
height: 100%;
}
}
.mu-date-display-slideIn-wrapper {
position: absolute;
height: 100%;
width: 100%;
top: 0px;
left: 0px;
}
.mu-date-display-monthday-title {
cursor: default;
width: 100%;
display: block;
.mu-date-display.selected-year & {
cursor: pointer;
}
}
.mu-date-display-next-enter-active,
.mu-date-display-next-leave-active,
.mu-date-display-prev-enter-active,
.mu-date-display-prev-leave-active {
transition: transform 450ms @easeOutFunction, opacity 450ms @easeOutFunction;
backface-visibility: hidden;
}
.mu-date-display-next-enter {
transform: translate3d(0, -100%, 0);
opacity: 0;
}
.mu-date-display-next-leave-active {
transform: translate3d(0, 100%, 0);
opacity: 0;
}
.mu-date-display-prev-enter {
transform: translate3d(0, 100%, 0);
opacity: 0;
}
.mu-date-display-prev-leave-active {
transform: translate3d(0, -100%, 0);
opacity: 0;
}
.mu-time-display-text {
color: @alternateTextColor;
margin: 6px 0px;
line-height: 58px;
height: 58px;
font-size: 58px;
display: flex;
justify-content: center;
align-items: center;
.mu-picker-landspace & {
margin: 0;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
height: auto;
align-items: center;
justify-content: center;
flex-direction: column;
font-size: 48px;
}
}
.mu-time-display-affix {
flex: 1 1;
position: relative;
line-height: 17px;
height: 17px;
font-size: 17px;
.mu-picker-landspace & {
flex: none;
height: auto;
display: flex;
flex-direction: column;
}
}
.mu-time-display-time {
margin: 0px 10px;
.mu-picker-landspace & {
margin-top: -28px;
}
}
.mu-time-display-clickable {
cursor: pointer;
&.inactive,
+ span {
opacity: 0.7
}
.mu-picker-landspace & {
margin-top: 8px;
}
}
.mu-time-display-affix-top {
position: absolute;
top: -20px;
left: 0px;
.mu-picker-landspace & {
position: static;
order: -1;
}
}
.mu-datepicker-monthday-container {
display: flex;
align-content: space-between;
justify-content: space-between;
flex-direction: column;
font-size: 12px;
font-weight: 400;
padding: 0px 8px;
transition: all .45s @easeOutFunction;
}
.mu-datepicker-week {
display: flex;
flex-direction: row;
justify-content: space-between;
font-weight: 500;
height: 20px;
line-height: 15px;
opacity: 0.5;
text-align: center;
color: @textColor;
}
.mu-datepicker-week-day {
width: 42px;
}
.mu-datepicker-monthday {
position: relative;
overflow: hidden;
height: 214px;
}
.mu-datepicker-monthday-slide {
height: 100%;
width: 100%;
}
.mu-datepicker-slide-next-enter-active,
.mu-datepicker-slide-next-leave-active,
.mu-datepicker-slide-prev-enter-active,
.mu-datepicker-slide-prev-leave-active {
transition: transform 450ms @easeOutFunction, opacity 450ms @easeOutFunction;
backface-visibility: hidden;
position: absolute;
left: 0;
right: 0;
top: 0;
}
.mu-datepicker-slide-next-enter {
transform: translate3d(100%, 0, 0);
}
.mu-datepicker-slide-next-leave-active {
transform: translate3d(-100%, 0, 0);
opacity: 0;
}
.mu-datepicker-slide-prev-enter {
transform: translate3d(-100%, 0, 0);
}
.mu-datepicker-slide-prev-leave-active {
transform: translate3d(100%, 0, 0);
opacity: 0;
}
.mu-datepicker-monthday-content {
display: flex;
flex-direction: column;
justify-content: flex-start;
font-weight: 400;
line-height: 2;
position: relative;
text-align: center;
}
.mu-datepicker-monthday-row {
display: flex;
flex-direction: row;
justify-content: space-around;
height: 34px;
margin-bottom: 2px;
}
.mu-datepicker-month-container {
display: flex;
align-content: space-between;
justify-content: space-between;
flex-direction: column;
font-size: 12px;
font-weight: 400;
padding: 0px 8px;
transition: all .45s @easeOutFunction;
}
.mu-datepicker-month {
position: relative;
overflow: hidden;
height: 234px;
}
.mu-datepicker-month-content {
display: flex;
flex-direction: column;
justify-content: flex-start;
font-weight: 400;
line-height: 2;
position: relative;
text-align: center;
}
.mu-datepicker-month-row {
display: flex;
flex-direction: row;
justify-content: space-around;
margin-bottom: 2px;
}
.mu-datepicker-toolbar {
display: flex;
justify-content: space-between;
height: 48px;
}
.mu-datepicker-tool-btn {
color: @textColor;
}
.mu-datepicker-toolbar-title-wrapper {
position: relative;
overflow: hidden;
height: 100%;
font-size: 14px;
font-weight: 500;
text-align: center;
width: 100%;
}
.mu-datepicker-toolbar-title {
position: absolute;
height: 100%;
width: 100%;
top: 0px;
left: 0px;
line-height: 48px;
color: @textColor;
&.clickable {
cursor: pointer;
&:hover {
color: currentColor;
}
}
}
.mu-datetime-picker-svg {
display: block;
fill: currentColor;
height: 24px;
width: 24px;
user-select: none;
}
.mu-datepicker-svg-icon {
display: block;
fill: currentColor;
height: 24px;
width: 24px;
user-select: none;
color: @textColor;
}
.mu-datepicker-year-container {
display: flex;
justify-content: space-between;
flex-direction: column;
margin-top: 10px;
width: 310px;
height: 272px;
overflow: hidden;
}
.mu-datepicker-year {
height: inherit;
line-height: 35px;
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
position: relative;
}
.mu-datepicker-year-list {
display: flex;
flex-direction: column;
justify-content: center;
min-height: 100%;
}
.mu-day-button {
display: inline-block;
background: none;
user-select: none;
outline: none;
text-decoration: none;
cursor: pointer;
margin: 0px;
padding: 4px 0px;
font-size: inherit;
font-weight: 400;
position: relative;
border: 10px;
width: 42px;
color: inherit;
&.disabled {
opacity: .4;
cursor: not-allowed;
}
}
.mu-day-empty {
font-weight: 400;
padding: 4px 0px;
position: relative;
width: 42px;
}
.mu-day-button-bg {
position: absolute;
top: 0;
left: 4px;
height: 34px;
background-color: currentColor;
border-radius: 50%;
opacity: 0;
transform: scale(0);
transition: all .45s @easeOutFunction;
width: 34px;
.mu-day-button:hover:not(:disabled) &,
.mu-day-button.selected & {
transform: scale(1);
}
.mu-day-button:hover:not(:disabled) & {
opacity: 0.6;
}
.mu-day-button.selected & {
opacity: 1;
}
}
.mu-day-button-text {
font-weight: 400;
position: relative;
color: @textColor;
.mu-day-button.now & {
color: currentColor;
}
.mu-day-button:hover:not(:disabled) &,
.mu-day-button.selected & {
color: @alternateTextColor;
}
}
.mu-month-button {
display: inline-block;
background: none;
user-select: none;
outline: none;
text-decoration: none;
cursor: pointer;
margin: 0px;
font-size: inherit;
font-weight: 400;
position: relative;
border: 10px;
width: 84px;
height: 56px;
padding: 10px 0;
color: inherit;
&:disabled {
cursor: not-allowed;
}
}
.mu-month-button-bg {
position: absolute;
left: 0;
right: 0;
top: 10px;
bottom: 10px;
background-color: currentColor;
border-radius: 2px;
opacity: 0;
.mu-month-button:hover & {
opacity: 0.6;
}
.mu-month-button.selected & {
opacity: 1;
}
.mu-month-button:disabled & {
opacity: 0;
}
}
.mu-month-button-text {
color: @textColor;
position: relative;
.mu-month-button:hover &,
.mu-month-button.selected & {
color: @alternateTextColor;
}
.mu-month-button:disabled & {
color: @disabledColor;
}
}
.mu-year-button {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
background: none;
cursor: pointer;
outline: none;
text-decoration: none;
margin: 0px auto;
padding: 0px;
font-size: 14px;
font-weight: inherit;
text-align: center;
line-height: inherit;
color: currentColor;
border: none;
height: 36px;
&:hover {
background-color: fade(@textColor, 10%);
}
&.selected {
height: 40px;
margin: 10px 0;
}
}
.mu-year-button-text {
align-self: center;
color: @textColor;
font-size: 16px;
line-height: 1.1;
font-weight: 400;
position: relative;
.mu-year-button.selected & {
color: currentColor;
font-size: 26px;
font-weight: 500;
}
.mu-year-button:hover & {
color: currentColor;
}
}
.mu-timepicker-clock {
height: 282px;
padding-left: 10px;
padding-right: 10px;
position: relative;
}
.mu-timepicker-circle {
position: absolute;
top: 12px;
width: 260px;
height: 260px;
border-radius: 100%;
background-color: fade(@darkBlack, 7%);
left: 50%;
margin-left: -130px;
.mu-picker-landspace & {
left: 50%;
margin-left: -130px;
}
}
.mu-timepicker-hours {
height: 100%;
width: 100%;
border-radius: 100%;
position: relative;
pointer-events: none;
box-sizing: border-box;
}
.mu-timepicker-hours-mask {
height: 100%;
width: 100%;
pointer-events: auto;
}
.mu-timepicker-minutes {
height: 100%;
width: 100%;
border-radius: 100%;
position: relative;
pointer-events: none;
box-sizing: border-box;
}
.mu-timepicker-minutes-mask {
height: 100%;
width: 100%;
pointer-events: auto;
}
.mu-timepicker-number {
display: inline-block;
width: 32px;
height: 32px;
line-height: 32px;
position: absolute;
top: 10px;
text-align: center;
font-size: 1.1em;
pointer-events: none;
border-radius: 100%;
box-sizing: border-box;
transform: translate(0px, 5px);
user-select: none;
color: @textColor;
}
.mu-timepicker-number__inner {
width: 28px;
height: 28px;
line-height: 28px;
}
.mu-timepicker-number__selected {
background-color: @primaryColor;
color: @alternateTextColor;
}
.mu-timepicker-pointer {
height: 40%;
background-color: currentColor;
width: 2px;
left: calc(50% - 1px);
position: absolute;
bottom: 50%;
transform-origin: center bottom 0px;
pointer-events: none;
&.inner {
height: 30%;
}
}
.mu-timepicker-pointer-mark {
box-sizing: content-box;
background-color: @alternateTextColor;
border: 4px solid currentColor;
width: 7px;
height: 7px;
position: absolute;
top: -5px;
left: -6px;
border-radius: 100%;
&.has-selected {
display: none;
}
}
.mu-timepicker-list {
display: flex;
justify-content: space-between;
margin-top: 10px;
height: 272px;
overflow: hidden;
}
.mu-timepicker-list-hours {
border-right: 1px solid @borderColor;
}
.mu-timepicker-list-hours,
.mu-timepicker-list-minutes {
width: 50%;
flex-shrink: 0;
height: inherit;
line-height: 35px;
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
&:hover::-webkit-scrollbar {
display: block;
}
&::-webkit-scrollbar {
width: 2px;
display: none;
}
&::-webkit-scrollbar-track {
background: #E3E3E3;
}
&::-webkit-scrollbar-thumb {
background: #C1C1C1;
border-radius: 2px;
}
}
.mu-timepicker-hour-button,
.mu-timepicker-minute-button {
position: relative;
display: block;
width: 100%;
background: none;
cursor: pointer;
outline: none;
text-decoration: none;
margin: 0px auto;
padding: 0px;
font-size: 14px;
font-weight: inherit;
text-align: center;
line-height: inherit;
color: @textColor;
border: none;
text-align: center;
height: 40px;
&:hover {
background-color: fade(@textColor, 10%);
}
&.is-active {
color: currentColor;
font-size: 26px;
}
}
.mu-picker-actions {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin: 0px;
max-height: 48px;
padding: 0px;
.mu-flat-button {
min-width: 64px;
margin: 4px 8px 0px 0px;
}
}
================================================
FILE: src/styles/components/popover.less
================================================
@import '../import.less';
.mu-popover{
position: fixed;
background: @dialogBackgroundColor;
border-radius: 2px;
max-height: 100%;
max-width: 80%;
.scrollable();
.depth(8);
&.transition-bottom-start {
transform-origin: left top;
}
&.transition-bottom {
transform-origin: center top;
&.mu-popover-transition-enter,
&.mu-popover-transition-leave-active {
transform: scaleY(.5);
}
}
&.transition-bottom-end {
transform-origin: right top;
}
&.transition-top-start {
transform-origin: left bottom;
}
&.transition-top {
transform-origin: center bottom;
&.mu-popover-transition-enter,
&.mu-popover-transition-leave-active {
transform: scaleY(.5);
}
}
&.transition-top-end {
transform-origin: right bottom;
}
&.transition-left-start {
transform-origin: right top;
}
&.transition-left {
transform-origin: right center;
}
&.transition-left-end {
transform-origin: right bottom;
}
&.transition-right-start {
transform-origin: left top;
}
&.transition-right {
transform-origin: left center;
}
&.transition-right-end {
transform-origin: left bottom;
}
}
================================================
FILE: src/styles/components/progress.less
================================================
@import "../import.less";
.mu-linear-progress{
position: relative;
height: 4px;
display: block;
width: 100%;
margin: 0px;
overflow: hidden;
&.mu-secondary-color {
background-color: transparent;
.mu-linear-progress-background,
.mu-linear-progress-indeterminate,
.mu-linear-progress-determinate {
background-color: @secondaryColor;
}
}
&.mu-success-color {
background-color: transparent;
.mu-linear-progress-background,
.mu-linear-progress-indeterminate,
.mu-linear-progress-determinate {
background-color: @successColor;
}
}
&.mu-warning-color {
background-color: transparent;
.mu-linear-progress-background,
.mu-linear-progress-indeterminate,
.mu-linear-progress-determinate {
background-color: @warningColor;
}
}
&.mu-info-color {
background-color: transparent;
.mu-linear-progress-background,
.mu-linear-progress-indeterminate,
.mu-linear-progress-determinate {
background-color: @infoColor;
}
}
&.mu-error-color {
background-color: transparent;
.mu-linear-progress-background,
.mu-linear-progress-indeterminate,
.mu-linear-progress-determinate {
background-color: @errorColor;
}
}
&.mu-primary-color {
background-color: transparent;
}
}
.mu-linear-progress-background {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: @primaryColor;
opacity: 0.3;
}
.mu-linear-progress-indeterminate{
position: absolute;
top: 0;
bottom: 0;
width: 40%;
background-color: @primaryColor;
animation: mu-linear-progress-animate 840ms @easeInOutFunction;
animation-iteration-count: infinite;
}
.mu-linear-progress-determinate{
position: absolute;
top: 0;
bottom: 0;
left: 0;
background-color: @primaryColor;
transition: width .3s linear;
}
@keyframes mu-linear-progress-animate {
0% {
left: -40%;
}
100% {
left: 100%;
}
}
.mu-focus-ripple{
position: absolute;
height: 100%;
width: 100%;
border-radius: 50%;
opacity: 0.16;
background-color: currentColor;
animation: mu-pulsate 750ms @easeInOutFunction;
animation-iteration-count: infinite;
animation-direction: alternate;
}
@keyframes mu-pulsate {
0% {
transform: scale(0.72);
}
100% {
transform: scale(0.85);
}
}
.mu-circle-wrapper {
display: inline-block;
position: relative;
width: 48px;
height: 48px;
&.active{
-webkit-animation: container-rotate 1568ms linear infinite;
animation: container-rotate 1568ms linear infinite;
}
.mu-circle {
border-radius: 50%;
}
.left {
float: left !important;
}
.right {
float: right !important;
}
}
.mu-circle-spinner {
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
border-color: @primaryColor;
opacity: 1;
-webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
&.mu-secondary-color {
border-color: @secondaryColor;
background-color: transparent;
}
&.mu-success-color {
border-color: @successColor;
background-color: transparent;
}
&.mu-warning-color {
border-color: @warningColor;
background-color: transparent;
}
&.mu-info-color {
border-color: @infoColor;
background-color: transparent;
}
&.mu-error-color {
border-color: @errorColor;
background-color: transparent;
}
&.mu-primary-color {
background-color: transparent;
}
&.mu-inverse {
color: inherit;
}
}
.mu-circle-clipper {
display: inline-block;
position: relative;
width: 50%;
height: 100%;
overflow: hidden;
border-color: inherit;
}
.mu-circle-gap-patch {
position: absolute;
top: 0;
left: 45%;
width: 10%;
height: 100%;
overflow: hidden;
border-color: inherit;
}
.mu-circle-gap-patch .mu-circle {
width: 1000%;
left: -450%;
}
.mu-circle-clipper .mu-circle {
width: 200%;
height: 100%;
border-width: 3px;
border-style: solid;
border-color: inherit;
border-bottom-color: transparent !important;
border-radius: 50%;
-webkit-animation: none;
animation: none;
position: absolute;
top: 0;
right: 0;
bottom: 0;
}
.mu-circle-spinner.active .mu-circle-clipper.left .mu-circle {
-webkit-animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
animation: left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
}
.mu-circle-spinner.active .mu-circle-clipper.right .mu-circle {
-webkit-animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
animation: right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;
}
.mu-circle-clipper.left .mu-circle {
left: 0;
border-right-color: transparent !important;
-webkit-transform: rotate(129deg);
transform: rotate(129deg);
}
.mu-circle-clipper.right .mu-circle {
left: -100%;
border-left-color: transparent !important;
-webkit-transform: rotate(-129deg);
transform: rotate(-129deg);
}
@-webkit-keyframes fill-unfill-rotate {
12.5% {
-webkit-transform: rotate(135deg);
}
25% {
-webkit-transform: rotate(270deg);
}
37.5% {
-webkit-transform: rotate(405deg);
}
50% {
-webkit-transform: rotate(540deg);
}
62.5% {
-webkit-transform: rotate(675deg);
}
75% {
-webkit-transform: rotate(810deg);
}
87.5% {
-webkit-transform: rotate(945deg);
}
to {
-webkit-transform: rotate(1080deg);
}
}
@keyframes fill-unfill-rotate {
12.5% {
transform: rotate(135deg);
}
25% {
transform: rotate(270deg);
}
37.5% {
transform: rotate(405deg);
}
50% {
transform: rotate(540deg);
}
62.5% {
transform: rotate(675deg);
}
75% {
transform: rotate(810deg);
}
87.5% {
transform: rotate(945deg);
}
to {
transform: rotate(1080deg);
}
}
@-webkit-keyframes left-spin {
from {
-webkit-transform: rotate(130deg);
}
50% {
-webkit-transform: rotate(-5deg);
}
to {
-webkit-transform: rotate(130deg);
}
}
@keyframes left-spin {
from {
transform: rotate(130deg);
}
50% {
transform: rotate(-5deg);
}
to {
transform: rotate(130deg);
}
}
@-webkit-keyframes right-spin {
from {
-webkit-transform: rotate(-130deg)
}
50% {
-webkit-transform: rotate(5deg)
}
to {
-webkit-transform: rotate(-130deg)
}
}
@keyframes right-spin {
from {
transform: rotate(-130deg)
}
50% {
transform: rotate(5deg)
}
to {
transform: rotate(-130deg)
}
}
@-webkit-keyframes container-rotate {
to {
-webkit-transform: rotate(360deg);
}
}
@keyframes container-rotate {
to {
transform: rotate(360deg);
}
}
.mu-circular-progress {
display: inline-block;
position: relative;
overflow: hidden;
&.mu-secondary-color {
background: transparent;
.mu-circular-progress-determinate-path {
stroke: @secondaryColor;
}
}
&.mu-success-color {
background: transparent;
.mu-circular-progress-determinate-path {
stroke: @successColor;
}
}
&.mu-warning-color {
background: transparent;
.mu-circular-progress-determinate-path {
stroke: @warningColor;
}
}
&.mu-info-color {
background: transparent;
.mu-circular-progress-determinate-path {
stroke: @infoColor;
}
}
&.mu-error-color {
background: transparent;
.mu-circular-progress-determinate-path {
stroke: @errorColor;
}
}
&.mu-primary-color {
background: transparent;
}
&.mu-inverse {
color: inherit;
}
}
.mu-circular-progress-determinate{
position: relative;
}
.mu-circular-progress-determinate-path{
stroke: @primaryColor;
stroke-linecap: round;
transition: all 0.3s linear;
}
================================================
FILE: src/styles/components/radio.less
================================================
@import "../import.less";
.mu-radio {
position: relative;
display: inline-block;
height: 24px;
line-height: 24px;
cursor: pointer;
user-select: none;
outline: none;
input[type="radio"] {
display: none;
}
&.disabled {
cursor: not-allowed;
color: @disabledColor;
}
}
.mu-radio-checked {
color: @primaryColor;
.mu-radio-icon-uncheck {
opacity: 0;
transform: scale(0);
}
.mu-radio-icon-checked {
opacity: 1;
transform: scale(1);
}
}
.mu-radio-wrapper{
display: flex;
width: 100%;
height: 24px;
align-items: center;
justify-content: space-between;
}
.mu-radio-icon{
width: 24px;
height: 24px;
vertical-align: middle;
position: relative;
margin-right: 8px;
.mu-radio.label-left &{
margin-right: 0;
margin-left: 8px;
}
.mu-radio.no-label &{
margin-left: 0;
margin-right: 0;
}
}
.mu-radio-label {
color: @textColor;
white-space: nowrap;
font-size: 16px;
.mu-radio.disabled & {
color: @disabledColor;
}
}
.mu-radio-svg-icon{
display: inline-block;
fill: currentColor;
height: 24px;
width: 24px;
user-select: none;
}
.mu-radio-icon-uncheck {
position: absolute;
left: 0;
top: 0;
opacity: 1;
transition: all 450ms @easeOutFunction;
}
.mu-radio-icon-checked {
position: absolute;
left: 0;
top: 0;
opacity: 0;
transform: scale(0);
transition: all 450ms @easeOutFunction;
}
.mu-radio-ripple-wrapper {
width: 48px;
height: 48px;
top: -12px;
left: -12px;
position: absolute;
.mu-radio.label-left & {
right: -12px;
left: auto;
}
}
================================================
FILE: src/styles/components/select.less
================================================
@import "../import.less";
.mu-select {
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
outline: none;
cursor: pointer;
&.is-readonly,
&.is-disabled {
cursor: default;
}
}
.mu-select-content {
flex: 1;
color: @textColor;
width: 100%;
min-height: 32px;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: center;
.mu-chip {
margin: 4px 4px 4px 0;
}
}
.mu-select-input {
appearance: none;
outline: none;
border: none;
background: none;
border-radius: 0 0 0 0;
box-shadow: none;
display: block;
padding: 0;
margin: 0;
width: 100%;
height: 32px;
font-style: inherit;
font-variant: inherit;
font-weight: inherit;
font-stretch: inherit;
font-size: inherit;
font-family: inherit;
color: @textColor;
font-family: inherit;
position: relative;
flex: 1;
cursor: inherit;
&.is-enable {
cursor: text;
}
&.is-break {
min-width: 100%;
}
}
.mu-select-action {
.flex-shrink(0);
padding: 0 6px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
}
.mu-select-icon {
fill: currentColor;
width: 24px;
height: 24px;
user-select: none;
transition: .3s @easeOutFunction;
.mu-select.is-open & {
transform: rotate(180deg);
}
}
.mu-selection-text {
&.is-active {
color: @primaryColor;
}
}
.mu-select-no-data {
height: 36px;
padding: 0 16px;
line-height: 36px;
color: @disabledColor;
}
.mu-option-list.mu-list {
outline: none;
.scrollable();
overflow-x: hidden;
overflow-y: auto;
}
.mu-option {
&.is-selected {
.mu-item {
color: @secondaryColor;
}
}
&.is-focused {
background-color: fade(@textColor, 10%);
}
&.is-disabled {
.mu-item {
color: @disabledColor;
}
}
}
================================================
FILE: src/styles/components/slide-picker.less
================================================
@import '../import.less';
.mu-slide-picker{
background: @dialogBackgroundColor;
overflow: hidden;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
position: relative;
-webkit-mask-box-image: -webkit-linear-gradient(bottom, transparent, transparent 5%, white 20%, white 80%, transparent 95%, transparent);
-webkit-mask-box-image: linear-gradient(to top, transparent, transparent 5%, white 20%, white 80%, transparent 95%, transparent);
}
.mu-slide-picker-center-highlight {
height: 36px;
box-sizing: border-box;
position: absolute;
left: 0;
width: 100%;
top: 50%;
margin-top: -18px;
pointer-events: none;
border-top: 1px solid @borderColor;
border-bottom: 1px solid @borderColor;
}
.mu-slide-picker-center-highlight:before {
left: 0;
top: 0;
bottom: auto;
right: auto;
}
.mu-slide-picker-center-highlight:after {
left: 0;
bottom: 0;
right: auto;
top: auto;
}
.mu-slide-picker-slot{
font-size: 18px;
overflow: hidden;
position: relative;
max-height: 100%;
text-align: center;
&.mu-slide-picker-slot-divider{
color: @textColor;
display: flex;
align-items: center;
line-height: 36px;
}
}
.mu-slide-picker-slot-wrapper.animate{
transition: transform .45s @easeOutFunction;
backface-visibility: hidden;
}
.mu-slide-picker-item{
height: 36px;
line-height: 36px;
padding: 0 10px;
font-size: 20px;
white-space: nowrap;
position: relative;
overflow: hidden;
text-overflow: ellipsis;
color: @secondaryTextColor;
left: 0;
top: 0;
width: 100%;
box-sizing: border-box;
transition-duration: .3s;
backface-visibility: hidden;
&.selected {
color: @textColor;
transform: translate3d(0, 0, 0) rotateX(0);
}
}
================================================
FILE: src/styles/components/slider.less
================================================
@import "../import.less";
.mu-slider {
width: 100%;
position: relative;
height: 24px;
margin-bottom: 16px;
display: flex;
align-items: center;
cursor: default;
user-select:none;
outline: none;
color: @primaryColor;
}
.mu-slider-display-value {
position: absolute;
top: -30px;
display: none;
width: 26px;
height: 26px;
text-align: center;
line-height: 26px;
font-size: 10px;
background: currentColor;
border-radius: 50% 50% 50% 0;
transform: scale(1) rotate(-45deg) translate(-11px, -8px);
.mu-slider.active &{
display: block;
}
.display-value-text {
display: inline-block;
color: @alternateTextColor;
transform: rotate(45deg);
}
}
.mu-slider-track{
position: absolute;
height: 2px;
left: 0;
right: 0;
top: 50%;
margin-top: -1px;
background-color: @trackColor;
}
.mu-slider-fill{
position: absolute;
height: 2px;
width: 100%;
background-color: currentColor;
left: 0;
top: 50%;
margin-top: -1px;
.mu-slider.disabled & {
background-color: @trackColor;
}
}
.mu-slider-thumb {
position: absolute;
top: 50%;
width: 12px;
height: 12px;
background-color: currentColor;
color: currentColor;
border-radius: 50%;
transform: translate(-50%, -50%);
transition: background 450ms @easeOutFunction, border-color 450ms @easeOutFunction, width 450ms @easeOutFunction, height 450ms @easeOutFunction;
cursor: pointer;
.mu-slider.active & {
width: 20px;
height: 20px;
}
.mu-slider.display-value & {
width: 0;
height: 0;
}
.mu-slider.zero &,
.mu-slider.disabled &{
border: 2px solid @trackColor;
color: @trackColor;
background-color: @alternateTextColor;
.mu-focus-ripple-wrapper {
top: -14px;
left: -14px;
}
}
.mu-slider.disabled & {
cursor: default;
}
.mu-focus-ripple-wrapper {
width: 36px;
height: 36px;
top: -12px;
left: -12px;
}
}
================================================
FILE: src/styles/components/snackbar.less
================================================
@import "../import.less";
.mu-snackbar {
display: flex;
justify-content: space-between;
align-items: center;
color: @alternateTextColor;
background-color: @textColor;
border-radius: 2px;
padding: 6px 16px;
line-height: 20px;
font-size: 14px;
min-height: 48px;
min-width: 288px;
max-width: 568px;
position: fixed;
flex-wrap: wrap;
.depth(1);
.mu-icon {
margin-right: 16px;
font-size: 20px;
}
}
.mu-snackbar-action {
display: flex;
justify-content: flex-start;
align-items: center;
flex-shrink: 0;
margin-right: -16px;
padding: 0 8px;
margin-left: auto;
.mu-circle-ripple {
opacity: .2;
}
}
.mu-snackbar-message {
padding: 8px 0;
display: flex;
justify-content: flex-start;
align-items: center;
}
.mu-snackbar-top {
left: 50%;
transform: translate3d(-50%, 0, 0);
top: 0;
&.mu-slide-bottom-transition-enter,
&.mu-slide-bottom-transition-leave-active {
transform: translate3d(-50%, 100%, 0);
}
&.mu-slide-top-transition-enter,
&.mu-slide-top-transition-leave-active {
transform: translate3d(-50%, -100%, 0);
}
}
.mu-snackbar-top-start {
left: 8px;
top: 8px;
}
.mu-snackbar-top-end {
right: 8px;
top: 8px;
}
.mu-snackbar-bottom {
left: 50%;
transform: translate3d(-50%, 0, 0);
bottom: 0;
&.mu-slide-bottom-transition-enter,
&.mu-slide-bottom-transition-leave-active {
transform: translate3d(-50%, 100%, 0);
}
&.mu-slide-top-transition-enter,
&.mu-slide-top-transition-leave-active {
transform: translate3d(-50%, -100%, 0);
}
}
.mu-snackbar-bottom-start {
left: 8px;
bottom: 8px;
}
.mu-snackbar-bottom-end {
right: 8px;
bottom: 8px;
}
@media only screen and (max-width: 600px) {
.mu-snackbar {
width: 100%;
max-width: 100%;
left: 0;
right: 0;
transform: translate3d(0, 0, 0);
&.mu-slide-bottom-transition-enter,
&.mu-slide-bottom-transition-leave-active {
transform: translate3d(0, 100%, 0);
}
&.mu-slide-top-transition-enter,
&.mu-slide-top-transition-leave-active {
transform: translate3d(0, -100%, 0);
}
}
.mu-snackbar-top-start,
.mu-snackbar-top-end {
top: 0;
}
.mu-snackbar-bottom-start,
.mu-snackbar-bottom-end {
bottom: 0;
}
}
================================================
FILE: src/styles/components/stepper.less
================================================
@import "../import.less";
.mu-stepper{
display: flex;
flex-direction: row;
align-content: center;
align-items: center;
justify-content: space-between;
}
.mu-stepper-vertical{
flex-direction: column;
align-items: stretch;
}
.mu-step{
flex: 0 0 auto;
margin-left: -6px;
.mu-stepper-vertical & {
margin-top: -14px;
margin-left: 0;
}
&:first-child {
margin-left: 0;
}
}
.mu-step-button {
border: 10px;
display: inline-block;
cursor: pointer;
text-decoration: none;
margin: 0px;
padding: 0px;
outline: none;
font-size: inherit;
font-weight: inherit;
transform: translate(0px, 0px);
background-color: transparent;
transition: all 450ms @easeOutFunction 0ms;
.mu-stepper-vertical & {
width: 100%;
}
&.hover {
background-color: fade(@black, 6%);
}
}
.mu-step-connector{
flex: 1 1 auto;
.mu-stepper-vertical &{
margin-left: 25px;
}
}
.mu-step-connector-line{
display: block;
border-color: @grey400;
margin-left: -6px;
border-top-style: solid;
border-top-width: 1px;
.mu-stepper-vertical & {
border-top: none;
border-left-style: solid;
border-left-width: 1px;
min-height: 28px;
margin-left: 0;
}
}
.mu-step-content{
margin-top: -14px;
margin-left: 25px;
padding-left: 21px;
padding-right: 16px;
overflow: hidden;
.mu-stepper-vertical & {
border-left: 1px solid @grey400;
}
&.last {
border-left: none;
}
}
.mu-step-content-inner {
position: relative;
width: 100%;
top: 0px;
left: 0px;
overflow: hidden;
}
.mu-step-label{
height: 72px;
color: @textColor;
display: flex;
align-items: center;
font-size: 14px;
padding-left: 14px;
padding-right: 14px;
.mu-stepper-vertical & {
height: 64px;
}
&.disabled {
color: @disabledColor;
cursor: not-allowed;
}
&.active {
font-weight: 500;
}
}
.mu-step-label-icon-container{
display: flex;
align-items: center;
margin-right: 8px;
width: 24px;
}
.mu-step-label-icon {
display: block;
font-size: 24px;
width: 24px;
height: 24px;
color: @grey500;
fill: currentColor;
.mu-step-label.disabled &{
color: @grey500;
}
.mu-step-label.completed &,
.mu-step-label.active & {
color: @primaryColor;
}
}
.mu-step-label-circle{
width: 20px;
height: 20px;
font-size: 12px;
line-height: 20px;
text-align: center;
overflow: hidden;
border-radius: 100%;
background-color: @grey500;
color: @alternateTextColor;
.mu-step-label.disabled &{
background-color: @grey500;
}
.mu-step-label.completed &,
.mu-step-label.active & {
background-color: @primaryColor;
}
}
================================================
FILE: src/styles/components/subheader.less
================================================
@import "../import.less";
.mu-sub-header {
color: @secondaryTextColor;
font-size: 14px;
line-height: 48px;
padding-left: 16px;
width: 100%;
&.inset{
padding-left: 72px;
}
}
================================================
FILE: src/styles/components/switch.less
================================================
@import "../import.less";
.mu-switch {
position: relative;
display: inline-block;
height: 24px;
line-height: 24px;
cursor: pointer;
user-select: none;
outline: none;
input[type="checkbox"] {
display: none;
}
&.disabled {
input[type="checkbox"]:checked{
+ .mu-switch-wrapper {
.mu-switch-track{
background-color: @trackColor;
}
.mu-switch-thumb{
background-color: @grey300;
}
}
}
}
* {
pointer-events: none;
}
&.disabled {
cursor: not-allowed;
}
}
.mu-switch-checked {
color: @primaryColor;
.mu-switch-track{
background-color: currentColor;
opacity: 0.5;
}
.mu-switch-thumb{
background-color: currentColor;
transform: translate3d(18px, 0, 0);
}
}
.mu-switch-wrapper{
display: flex;
width: 100%;
height: 24px;
align-items: center;
justify-content: space-between;
}
.mu-switch-container{
width: 38px;
padding: 4px 0px 4px 2px;
position: relative;
margin-right: 8px;
.mu-switch.label-left &{
margin-right: 0;
margin-left: 8px;
}
.mu-switch.no-label &{
margin-left: 0;
margin-right: 0;
}
}
.mu-switch-label {
color: @textColor;
.mu-switch.disabled & {
color: @disabledColor;
}
}
.mu-switch-track {
width: 100%;
height: 14px;
border-radius: 30px;
background-color: @trackColor;
transition: all 450ms @easeOutFunction;
.mu-switch.disabled & {
background-color: @trackColor;
}
}
.mu-switch-thumb {
position: absolute;
top: 1px;
left: 0px;
width: 20px;
height: 20px;
line-height: 24px;
background-color: @backgroundColor;
border-radius: 50%;
.depth(1);
transition: all 450ms @easeOutFunction;
backface-visibility: hidden;
.mu-switch.disabled & {
background-color: @grey300;
}
}
.mu-switch-ripple-wrapper {
height: 200%;
width: 200%;
top: -10px;
left: -10px;
position: absolute;
}
================================================
FILE: src/styles/components/tabs.less
================================================
@import '../import.less';
.mu-tabs{
display: flex;
justify-content: flex-start;
align-items: center;
background-color: @primaryColor;
color: fade(@alternateTextColor, 70%);
position: relative;
z-index: 100;
width: 100%;
overflow: hidden;
}
.mu-tabs-inverse {
background-color: @backgroundColor;
color: @secondaryTextColor;
}
.mu-tabs-center {
justify-content: center;
}
.mu-tab-link-highlight{
position: absolute;
left: 0;
bottom: 0;
height: 2px;
background-color: @secondaryColor;
transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
will-change: width transform;
backface-visibility: hidden;
}
.mu-tab{
font-size: 14px;
min-width: 72px;
max-width: 264px;
background: none;
appearance: none;
text-decoration: none;
border: none;
outline: none;
color: inherit;
position: relative;
line-height: normal;
transition: all .45s @easeInOutFunction;
cursor: pointer;
.mu-tabs-full-width & {
flex: 1;
max-width: 100%;
}
.mu-icon {
margin-bottom: 8px;
}
}
.mu-tab-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 48px;
padding: 12px;
}
.mu-tab-active {
color: @alternateTextColor;
&.is-inverse {
color: @textColor;
}
}
@media (min-width: 960px) {
.mu-tab {
min-width: 160px;
}
}
================================================
FILE: src/styles/components/text-field.less
================================================
@import "../import.less";
.mu-text-field {
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
}
.mu-text-field-input {
appearance: none;
outline: none;
border: none;
background: none;
border-radius: 0 0 0 0;
box-shadow: none;
display: block;
padding: 0;
margin: 0;
width: 100%;
height: 32px;
font-style: inherit;
font-variant: inherit;
font-weight: inherit;
font-stretch: inherit;
font-size: inherit;
font-family: inherit;
color: @textColor;
font-family: inherit;
position: relative;
flex: 1;
}
.mu-text-field-action {
.flex-shrink(0);
padding: 0 6px;
cursor: pointer;
}
.mu-text-field-suffix {
color: @secondaryTextColor;
white-space: nowrap;
.flex-shrink(0);
}
.mu-text-field-textarea{
resize: vertical;
line-height: 1.5;
position: relative;
height: 100%;
resize: none;
}
.mu-text-field-multiline{
width: 100%;
position: relative;
}
.mu-text-field-textarea-hide{
width: 100%;
height: initial;
resize: none;
position: absolute;
padding: 0;
overflow: auto;
visibility: hidden;
}
================================================
FILE: src/styles/components/tooltip.less
================================================
@import "../import.less";
.mu-tooltip {
position: fixed;
font-size: 10px;
line-height: 22px;
padding: 4px 8px;
color: @fullWhite;
border-radius: 2px;
background-color: @grey700;
opacity: 0.9;
left: 300px;
top: 400px;
}
.mu-tooltip-top-enter-active,
.mu-tooltip-top-leave-active {
transition: transform .3s @easeOutFunction, opacity .3s @easeOutFunction;
backface-visibility: hidden;
}
.mu-tooltip-top-enter,
.mu-tooltip-top-leave-active {
transform: translate3d(0, 60%, 0);
opacity: 0;
}
.mu-tooltip-bottom-enter-active,
.mu-tooltip-bottom-leave-active {
transition: transform .3s @easeOutFunction, opacity .3s @easeOutFunction;
backface-visibility: hidden;
}
.mu-tooltip-bottom-enter,
.mu-tooltip-bottom-leave-active {
transform: translate3d(0, -60%, 0);
opacity: 0;
}
.mu-tooltip-left-enter-active,
.mu-tooltip-left-leave-active {
transition: transform .3s @easeOutFunction, opacity .3s @easeOutFunction;
backface-visibility: hidden;
}
.mu-tooltip-left-enter,
.mu-tooltip-left-leave-active {
transform: translate3d(24px, 0, 0);
opacity: 0;
}
.mu-tooltip-right-enter-active,
.mu-tooltip-right-leave-active {
transition: transform .3s @easeOutFunction, opacity .3s @easeOutFunction;
backface-visibility: hidden;
}
.mu-tooltip-right-enter,
.mu-tooltip-right-leave-active {
transform: translate3d(-24px, 0, 0);
opacity: 0;
}
================================================
FILE: src/styles/components/touch-ripple.less
================================================
.mu-ripple-wrapper {
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
}
================================================
FILE: src/styles/import.less
================================================
@import "./vars.less";
@import "./mixins.less";
================================================
FILE: src/styles/index.less
================================================
================================================
FILE: src/styles/mixins.less
================================================
.transition(@d) {
-webkit-transition-duration: @d;
transition-duration: @d;
}
.delay(@d) {
-webkit-transition-delay: @d;
transition-delay: @d;
}
.transform(@t) {
-webkit-transform: @t;
transform: @t;
}
.transform-origin(@to) {
-webkit-transform-origin: @to;
transform-origin: @to;
}
.translate3d(@x:0, @y:0, @z:0) {
-webkit-transform: translate3d(@x,@y,@z);
transform: translate3d(@x,@y,@z);
}
.animation (@a) {
-webkit-animation: @a;
animation: @a;
}
.scrollable() {
overflow: auto;
-webkit-overflow-scrolling: touch;
}
.flex-shrink(@fs) {
-webkit-box-flex: @fs;
-webkit-flex-shrink: @fs;
-ms-flex: 0 @fs auto;
flex-shrink: @fs;
}
.clearfix() {
&:after,
&:before {
content: " ";
display: table;
}
&:after {
clear: both;
}
}
.hairline(@position, @color) when (@position = top) {
&:before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: auto;
right: auto;
height: 1px;
width: 100%;
background-color: @color;
display: block;
z-index: 15;
// .transform-origin(50% 0%);
html.pixel-ratio-2 & {
.transform(scaleY(0.5));
}
html.pixel-ratio-3 & {
.transform(scaleY(0.33));
}
}
}
.hairline(@position, @color) when (@position = left) {
&:before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: auto;
right: auto;
width: 1px;
height: 100%;
background-color: @color;
display: block;
z-index: 15;
// .transform-origin(0% 50%);
html.pixel-ratio-2 & {
.transform(scaleX(0.5));
}
html.pixel-ratio-3 & {
.transform(scaleX(0.33));
}
}
}
.hairline(@position, @color) when (@position = bottom) {
&:after {
content: '';
position: absolute;
left: 0;
bottom: 0;
right: auto;
top: auto;
height: 1px;
width: 100%;
background-color: @color;
display: block;
z-index: 15;
html.pixel-ratio-2 & {
.transform(scaleY(0.5));
}
html.pixel-ratio-3 & {
.transform(scaleY(0.33));
}
}
}
.hairline(@position, @color) when (@position = right) {
&:after {
content: '';
position: absolute;
right: 0;
top: 0;
left: auto;
bottom: auto;
width: 1px;
height: 100%;
background-color: @color;
display: block;
z-index: 15;
// .transform-origin(100% 50%);
html.pixel-ratio-2 & {
.transform(scaleX(0.5));
}
html.pixel-ratio-3 & {
.transform(scaleX(0.33));
}
}
}
// For right and bottom
.hairline-remove(@position) when not (@position = left) and not (@position = top) {
&:after {
display: none;
}
}
// For left and top
.hairline-remove(@position) when not (@position = right) and not (@position = bottom) {
&:before {
display: none;
}
}
// For right and bottom
.hairline-color(@position, @color) when not (@position = left) and not (@position = top) {
&:after {
background-color: @color;
}
}
// For left and top
.hairline-color(@position, @color) when not (@position = right) and not (@position = bottom) {
&:before {
background-color: @color;
}
}
// Encoded SVG Background
.encoded-svg-background(@svg) {
@url: `encodeURIComponent(@{svg})`;
background-image: url("data:image/svg+xml;charset=utf-8,@{url}");
}
// Preserve3D
.preserve3d() {
-webkit-transform-style: preserve-3d;
-moz-transform-style: preserve-3d;
-ms-transform-style: preserve-3d;
transform-style: preserve-3d;
}
// Shadow
.depth(@level:1) {
& when (@level = 0) {
box-shadow: none;
}
& when (@level = 1) {
box-shadow: 0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);
}
& when (@level = 2) {
box-shadow: 0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12);
}
& when (@level = 3) {
box-shadow: 0 3px 3px -2px rgba(0,0,0,.2),0 3px 4px 0 rgba(0,0,0,.14),0 1px 8px 0 rgba(0,0,0,.12);
}
& when (@level = 4) {
box-shadow: 0 2px 4px -1px rgba(0,0,0,.2),0 4px 5px 0 rgba(0,0,0,.14),0 1px 10px 0 rgba(0,0,0,.12);
}
& when (@level = 5) {
box-shadow: 0 3px 5px -1px rgba(0,0,0,.2),0 5px 8px 0 rgba(0,0,0,.14),0 1px 14px 0 rgba(0,0,0,.12);
}
& when (@level = 6) {
box-shadow: 0 3px 5px -1px rgba(0,0,0,.2),0 6px 10px 0 rgba(0,0,0,.14),0 1px 18px 0 rgba(0,0,0,.12);
}
& when (@level = 7) {
box-shadow: 0 4px 5px -2px rgba(0,0,0,.2),0 7px 10px 1px rgba(0,0,0,.14),0 2px 16px 1px rgba(0,0,0,.12);
}
& when (@level = 8) {
box-shadow: 0 5px 5px -3px rgba(0,0,0,.2),0 8px 10px 1px rgba(0,0,0,.14),0 3px 14px 2px rgba(0,0,0,.12);
}
& when (@level = 9) {
box-shadow: 0 5px 6px -3px rgba(0,0,0,.2),0 9px 12px 1px rgba(0,0,0,.14),0 3px 16px 2px rgba(0,0,0,.12);
}
& when (@level = 10) {
box-shadow: 0 6px 6px -3px rgba(0,0,0,.2),0 10px 14px 1px rgba(0,0,0,.14),0 4px 18px 3px rgba(0,0,0,.12);
}
& when (@level = 11) {
box-shadow: 0 6px 7px -4px rgba(0,0,0,.2),0 11px 15px 1px rgba(0,0,0,.14),0 4px 20px 3px rgba(0,0,0,.12);
}
& when (@level = 12) {
box-shadow: 0 7px 8px -4px rgba(0,0,0,.2),0 12px 17px 2px rgba(0,0,0,.14),0 5px 22px 4px rgba(0,0,0,.12);
}
& when (@level = 13) {
box-shadow: 0 7px 8px -4px rgba(0,0,0,.2),0 13px 19px 2px rgba(0,0,0,.14),0 5px 24px 4px rgba(0,0,0,.12);
}
& when (@level = 14) {
box-shadow: 0 7px 9px -4px rgba(0,0,0,.2),0 14px 21px 2px rgba(0,0,0,.14),0 5px 26px 4px rgba(0,0,0,.12);
}
& when (@level = 15) {
box-shadow: 0 8px 9px -5px rgba(0,0,0,.2),0 15px 22px 2px rgba(0,0,0,.14),0 6px 28px 5px rgba(0,0,0,.12);
}
& when (@level = 16) {
box-shadow: 0 8px 10px -5px rgba(0,0,0,.2),0 16px 24px 2px rgba(0,0,0,.14),0 6px 30px 5px rgba(0,0,0,.12);
}
& when (@level = 17) {
box-shadow: 0 8px 11px -5px rgba(0,0,0,.2),0 17px 26px 2px rgba(0,0,0,.14),0 6px 32px 5px rgba(0,0,0,.12);
}
& when (@level = 18) {
box-shadow: 0 9px 11px -5px rgba(0,0,0,.2),0 18px 28px 2px rgba(0,0,0,.14),0 7px 34px 6px rgba(0,0,0,.12);
}
& when (@level = 19) {
box-shadow: 0 9px 12px -6px rgba(0,0,0,.2),0 19px 29px 2px rgba(0,0,0,.14),0 7px 36px 6px rgba(0,0,0,.12);
}
& when (@level = 20) {
box-shadow: 0 10px 13px -6px rgba(0,0,0,.2),0 20px 31px 3px rgba(0,0,0,.14),0 8px 38px 7px rgba(0,0,0,.12);
}
& when (@level = 21) {
box-shadow: 0 10px 13px -6px rgba(0,0,0,.2),0 21px 33px 3px rgba(0,0,0,.14),0 8px 40px 7px rgba(0,0,0,.12);
}
& when (@level = 22) {
box-shadow: 0 10px 14px -6px rgba(0,0,0,.2),0 22px 35px 3px rgba(0,0,0,.14),0 8px 42px 7px rgba(0,0,0,.12);
}
& when (@level = 23) {
box-shadow: 0 11px 14px -7px rgba(0,0,0,.2),0 23px 36px 3px rgba(0,0,0,.14),0 9px 44px 8px rgba(0,0,0,.12);
}
& when (@level = 24) {
box-shadow: 0 11px 15px -7px rgba(0,0,0,.2),0 24px 38px 3px rgba(0,0,0,.14),0 9px 46px 8px rgba(0,0,0,.12);
}
// & when (@level = 1) {
// box-shadow: rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px;
// }
// & when (@level = 2) {
// box-shadow: rgba(0, 0, 0, 0.156863) 0px 3px 10px, rgba(0, 0, 0, 0.227451) 0px 3px 10px;
// }
// & when (@level = 3) {
// box-shadow: rgba(0, 0, 0, 0.188235) 0px 10px 30px, rgba(0, 0, 0, 0.227451) 0px 6px 10px;
// }
// & when (@level = 4) {
// box-shadow: rgba(0, 0, 0, 0.247059) 0px 14px 45px, rgba(0, 0, 0, 0.219608) 0px 10px 18px;
// }
// & when (@level = 5) {
// box-shadow: rgba(0, 0, 0, 0.298039) 0px 19px 60px, rgba(0, 0, 0, 0.219608) 0px 15px 20px;
// }
}
// Highlighted Links
.active-highlight(@color:rgba(255, 255, 255, 0.15)){
&:before {
content: '';
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
background-color: @color;
background-repeat: no-repeat;
background-position: center;
background-size: 100% 100%;
opacity: 0;
pointer-events: none;
.transition(600ms);
}
&.active-state:before,
html:not(.watch-active-state) &:active:before {
opacity: 1;
.transition(150ms);
}
}
.active-highlight-color(@color) {
&:before {
background-image: -webkit-radial-gradient(center, circle cover, @color 66%, rgba(red(@color),green(@color),blue(@color),0) 66%);
background-image: radial-gradient(circle at center, @color 66%, rgba(red(@color),green(@color),blue(@color),0) 66%);
}
}
// No Scrollbar
.no-scrollbar() {
&::-webkit-scrollbar {
display: none !important;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
opacity: 0 !important;
}
}
.ellipsis() {
white-space:nowrap;
text-overflow:ellipsis;
overflow:hidden;
word-wrap: break-word;
}
================================================
FILE: src/styles/normalize.less
================================================
/*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */
/**
* 1. Change the default font family in all browsers (opinionated).
* 2. Prevent adjustments of font size after orientation changes in IE and iOS.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove the margin in all browsers (opinionated).
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Add the correct display in IE 9-.
* 1. Add the correct display in Edge, IE, and Firefox.
* 2. Add the correct display in IE.
*/
article,
aside,
details, /* 1 */
figcaption,
figure,
footer,
header,
main, /* 2 */
menu,
nav,
section,
summary { /* 1 */
display: block;
}
/**
* Add the correct display in IE 9-.
*/
audio,
canvas,
progress,
video {
display: inline-block;
}
/**
* Add the correct display in iOS 4-7.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Add the correct display in IE 10-.
* 1. Add the correct display in IE.
*/
template, /* 1 */
[hidden] {
display: none;
}
/* Links
========================================================================== */
/**
* 1. Remove the gray background on active links in IE 10.
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
*/
a {
background-color: transparent; /* 1 */
-webkit-text-decoration-skip: objects; /* 2 */
}
/**
* Remove the outline on focused links when they are also active or hovered
* in all browsers (opinionated).
*/
a:active,
a:hover {
outline-width: 0;
}
/* Text-level semantics
========================================================================== */
/**
* 1. Remove the bottom border in Firefox 39-.
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
*/
b,
strong {
font-weight: inherit;
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* Add the correct font style in Android 4.3-.
*/
dfn {
font-style: italic;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Add the correct background and color in IE 9-.
*/
mark {
background-color: #ff0;
color: #000;
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10-.
*/
img {
border-style: none;
}
/**
* Hide the overflow in IE.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct margin in IE 8.
*/
figure {
margin: 1em 40px;
}
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/* Forms
========================================================================== */
/**
* 1. Change font properties to `inherit` in all browsers (opinionated).
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
select,
textarea {
font: inherit; /* 1 */
margin: 0; /* 2 */
}
/**
* Restore the font weight unset by the previous rule.
*/
optgroup {
font-weight: bold;
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
* controls in Android 4.
* 2. Correct the inability to style clickable types in iOS and Safari.
*/
button,
html [type="button"], /* 1 */
[type="reset"],
[type="submit"] {
-webkit-appearance: button; /* 2 */
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Change the border, margin, and padding in all browsers (opinionated).
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Remove the default vertical scrollbar in IE.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10-.
* 2. Remove the padding in IE 10-.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding and cancel buttons in Chrome and Safari on OS X.
*/
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Correct the text style of placeholders in Chrome, Edge, and Safari.
*/
::-webkit-input-placeholder {
color: inherit;
opacity: 0.54;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
================================================
FILE: src/styles/theme.less
================================================
@import "./import.less";
.mu-primary-color {
background-color: @primaryColor;
}
.mu-secondary-color {
background-color: @secondaryColor;
}
.mu-success-color {
background-color: @successColor;
}
.mu-warning-color {
background-color: @warningColor;
}
.mu-info-color {
background-color: @infoColor;
}
.mu-error-color {
background-color: @errorColor;
}
.mu-inverse {
color: @alternateTextColor;
}
.mu-primary-text-color {
color: @primaryColor;
}
.mu-secondary-text-color {
color: @secondaryColor;
}
.mu-success-text-color {
color: @successColor;
}
.mu-warning-text-color {
color: @warningColor;
}
.mu-info-text-color {
color: @infoColor;
}
.mu-error-text-color {
color: @errorColor;
}
================================================
FILE: src/styles/transitions.less
================================================
@import "./import.less";
.mu-fade-transition-enter-active,
.mu-fade-transition-leave-active {
transition: opacity .45s cubic-bezier(0.23, 1, 0.32, 1);
}
.mu-fade-transition-enter,
.mu-fade-transition-leave-active {
opacity: 0 !important;
}
.mu-popover-transition-enter-active, .mu-popover-transition-leave-active{
transition-duration: 300ms;
// transform-origin: left top;
transition-property: opacity, transform;
backface-visibility: hidden;
}
.mu-popover-transition-enter, .mu-popover-transition-leave-active {
transform: scale(0.6);
opacity: 0;
}
.mu-bottom-sheet-transition-enter-active,
.mu-bottom-sheet-transition-leave-active{
transition: transform .45s @easeOutFunction;
backface-visibility: hidden;
}
.mu-bottom-sheet-transition-enter,
.mu-bottom-sheet-transition-leave-active{
transform: translate3d(0, 100%, 0);
}
.mu-slide-top-transition-enter-active,
.mu-slide-top-transition-leave-active {
transition: transform .45s @easeOutFunction, opacity .45s @easeOutFunction;
backface-visibility: hidden;
}
.mu-slide-top-transition-enter,
.mu-slide-top-transition-leave-active {
transform: translate3d(0, -100%, 0);
opacity: 0;
}
.mu-slide-bottom-transition-enter-active,
.mu-slide-bottom-transition-leave-active {
transition: transform .45s @easeOutFunction, opacity .45s @easeOutFunction;
backface-visibility: hidden;
}
.mu-slide-bottom-transition-enter,
.mu-slide-bottom-transition-leave-active {
transform: translate3d(0, 100%, 0);
opacity: 0;
}
.mu-slide-left-transition-enter-active,
.mu-slide-left-transition-leave-active {
transition: transform .45s @easeOutFunction, opacity .45s @easeOutFunction;
backface-visibility: hidden;
}
.mu-slide-left-transition-enter,
.mu-slide-left-transition-leave-active {
transform: translate3d(-100%, 0, 0);
opacity: 0;
}
.mu-slide-right-transition-enter-active,
.mu-slide-right-transition-leave-active {
transition: transform .45s @easeOutFunction, opacity .45s @easeOutFunction;
backface-visibility: hidden;
}
.mu-slide-right-transition-enter,
.mu-slide-right-transition-leave-active {
transform: translate3d(100%, 0, 0);
opacity: 0;
}
.mu-scale-transition-enter-active,
.mu-scale-transition-leave-active {
transition: all .45s @easeOutFunction;
backface-visibility: hidden;
}
.mu-scale-transition-enter,
.mu-scale-transition-leave-active {
transform: scale(0);
opacity: 0;
}
================================================
FILE: src/styles/vars.less
================================================
@import "./colors.less";
@fontFamily: Roboto, Lato, sans-serif;
// primary and accent
@primaryColor: @blue; // 主色
@secondaryColor: @pinkA200;
@successColor: @green;
@warningColor: @yellow600;
@errorColor: @red;
@infoColor: @blue;
@trackColor: @grey400;
// textColor or borderColor
@textColor: @darkBlack;
@secondaryTextColor: fade(@fullBlack, 54%);
@alternateTextColor: @white;
@borderColor: fade(@fullBlack, 12%);
@disabledColor: fade(@fullBlack, 38%);
// background
@backgroundColor: @grey50;
@dialogBackgroundColor: @white; // dialogs or cards
@chipBackgroundColor: @grey300;
// icon color
@activeIconColor: fade(@fullBlack, 54%);
@inActiveIconColor: fade(@fullBlack, 38%);
// spaceing
@iconSize: 24px;
@desktopGutter: 24px;
@desktopGutterMore: 32px;
@desktopGutterLess: 16px;
@desktopGutterMini: 8px;
@desktopKeylineIncrement: 64px;
@desktopDropDownMenuItemHeight: 32px;
@desktopDropDownMenuFontSize: 15px;
@desktopDrawerMenuItemHeight: 48px;
@desktopSubheaderHeight: 48px;
@desktopToolbarHeight: 56px;
// animate
@easeOutFunction: cubic-bezier(0.23, 1, 0.32, 1);
@easeInOutFunction: cubic-bezier(0.445, 0.05, 0.55, 0.95);
================================================
FILE: src/theme/baseTheme.js
================================================
export default (theme) => {
return `
body{
background-color: ${theme.background.default};
color: ${theme.text.primary};
}
a{
color: ${theme.secondary};
}
`;
};
================================================
FILE: src/theme/colorTheme.js
================================================
export default (theme) => {
return `
.mu-primary-color {
background-color: ${theme.primary};
}
.mu-secondary-color {
background-color: ${theme.secondary};
}
.mu-success-color {
background-color: ${theme.success};
}
.mu-warning-color {
background-color: ${theme.warning};
}
.mu-info-color {
background-color: ${theme.info};
}
.mu-error-color {
background-color: ${theme.error};
}
.mu-inverse {
color: #fff;
}
.mu-primary-text-color {
color: ${theme.primary};
}
.mu-secondary-text-color {
color: ${theme.secondary};
}
.mu-success-text-color {
color: ${theme.success};
}
.mu-warning-text-color {
color: ${theme.warning};
}
.mu-info-text-color {
color: ${theme.info};
}
.mu-error-text-color {
color: ${theme.error};
}
`;
};
================================================
FILE: src/theme/colors.js
================================================
export const red50 = '#ffebee';
export const red100 = '#ffcdd2';
export const red200 = '#ef9a9a';
export const red300 = '#e57373';
export const red400 = '#ef5350';
export const red500 = '#f44336';
export const red600 = '#e53935';
export const red700 = '#d32f2f';
export const red800 = '#c62828';
export const red900 = '#b71c1c';
export const redA100 = '#ff8a80';
export const redA200 = '#ff5252';
export const redA400 = '#ff1744';
export const redA700 = '#d50000';
export const red = red500;
export const pink50 = '#fce4ec';
export const pink100 = '#f8bbd0';
export const pink200 = '#f48fb1';
export const pink300 = '#f06292';
export const pink400 = '#ec407a';
export const pink500 = '#e91e63';
export const pink600 = '#d81b60';
export const pink700 = '#c2185b';
export const pink800 = '#ad1457';
export const pink900 = '#880e4f';
export const pinkA100 = '#ff80ab';
export const pinkA200 = '#ff4081';
export const pinkA400 = '#f50057';
export const pinkA700 = '#c51162';
export const pink = pink500;
export const purple50 = '#f3e5f5';
export const purple100 = '#e1bee7';
export const purple200 = '#ce93d8';
export const purple300 = '#ba68c8';
export const purple400 = '#ab47bc';
export const purple500 = '#9c27b0';
export const purple600 = '#8e24aa';
export const purple700 = '#7b1fa2';
export const purple800 = '#6a1b9a';
export const purple900 = '#4a148c';
export const purpleA100 = '#ea80fc';
export const purpleA200 = '#e040fb';
export const purpleA400 = '#d500f9';
export const purpleA700 = '#aa00ff';
export const purple = purple500;
export const deepPurple50 = '#ede7f6';
export const deepPurple100 = '#d1c4e9';
export const deepPurple200 = '#b39ddb';
export const deepPurple300 = '#9575cd';
export const deepPurple400 = '#7e57c2';
export const deepPurple500 = '#673ab7';
export const deepPurple600 = '#5e35b1';
export const deepPurple700 = '#512da8';
export const deepPurple800 = '#4527a0';
export const deepPurple900 = '#311b92';
export const deepPurpleA100 = '#b388ff';
export const deepPurpleA200 = '#7c4dff';
export const deepPurpleA400 = '#651fff';
export const deepPurpleA700 = '#6200ea';
export const deepPurple = deepPurple500;
export const indigo50 = '#e8eaf6';
export const indigo100 = '#c5cae9';
export const indigo200 = '#9fa8da';
export const indigo300 = '#7986cb';
export const indigo400 = '#5c6bc0';
export const indigo500 = '#3f51b5';
export const indigo600 = '#3949ab';
export const indigo700 = '#303f9f';
export const indigo800 = '#283593';
export const indigo900 = '#1a237e';
export const indigoA100 = '#8c9eff';
export const indigoA200 = '#536dfe';
export const indigoA400 = '#3d5afe';
export const indigoA700 = '#304ffe';
export const indigo = indigo500;
export const blue50 = '#e3f2fd';
export const blue100 = '#bbdefb';
export const blue200 = '#90caf9';
export const blue300 = '#64b5f6';
export const blue400 = '#42a5f5';
export const blue500 = '#2196f3';
export const blue600 = '#1e88e5';
export const blue700 = '#1976d2';
export const blue800 = '#1565c0';
export const blue900 = '#0d47a1';
export const blueA100 = '#82b1ff';
export const blueA200 = '#448aff';
export const blueA400 = '#2979ff';
export const blueA700 = '#2962ff';
export const blue = blue500;
export const lightBlue50 = '#e1f5fe';
export const lightBlue100 = '#b3e5fc';
export const lightBlue200 = '#81d4fa';
export const lightBlue300 = '#4fc3f7';
export const lightBlue400 = '#29b6f6';
export const lightBlue500 = '#03a9f4';
export const lightBlue600 = '#039be5';
export const lightBlue700 = '#0288d1';
export const lightBlue800 = '#0277bd';
export const lightBlue900 = '#01579b';
export const lightBlueA100 = '#80d8ff';
export const lightBlueA200 = '#40c4ff';
export const lightBlueA400 = '#00b0ff';
export const lightBlueA700 = '#0091ea';
export const lightBlue = lightBlue500;
export const cyan50 = '#e0f7fa';
export const cyan100 = '#b2ebf2';
export const cyan200 = '#80deea';
export const cyan300 = '#4dd0e1';
export const cyan400 = '#26c6da';
export const cyan500 = '#00bcd4';
export const cyan600 = '#00acc1';
export const cyan700 = '#0097a7';
export const cyan800 = '#00838f';
export const cyan900 = '#006064';
export const cyanA100 = '#84ffff';
export const cyanA200 = '#18ffff';
export const cyanA400 = '#00e5ff';
export const cyanA700 = '#00b8d4';
export const cyan = cyan500;
export const teal50 = '#e0f2f1';
export const teal100 = '#b2dfdb';
export const teal200 = '#80cbc4';
export const teal300 = '#4db6ac';
export const teal400 = '#26a69a';
export const teal500 = '#009688';
export const teal600 = '#00897b';
export const teal700 = '#00796b';
export const teal800 = '#00695c';
export const teal900 = '#004d40';
export const tealA100 = '#a7ffeb';
export const tealA200 = '#64ffda';
export const tealA400 = '#1de9b6';
export const tealA700 = '#00bfa5';
export const teal = teal500;
export const green50 = '#e8f5e9';
export const green100 = '#c8e6c9';
export const green200 = '#a5d6a7';
export const green300 = '#81c784';
export const green400 = '#66bb6a';
export const green500 = '#4caf50';
export const green600 = '#43a047';
export const green700 = '#388e3c';
export const green800 = '#2e7d32';
export const green900 = '#1b5e20';
export const greenA100 = '#b9f6ca';
export const greenA200 = '#69f0ae';
export const greenA400 = '#00e676';
export const greenA700 = '#00c853';
export const green = green500;
export const lightGreen50 = '#f1f8e9';
export const lightGreen100 = '#dcedc8';
export const lightGreen200 = '#c5e1a5';
export const lightGreen300 = '#aed581';
export const lightGreen400 = '#9ccc65';
export const lightGreen500 = '#8bc34a';
export const lightGreen600 = '#7cb342';
export const lightGreen700 = '#689f38';
export const lightGreen800 = '#558b2f';
export const lightGreen900 = '#33691e';
export const lightGreenA100 = '#ccff90';
export const lightGreenA200 = '#b2ff59';
export const lightGreenA400 = '#76ff03';
export const lightGreenA700 = '#64dd17';
export const lightGreen = lightGreen500;
export const lime50 = '#f9fbe7';
export const lime100 = '#f0f4c3';
export const lime200 = '#e6ee9c';
export const lime300 = '#dce775';
export const lime400 = '#d4e157';
export const lime500 = '#cddc39';
export const lime600 = '#c0ca33';
export const lime700 = '#afb42b';
export const lime800 = '#9e9d24';
export const lime900 = '#827717';
export const limeA100 = '#f4ff81';
export const limeA200 = '#eeff41';
export const limeA400 = '#c6ff00';
export const limeA700 = '#aeea00';
export const lime = lime500;
export const yellow50 = '#fffde7';
export const yellow100 = '#fff9c4';
export const yellow200 = '#fff59d';
export const yellow300 = '#fff176';
export const yellow400 = '#ffee58';
export const yellow500 = '#ffeb3b';
export const yellow600 = '#fdd835';
export const yellow700 = '#fbc02d';
export const yellow800 = '#f9a825';
export const yellow900 = '#f57f17';
export const yellowA100 = '#ffff8d';
export const yellowA200 = '#ffff00';
export const yellowA400 = '#ffea00';
export const yellowA700 = '#ffd600';
export const yellow = yellow500;
export const amber50 = '#fff8e1';
export const amber100 = '#ffecb3';
export const amber200 = '#ffe082';
export const amber300 = '#ffd54f';
export const amber400 = '#ffca28';
export const amber500 = '#ffc107';
export const amber600 = '#ffb300';
export const amber700 = '#ffa000';
export const amber800 = '#ff8f00';
export const amber900 = '#ff6f00';
export const amberA100 = '#ffe57f';
export const amberA200 = '#ffd740';
export const amberA400 = '#ffc400';
export const amberA700 = '#ffab00';
export const amber = amber500;
export const orange50 = '#fff3e0';
export const orange100 = '#ffe0b2';
export const orange200 = '#ffcc80';
export const orange300 = '#ffb74d';
export const orange400 = '#ffa726';
export const orange500 = '#ff9800';
export const orange600 = '#fb8c00';
export const orange700 = '#f57c00';
export const orange800 = '#ef6c00';
export const orange900 = '#e65100';
export const orangeA100 = '#ffd180';
export const orangeA200 = '#ffab40';
export const orangeA400 = '#ff9100';
export const orangeA700 = '#ff6d00';
export const orange = orange500;
export const deepOrange50 = '#fbe9e7';
export const deepOrange100 = '#ffccbc';
export const deepOrange200 = '#ffab91';
export const deepOrange300 = '#ff8a65';
export const deepOrange400 = '#ff7043';
export const deepOrange500 = '#ff5722';
export const deepOrange600 = '#f4511e';
export const deepOrange700 = '#e64a19';
export const deepOrange800 = '#d84315';
export const deepOrange900 = '#bf360c';
export const deepOrangeA100 = '#ff9e80';
export const deepOrangeA200 = '#ff6e40';
export const deepOrangeA400 = '#ff3d00';
export const deepOrangeA700 = '#dd2c00';
export const deepOrange = deepOrange500;
export const brown50 = '#efebe9';
export const brown100 = '#d7ccc8';
export const brown200 = '#bcaaa4';
export const brown300 = '#a1887f';
export const brown400 = '#8d6e63';
export const brown500 = '#795548';
export const brown600 = '#6d4c41';
export const brown700 = '#5d4037';
export const brown800 = '#4e342e';
export const brown900 = '#3e2723';
export const brown = brown500;
export const blueGrey50 = '#eceff1';
export const blueGrey100 = '#cfd8dc';
export const blueGrey200 = '#b0bec5';
export const blueGrey300 = '#90a4ae';
export const blueGrey400 = '#78909c';
export const blueGrey500 = '#607d8b';
export const blueGrey600 = '#546e7a';
export const blueGrey700 = '#455a64';
export const blueGrey800 = '#37474f';
export const blueGrey900 = '#263238';
export const blueGrey = blueGrey500;
export const grey50 = '#fafafa';
export const grey100 = '#f5f5f5';
export const grey200 = '#eeeeee';
export const grey300 = '#e0e0e0';
export const grey400 = '#bdbdbd';
export const grey500 = '#9e9e9e';
export const grey600 = '#757575';
export const grey700 = '#616161';
export const grey800 = '#424242';
export const grey900 = '#212121';
export const grey = grey500;
export const black = '#000000';
export const white = '#ffffff';
export const transparent = 'rgba(0, 0, 0, 0)';
export const fullBlack = 'rgba(0, 0, 0, 1)';
export const darkBlack = 'rgba(0, 0, 0, 0.87)';
export const lightBlack = 'rgba(0, 0, 0, 0.54)';
export const minBlack = 'rgba(0, 0, 0, 0.26)';
export const faintBlack = 'rgba(0, 0, 0, 0.12)';
export const fullWhite = 'rgba(255, 255, 255, 1)';
export const darkWhite = 'rgba(255, 255, 255, 0.87)';
export const lightWhite = 'rgba(255, 255, 255, 0.54)';
================================================
FILE: src/theme/dark.js
================================================
import * as Colors from './colors';
export default {
type: 'dark',
primary: Colors.blue700,
secondary: Colors.pinkA200,
success: Colors.green,
warning: Colors.yellow600,
info: Colors.blue,
error: Colors.red,
track: Colors.grey600,
text: {
primary: Colors.white,
secondary: 'rgba(255, 255, 255, 0.7)',
alternate: '#303030',
disabled: 'rgba(255, 255, 255, 0.3)',
hint: 'rgba(255, 255, 255, 0.3)' // 提示文字颜色
},
divider: 'rgba(255, 255, 255, 0.3)',
background: {
paper: Colors.grey800,
chip: Colors.grey700,
default: '#303030'
}
};
================================================
FILE: src/theme/index.js
================================================
import BaseTheme from './baseTheme';
import ColorTheme from './colorTheme';
import light from './light';
import dark from './dark';
const themes = [
BaseTheme, ColorTheme
];
const vars = {
light,
dark
};
function getThemeStyle () {
const themeId = 'muse-theme';
let styleEl = document.getElementById(themeId);
if (styleEl) return styleEl;
styleEl = document.createElement('style');
styleEl.id = themeId;
document.body.appendChild(styleEl);
return styleEl;
}
export default {
addCreateTheme (theme) {
const length = themes.length;
themes.splice(length - 1, 0, theme);
return this;
},
add (name, varObj = {}, extendName = 'light') {
const theme = {
name,
...vars[extendName],
...varObj
};
vars[name] = theme;
return this;
},
use (name) {
const themeEl = getThemeStyle();
themeEl.innerHTML = themes.map((theme) => theme(vars[name], vars[name].type, name)).join(' ');
return this;
},
generate (name) {
return themes.map((theme) => theme(vars[name], vars[name].type, name)).join(' ');
}
};
================================================
FILE: src/theme/light.js
================================================
import * as Colors from './colors';
export default {
type: 'light',
primary: Colors.blue,
secondary: Colors.pinkA200,
success: Colors.green,
warning: Colors.yellow600,
info: Colors.blue,
error: Colors.red,
track: Colors.grey400,
text: {
primary: Colors.darkBlack,
secondary: Colors.lightBlack,
alternate: Colors.white,
disabled: 'rgba(0, 0, 0, 0.38)',
hint: 'rgba(0, 0, 0, 0.38)' // 提示文字颜色
},
divider: Colors.faintBlack,
background: {
paper: Colors.white,
chip: Colors.grey300,
default: Colors.grey50
}
};
================================================
FILE: src/utils/colorManipulator.js
================================================
/**
* material-ui colorManipulator.js
* https://github.com/mui-org/material-ui/blob/master/src/utils/colorManipulator.js
*/
/**
* Returns a number whose value is limited to the given range.
*
* @param {number} value The value to be clamped
* @param {number} min The lower boundary of the output range
* @param {number} max The upper boundary of the output range
* @returns {number} A number in the range [min, max]
*/
function clamp (value, min, max) {
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
/**
* Converts a color object with type and values to a string.
*
* @param {object} color - Decomposed color
* @param {string} color.type - One of, 'rgb', 'rgba', 'hsl', 'hsla'
* @param {array} color.values - [n,n,n] or [n,n,n,n]
* @returns {string} A CSS color string
*/
export function convertColorToString (color) {
const { type, values } = color;
if (type.indexOf('rgb') > -1) {
// Only convert the first 3 values to int (i.e. not alpha)
for (let i = 0; i < 3; i++) {
values[i] = parseInt(values[i]);
}
}
let colorString;
if (type.indexOf('hsl') > -1) {
colorString = `${color.type}(${values[0]}, ${values[1]}%, ${values[2]}%`;
} else {
colorString = `${color.type}(${values[0]}, ${values[1]}, ${values[2]}`;
}
if (values.length === 4) {
colorString += `, ${color.values[3]})`;
} else {
colorString += ')';
}
return colorString;
}
/**
* Converts a color from CSS hex format to CSS rgb format.
*
* @param {string} color - Hex color, i.e. #nnn or #nnnnnn
* @returns {string} A CSS rgb color string
*/
export function convertHexToRGB (color) {
if (color.length === 4) {
let extendedColor = '#';
for (let i = 1; i < color.length; i++) {
extendedColor += color.charAt(i) + color.charAt(i);
}
color = extendedColor;
}
const values = {
r: parseInt(color.substr(1, 2), 16),
g: parseInt(color.substr(3, 2), 16),
b: parseInt(color.substr(5, 2), 16)
};
return `rgb(${values.r}, ${values.g}, ${values.b})`;
}
/**
* Returns an object with the type and values of a color.
*
* Note: Does not support rgb % values and color names.
*
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
* @returns {{type: string, values: number[]}} A MUI color object
*/
export function decomposeColor (color) {
if (color.charAt(0) === '#') {
return decomposeColor(convertHexToRGB(color));
}
const marker = color.indexOf('(');
const type = color.substring(0, marker);
let values = color.substring(marker + 1, color.length - 1).split(',');
values = values.map((value) => parseFloat(value));
return { type, values };
}
/**
* Calculates the contrast ratio between two colors.
*
* Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
*
* @param {string} foreground - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
* @param {string} background - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
* @returns {number} A contrast ratio value in the range 0 - 21 with 2 digit precision.
*/
export function getContrastRatio (foreground, background) {
const lumA = getLuminance(foreground);
const lumB = getLuminance(background);
const contrastRatio = (Math.max(lumA, lumB) + 0.05) / (Math.min(lumA, lumB) + 0.05);
return Number(contrastRatio.toFixed(2)); // Truncate at two digits
}
/**
* The relative brightness of any point in a color space,
* normalized to 0 for darkest black and 1 for lightest white.
*
* Formula: https://www.w3.org/WAI/GL/wiki/Relative_luminance
*
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
* @returns {number} The relative brightness of the color in the range 0 - 1
*/
export function getLuminance (color) {
color = decomposeColor(color);
if (color.type.indexOf('rgb') > -1) {
const rgb = color.values.map((val) => {
val /= 255; // normalized
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
});
return Number((0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]).toFixed(3)); // Truncate at 3 digits
} else if (color.type.indexOf('hsl') > -1) {
return color.values[2] / 100;
}
}
/**
* Darken or lighten a colour, depending on its luminance.
* Light colors are darkened, dark colors are lightened.
*
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
* @param {number} coefficient=0.15 - multiplier in the range 0 - 1
* @returns {string} A CSS color string. Hex input values are returned as rgb
*/
export function emphasize (color, coefficient = 0.15) {
return getLuminance(color) > 0.5 ? darken(color, coefficient) : lighten(color, coefficient);
}
/**
* Set the absolute transparency of a color.
* Any existing alpha values are overwritten.
*
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
* @param {number} value - value to set the alpha channel to in the range 0 -1
* @returns {string} A CSS color string. Hex input values are returned as rgb
*/
export function fade (color, value) {
color = decomposeColor(color);
value = clamp(value, 0, 1);
if (color.type === 'rgb' || color.type === 'hsl') {
color.type += 'a';
}
color.values[3] = value;
return convertColorToString(color);
}
/**
* Darkens a color.
*
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
* @param {number} coefficient - multiplier in the range 0 - 1
* @returns {string} A CSS color string. Hex input values are returned as rgb
*/
export function darken (color, coefficient) {
color = decomposeColor(color);
coefficient = clamp(coefficient, 0, 1);
if (color.type.indexOf('hsl') > -1) {
color.values[2] *= 1 - coefficient;
} else if (color.type.indexOf('rgb') > -1) {
for (let i = 0; i < 3; i++) {
color.values[i] *= 1 - coefficient;
}
}
return convertColorToString(color);
}
/**
* Lightens a color.
*
* @param {string} color - CSS color, i.e. one of: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla()
* @param {number} coefficient - multiplier in the range 0 - 1
* @returns {string} A CSS color string. Hex input values are returned as rgb
*/
export function lighten (color, coefficient) {
color = decomposeColor(color);
coefficient = clamp(coefficient, 0, 1);
if (color.type.indexOf('hsl') > -1) {
color.values[2] += (100 - color.values[2]) * coefficient;
} else if (color.type.indexOf('rgb') > -1) {
for (let i = 0; i < 3; i++) {
color.values[i] += (255 - color.values[i]) * coefficient;
}
}
return convertColorToString(color);
}
================================================
FILE: src/utils/dom.js
================================================
export function getScrollEventTarget (element) {
let currentNode = element;
while (currentNode && currentNode.tagName !== 'HTML' && currentNode.nodeType === 1) {
const overflowY = window.getComputedStyle(currentNode).overflowY;
if (overflowY === 'scroll' || overflowY === 'auto') {
return currentNode;
}
currentNode = currentNode.parentNode;
}
return window;
}
export function getScrollTop (element) {
if (element === window) {
return Math.max(window.pageYOffset || 0, document.documentElement.scrollTop);
} else {
return element.scrollTop;
}
}
export function getOffset (el) {
const box = el.getBoundingClientRect();
const body = document.body;
const clientTop = el.clientTop || body.clientTop || 0;
const clientLeft = el.clientLeft || body.clientLeft || 0;
const scrollTop = window.pageYOffset || el.scrollTop;
const scrollLeft = window.pageXOffset || el.scrollLeft;
return {
top: box.top + scrollTop - clientTop,
left: box.left + scrollLeft - clientLeft
};
}
export function transitionEnd (el, fun) {
const arr = ['msTransitionEnd', 'mozTransitionEnd', 'oTransitionEnd', 'webkitTransitionEnd', 'transitionend'];
const handler = {
handleEvent (event) {
arr.forEach(function (eventName) {
el.removeEventListener(eventName, handler, false);
});
fun.apply(el, arguments);
}
};
arr.forEach(function (eventName) {
el.addEventListener(eventName, handler, false);
});
}
export function hasClass (el, cls) {
if (!el || !cls) return false;
if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.');
if (el.classList) {
return el.classList.contains(cls);
} else {
return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1;
}
};
export function addClass (el, cls) {
if (!el) return;
var curClass = el.className;
var classes = (cls || '').split(' ');
for (var i = 0, j = classes.length; i < j; i++) {
var clsName = classes[i];
if (!clsName) continue;
if (el.classList) {
el.classList.add(clsName);
} else {
if (!hasClass(el, clsName)) {
curClass += ' ' + clsName;
}
}
}
if (!el.classList) {
el.className = curClass;
}
};
export function removeClass (el, cls) {
if (!el || !cls) return;
var classes = cls.split(' ');
var curClass = ' ' + el.className + ' ';
for (var i = 0, j = classes.length; i < j; i++) {
var clsName = classes[i];
if (!clsName) continue;
if (el.classList) {
el.classList.remove(clsName);
} else {
if (hasClass(el, clsName)) {
curClass = curClass.replace(' ' + clsName + ' ', ' ');
}
}
}
if (!el.classList) {
el.className = curClass ? curClass.trim() : curClass;
}
};
================================================
FILE: src/utils/drag.js
================================================
const IS_TOUCH = typeof window !== 'undefined' && (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);
export default class Drag {
constructor (element, onlyTouch) {
this.el = element;
this.startPos = {};
this.endPos = {};
this.starts = [];
this.drags = [];
this.ends = [];
this.onlyTouch = onlyTouch;
if (IS_TOUCH || onlyTouch) {
this.el.addEventListener('touchstart', this);
} else {
this.el.addEventListener('mousedown', this);
}
}
handleEvent (event) {
switch (event.type) {
case 'touchstart':
this.touchStart(event);
break;
case 'touchmove':
this.touchMove(event);
break;
case 'touchcancel':
case 'touchend':
this.touchEnd(event);
break;
case 'mousedown':
this.mouseStart(event);
break;
case 'mousemove':
this.mouseMove(event);
break;
case 'mouseleave':
case 'mouseup':
this.mouseEnd(event);
break;
}
}
touchStart (event) {
const touch = event.touches[0];
this.startPos = { // 取第一个touch的坐标值
x: touch.pageX,
y: touch.pageY,
time: new Date().getTime()
};
this.endPos = {};
document.addEventListener('touchmove', this, {
passive: false
});
document.addEventListener('touchend', this, {
passive: false
});
this.starts.map((func) => {
func.call(this, this.startPos, event);
});
}
touchMove (event) {
if (event.touches.length > 1 || event.scale && event.scale !== 1) return;
const touch = event.touches[0];
this.endPos = {
x: touch.pageX - this.startPos.x,
y: touch.pageY - this.startPos.y,
time: new Date().getTime() - this.startPos.time
};
this.drags.map((func) => {
func.call(this, this.endPos, event);
});
}
touchEnd (event) {
this.endPos.time = new Date().getTime() - this.startPos.time;
document.removeEventListener('touchmove', this);
document.removeEventListener('touchend', this);
this.ends.map((func) => {
func.call(this, this.endPos, event);
});
}
mouseStart (event) {
this.startPos = {
x: event.clientX,
y: event.clientY,
time: new Date().getTime()
};
this.endPos = {};
document.addEventListener('mousemove', this);
document.addEventListener('mouseup', this);
this.starts.map((func) => {
func.call(this, this.startPos, event);
});
}
mouseMove (event) {
this.endPos = {
x: event.clientX - this.startPos.x,
y: event.clientY - this.startPos.y
};
this.drags.map((func) => {
func.call(this, this.endPos, event);
});
}
mouseEnd (event) {
document.removeEventListener('mousemove', this);
document.removeEventListener('mouseup', this);
this.endPos.time = new Date().getTime() - this.startPos.time;
this.ends.map((func) => {
func.call(this, this.endPos, event);
});
}
start (fun) {
this.starts.push(fun);
return this;
}
end (fun) {
this.ends.push(fun);
return this;
}
drag (fun) {
this.drags.push(fun);
return this;
}
reset (event) {
const touch = event.touches ? event.touches[0] : {};
this.startPos = { // 取第一个touch的坐标值
x: touch.pageX || event.clientX,
y: touch.pageY || event.clientY,
time: new Date().getTime()
};
this.endPos = {
x: 0,
y: 0
};
}
destory () {
if (IS_TOUCH || this.onlyTouch) {
this.el.removeEventListener('touchstart', this);
} else {
this.el.removeEventListener('mousedown', this);
}
}
}
================================================
FILE: src/utils/index.js
================================================
import * as colorsObj from '../theme/colors';
const colors = Object.keys(colorsObj);
export function getColor (color) {
if (!color || ['primary', 'secondary', 'success', 'warning', 'info', 'error'].indexOf(color) !== -1) return '';
return colors.indexOf(color) !== -1 ? colorsObj[color] : color;
};
export function isNotNull (val) {
return val !== undefined && val !== null;
}
export function isNull (val) {
return val === undefined || val === null;
}
export function getWidth (w) {
let width = String(w);
if (width && width.indexOf('%') === -1 && width.indexOf('px') === -1) width += 'px';
return width;
}
export function isPc () {
var uaInfo = typeof navigator !== 'undefined' ? navigator.userAgent : '';
var agents = ['Android', 'iPhone', 'Windows Phone', 'iPad', 'iPod'];
var flag = true;
for (var i = 0; i < agents.length; i++) {
if (uaInfo.indexOf(agents[i]) > 0) {
flag = false;
break;
}
}
return flag;
}
export function retina () {
// 处理retina屏幕显示效果
if (isPc()) return;
var classNames = [];
const pixelRatio = typeof window !== undefined && window.devicePixelRatio || 1;
classNames.push('pixel-ratio-' + Math.floor(pixelRatio));
if (pixelRatio >= 2) {
classNames.push('retina');
}
const html = document.getElementsByTagName('html')[0];
classNames.forEach((className) => html.classList.add(className));
}
/**
* 将 String, Object, Array 转成 Array
*/
export function convertClass (classes) {
let newClasses = [];
if (!classes) return newClasses;
if (classes instanceof Array) {
newClasses = newClasses.concat(classes);
} else if (classes instanceof Object) {
for (const name in classes) {
if (classes[name]) newClasses.push(name);
}
} else {
newClasses = newClasses.concat(classes.split(' '));
}
return newClasses;
}
export function createSimpleFunctional (c, el = 'div', name) {
return {
name,
functional: true,
render: (h, { data, children }) => {
data.staticClass = (`${c} ${data.staticClass || ''}`).trim();
return h(el, data, children);
}
};
}
export function getFirstComponentChild (children) {
return children && children.filter(c => c && c.tag)[0];
};
export function isPromise (val) {
return val && typeof val.then === 'function';
}
export function isObject (val) {
return val !== null && val && typeof val === 'object' && !Array.isArray(val);
}
export function getObjAttr (obj, attrs) {
if (!obj || !attrs) return;
let value = obj;
attrs.split('.').forEach((key, index) => {
if (!value) return;
value = value[key];
});
return value;
}
export function setObjAttr (obj, attrs, value) {
attrs.split('.').forEach((key, index) => {
if (attrs.length - index <= 1) {
obj[key] = value;
return;
}
obj = obj[key];
});
}
================================================
FILE: src/utils/resize-event.js
================================================
import ResizeObserver from 'resize-observer-polyfill';
const isServer = typeof window === 'undefined';
const resizeHandler = function (entries) {
for (const entry of entries) {
const listeners = entry.target.__resizeListeners__ || [];
if (listeners.length) {
listeners.forEach(fn => {
fn();
});
}
}
};
export const addResizeListener = function (element, fn) {
if (isServer) return;
if (!element.__resizeListeners__) {
element.__resizeListeners__ = [];
element.__ro__ = new ResizeObserver(resizeHandler);
element.__ro__.observe(element);
}
element.__resizeListeners__.push(fn);
};
export const removeResizeListener = function (element, fn) {
if (!element || !element.__resizeListeners__) return;
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
if (!element.__resizeListeners__.length) {
element.__ro__.disconnect();
}
};
================================================
FILE: src/utils/testing.js
================================================
import Vue from 'vue';
import { mount, shallow } from 'avoriaz';
import toHaveBeenWarnedInit from './to-have-ben-warned';
import MuseUI from '../index';
export default function test (name, cb) {
toHaveBeenWarnedInit();
MuseUI.install(Vue);
rafPolyfill(window);
describe(name, () =>
cb({
functionalContext,
mount,
shallow
})
);
}
test.skip = describe.skip;
function functionalContext (context = {}, children = []) {
if (!Array.isArray(children)) children = [children];
return {
context: Object.assign(
{
data: {},
props: {}
},
context
),
children
};
}
// requestAnimationFrame polyfill | Milos Djakonovic ( @Miloshio ) | MIT | https://github.com/milosdjakonovic/requestAnimationFrame-polyfill
function rafPolyfill (w) {
const FRAME_RATE_INTERVAL = 1000 / 60;
let allCallbacks = [];
let executeAllScheduled = false;
let shouldCheckCancelRaf = false;
const callbacksForCancellation = [];
function isToBeCancelled (cb) {
for (var i = 0; i < callbacksForCancellation.length; i++) {
if (callbacksForCancellation[i] === cb) {
callbacksForCancellation.splice(i, 1);
return true;
}
}
}
function executeAll () {
executeAllScheduled = false;
var _allCallbacks = allCallbacks;
allCallbacks = [];
for (var i = 0; i < _allCallbacks.length; i++) {
if (shouldCheckCancelRaf === true) {
if (isToBeCancelled(_allCallbacks[i])) {
shouldCheckCancelRaf = false;
return;
}
}
_allCallbacks[i].apply(w, [new Date().getTime()]);
}
}
function raf (callback) {
allCallbacks.push(callback);
if (executeAllScheduled === false) {
w.setTimeout(executeAll, FRAME_RATE_INTERVAL);
executeAllScheduled = true;
}
return callback;
}
function cancelRaf (callback) {
callbacksForCancellation.push(callback);
shouldCheckCancelRaf = true;
}
// https://gist.github.com/paulirish/1579671
const vendors = ['ms', 'moz', 'webkit', 'o'];
for (var x = 0; x < vendors.length && !w.requestAnimationFrame; ++x) {
w.requestAnimationFrame = w[vendors[x] + 'RequestAnimationFrame'];
w.cancelAnimationFrame =
w[vendors[x] + 'CancelAnimationFrame'] ||
w[vendors[x] + 'CancelRequestAnimationFrame'];
}
if (!w.requestAnimationFrame) w.requestAnimationFrame = raf;
if (!w.cancelAnimationFrame) w.cancelAnimationFrame = cancelRaf;
}
================================================
FILE: src/utils/to-have-ben-warned.js
================================================
/*eslint-disable*/
function noop () {}
if (typeof console === 'undefined') {
window.console = {
warn: noop,
error: noop
};
}
// avoid info messages during test
console.info = noop;
const asserted = [];
function createCompareFn (spy) {
const hasWarned = msg => {
for (const args of spy.calls.allArgs()) {
if (args.some(arg => arg.toString().includes(msg))) return true;
}
return false;
};
return {
compare: msg => {
asserted.push(msg);
const warned = Array.isArray(msg) ? msg.some(hasWarned) : hasWarned(msg);
return {
pass: warned,
message: warned
? () => `Expected message "${msg}" not to have been warned`
: () => `Expected message "${msg}" to have been warned`
};
}
};
}
function toHaveBeenWarnedInit () {
// define custom matcher for warnings
beforeEach(() => {
asserted.length = 0;
spyOn(console, 'warn');
spyOn(console, 'error');
jasmine.addMatchers({
toHaveBeenWarned: () => createCompareFn(console.error),
toHaveBeenTipped: () => createCompareFn(console.warn)
});
});
afterEach(done => {
for (const type of ['error', 'warn']) {
const warned = msg =>
asserted.some(assertedMsg => msg.toString().includes(assertedMsg));
for (const args of console[type].calls.allArgs()) {
if (!warned(args[0])) {
done.fail(`Unexpected console.${type} message: ${args[0]}`);
return;
}
}
}
done();
});
}
export default toHaveBeenWarnedInit;
================================================
FILE: src/utils/translate.js
================================================
var docStyle = typeof document !== 'undefined' ? document.documentElement.style : {};
var engine;
var translate3d = false;
if (typeof window !== 'undefined' && window.opera && Object.prototype.toString.call(window.opera) === '[object Opera]') {
engine = 'presto';
} else if ('MozAppearance' in docStyle) {
engine = 'gecko';
} else if ('WebkitAppearance' in docStyle) {
engine = 'webkit';
} else if (typeof navigator !== 'undefined' && typeof navigator.cpuClass === 'string') {
engine = 'trident';
} else {
engine = 'node';
}
var cssPrefix = { trident: '-ms-', gecko: '-moz-', webkit: '-webkit-', presto: '-o-' }[engine];
var vendorPrefix = { trident: 'ms', gecko: 'Moz', webkit: 'Webkit', presto: 'O' }[engine];
var helperElem = typeof document !== 'undefined' ? document.createElement('div') : {};
var perspectiveProperty = vendorPrefix + 'Perspective';
var transformProperty = vendorPrefix + 'Transform';
var transformStyleName = cssPrefix + 'transform';
var transitionProperty = vendorPrefix + 'Transition';
var transitionStyleName = cssPrefix + 'transition';
var transitionEndProperty = (vendorPrefix || '').toLowerCase() + 'TransitionEnd';
if (helperElem.style && helperElem.style[perspectiveProperty] !== undefined) {
translate3d = true;
}
var getTranslate = function (element) {
var result = { left: 0, top: 0 };
if (element === null || element.style === null) return result;
var transform = element.style[transformProperty];
var matches = /translate\(\s*(-?\d+(\.?\d+?)?)px,\s*(-?\d+(\.\d+)?)px\)\s*translateZ\(0px\)/g.exec(transform);
if (matches) {
result.left = +matches[1];
result.top = +matches[3];
}
return result;
};
var translateElement = function (element, x, y) {
if (x === null && y === null) return;
if (element === null || element.style === null) return;
if (!element.style[transformProperty] && x === 0 && y === 0) return;
if (x === null || y === null) {
var translate = getTranslate(element);
if (x === null) {
x = translate.left;
}
if (y === null) {
y = translate.top;
}
}
cancelTranslateElement(element);
if (translate3d) {
element.style[transformProperty] += ' translate(' + (x ? x + 'px' : '0px') + ',' + (y ? y + 'px' : '0px') + ') translateZ(0px)';
} else {
element.style[transformProperty] += ' translate(' + (x ? x + 'px' : '0px') + ',' + (y ? y + 'px' : '0px') + ')';
}
};
var cancelTranslateElement = function (element) {
if (element === null || element.style === null) return;
var transformValue = element.style[transformProperty];
if (transformValue) {
transformValue = transformValue.replace(/translate\(\s*(-?\d+(\.?\d+?)?)px,\s*(-?\d+(\.\d+)?)px\)\s*translateZ\(0px\)/g, '');
element.style[transformProperty] = transformValue;
}
};
export default {
transformProperty: transformProperty,
transformStyleName: transformStyleName,
transitionProperty: transitionProperty,
transitionStyleName: transitionStyleName,
transitionEndProperty: transitionEndProperty,
getElementTranslate: getTranslate,
translateElement: translateElement,
cancelTranslateElement: cancelTranslateElement
};
================================================
FILE: types/index.d.ts
================================================
export * from './muse-ui';
import * as MuseUI from './muse-ui';
export default MuseUI;
declare module './muse-ui/lib/theme' {
import * as MuseUI from './muse-ui';
export default MuseUI.theme;
}
declare module './muse-ui/lib/theme/colors' {
import * as MuseUI from './muse-ui';
export default MuseUI.Colors;
}
================================================
FILE: types/muse-ui.d.ts
================================================
import Vue, { PluginObject, PluginFunction } from 'vue';
import { MuTheme } from './theme';
/** The version of muse-ui */
export const version: string;
/**
* Install all muse-ui components into Vue.
* Please do not invoke this method directly.
* Call `Vue.use(MuseUI)` to install.
*/
export const install: PluginFunction;
export const Colors: { [key: string]: string };
export const theme: MuTheme;
// components
export const Alert: PluginObject;
export const AppBar: PluginObject;
export const AutoComplete: PluginObject;
export const Avatar: PluginObject;
export const Badge: PluginObject;
export const BottomNav: PluginObject;
export const BottomSheet: PluginObject;
export const Breadcrumbs: PluginObject;
export const Button: PluginObject;
export const Card: PluginObject;
export const Carousel: PluginObject;
export const Checkbox: PluginObject;
export const Chip: PluginObject;
export const DateInput: PluginObject;
export const DataTable: PluginObject;
export const Dialog: PluginObject;
export const Divider: PluginObject;
export const Drawer: PluginObject;
export const ExpansionPanel: PluginObject;
export const Form: PluginObject;
export const Grid: PluginObject;
export const GridList: PluginObject;
export const Helpers: PluginObject;
export const Icon: PluginObject;
export const List: PluginObject;
export const LoadMore: PluginObject;
export const Menu: PluginObject;
export const Pagination: PluginObject;
export const Paper: PluginObject;
export const Picker: PluginObject;
export const Popover: PluginObject;
export const Progress: PluginObject;
export const Radio: PluginObject;
export const Select: PluginObject;
export const SlidePicker: PluginObject;
export const Slider: PluginObject;
export const Snackbar: PluginObject;
export const Stepper: PluginObject;
export const SubHeader: PluginObject;
export const Switch: PluginObject;
export const Tabs: PluginObject;
export const TextField: PluginObject;
export const Tooltip: PluginObject;
================================================
FILE: types/theme.d.ts
================================================
export interface ThemeOptions {
type?: string;
primary?: string;
secondary?: string;
success?: string;
warning?: string;
info?: string;
error?: string;
track?: string;
text?: {
primary?: string;
secondary?: string;
alternate?: string;
disabled?: string;
hint?: string;
};
divider?: string;
background?: {
paper?: string;
chip?: string;
default?: string;
};
}
export type ThemeType = 'dark' | 'light';
export interface ThemeFunction {
(theme: ThemeOptions, type: ThemeType, name: string): void;
}
export interface MuTheme {
addCreateTheme(theme: ThemeFunction): MuTheme;
use(name: string): MuTheme;
add(name: string, options: ThemeOptions, extendName: string): MuTheme;
generate(name: string): string;
}