diff --git a/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx b/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx index f9e813e10344..d8280e9f1bf9 100644 --- a/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx +++ b/apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx @@ -66,7 +66,7 @@ const AdvancedStyleSection = (props: { export const Section = () => { const styleMap = useStore($advancedStylesLonghands); const apiRef = useRef(); - const properties = Array.from(styleMap.keys()) as Array; + const properties = Array.from(styleMap.keys()); const selectedInstanceKey = useStore($selectedInstanceKey); // Memorizing recent properties by instance id, so that when user switches between instances and comes back // they are still in-place @@ -90,16 +90,14 @@ export const Section = () => { setRecentPropertiesMap(newRecentPropertiesMap); }; - const handleAddProperties = (styleMap: CssStyleMap) => { + const handleAddDeclarations = (styleMap: CssStyleMap) => { const batch = createBatchUpdate(); for (const [property, value] of styleMap) { - batch.setProperty(property as CssProperty)(value); + batch.setProperty(property)(value); } batch.publish({ listed: true }); - const insertedProperties = Array.from( - styleMap.keys() - ) as Array; + const insertedProperties = Array.from(styleMap.keys()); updateRecentProperties([...recentProperties, ...insertedProperties]); }; @@ -114,6 +112,19 @@ export const Section = () => { ); }; + const handleDeleteAllDeclarations = (styleMap: CssStyleMap) => { + const batch = createBatchUpdate(); + for (const [property] of styleMap) { + batch.deleteProperty(property); + } + batch.publish(); + updateRecentProperties( + recentProperties.filter( + (recentProperty) => styleMap.has(recentProperty) === false + ) + ); + }; + return ( { styleMap={styleMap} onDeleteProperty={handleDeleteProperty} onSetProperty={setProperty} - onAddProperties={handleAddProperties} + onAddDeclarations={handleAddDeclarations} + onDeleteAllDeclarations={handleDeleteAllDeclarations} apiRef={apiRef} recentProperties={recentProperties} /> diff --git a/apps/builder/app/builder/shared/css-editor/copy-paste-menu.tsx b/apps/builder/app/builder/shared/css-editor/css-editor-context-menu.tsx similarity index 62% rename from apps/builder/app/builder/shared/css-editor/copy-paste-menu.tsx rename to apps/builder/app/builder/shared/css-editor/css-editor-context-menu.tsx index 63d98d0b0d4f..540200c46696 100644 --- a/apps/builder/app/builder/shared/css-editor/copy-paste-menu.tsx +++ b/apps/builder/app/builder/shared/css-editor/css-editor-context-menu.tsx @@ -8,7 +8,6 @@ import { } from "@webstudio-is/design-system"; import { generateStyleMap, - hyphenateProperty, mergeStyles, toValue, type CssProperty, @@ -17,16 +16,20 @@ import { export const copyAttribute = "data-declaration"; -export const CopyPasteMenu = ({ +export const CssEditorContextMenu = ({ children, properties, styleMap, onPaste, + onDeleteProperty, + onDeleteAllDeclarations, }: { children: ReactNode; - properties: Array; + properties: Array; styleMap: CssStyleMap; onPaste: (cssText: string) => void; + onDeleteProperty: (property: CssProperty) => void; + onDeleteAllDeclarations: (styleMap: CssStyleMap) => void; }) => { const lastClickedProperty = useRef(); @@ -34,37 +37,56 @@ export const CopyPasteMenu = ({ navigator.clipboard.readText().then(onPaste); }; - const handleCopyAll = () => { + // Gets all currently visible declarations based on what's in the search or filters. + const getAllDeclarations = () => { // We want to only copy properties that are currently in front of the user. // That includes search or any future filters. const currentStyleMap: CssStyleMap = new Map(); for (const [property, value] of styleMap) { const isEmpty = toValue(value) === ""; if (properties.includes(property) && isEmpty === false) { - currentStyleMap.set(hyphenateProperty(property), value); + currentStyleMap.set(property, value); } } + return currentStyleMap; + }; - const css = generateStyleMap(mergeStyles(currentStyleMap)); + const handleCopyAll = () => { + const styleMap = getAllDeclarations(); + const css = generateStyleMap(mergeStyles(styleMap)); navigator.clipboard.writeText(css); }; const handleCopy = () => { - const property = lastClickedProperty.current; + const property = lastClickedProperty.current as CssProperty; if (property === undefined) { return; } - const value = styleMap.get(property as CssProperty); + const value = styleMap.get(property); if (value === undefined) { return; } - const style = new Map([[property, value]]); - const css = generateStyleMap(style); + + const css = generateStyleMap(new Map([[property, value]])); navigator.clipboard.writeText(css); }; + const handleDelete = () => { + const property = lastClickedProperty.current as CssProperty; + const value = styleMap.get(property); + if (value === undefined) { + return; + } + onDeleteProperty(property); + }; + + const handleDeleteAllDeclarations = () => { + const styleMap = getAllDeclarations(); + onDeleteAllDeclarations(styleMap); + }; + return ( Paste declarations + + Delete declaration + + + Delete all declarations + ); diff --git a/apps/builder/app/builder/shared/css-editor/css-editor.stories.tsx b/apps/builder/app/builder/shared/css-editor/css-editor.stories.tsx index 2c6f41930f2f..c09665ce2dbc 100644 --- a/apps/builder/app/builder/shared/css-editor/css-editor.stories.tsx +++ b/apps/builder/app/builder/shared/css-editor/css-editor.stories.tsx @@ -55,7 +55,8 @@ export const CssEditor = () => { styleMap={styleMap} onDeleteProperty={() => undefined} onSetProperty={() => () => undefined} - onAddProperties={() => undefined} + onAddDeclarations={() => undefined} + onDeleteAllDeclarations={() => undefined} /> ); }; diff --git a/apps/builder/app/builder/shared/css-editor/css-editor.tsx b/apps/builder/app/builder/shared/css-editor/css-editor.tsx index 84d931e82ab9..1900fa0e6885 100644 --- a/apps/builder/app/builder/shared/css-editor/css-editor.tsx +++ b/apps/builder/app/builder/shared/css-editor/css-editor.tsx @@ -31,6 +31,7 @@ import { type CssProperty, type CssStyleMap, } from "@webstudio-is/css-engine"; +// @todo all style panel stuff needs to be moved to shared and/or decoupled from style panel import { CssValueInputContainer } from "../../features/style-panel/shared/css-value-input"; import { styleConfigByName } from "../../features/style-panel/shared/configs"; import { @@ -40,7 +41,7 @@ import { import { PropertyInfo } from "../../features/style-panel/property-label"; import { ColorPopover } from "../../features/style-panel/shared/color-picker"; import { useClientSupports } from "~/shared/client-supports"; -import { CopyPasteMenu, copyAttribute } from "./copy-paste-menu"; +import { CssEditorContextMenu, copyAttribute } from "./css-editor-context-menu"; import { AddStyleInput } from "./add-style-input"; import { parseStyleInput } from "./parse-style-input"; import type { @@ -309,7 +310,8 @@ export type CssEditorApi = { showAddStyleInput: () => void } | undefined; export const CssEditor = ({ onDeleteProperty, onSetProperty, - onAddProperties, + onAddDeclarations, + onDeleteAllDeclarations, styleMap, apiRef, showSearch = true, @@ -317,7 +319,8 @@ export const CssEditor = ({ }: { onDeleteProperty: DeleteProperty; onSetProperty: SetProperty; - onAddProperties: (styleMap: CssStyleMap) => void; + onAddDeclarations: (styleMap: CssStyleMap) => void; + onDeleteAllDeclarations: (styleMap: CssStyleMap) => void; styleMap: CssStyleMap; apiRef?: RefObject; showSearch?: boolean; @@ -357,7 +360,7 @@ export const CssEditor = ({ if (styleMap.size === 0) { return new Map(); } - onAddProperties(styleMap); + onAddDeclarations(styleMap); return styleMap; }; @@ -415,8 +418,10 @@ export const CssEditor = ({ /> )} - - + ); }; diff --git a/packages/design-system/package.json b/packages/design-system/package.json index 0c02aa19da1d..411107b61852 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -65,6 +65,7 @@ "match-sorter": "^8.0.0", "react-hot-toast": "^2.5.1", "token-transformer": "^0.0.28", + "type-fest": "^4.32.0", "use-debounce": "^10.0.4", "warn-once": "^0.1.1" }, diff --git a/packages/design-system/src/components/context-menu.tsx b/packages/design-system/src/components/context-menu.tsx index fb5379ba1b7c..347e89248e1d 100644 --- a/packages/design-system/src/components/context-menu.tsx +++ b/packages/design-system/src/components/context-menu.tsx @@ -19,6 +19,7 @@ import { MenuCheckedIcon, } from "./menu"; export { DropdownMenuArrow } from "./menu"; +import type { Simplify } from "type-fest"; export const ContextMenu = ContextMenuPrimitive.Root; @@ -57,9 +58,10 @@ export const ContextMenuLabel = styled(ContextMenuPrimitive.Label, labelCss); const StyledMenuItem = styled(ContextMenuPrimitive.Item, menuItemCss, { defaultVariants: { withIndicator: true }, }); + export const ContextMenuItem = forwardRef< ElementRef, - ComponentProps & { icon?: ReactNode } + Simplify & { icon?: ReactNode }> >(({ icon, children, withIndicator, ...props }, forwardedRef) => icon ? (