UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

227 lines (226 loc) โ€ข 7.46 kB
"use client"; import Icon from "../../Icon/Icon.mjs"; import { useTranslation } from "../../i18n/useTranslation.mjs"; import { rootVariants } from "../Input/style.mjs"; import Input_default from "../Input/Input.mjs"; import InputNumber_default from "../Input/InputNumber.mjs"; import { panelStyles } from "../panelStyles.mjs"; import { PopoverPopup, PopoverPortal, PopoverPositioner, PopoverRoot, PopoverTriggerElement } from "../Popover/atoms.mjs"; import Slider from "../Slider/Slider.mjs"; import colorPicker_default from "../../i18n/resources/en/colorPicker.mjs"; import { formatColor, normalizeHexInput, parseColor } from "./color.mjs"; import { styles } from "./style.mjs"; import SaturationArea from "./SaturationArea.mjs"; import { memo, useEffect, useRef, useState } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; import { cx, useThemeMode } from "antd-style"; import useMergeState from "use-merge-value"; import { Pipette } from "lucide-react"; //#region src/base-ui/ColorPicker/ColorPicker.tsx const DEFAULT_COLOR = "#000000"; const hasEyeDropper = () => typeof window !== "undefined" && "EyeDropper" in window; const ColorPicker = memo(({ alpha = false, children, className, defaultValue = DEFAULT_COLOR, disabled, onChange, onChangeComplete, presets, showText, size = "middle", style, value }) => { const { t } = useTranslation(colorPicker_default); const { isDarkMode } = useThemeMode(); const [hex, setHex] = useMergeState(defaultValue, { defaultValue, onChange, value }); const [hsva, setHsva] = useState(() => parseColor(hex)); const [draft, setDraft] = useState(hex.slice(1).toUpperCase()); const popupRef = useRef(null); useEffect(() => { setHsva((current) => formatColor(current, alpha) === hex.toLowerCase() ? current : parseColor(hex, current.h)); setDraft(hex.slice(1).toUpperCase()); }, [hex, alpha]); const update = (next) => { setHsva(next); setHex(formatColor(next, alpha)); }; const commit = (next) => onChangeComplete?.(formatColor(next, alpha)); const apply = (next) => { update(next); commit(next); }; const commitDraft = () => { const normalized = normalizeHexInput(draft); if (!normalized) return setDraft(hex.slice(1).toUpperCase()); apply(parseColor(normalized, hsva.h)); }; const swatch = (dimension) => /* @__PURE__ */ jsx("span", { className: styles.swatch, style: { height: dimension, width: dimension }, children: /* @__PURE__ */ jsx("span", { style: { background: hex } }) }); const trigger = children ?? (showText ? /* @__PURE__ */ jsxs("button", { "aria-label": t("colorPicker.trigger"), disabled, style, type: "button", className: cx(rootVariants({ size, variant: isDarkMode ? "filled" : "outlined" }), styles.textTrigger, className), children: [swatch(18), /* @__PURE__ */ jsx("span", { style: { fontFamily: "var(--lobe-font-family-code, monospace)" }, children: hex.toUpperCase() })] }) : /* @__PURE__ */ jsx("button", { "aria-label": t("colorPicker.trigger"), className: cx(styles.swatchButton, className), disabled, style, type: "button", children: swatch(24) })); return /* @__PURE__ */ jsxs(PopoverRoot, { children: [/* @__PURE__ */ jsx(PopoverTriggerElement, { children: trigger }), /* @__PURE__ */ jsx(PopoverPortal, { children: /* @__PURE__ */ jsx(PopoverPositioner, { placement: "bottomLeft", children: /* @__PURE__ */ jsx(PopoverPopup, { className: panelStyles.popup, ref: popupRef, initialFocus: () => popupRef.current?.querySelector("[role=\"slider\"]") ?? true, children: /* @__PURE__ */ jsxs("div", { className: styles.panel, children: [ /* @__PURE__ */ jsxs("div", { className: styles.summary, children: [ swatch(40), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { className: styles.value, children: hex.toUpperCase() }), /* @__PURE__ */ jsx("div", { className: panelStyles.label, children: alpha ? `HEX ยท ${Math.round(hsva.a * 100)}%` : "HEX" })] }), hasEyeDropper() && /* @__PURE__ */ jsx("button", { "aria-label": t("colorPicker.eyeDropper"), className: panelStyles.nav, style: { marginInlineStart: "auto" }, type: "button", onClick: async () => { const result = await new window.EyeDropper().open().catch(() => null); if (result) apply({ ...parseColor(result.sRGBHex, hsva.h), a: hsva.a }); }, children: /* @__PURE__ */ jsx(Icon, { icon: Pipette, size: 14 }) }) ] }), /* @__PURE__ */ jsx(SaturationArea, { hue: hsva.h, label: t("colorPicker.saturation"), saturation: hsva.s, value: hsva.v, onChange: (s, v) => update({ ...hsva, s, v }), onChangeComplete: (s, v) => commit({ ...hsva, s, v }) }), /* @__PURE__ */ jsx(Slider, { "aria-label": t("colorPicker.hue"), max: 360, min: 0, value: hsva.h, classNames: { indicator: styles.hidden, thumb: styles.sliderThumb, track: styles.hueTrack }, onChange: (h) => update({ ...hsva, h }), onChangeComplete: (h) => commit({ ...hsva, h }) }), alpha && /* @__PURE__ */ jsx(Slider, { "aria-label": t("colorPicker.alpha"), max: 100, min: 0, value: Math.round(hsva.a * 100), classNames: { indicator: styles.hidden, thumb: styles.sliderThumb, track: styles.alphaTrack }, styles: { track: { "--lobe-color-picker-alpha": formatColor(hsva, false) } }, onChange: (a) => update({ ...hsva, a: a / 100 }), onChangeComplete: (a) => commit({ ...hsva, a: a / 100 }) }), /* @__PURE__ */ jsxs("div", { className: styles.hexRow, children: [/* @__PURE__ */ jsx(Input_default, { "aria-label": "HEX", prefix: /* @__PURE__ */ jsx("span", { className: panelStyles.label, children: "HEX" }), value: draft, variant: "filled", onBlur: commitDraft, onChange: (event) => setDraft(event.target.value), onPressEnter: commitDraft }), alpha && /* @__PURE__ */ jsx(InputNumber_default, { "aria-label": t("colorPicker.alpha"), className: styles.alphaField, controls: false, max: 100, min: 0, suffix: "%", value: Math.round(hsva.a * 100), variant: "filled", onChange: (a) => a !== null && apply({ ...hsva, a: a / 100 }) })] }), presets && presets.length > 0 && /* @__PURE__ */ jsx("div", { "aria-label": t("colorPicker.presets"), className: styles.presets, role: "group", children: presets.map((color) => /* @__PURE__ */ jsx("button", { "aria-label": color, "aria-pressed": color.toLowerCase() === hex.slice(0, 7).toLowerCase(), className: styles.preset, style: { background: color }, type: "button", onClick: () => apply({ ...parseColor(color, hsva.h), a: parseColor(color).a }) }, color)) }) ] }) }) }) })] }); }); ColorPicker.displayName = "ColorPicker"; //#endregion export { ColorPicker as default }; //# sourceMappingURL=ColorPicker.mjs.map