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 @@
-
-
- Primary
-
-
Default
-
- Dashed
-
-
- Danger
-
-
- 按钮
-
-
- Link
-
-
+
+
+
+
+ English
+
+
+ 中文
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
切换格式 [color:{{ color2 }}]
+
+
+
+
+
+
+
+
+
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: [
[