@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
227 lines (226 loc) โข 7.46 kB
JavaScript
"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