Skip to content

Commit

Permalink
feat(color-picker): add color-picker component
Browse files Browse the repository at this point in the history
  • Loading branch information
oljc committed Feb 1, 2024
1 parent 6678afb commit aecd552
Show file tree
Hide file tree
Showing 53 changed files with 2,323 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/arco-vue-docs/locale/en-us.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export default {
scrollbar: 'Scrollbar',
watermark: 'Watermark',
verificationCode: 'VerificationCode',
colorPicker: 'ColorPicker',
},
footer: {
design: 'Design',
Expand Down
1 change: 1 addition & 0 deletions packages/arco-vue-docs/locale/zh-cn.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export default {
scrollbar: '滚动条 Scrollbar',
watermark: '水印 Watermark',
verificationCode: '验证码输入框 VerificationCode',
colorPicker: '颜色选择器 ColorPicker',
},
footer: {
design: '设计',
Expand Down
9 changes: 9 additions & 0 deletions packages/arco-vue-docs/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ const VerificationCode = () =>
import('@web-vue/components/verification-code/README.zh-CN.md');
const VerificationCodeEn = () =>
import('@web-vue/components/verification-code/README.en-US.md');
const ColorPicker = () =>
import('@web-vue/components/color-picker/README.zh-CN.md');
const ColorPickerEn = () =>
import('@web-vue/components/color-picker/README.en-US.md');

const docs = [
{
Expand Down Expand Up @@ -459,6 +463,11 @@ const components = [
component: Checkbox,
componentEn: CheckboxEn,
},
{
name: 'colorPicker',
component: ColorPicker,
componentEn: ColorPickerEn,
},
{
name: 'datePicker',
component: DatePicker,
Expand Down
215 changes: 215 additions & 0 deletions packages/web-vue/components/_utils/color.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
// https://github.com/scttcper/tinycolor
export const hsvToRgb = (h: number, s: number, v: number) => {
const i = Math.floor(h * 6);
const f = h * 6 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
const mod = i % 6;
const r = [v, q, p, p, t, v][mod];
const g = [t, v, v, q, p, p][mod];
const b = [p, p, t, v, v, q][mod];

return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255),
};
};

export const rgbToHsv = (r: number, g: number, b: number) => {
r /= 255;
g /= 255;
b /= 255;

const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = 0;
const v = max;
const d = max - min;
const s = max === 0 ? 0 : d / max;

if (max === min) {
h = 0;
} else {
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
default:
break;
}

h /= 6;
}

return { h, s, v };
};

// <http://www.w3.org/TR/css3-values/#integers>
const CSS_INTEGER = '[-\\+]?\\d+%?';

// <http://www.w3.org/TR/css3-values/#number-value>
const CSS_NUMBER = '[-\\+]?\\d*\\.\\d+%?';

// Allow positive/negative integer/number. Don't capture the either/or, just the entire outcome.
const CSS_UNIT = `(?:${CSS_NUMBER})|(?:${CSS_INTEGER})`;

// Actual matching.
// Parentheses and commas are optional, but not required.
// Whitespace can take the place of commas or opening paren
const PERMISSIVE_MATCH3 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;
const PERMISSIVE_MATCH4 = `[\\s|\\(]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})[,|\\s]+(${CSS_UNIT})\\s*\\)?`;

const matchers = {
rgb: new RegExp(`rgb${PERMISSIVE_MATCH3}`),
rgba: new RegExp(`rgba${PERMISSIVE_MATCH4}`),
hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
};

export const parseIntFromHex = (val: string): number => {
return parseInt(val, 16);
};

export const convertHexToDecimal = (h: string): number => {
return parseIntFromHex(h) / 255;
};

export const formatInputToRgb = (
color: string
): { r: number; g: number; b: number; a?: number } | false => {
let match = matchers.rgb.exec(color);
if (match) {
return {
r: parseInt(match[1], 10),
g: parseInt(match[2], 10),
b: parseInt(match[3], 10),
};
}

match = matchers.rgba.exec(color);
if (match) {
return {
r: parseInt(match[1], 10),
g: parseInt(match[2], 10),
b: parseInt(match[3], 10),
a: parseFloat(match[4]),
};
}

match = matchers.hex8.exec(color);
if (match) {
return {
r: parseIntFromHex(match[1]),
g: parseIntFromHex(match[2]),
b: parseIntFromHex(match[3]),
a: convertHexToDecimal(match[4]),
};
}

match = matchers.hex6.exec(color);
if (match) {
return {
r: parseIntFromHex(match[1]),
g: parseIntFromHex(match[2]),
b: parseIntFromHex(match[3]),
};
}

match = matchers.hex4.exec(color);
if (match) {
return {
r: parseIntFromHex(match[1] + match[1]),
g: parseIntFromHex(match[2] + match[2]),
b: parseIntFromHex(match[3] + match[3]),
a: convertHexToDecimal(match[4] + match[4]),
};
}

match = matchers.hex3.exec(color);
if (match) {
return {
r: parseIntFromHex(match[1] + match[1]),
g: parseIntFromHex(match[2] + match[2]),
b: parseIntFromHex(match[3] + match[3]),
};
}

return false;
};

export const formatInputToHSVA = (color: string) => {
const rgba = formatInputToRgb(color);
if (rgba) {
const hsv = rgbToHsv(rgba.r, rgba.g, rgba.b);
return {
...hsv,
a: rgba.a ?? 1,
};
}
return {
h: 0,
s: 1,
v: 1,
a: 1,
};
};

export const hexToRgb = (color: string): any => {
color = color.trim().toLowerCase();
if (color.length === 0) {
return false;
}

let match = matchers.hex6.exec(color);
if (match) {
return {
r: parseIntFromHex(match[1]),
g: parseIntFromHex(match[2]),
b: parseIntFromHex(match[3]),
};
}

match = matchers.hex3.exec(color);
if (match) {
return {
r: parseIntFromHex(match[1] + match[1]),
g: parseIntFromHex(match[2] + match[2]),
b: parseIntFromHex(match[3] + match[3]),
};
}

return false;
};

export const rgbToHex = (r: number, g: number, b: number) => {
const hex = [
Math.round(r).toString(16).padStart(2, '0'),
Math.round(g).toString(16).padStart(2, '0'),
Math.round(b).toString(16).padStart(2, '0'),
];

return hex.join('').toUpperCase();
};

export const rgbaToHex = (r: number, g: number, b: number, a: number) => {
const hex = [
Math.round(r).toString(16).padStart(2, '0'),
Math.round(g).toString(16).padStart(2, '0'),
Math.round(b).toString(16).padStart(2, '0'),
Math.round(a * 255)
.toString(16)
.padStart(2, '0'),
];

return hex.join('').toUpperCase();
};
2 changes: 2 additions & 0 deletions packages/web-vue/components/arco-vue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Carousel, { CarouselItem } from './carousel';
import Cascader, { CascaderPanel } from './cascader';
import Checkbox, { CheckboxGroup } from './checkbox';
import Collapse, { CollapseItem } from './collapse';
import ColorPicker from './color-picker';
import Comment from './comment';
import ConfigProvider from './config-provider';
import DatePicker, {
Expand Down Expand Up @@ -116,6 +117,7 @@ const components: Record<string, Plugin> = {
Carousel,
Collapse,
Comment,
ColorPicker,
Descriptions,
Empty,
Image,
Expand Down
47 changes: 47 additions & 0 deletions packages/web-vue/components/color-picker/README.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
```yaml
meta:
type: Component
category: Data Entry
title: ColorPicker
description: Used for select and display colors.
```
*Auto translate by google.*
@import ./__demo__/basic.md
@import ./__demo__/size.md
@import ./__demo__/disabled.md
@import ./__demo__/format.md
@import ./__demo__/colors.md
@import ./__demo__/trigger.md
@import ./__demo__/trigger-element.md
@import ./__demo__/only-panel.md
## API
### `<color-picker>` Props

|Attribute|Description|Type|Default|
|---|---|---|:---:|
|model-value **(v-model)**|Value|`string`|`-`|
|default-value|Default value (uncontrolled state)|`string`|`-`|
|format|Color value format|`'hex' \| 'rgb'`|`-`|
|size|Size|`'mini' \| 'small' \| 'medium' \| 'large'`|`'medium'`|
|show-text|Show color value|`boolean`|`false`|
|show-history|Show history colors|`boolean`|`false`|
|show-preset|Show preset colors|`boolean`|`false`|
|disabled|disabled|`boolean`|`false`|
|disabled-alpha|Disable transparency channel|`boolean`|`false`|
|hide-trigger|There is no trigger element, only the color panel is displayed|`boolean`|`false`|
|trigger-props|Can accept Props of all [Trigger](/vue/component/trigger) components|`Partial<TriggerProps>`|`-`|
|history-colors|Color array of historical colors|`string[]`|`-`|
|preset-colors|Color array of preset colors|`string[]`|`() => colors`|
### `<color-picker>` Events

|Event Name|Description|Parameters|
|---|---|---|
|change|Triggered when the color value changes|value: `string`|
|popup-visible-change|Triggered when the color panel is expanded and collapsed|visible: `boolean`<br>value: `string`|


45 changes: 45 additions & 0 deletions packages/web-vue/components/color-picker/README.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
```yaml
meta:
type: 组件
category: 数据输入
title: 颜色选择器 ColorPicker
description: 用于选择和展示颜色
```
@import ./__demo__/basic.md
@import ./__demo__/size.md
@import ./__demo__/disabled.md
@import ./__demo__/format.md
@import ./__demo__/colors.md
@import ./__demo__/trigger.md
@import ./__demo__/trigger-element.md
@import ./__demo__/only-panel.md
## API
### `<color-picker>` Props

|参数名|描述|类型|默认值|
|---|---|---|:---:|
|model-value **(v-model)**|绑定值|`string`|`-`|
|default-value|默认值(非受控状态)|`string`|`-`|
|format|颜色值的格式|`'hex' \| 'rgb'`|`-`|
|size|尺寸|`'mini' \| 'small' \| 'medium' \| 'large'`|`'medium'`|
|show-text|显示颜色值|`boolean`|`false`|
|show-history|显示历史颜色|`boolean`|`false`|
|show-preset|显示预设颜色|`boolean`|`false`|
|disabled|禁用|`boolean`|`false`|
|disabled-alpha|禁用透明通道|`boolean`|`false`|
|hide-trigger|没有触发元素,只显示颜色面板|`boolean`|`false`|
|trigger-props|接受所有 [Trigger](/vue/component/trigger) 组件的Props|`Partial<TriggerProps>`|`-`|
|history-colors|历史颜色的颜色数组|`string[]`|`-`|
|preset-colors|预设颜色的颜色数组|`string[]`|`() => colors`|
### `<color-picker>` Events

|事件名|描述|参数|
|---|---|---|
|change|颜色值改变时触发|value: `string`|
|popup-visible-change|颜色面板展开和收起时触发|visible: `boolean`<br>value: `string`|


31 changes: 31 additions & 0 deletions packages/web-vue/components/color-picker/TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## zh-CN
```yaml
meta:
type: 组件
category: 数据输入
title: 颜色选择器 ColorPicker
description: 用于选择和展示颜色
```
---
## en-US
```yaml
meta:
type: Component
category: Data Entry
title: ColorPicker
description: Used for select and display colors.
```
---
@import ./__demo__/basic.md
@import ./__demo__/size.md
@import ./__demo__/disabled.md
@import ./__demo__/format.md
@import ./__demo__/colors.md
@import ./__demo__/trigger.md
@import ./__demo__/trigger-element.md
@import ./__demo__/only-panel.md
## API
%%API(color-picker.tsx)%%
Loading

0 comments on commit aecd552

Please sign in to comment.