import {ChevronDownIcon, SearchIcon} from '@sanity/icons' import {CloseIcon} from '@sanity/icons' import { Box, Button, Card, Container, Flex, Inline, Popover, Stack, Text, TextInput, } from '@sanity/ui' import {type ColorResult, type HsvaColor} from '@uiw/react-color' import {useCallback, useEffect, useRef, useState} from 'react' import {type ObjectInputProps, type ObjectOptions, type ObjectSchemaType, set, unset} from 'sanity' export interface SimplerColorType { label: string value: string _type?: string // included in textColor and highlightColor annotation types _key?: string // included in textColor and highlightColor annotation types } export type ColorFormatType = 'hex' | 'hexa' | 'rgb' | 'rgba' | 'hsl' | 'hsla' export interface ColorOptions extends Omit { colorList?: Array colorFormat?: ColorFormatType defaultColorList?: Array defaultColorFormat?: ColorFormatType enableSearch?: boolean showColorValue?: boolean } export type SimplerColorSchemaType = Omit & { options?: ColorOptions } export type SimplerColorInputProps = ObjectInputProps export const SimplerColorInput = (props: ObjectInputProps) => { const [isOpen, setIsOpen] = useState(false) const [pickerIsOpen, setPickerIsOpen] = useState(false) const [searchValue, setSearchValue] = useState('') const {onChange} = props const value = props.value as SimplerColorType | undefined const type = props.schemaType as SimplerColorSchemaType const showColorValue = Boolean(type.options?.showColorValue ?? true) const [selectedColor, setSelectedColor] = useState | undefined>(value) const handleChange = useCallback( (color: SimplerColorType) => { setSelectedColor(color) setIsOpen(false) onChange(set({...props.value, ...color})) }, [onChange, props.value], ) const colorList: SimplerColorType[] = type.options?.colorList || type.type?.options?.defaultColorList const colorFormat = type.options?.colorFormat ?? type.type?.options?.defaultColorFormat const enableSearch = Boolean(type.options?.enableSearch ?? type.type?.options?.enableSearch) const filteredColorList = searchValue.length ? colorList?.filter((color) => { return color.label.toLowerCase().includes(searchValue.toLowerCase()) }) : colorList const handlePickerChange = (color: ColorResult) => { let colorValue: string switch (colorFormat) { case 'hexa': colorValue = color.hexa break case 'rgb': colorValue = `rgb(${color.rgb.r}, ${color.rgb.g}, ${color.rgb.b})` break case 'rgba': colorValue = `rgba(${color.rgba.r}, ${color.rgba.g}, ${ color.rgba.b }, ${color.rgba.a.toFixed(2)})` break case 'hsl': colorValue = `hsl(${color.hsl.h.toFixed(0)}, ${color.hsl.s.toFixed( 0, )}%, ${color.hsl.l.toFixed(0)}%)` break case 'hsla': colorValue = `hsla(${color.hsla.h.toFixed(0)}, ${color.hsla.s.toFixed( 0, )}%, ${color.hsla.l.toFixed(0)}%, ${color.hsla.a.toFixed(2)})` break default: colorValue = color.hex break } const formattedColor = { label: 'Custom', value: colorValue, } setSelectedColor(formattedColor) onChange(set({...props.value, ...formattedColor})) } const ref = useRef(null) useEffect(() => { if (!isOpen) setSearchValue('') const handleClickOutside = (event: MouseEvent) => { if (ref.current && !ref.current.contains(event.target as Node)) { setPickerIsOpen(false) setIsOpen(false) } } document.addEventListener('click', handleClickOutside, true) return () => { document.removeEventListener('click', handleClickOutside, true) } }, [isOpen, pickerIsOpen, ref]) /* @ts-expect-error we can assume type exists */ const isRequired: boolean = type.validation[0]._required === 'required' const [Component, setComponent] = useState(
Loading...
) useEffect(() => { import('@uiw/react-color').then((module) => { const {Chrome, rgbStringToHsva, hslStringToHsva} = module // convert rgb and hsl strings to hsva so they can be read by the picker successfully let pickerColor: string | HsvaColor = selectedColor?.value || '#ffffff' if (pickerColor.startsWith('rgb')) pickerColor = rgbStringToHsva(pickerColor) else if (pickerColor.startsWith('hsl')) pickerColor = hslStringToHsva(pickerColor) setComponent( } portal open={pickerIsOpen} > {!isRequired && ( )} {isOpen && colorList && ( {enableSearch && ( { setSearchValue(event.currentTarget.value) }} autoFocus /> )} {filteredColorList?.map((color: SimplerColorType) => color.value === 'custom' ? ( ) : ( ), )} )} , ) }) }, [isOpen, selectedColor, searchValue, pickerIsOpen]) return Component }