diff --git a/packages/components/color-picker/_example/eye-dropper.tsx b/packages/components/color-picker/_example/eye-dropper.tsx new file mode 100644 index 0000000000..3efad269b2 --- /dev/null +++ b/packages/components/color-picker/_example/eye-dropper.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { ColorPicker } from 'tdesign-react'; + +export default function EyeDropperExample() { + return ; +} diff --git a/packages/components/color-picker/components/panel/header.tsx b/packages/components/color-picker/components/panel/header.tsx index 557ae1ed8b..8472a186d9 100644 --- a/packages/components/color-picker/components/panel/header.tsx +++ b/packages/components/color-picker/components/panel/header.tsx @@ -1,8 +1,9 @@ -import React from 'react'; -import { COLOR_MODES } from '@tdesign/common-js/color-picker/constants'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { COLOR_MODES } from '@tdesign/common-js/color-picker/index'; import { useLocaleReceiver } from '../../../locale/LocalReceiver'; import Radio from '../../../radio'; +import { isEyeDropperSupported, openEyeDropper } from '../../utils/eyedropper'; import type { RadioValue } from '../../../radio'; import type { TdColorModes } from '../../interface'; @@ -12,30 +13,67 @@ export interface ColorPanelHeaderProps extends TdColorPickerProps { mode?: TdColorModes; onModeChange?: (value: RadioValue, context: { e: React.ChangeEvent }) => void; baseClassName?: string; + onEyeDropperPick?: (hex: string) => void; } +const supported = isEyeDropperSupported(); + const Header = (props: ColorPanelHeaderProps) => { const [local, t] = useLocaleReceiver('colorPicker'); + const { baseClassName, mode = 'monochrome', colorModes, onModeChange, eyeDropper, onEyeDropperPick } = props; + const [picking, setPicking] = useState(false); + const abortRef = useRef(null); + + useEffect( + () => () => { + abortRef.current?.abort(); + }, + [], + ); - const { baseClassName, mode = 'monochrome', colorModes, onModeChange } = props; + const handleEyeDropperClick = useCallback(async () => { + if (picking || !supported) return; + abortRef.current = new AbortController(); + setPicking(true); + try { + const hex = await openEyeDropper(abortRef.current.signal); + if (hex) onEyeDropperPick?.(hex); + } finally { + setPicking(false); + abortRef.current = null; + } + }, [picking, onEyeDropperPick]); const isSingleMode = colorModes?.length === 1; - if (isSingleMode) { - return null; - } + if (isSingleMode && !eyeDropper) return null; return (
-
- - {Object.keys(COLOR_MODES).map((key) => ( - - {t(local[COLOR_MODES[key]])} - - ))} - -
+ {!isSingleMode && ( +
+ + {Object.keys(COLOR_MODES).map((key) => ( + + {t(local[COLOR_MODES[key]])} + + ))} + +
+ )} + {eyeDropper && ( + + )}
); }; diff --git a/packages/components/color-picker/components/panel/index.tsx b/packages/components/color-picker/components/panel/index.tsx index 811b050340..464e7a22f9 100644 --- a/packages/components/color-picker/components/panel/index.tsx +++ b/packages/components/color-picker/components/panel/index.tsx @@ -39,6 +39,7 @@ const Panel = forwardRef((props, ref) => { disabled, enableAlpha, enableMultipleGradient, + eyeDropper, format, style, swatchColors, @@ -114,6 +115,14 @@ const Panel = forwardRef((props, ref) => { emitColorChange(); }; + const handleEyeDropperPick = useCallback( + (hex: string) => { + updateColor(hex); + emitColorChange('eyedropper'); + }, + [emitColorChange], // eslint-disable-line react-hooks/exhaustive-deps + ); + /** * 最近使用颜色变化 */ @@ -281,7 +290,14 @@ const Panel = forwardRef((props, ref) => { style={{ ...style }} ref={ref} > - +
{isGradient && ( ; +} + +interface EyeDropperConstructor { + new (): EyeDropperInstance; +} + +function getEyeDropperCtor(): EyeDropperConstructor | undefined { + if (typeof window === 'undefined') return undefined; + const { EyeDropper } = window as Window & { EyeDropper?: unknown }; + return typeof EyeDropper === 'function' ? (EyeDropper as EyeDropperConstructor) : undefined; +} + +export const isEyeDropperSupported = (): boolean => getEyeDropperCtor() !== undefined; + +export const openEyeDropper = async (signal?: AbortSignal): Promise => { + const Ctor = getEyeDropperCtor(); + if (!Ctor) return null; + try { + const result = await new Ctor().open(signal ? { signal } : undefined); + return result.sRGBHex; + } catch { + return null; + } +}; diff --git a/packages/components/config-provider/type.ts b/packages/components/config-provider/type.ts index 4227f35ed9..f2bb1b1d47 100644 --- a/packages/components/config-provider/type.ts +++ b/packages/components/config-provider/type.ts @@ -311,6 +311,11 @@ export interface ColorPickerConfig { * @default '' */ swatchColorTitle?: string; + /** + * 语言配置,"吸色" 按钮 title 文案 + * @default '' + */ + eyeDropper?: string; } export interface DatePickerConfig {