From d2049ec3670e2faab35c74a3d0981ee8d2ac41c0 Mon Sep 17 00:00:00 2001 From: Ben Life <77246839+benlife5@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:16:54 -0400 Subject: [PATCH] fix: sidebar loses focus on input (#100) creates a ref for the themeHistory state, which allows access to the latest values inside the callback without regenerating the components --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- .../components/InternalThemeEditor.tsx | 34 ++++++--- src/internal/components/ThemeEditor.tsx | 4 +- .../puck/components/ColorSelector.tsx | 75 ++++++++----------- src/internal/puck/components/ThemeSidebar.tsx | 16 +++- .../utils/constructThemePuckFields.ts | 13 +++- src/utils/devLogger.ts | 2 +- 6 files changed, 79 insertions(+), 65 deletions(-) diff --git a/src/internal/components/InternalThemeEditor.tsx b/src/internal/components/InternalThemeEditor.tsx index 278c638e..49ea4aec 100644 --- a/src/internal/components/InternalThemeEditor.tsx +++ b/src/internal/components/InternalThemeEditor.tsx @@ -1,5 +1,5 @@ import { Puck, Config, InitialHistory } from "@measured/puck"; -import React from "react"; +import React, { useCallback, useEffect, useRef } from "react"; import { useState } from "react"; import { TemplateMetadata } from "../types/templateMetadata.ts"; import { EntityFieldProvider } from "../../components/EntityField.tsx"; @@ -45,6 +45,11 @@ export const InternalThemeEditor = ({ buildThemeLocalStorageKey, }: InternalThemeEditorProps) => { const [canEdit, setCanEdit] = useState<boolean>(false); // helps sync puck preview and save state + const themeHistoriesRef = useRef(themeHistories); + + useEffect(() => { + themeHistoriesRef.current = themeHistories; + }, [themeHistories]); const handlePublishTheme = async () => { devLogger.logFunc("saveThemeData"); @@ -69,21 +74,22 @@ export const InternalThemeEditor = ({ }; const handleThemeChange = (topLevelKey: string, newValue: any) => { - if (!themeHistories || !themeConfig) { + if (!themeHistoriesRef.current || !themeConfig) { return; } const newThemeValues = { - ...themeHistories.histories[themeHistories.index]?.data, + ...themeHistoriesRef.current.histories[themeHistoriesRef.current.index] + ?.data, ...generateCssVariablesFromPuckFields(newValue, topLevelKey), }; const newHistory = { histories: [ - ...themeHistories.histories, + ...themeHistoriesRef.current.histories, { id: uuidv4(), data: newThemeValues }, ] as ThemeHistory[], - index: themeHistories.histories.length, + index: themeHistoriesRef.current.histories.length, }; window.localStorage.setItem( @@ -124,6 +130,16 @@ export const InternalThemeEditor = ({ } }; + const fieldsOverride = useCallback(() => { + return ( + <ThemeSidebar + themeHistoriesRef={themeHistoriesRef} + themeConfig={themeConfig} + onThemeChange={handleThemeChange} + /> + ); + }, []); + return ( <EntityFieldProvider> <Puck @@ -152,13 +168,7 @@ export const InternalThemeEditor = ({ ), actionBar: () => <></>, components: () => <></>, - fields: () => ( - <ThemeSidebar - themeConfig={themeConfig} - themeHistory={themeHistories!.histories} - onThemeChange={handleThemeChange} - /> - ), + fields: fieldsOverride, }} /> </EntityFieldProvider> diff --git a/src/internal/components/ThemeEditor.tsx b/src/internal/components/ThemeEditor.tsx index 6301f8da..e3f66623 100644 --- a/src/internal/components/ThemeEditor.tsx +++ b/src/internal/components/ThemeEditor.tsx @@ -182,9 +182,9 @@ export const ThemeEditor = (props: ThemeEditorProps) => { buildThemeLocalStorageKey, ]); - // Log THEME_INITIAL_HISTORY on load and update theme in editor to reflect save state + // Log THEME_HISTORIES on load and update theme in editor to reflect save state useEffect(() => { - devLogger.logData("THEME_INITIAL_HISTORY", themeHistories); + devLogger.logData("THEME_HISTORIES", themeHistories); if (themeHistories && themeConfig) { updateThemeInEditor( themeHistories.histories[themeHistories.index]?.data as SavedTheme, diff --git a/src/internal/puck/components/ColorSelector.tsx b/src/internal/puck/components/ColorSelector.tsx index b78eb882..e6c2c63c 100644 --- a/src/internal/puck/components/ColorSelector.tsx +++ b/src/internal/puck/components/ColorSelector.tsx @@ -1,52 +1,39 @@ import React, { useState } from "react"; -import { Field, FieldLabel } from "@measured/puck"; +import { FieldLabel } from "@measured/puck"; import { RenderProps } from "../../utils/renderEntityFields.ts"; import { Color, ColorResult, SketchPicker } from "react-color"; -export type ColorSelectorProps = { - label: string; -}; - -export const ColorSelector = (props: ColorSelectorProps): Field => { - return { - type: "custom", - label: props.label, - render: ({ field, value, onChange }: RenderProps) => { - const [isOpen, setIsOpen] = useState(false); +export const ColorSelector = ({ field, value, onChange }: RenderProps) => { + const [isOpen, setIsOpen] = useState(false); - const fieldStyles = colorPickerStyles(value); - return ( - <> - <FieldLabel - label={field.label || "Label is undefined"} - className="ve-mt-2.5" - > - <div - style={fieldStyles.swatch} - onClick={() => setIsOpen((current) => !current)} - > - <div style={fieldStyles.color} /> - </div> - {isOpen && ( - <div style={fieldStyles.popover}> - <div - style={fieldStyles.cover} - onClick={() => setIsOpen(false)} - /> - <SketchPicker - disableAlpha={true} - color={value} - onChange={(colorResult: ColorResult) => { - onChange(colorResult.hex); - }} - /> - </div> - )} - </FieldLabel> - </> - ); - }, - }; + const fieldStyles = colorPickerStyles(value); + return ( + <> + <FieldLabel + label={field.label || "Label is undefined"} + className="ve-mt-2.5" + > + <div + style={fieldStyles.swatch} + onClick={() => setIsOpen((current) => !current)} + > + <div style={fieldStyles.color} /> + </div> + {isOpen && ( + <div style={fieldStyles.popover}> + <div style={fieldStyles.cover} onClick={() => setIsOpen(false)} /> + <SketchPicker + disableAlpha={true} + color={value} + onChange={(colorResult: ColorResult) => { + onChange(colorResult.hex); + }} + /> + </div> + )} + </FieldLabel> + </> + ); }; const colorPickerStyles = (color: Color) => { diff --git a/src/internal/puck/components/ThemeSidebar.tsx b/src/internal/puck/components/ThemeSidebar.tsx index 6409cd8d..b04ead59 100644 --- a/src/internal/puck/components/ThemeSidebar.tsx +++ b/src/internal/puck/components/ThemeSidebar.tsx @@ -6,16 +6,21 @@ import { constructThemePuckFields, constructThemePuckValues, } from "../../utils/constructThemePuckFields.ts"; -import { ThemeHistory } from "../../types/themeData.ts"; +import { ThemeHistories } from "../../types/themeData.ts"; type ThemeSidebarProps = { + themeHistoriesRef: React.MutableRefObject<ThemeHistories | undefined>; themeConfig?: ThemeConfig; - themeHistory: ThemeHistory[]; onThemeChange: (parentStyleKey: string, value: Record<string, any>) => void; }; const ThemeSidebar = (props: ThemeSidebarProps) => { - const { themeConfig, themeHistory, onThemeChange } = props; + const { themeConfig, themeHistoriesRef, onThemeChange } = props; + + if (!themeHistoriesRef.current) { + return; + } + if (!themeConfig) { return ( <div> @@ -28,6 +33,9 @@ const ThemeSidebar = (props: ThemeSidebarProps) => { ); } + const themeData = + themeHistoriesRef.current?.histories[themeHistoriesRef.current?.index].data; + return ( <div> <Alert> @@ -39,7 +47,7 @@ const ThemeSidebar = (props: ThemeSidebarProps) => { {Object.entries(themeConfig).map(([parentStyleKey, parentStyle]) => { const field = constructThemePuckFields(parentStyle); const values = constructThemePuckValues( - themeHistory[themeHistory.length - 1]?.data, + themeData, parentStyle, parentStyleKey ); diff --git a/src/internal/utils/constructThemePuckFields.ts b/src/internal/utils/constructThemePuckFields.ts index d72f220a..b3b5311a 100644 --- a/src/internal/utils/constructThemePuckFields.ts +++ b/src/internal/utils/constructThemePuckFields.ts @@ -1,4 +1,9 @@ -import { ObjectField, SelectField, NumberField } from "@measured/puck"; +import { + ObjectField, + SelectField, + NumberField, + CustomField, +} from "@measured/puck"; import { ParentStyle, SavedTheme, Style } from "../../utils/themeResolver.ts"; import { ColorSelector } from "../puck/components/ColorSelector.tsx"; @@ -36,7 +41,11 @@ export const convertStyleToPuckField = (style: Style) => { options: style.options, } as SelectField; case "color": - return ColorSelector({ label: style.label }); + return { + label: style.label, + type: "custom", + render: ColorSelector, + } as CustomField; } }; diff --git a/src/utils/devLogger.ts b/src/utils/devLogger.ts index 1f276c5f..309bd2f7 100644 --- a/src/utils/devLogger.ts +++ b/src/utils/devLogger.ts @@ -5,7 +5,7 @@ export type DevLoggerPrefix = | "THEME_SAVE_STATE" | "VISUAL_CONFIGURATION_DATA" | "THEME_DATA" - | "THEME_INITIAL_HISTORY" + | "THEME_HISTORIES" | "DOCUMENT" | "PUCK_INDEX" | "PUCK_HISTORY"