* Добавлен метод копирования текста в буфер

* Добавлен компонент выбора цвета
This commit is contained in:
goodmice 2022-08-04 01:33:43 +05:00
parent 64246bd2fd
commit 354d6945d7
3 changed files with 159 additions and 0 deletions

View File

@ -0,0 +1,129 @@
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import { Input, Popover, Slider } from 'antd'
import { CopyOutlined } from '@ant-design/icons'
import { copyToClipboard } from './factory'
import '@styles/components/color_picker.less'
export class Color {
public r: number
public g: number
public b: number
public a: number = 1
public constructor(color: Color | string)
public constructor(r: number, g: number, b: number, a?: number)
constructor(...args: any[]) {
let out
if (args[0] instanceof Color) {
out = args[0]
} else if (typeof args[0] === 'string') {
out = Color.parseToObject(args[0])
} else if (typeof args[0] === 'number') {
out = { r: args[0], g: args[1], b: args[2], a: args[3] ?? 1 }
} else throw new Error('Некорректные аргументы')
this.r = out.r
this.g = out.g
this.b = out.b
this.a = out.a
}
public static parse(str: string): Color {
const out = Color.parseToObject(str)
return new Color(out.r, out.g, out.b, out.a)
}
private static parseToObject(str: string) {
let rgb: number[] = []
let a: number = 1
if (str.startsWith('rgb')) {
const parts = str.replaceAll(/\s/g, '').match(/rgba?\((\d+),(\d+),(\d)+(?:,([\d.]+))?\)/)
if (parts) {
rgb = parts.slice(1, 4).map((v) => Math.min(0, Math.max(parseInt(v), 255)))
if (parts[4]) a = parseFloat(`0${parts[4]}`)
}
} else if (str.startsWith('#')) {
const parts = str.slice(1)
let rgba: string[] | null = parts.length > 5 ? parts.match(/.{1,2}/g) : [...parts]
if (rgba) {
rgb = rgba.slice(0, 3).map((v) => parseInt(v, 16))
if (rgba[3]) a = parseInt(rgba[3], 16) / 255
}
}
if (rgb.length < 3)
throw new Error('Некорректная строка')
return { r: rgb[0], g: rgb[1], b: rgb[2], a }
}
public toString = () => this.toHexString()
public toCssString = () => `rgba(${this.r},${this.g},${this.b},${this.a})`
public toHexString() {
const a = Math.floor(this.a * 255)
let out = '#' + [this.r, this.g, this.b].map((v) => v.toString(16).padStart(2, '0')).join('')
if (a < 255) out += a.toString(16).padStart(2, '0')
return out
}
}
export type ColorPickerProps = {
value?: string | Color
onChange?: (value: Color) => void
size?: number | string
id?: string
}
const makeChangeColor = (set: React.Dispatch<React.SetStateAction<Color>>, accessor: 'r' | 'g' | 'b' | 'a') => (value: number) => set((prev: Color) => {
const out = new Color(prev)
out[accessor] = value
return out
})
export const ColorPicker = memo<ColorPickerProps>(({ value = '#AA33BB', onChange, size, ...other }) => {
const [color, setColor] = useState<Color>(new Color(255, 255, 255))
useEffect(() => setColor(new Color(value)), [value])
const divStyle = useMemo(() => ({
width: size,
height: size,
backgroundColor: color.toCssString(),
}), [size, color])
const changeR = useMemo(() => makeChangeColor(setColor, 'r'), [])
const changeG = useMemo(() => makeChangeColor(setColor, 'g'), [])
const changeB = useMemo(() => makeChangeColor(setColor, 'b'), [])
const changeA = useMemo(() => makeChangeColor(setColor, 'a'), [])
const onClose = useCallback((visible: boolean) => {
if (!visible)
onChange?.(color)
}, [color, onChange])
const onCopyClick = useCallback(() => copyToClipboard(color.toHexString()), [color])
return (
<Popover
trigger={'click'}
onVisibleChange={onClose}
content={(
<div className={'asb-color-picker-content'}>
<div className={'asb-color-picker-sliders'}>
<Slider vertical min={0} max={255} defaultValue={color.r} onChange={changeR} />
<Slider vertical min={0} max={255} defaultValue={color.g} onChange={changeG} />
<Slider vertical min={0} max={255} defaultValue={color.b} onChange={changeB} />
<Slider vertical min={0} max={1} step={0.01} defaultValue={color.a} onChange={changeA} />
</div>
<Input {...other} value={color.toHexString()} addonBefore={(
<CopyOutlined onClick={onCopyClick} />
)} />
</div>
)}
>
<div className={'asb-color-picker-preview'} style={divStyle}/>
</Popover>
)
})
export default ColorPicker

View File

@ -35,6 +35,15 @@ export const notify = (body?: ReactNode, notifyType: NotifyType = 'info', other?
})
}
export const copyToClipboard = (value: string, successText?: string, errorText?: string) => {
try {
navigator.clipboard.writeText(value)
notify(successText ?? 'Текст успешно скопирован в буфер обмена', 'info')
} catch (ex) {
notify(errorText ?? 'Не удалось скопировать текст в буфер обмена', 'error')
}
}
type asyncFunction = (...args: any) => Promise<any|void>
const parseApiEror = (err: unknown, actionName?: string) => {

View File

@ -0,0 +1,21 @@
.asb-color-picker-content {
display: flex;
flex-direction: column;
align-items: stretch;
width: 150px;
height: 150px;
& > .asb-color-picker-sliders {
flex-grow: 1;
display: flex;
align-items: stretch;
justify-content: space-between;
padding-bottom: 20px;
}
}
.asb-color-picker-preview {
border: 1px solid black;
width: 20px;
height: 20px;
}