diff --git a/src/components/ui/color-picker.tsx b/src/components/ui/color-picker.tsx new file mode 100644 index 0000000..ea5eb30 --- /dev/null +++ b/src/components/ui/color-picker.tsx @@ -0,0 +1,141 @@ +import Block from "@uiw/react-color-block"; +import Colorful from "@uiw/react-color-colorful"; +import { useEffect, useState } from "react"; +import { Button } from "./button"; +import { Input } from "./input"; + +export default function ColorPicker({ + selectedColor, + colorPalette, + translations, + clearBackgroundOption = false, + onUpdateColor, +}: { + selectedColor: string; + colorPalette: string[]; + translations: Record<"colorWheel" | "colorPalette", string> & + Partial>; + clearBackgroundOption?: boolean; + onUpdateColor: (color: string) => void; +}) { + const [colorMode, setColorMode] = useState<"wheel" | "palette">("wheel"); + const [hexInput, setHexInput] = useState(selectedColor); + + useEffect(() => { + setHexInput(selectedColor); + }, [selectedColor]); + + const getTextColor = (color: string) => { + if (color === "transparent") return "#ffffff"; + const r = parseInt(color.slice(1, 3), 16); + const g = parseInt(color.slice(3, 5), 16); + const b = parseInt(color.slice(5, 7), 16); + const luminance = 0.299 * r + 0.587 * g + 0.114 * b; + if (luminance > 186) return "#000000"; + return "#ffffff"; + }; + + // Normalize the hex input. + // Adds a # at the beginning of the input if it's not there. + const normalizeHexDraft = (raw: string) => { + const trimmed = raw.trim(); + if (trimmed === "") return ""; + if (/^[0-9A-Fa-f]/.test(trimmed[0])) return `#${trimmed}`; + return trimmed; + }; + + const handleColorInputChange = (e: React.ChangeEvent) => { + const normalized = normalizeHexDraft(e.target.value); + setHexInput(normalized); + // Check if the normalized hex is a valid hex color. + // It should follow the format #RRGGBB or #RGB. + const isValidHexColor = + /^#[0-9A-Fa-f]{3}$/.test(normalized) || /^#[0-9A-Fa-f]{6}$/.test(normalized); + if (isValidHexColor) { + onUpdateColor(normalized); + } + }; + return ( +
+
+ + +
+ {colorMode === "wheel" && ( + <> +
+ {selectedColor} +
+ { + onUpdateColor(color.hex); + }} + style={{ + borderRadius: "8px", + }} + disableAlpha={true} + /> + + + )} + {colorMode === "palette" && ( + { + onUpdateColor(color.hex); + }} + style={{ + width: "100%", + borderRadius: "8px", + }} + /> + )} + {clearBackgroundOption && ( + + )} +
+ ); +} diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index c897c03..eb6a9be 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -1,5 +1,4 @@ import Block from "@uiw/react-color-block"; -import Colorful from "@uiw/react-color-colorful"; import { AlignCenter, AlignLeft, @@ -31,6 +30,7 @@ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { useScopedT } from "@/contexts/I18nContext"; import { type CustomFont, getCustomFonts } from "@/lib/customFonts"; import { cn } from "@/lib/utils"; +import ColorPicker from "../ui/color-picker"; import { AddCustomFontDialog } from "./AddCustomFontDialog"; import { getArrowComponent } from "./ArrowSvgs"; import type { AnnotationRegion, AnnotationType, ArrowDirection, FigureData } from "./types"; @@ -68,7 +68,6 @@ export function AnnotationSettingsPanel({ const t = useScopedT("settings"); const fileInputRef = useRef(null); const [customFonts, setCustomFonts] = useState([]); - const [colorMode, setColorMode] = useState<"wheel" | "palette">("wheel"); const fontStyleLabels: Record = { classic: t("fontStyles.classic"), editor: t("fontStyles.editor"), @@ -140,15 +139,6 @@ export function AnnotationSettingsPanel({ event.target.value = ""; }; - const getTextColor = (color: string) => { - if (color === "transparent") return "#ffffff"; - const r = parseInt(color.slice(1, 3), 16); - const g = parseInt(color.slice(3, 5), 16); - const b = parseInt(color.slice(5, 7), 16); - const luminance = 0.299 * r + 0.587 * g + 0.114 * b; - if (luminance > 186) return "#000000"; - return "#ffffff"; - }; return (
@@ -394,64 +384,17 @@ export function AnnotationSettingsPanel({ side="top" className="w-[260px] p-3 bg-[#1a1a1c] border border-white/10 rounded-xl shadow-xl" > -
- {colorMode === "palette" && ( - { - onStyleChange({ color: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - /> - )} - {colorMode === "wheel" && ( - <> -
- - {annotation.style.color} - -
- { - onStyleChange({ color: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - disableAlpha={true} - /> - - )} -
- - -
-
+ { + onStyleChange({ color: color }); + }} + />
@@ -484,80 +427,19 @@ export function AnnotationSettingsPanel({ side="top" className="w-[260px] p-3 bg-[#1a1a1c] border border-white/10 rounded-xl shadow-xl" > -
- {colorMode === "palette" && ( - { - onStyleChange({ backgroundColor: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - /> - )} - {colorMode === "wheel" && ( - <> -
- - {annotation.style.backgroundColor} - -
- { - onStyleChange({ backgroundColor: color.hex }); - }} - style={{ - borderRadius: "8px", - }} - disableAlpha={true} - /> - - )} -
- - -
-
- + clearBackgroundOption={true} + onUpdateColor={(color) => { + onStyleChange({ backgroundColor: color }); + }} + />
diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 6df3574..05d4940 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -1,5 +1,3 @@ -import Block from "@uiw/react-color-block"; -import Colorful from "@uiw/react-color-colorful"; import { Bug, Crop, @@ -42,7 +40,7 @@ import { GIF_FRAME_RATES, GIF_SIZE_PRESETS } from "@/lib/exporter"; import { cn } from "@/lib/utils"; import { type AspectRatio, isPortraitAspectRatio } from "@/utils/aspectRatioUtils"; import { getTestId } from "@/utils/getTestId"; -import { Input } from "../ui/input"; +import ColorPicker from "../ui/color-picker"; import { AnnotationSettingsPanel } from "./AnnotationSettingsPanel"; import { CropControl } from "./CropControl"; import { KeyboardShortcutsHelp } from "./KeyboardShortcutsHelp"; @@ -229,7 +227,6 @@ export function SettingsPanel({ const t = useScopedT("settings"); const [wallpaperPaths, setWallpaperPaths] = useState([]); const [customImages, setCustomImages] = useState([]); - const [backgroundColorMode, setBackgroundColorMode] = useState<"wheel" | "palette">("wheel"); const fileInputRef = useRef(null); useEffect(() => { @@ -322,16 +319,6 @@ export function SettingsPanel({ [cropRegion, onCropChange, videoWidth, videoHeight, cropAspectLocked], ); - const getTextColor = (color: string) => { - if (color === "transparent") return "#ffffff"; - const r = parseInt(color.slice(1, 3), 16); - const g = parseInt(color.slice(3, 5), 16); - const b = parseInt(color.slice(5, 7), 16); - const luminance = 0.299 * r + 0.587 * g + 0.114 * b; - if (luminance > 186) return "#000000"; - return "#ffffff"; - }; - const applyCropAspectPreset = useCallback( (preset: string) => { if (!cropRegion || !onCropChange) return; @@ -1001,84 +988,18 @@ export function SettingsPanel({ -
-
- - -
- {backgroundColorMode === "wheel" && ( - <> -
- - {selectedColor} - -
- { - setSelectedColor(color.hex); - onWallpaperChange(color.hex); - }} - style={{ - borderRadius: "8px", - }} - disableAlpha={true} - /> - { - setSelectedColor(e.target.value); - onWallpaperChange(e.target.value); - }} - /> - - )} - {backgroundColorMode === "palette" && ( - { - setSelectedColor(color.hex); - onWallpaperChange(color.hex); - }} - style={{ - width: "100%", - borderRadius: "8px", - }} - /> - )} -
+ { + setSelectedColor(color); + onWallpaperChange(color); + }} + />