forked from ddrilling/asb_cloud_front
* Добавлен метод копирования текста в буфер
* Добавлен компонент выбора цвета
This commit is contained in:
parent
64246bd2fd
commit
354d6945d7
129
src/components/ColorPicker.tsx
Normal file
129
src/components/ColorPicker.tsx
Normal 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
|
@ -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) => {
|
||||
|
21
src/styles/components/color_picker.less
Normal file
21
src/styles/components/color_picker.less
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user