diff --git a/components/color-picker/ColorPicker.jsx b/components/color-picker/ColorPicker.jsx new file mode 100644 index 000000000..37913b091 --- /dev/null +++ b/components/color-picker/ColorPicker.jsx @@ -0,0 +1,208 @@ +import PropTypes from '../_util/vue-types'; +import { ConfigConsumerProps } from '../config-provider'; +import BaseMixin from '../_util/BaseMixin'; +import Pickr from '@simonwep/pickr/dist/pickr.es5.min'; +import Icon from '../icon'; +import LocaleReceiver from '../locale-provider/LocaleReceiver'; +import enUS from './locale/en_US'; +import debounce from 'lodash/debounce'; + +import { getOptionProps } from '../_util/props-util'; +let colors = '#194d33'; +export default { + name: 'AColorPicker', + mixins: [BaseMixin], + model: { + prop: 'value', + event: 'change.value', //为了支持v-model直接返回颜色字符串 所以用了自定义的事件,与pickr自带change事件进行区分 + }, + props: { + prefixCls: PropTypes.string, + defaultValue: PropTypes.string, //默认值 + config: PropTypes.object, //pickr配置 + value: PropTypes.string, //颜色值 + locale: PropTypes.object, //双语包 + colorRounded: PropTypes.number, //颜色数值保留几位小数 + size: PropTypes.oneOf(['default', 'small', 'large']).def('default'), //尺寸 + getPopupContainer: PropTypes.func, //指定渲染容器 + disabled: PropTypes.bool.def(false), //是否禁用 + format: PropTypes.string, //颜色格式设置 + alpha: PropTypes.bool.def(false), //是否开启透明通道 + hue: PropTypes.bool.def(true), //是否开启色彩预选 + }, + inject: { + configProvider: { default: () => ConfigConsumerProps }, + }, + data() { + return { + colors, + myOpen: false, + pickr: null, + i18n: enUS, + }; + }, + watch: { + 'configProvider.locale.ColorPicker': { + handler(val) { + if (this.locale) return; + this.i18n = val; + this.reInitialize(); + }, + }, + locale(val) { + this.i18n = val.ColorPicker || val.lang; + this.reInitialize(); + }, + value(val) { + this.setColor(val); + }, + disabled(val) { + this.pickr[val ? 'disable' : 'enable'](); + }, + config: { + handler() { + this.reInitialize(); + }, + deep: true, + }, + format(val) { + const type = val.toLocaleUpperCase(); + let res = this.pickr.setColorRepresentation(type); + if (res) { + this.pickr.applyColor(); + } else { + throw new TypeError('format was invalid'); + } + }, + }, + mounted() { + if (this.locale) { + this.i18n = this.locale.ColorPicker || this.locale.lang; + } + this.createPickr(); + this.eventsBinding(); + }, + destroyed() { + this.pickr.destroyAndRemove(); + }, + methods: { + reInitialize() { + this.pickr.destroyAndRemove(); + const dom = document.createElement('div'); + dom.id = 'color-picker' + this._uid; + const box = this.$el.querySelector('#color-picker-box' + this._uid); + box.appendChild(dom); + this.createPickr(); + this.eventsBinding(); + }, + setColor: debounce(function(val) { + this.pickr.setColor(val); + }, 1000), + eventsBinding() { + const pickrEvents = [ + 'init', + 'hide', + 'show', + 'save', + 'clear', + 'change', + 'changestop', + 'cancel', + 'swatchselect', + ]; + Object.keys(this.$listeners).forEach(event => { + pickrEvents.includes(event) && this.pickr.on(event, this.$listeners[event]); + }); + }, + createPickr() { + const { getPopupContainer } = getOptionProps(this); + const { getPopupContainer: getContextPopupContainer } = this.configProvider; + const container = getPopupContainer || getContextPopupContainer; + this.pickr = Pickr.create( + Object.assign( + { + el: '#color-picker' + this._uid, + container: (container && container(this.$el)) || document.body, + theme: 'monolith', // or 'monolith', or 'nano' + default: this.value || this.defaultValue || null, // 有默认颜色pickr才可以获取到_representation + components: { + // Main components + preview: true, + opacity: this.alpha, + hue: this.hue, + // Input / output Options + interaction: { + hex: true, + rgba: true, + input: true, + clear: true, + save: true, + }, + }, + }, + this.config, + { i18n: this.i18n }, + ), + ) + .on('save', (color, instance) => { + if (color) { + let _representation = instance._representation || 'HEXA'; + color = color['to' + _representation]().toString(this.colorRounded || 0); + } + this.$emit('change.value', color || ''); + }) + .on('hide', () => { + this.setState({ myOpen: false }); + }); + }, + handleOpenChange() { + const open = !this.myOpen; + this.setState({ myOpen: open }); + this.pickr[open ? 'show' : 'hide'](); + this.$emit('openChange', open); + }, + getDefaultLocale() { + const result = { + ...enUS, + ...this.$props.locale, + }; + result.lang = { + ...result.lang, + ...(this.$props.locale || {}).lang, + }; + return result; + }, + renderColorPicker() { + const { prefixCls: customizePrefixCls } = this.$props; + const { getPrefixCls } = this.configProvider; + const prefixCls = getPrefixCls('color-picker', customizePrefixCls); + const { disabled } = getOptionProps(this); + const classString = { + [`${prefixCls}-box`]: true, + [`${prefixCls}-open`]: this.myOpen, + [`${prefixCls}-lg`]: this.size === 'large', + [`${prefixCls}-sm`]: this.size === 'small', + [`${prefixCls}-disabled`]: this.disabled, + }; + return ( +
+
+
+
+
+ +
+
+ ); + }, + }, + render() { + return ( + + ); + }, +}; diff --git a/components/color-picker/__tests__/__snapshots__/index.test.js.snap b/components/color-picker/__tests__/__snapshots__/index.test.js.snap new file mode 100644 index 000000000..a7c0a86e4 --- /dev/null +++ b/components/color-picker/__tests__/__snapshots__/index.test.js.snap @@ -0,0 +1,337 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ColorPicker prop locale should works 1`] = ` +
+
+
+
+ + + + +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+
+`; + +exports[`ColorPicker save event should works 1`] = ` +
+
+
+
+ + + + +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+
+`; + +exports[`ColorPicker should support default value 1`] = ` +
+
+
+
+ + + + +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+
+`; + +exports[`ColorPicker should support disabled 1`] = ` +
+
+
+
+ + + + +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+
+`; + +exports[`ColorPicker should support format 1`] = ` +
+
+
+
+ + + + +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+
+`; + +exports[`ColorPicker should support v-model 1`] = ` +
+
+
+
+ + + + +
+
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+ + +
+ +
+ +
+ + + + + + + + + + + +
+
+
+`; diff --git a/components/color-picker/__tests__/demo.test.js b/components/color-picker/__tests__/demo.test.js new file mode 100644 index 000000000..131bad76c --- /dev/null +++ b/components/color-picker/__tests__/demo.test.js @@ -0,0 +1,3 @@ +import demoTest from '../../../tests/shared/demoTest'; + +demoTest('color-picker'); diff --git a/components/color-picker/__tests__/index.test.js b/components/color-picker/__tests__/index.test.js new file mode 100644 index 000000000..61b963c05 --- /dev/null +++ b/components/color-picker/__tests__/index.test.js @@ -0,0 +1,155 @@ +import { mount } from '@vue/test-utils'; +import ColorPicker from '..'; +import { asyncExpect } from '@/tests/utils'; +describe('ColorPicker', () => { + it('should support default value', async () => { + const wrapper = mount( + { + render() { + return p}>; + }, + }, + { sync: false, attachToDocument: true }, + ); + await asyncExpect(() => { + expect(wrapper.html()).toMatchSnapshot(); + wrapper.destroy(); + }, 1000); + }); + it('should support v-model', async () => { + let color = 'rgba(10, 10, 10, 1)'; + const wrapper = mount( + { + data() { + return { + color, + }; + }, + render() { + return p}>; + }, + mounted() { + this.color = 'rgba(110, 120, 130, 1)'; + }, + }, + { sync: false, attachToDocument: true }, + ); + + await asyncExpect(() => { + expect(wrapper.html()).toMatchSnapshot(); + wrapper.destroy(); + }, 1000); + }); + it('should support disabled', async () => { + const wrapper = mount( + { + data() { + return { + disabled: false, + }; + }, + render() { + return p}>; + }, + mounted() { + this.disabled = true; + }, + }, + { sync: false, attachToDocument: true }, + ); + + await asyncExpect(async () => { + expect(wrapper.html()).toMatchSnapshot(); + await asyncExpect(() => { + wrapper.destroy(); + }); + }, 1000); + }); + it('should support format', async () => { + const wrapper = mount( + { + data() { + return { + format: 'RGBA', + }; + }, + render() { + return p}>; + }, + mounted() { + this.format = 'HEX'; + }, + }, + { sync: false, attachToDocument: true }, + ); + + await asyncExpect(async () => { + expect(wrapper.html()).toMatchSnapshot(); + await asyncExpect(() => { + wrapper.destroy(); + }); + }, 1000); + }); + it('prop locale should works', async () => { + const wrapper = mount( + { + data() { + return { + locale: { + lang: { + 'btn:save': 'セーブ', + 'btn:cancel': 'キャンセル', + 'btn:clear': '晴れ', + }, + }, + }; + }, + render() { + return ( + p} /> + ); + }, + mounted() { + this.locale = { + lang: { + 'btn:save': '1セーブ', + 'btn:cancel': '1キャンセル', + 'btn:clear': '1晴れ', + }, + }; + }, + }, + { sync: false, attachToDocument: true }, + ); + await asyncExpect(async () => { + expect(wrapper.html()).toMatchSnapshot(); + await asyncExpect(() => { + wrapper.destroy(); + }); + }, 1000); + }); + it('save event should works', async () => { + const wrapper = mount( + { + render() { + return ( + p} onSave={this.save} /> + ); + }, + methods: { + save(val) { + return val; + }, + }, + }, + { sync: false, attachToDocument: true }, + ); + await asyncExpect(async () => { + wrapper.find('.pcr-save').trigger('click'); + expect(wrapper.html()).toMatchSnapshot(); + await asyncExpect(() => { + wrapper.destroy(); + }); + }, 1000); + }); +}); diff --git a/components/color-picker/index.js b/components/color-picker/index.js new file mode 100644 index 000000000..9b7c87faa --- /dev/null +++ b/components/color-picker/index.js @@ -0,0 +1,9 @@ +import ColorPicker from './ColorPicker'; +import Base from '../base'; +/* istanbul ignore next */ +ColorPicker.install = function(Vue) { + Vue.use(Base); + Vue.component(ColorPicker.name, ColorPicker); +}; + +export default ColorPicker; diff --git a/components/color-picker/locale/en_US.js b/components/color-picker/locale/en_US.js new file mode 100644 index 000000000..d8f2854de --- /dev/null +++ b/components/color-picker/locale/en_US.js @@ -0,0 +1,5 @@ +export default { + 'btn:save': 'Save', + 'btn:cancel': 'Cancel', + 'btn:clear': 'Clear', +}; diff --git a/components/color-picker/locale/zh_CN.js b/components/color-picker/locale/zh_CN.js new file mode 100644 index 000000000..74117e2c7 --- /dev/null +++ b/components/color-picker/locale/zh_CN.js @@ -0,0 +1,5 @@ +export default { + 'btn:save': '保存', + 'btn:cancel': '取消', + 'btn:clear': '清除', +}; diff --git a/components/color-picker/locale/zh_TW.js b/components/color-picker/locale/zh_TW.js new file mode 100644 index 000000000..74117e2c7 --- /dev/null +++ b/components/color-picker/locale/zh_TW.js @@ -0,0 +1,5 @@ +export default { + 'btn:save': '保存', + 'btn:cancel': '取消', + 'btn:clear': '清除', +}; diff --git a/components/color-picker/style/index.js b/components/color-picker/style/index.js new file mode 100644 index 000000000..5c465d6dc --- /dev/null +++ b/components/color-picker/style/index.js @@ -0,0 +1,4 @@ +import '../../style/index.less'; +import './index.less'; +// style dependencies +import '../../grid/style'; diff --git a/components/color-picker/style/index.less b/components/color-picker/style/index.less new file mode 100644 index 000000000..0b10ddc6b --- /dev/null +++ b/components/color-picker/style/index.less @@ -0,0 +1,125 @@ +@import '../../style/themes/index'; +@import '../../input/style/mixin'; +@import '~@simonwep/pickr/dist/themes/classic.min.css'; // 'classic' theme +@import '~@simonwep/pickr/dist/themes/monolith.min.css'; // 'monolith' theme +@import '~@simonwep/pickr/dist/themes/nano.min.css'; // 'nano' theme +@color-picker-prefix-cls: ~'@{ant-prefix}-color-picker'; +.@{color-picker-prefix-cls} { + &-box{ + box-sizing: border-box; + margin: 0; + padding: 0; + color: rgba(0, 0, 0, 0.65); + font-size: 14px; + font-variant: tabular-nums; + line-height: 1.5; + list-style: none; + font-feature-settings: 'tnum'; + position: relative; + display: inline-block; + outline: none; + cursor: pointer; + transition: opacity 0.3s; + min-width: 55px; + .pickr{ + display: inline-block; + button{ + width: 18px; + height: 18px; + margin-left: 7px; + &:focus{ + box-shadow: none; + } + } + } + &.@{color-picker-prefix-cls}-disabled{ + cursor: not-allowed; + .@{color-picker-prefix-cls}-selection { + background: @input-disabled-bg; + box-shadow: none; + border: @border-width-base @border-style-base @select-border-color; + &:hover, + &:focus, + &:active { + border: @border-width-base @border-style-base @select-border-color; + box-shadow: none; + } + } + &.@{color-picker-prefix-cls}-open { + .@{color-picker-prefix-cls}-icon { + & svg { + transform: none; + } + } + } + } + } + &-open { + .@{color-picker-prefix-cls}-icon { + & svg { + transform: rotate(180deg); + } + } + .@{color-picker-prefix-cls}-selection { + .active(); + } + } + &-selection{ + display: block; + box-sizing: border-box; + background-color: @select-background; + border: @border-width-base @border-style-base @select-border-color; + border-top-width: @border-width-base + 0.02px; + border-radius: @border-radius-base; + outline: none; + transition: all 0.3s @ease-in-out; + user-select: none; + + position: relative; + height: @input-height-base; + cursor: inherit; + &:hover { + .hover; + } + } + &-icon{ + .iconfont-mixin(); + position: absolute; + top: 50%; + right: @control-padding-horizontal - 4px; + margin-top: -@font-size-sm / 2; + color: @disabled-color; + font-size: @font-size-sm; + line-height: 1; + transform-origin: 50% 50%; + & svg { + transition: transform 0.3s; + } + } + &-lg { + font-size: @font-size-lg; + .@{color-picker-prefix-cls}-selection { + line-height: @input-height-lg - 12; + height: @input-height-lg; + } + .@{color-picker-prefix-cls}-icon { + top: @input-height-lg / 2; + } + } + + &-sm { + .@{color-picker-prefix-cls}-selection { + line-height: @input-height-sm - 12; + height: @input-height-sm; + } + .pickr button{ + width: 14px; + height: 14px; + } + .@{color-picker-prefix-cls}-icon { + right: @control-padding-horizontal - 2px; + top: @input-height-sm / 2; + font-size: 10px; + } + } +} diff --git a/components/index.js b/components/index.js index 2afea9c2f..c92ffd057 100644 --- a/components/index.js +++ b/components/index.js @@ -136,6 +136,8 @@ import { default as Skeleton } from './skeleton'; import { default as Comment } from './comment'; +import { default as ColorPicker } from './color-picker'; + import { default as ConfigProvider } from './config-provider'; import { default as Empty } from './empty'; @@ -203,6 +205,7 @@ const components = [ Drawer, Skeleton, Comment, + ColorPicker, ConfigProvider, Empty, Result, @@ -292,6 +295,7 @@ export { Drawer, Skeleton, Comment, + ColorPicker, ConfigProvider, Empty, Result, diff --git a/components/locale-provider/LocaleReceiver.jsx b/components/locale-provider/LocaleReceiver.jsx index 0251b7042..bfd7301f9 100644 --- a/components/locale-provider/LocaleReceiver.jsx +++ b/components/locale-provider/LocaleReceiver.jsx @@ -34,7 +34,6 @@ export default { return localeCode; }, }, - render() { const { $scopedSlots } = this; const children = this.children || $scopedSlots.default; diff --git a/components/locale/default.js b/components/locale/default.js index b7c5c1654..1d9655ef4 100644 --- a/components/locale/default.js +++ b/components/locale/default.js @@ -2,6 +2,7 @@ import Pagination from '../vc-pagination/locale/en_US'; import DatePicker from '../date-picker/locale/en_US'; import TimePicker from '../time-picker/locale/en_US'; import Calendar from '../calendar/locale/en_US'; +import ColorPicker from '../color-picker/locale/en_US'; export default { locale: 'en', @@ -9,6 +10,7 @@ export default { DatePicker, TimePicker, Calendar, + ColorPicker, global: { placeholder: 'Please select', }, diff --git a/components/locale/zh_CN.js b/components/locale/zh_CN.js index 65283c371..d77241d3b 100644 --- a/components/locale/zh_CN.js +++ b/components/locale/zh_CN.js @@ -2,6 +2,7 @@ import Pagination from '../vc-pagination/locale/zh_CN'; import DatePicker from '../date-picker/locale/zh_CN'; import TimePicker from '../time-picker/locale/zh_CN'; import Calendar from '../calendar/locale/zh_CN'; +import ColorPicker from '../color-picker/locale/zh_CN'; export default { locale: 'zh-cn', @@ -9,6 +10,7 @@ export default { DatePicker, TimePicker, Calendar, + ColorPicker, // locales for all comoponents global: { placeholder: '请选择', diff --git a/components/locale/zh_TW.js b/components/locale/zh_TW.js index bc216f302..e53a3faf5 100644 --- a/components/locale/zh_TW.js +++ b/components/locale/zh_TW.js @@ -2,6 +2,7 @@ import Pagination from '../vc-pagination/locale/zh_TW'; import DatePicker from '../date-picker/locale/zh_TW'; import TimePicker from '../time-picker/locale/zh_TW'; import Calendar from '../calendar/locale/zh_TW'; +import ColorPicker from '../color-picker/locale/zh_TW'; export default { locale: 'zh-tw', @@ -9,6 +10,7 @@ export default { DatePicker, TimePicker, Calendar, + ColorPicker, Table: { filterTitle: '篩選器', filterConfirm: '確 定', diff --git a/components/style.js b/components/style.js index b9c6f1ff7..6c1bbbbbf 100644 --- a/components/style.js +++ b/components/style.js @@ -60,3 +60,4 @@ import './result/style'; import './descriptions/style'; import './page-header/style'; import './form-model/style'; +import './color-picker/style'; diff --git a/examples/App.vue b/examples/App.vue index b65964f0e..535d21598 100644 --- a/examples/App.vue +++ b/examples/App.vue @@ -1,26 +1,119 @@ diff --git a/package.json b/package.json index 944a0e7f7..201a210f4 100644 --- a/package.json +++ b/package.json @@ -174,6 +174,7 @@ "dependencies": { "@ant-design/icons": "^2.1.1", "@ant-design/icons-vue": "^2.0.0", + "@simonwep/pickr": "^1.6.0", "add-dom-event-listener": "^1.0.2", "array-tree-filter": "^2.1.0", "async-validator": "^3.0.3", diff --git a/tests/__snapshots__/index.test.js.snap b/tests/__snapshots__/index.test.js.snap index a5f701213..7200a421e 100644 --- a/tests/__snapshots__/index.test.js.snap +++ b/tests/__snapshots__/index.test.js.snap @@ -63,6 +63,7 @@ Array [ "Drawer", "Skeleton", "Comment", + "ColorPicker", "ConfigProvider", "Empty", "Result", diff --git a/types/color-picker.d.ts b/types/color-picker.d.ts new file mode 100644 index 000000000..bfd393ba7 --- /dev/null +++ b/types/color-picker.d.ts @@ -0,0 +1,51 @@ +// Project: https://github.com/vueComponent/ant-design-vue Definitions by: +// https://github.com/vueComponent/ant-design-vue/types + +import { AntdComponent } from './component'; +import Pickr from '@simonwep/pickr'; + +export declare class ColorPicker extends AntdComponent { + /** simonwep/pickr's options */ + config?:Pickr.Options + /**prefix class name */ + prefixCls?: string + /** default color value */ + defaultValue?: string + /** color value */ + value?: string + /** + * language package setting + * @type object + */ + locale: object; + /** + * precision of color value + * @default 0 + * @type number + * */ + colorRounded?:number + /** + * descriptions size type + * @default 'default' + * @type string + */ + size: 'large' | 'default' | 'small'; + /** + * Parent Node which the selector should be rendered to. Default to body. + * When position issues happen, try to modify it into scrollable content and position it relative. + * @default () => document.body + * @type Function + */ + getPopupContainer: (triggerNode: any) => HTMLElement; + /** + * Disabled or not + * @default false + * @type boolean + */ + disabled: boolean + /** + * to set the color format + * @default "HEXA" + */ + format: Pickr.Representation +} diff --git a/webpack.config.js b/webpack.config.js index d1f87c2e4..757c5c95e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -18,6 +18,7 @@ module.exports = { { test: /\.(js|jsx)$/, loader: 'babel-loader', + exclude: /pickr.*js/, options: { presets: [ [