mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-08 21:15:18 +09:00
feat: Delete declaration and delete all declarations in advanced style panel context menu (#4948)
## Description 1. added additional way to delete a declaration in addition to cmd+click 2. delete all declarations ## Steps for reproduction 1. click button 3. expect xyz ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 0000) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file
This commit is contained in:
1 parent
5ba9dc38a8
commit
02bcc55299
7 files changed
+77
-25
No files matched your search
@@ -66,7 +66,7 @@ const AdvancedStyleSection = (props: {
|
||||
export const Section = () => {
|
||||
const styleMap = useStore($advancedStylesLonghands);
|
||||
const apiRef = useRef<CssEditorApi>();
|
||||
const properties = Array.from(styleMap.keys()) as Array<CssProperty>;
|
||||
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<CssProperty>;
|
||||
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 (
|
||||
<AdvancedStyleSection
|
||||
label="Advanced"
|
||||
@@ -126,7 +137,8 @@ export const Section = () => {
|
||||
styleMap={styleMap}
|
||||
onDeleteProperty={handleDeleteProperty}
|
||||
onSetProperty={setProperty}
|
||||
onAddProperties={handleAddProperties}
|
||||
onAddDeclarations={handleAddDeclarations}
|
||||
onDeleteAllDeclarations={handleDeleteAllDeclarations}
|
||||
apiRef={apiRef}
|
||||
recentProperties={recentProperties}
|
||||
/>
|
||||
|
||||
+38
-10
@@ -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<string>;
|
||||
properties: Array<CssProperty>;
|
||||
styleMap: CssStyleMap;
|
||||
onPaste: (cssText: string) => void;
|
||||
onDeleteProperty: (property: CssProperty) => void;
|
||||
onDeleteAllDeclarations: (styleMap: CssStyleMap) => void;
|
||||
}) => {
|
||||
const lastClickedProperty = useRef<string>();
|
||||
|
||||
@@ -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 (
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger
|
||||
@@ -93,6 +115,12 @@ export const CopyPasteMenu = ({
|
||||
<ContextMenuItem onSelect={handlePaste}>
|
||||
Paste declarations
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem destructive onSelect={handleDelete}>
|
||||
Delete declaration
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem destructive onSelect={handleDeleteAllDeclarations}>
|
||||
Delete all declarations
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
);
|
||||
@@ -55,7 +55,8 @@ export const CssEditor = () => {
|
||||
styleMap={styleMap}
|
||||
onDeleteProperty={() => undefined}
|
||||
onSetProperty={() => () => undefined}
|
||||
onAddProperties={() => undefined}
|
||||
onAddDeclarations={() => undefined}
|
||||
onDeleteAllDeclarations={() => undefined}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<CssEditorApi>;
|
||||
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 = ({
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<CopyPasteMenu
|
||||
<CssEditorContextMenu
|
||||
onPaste={handleInsertStyles}
|
||||
onDeleteProperty={onDeleteProperty}
|
||||
onDeleteAllDeclarations={onDeleteAllDeclarations}
|
||||
styleMap={styleMap}
|
||||
properties={
|
||||
searchProperties ?? [...recentProperties, ...currentProperties]
|
||||
@@ -496,7 +501,7 @@ export const CssEditor = ({
|
||||
})}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</CopyPasteMenu>
|
||||
</CssEditorContextMenu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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<typeof StyledMenuItem>,
|
||||
ComponentProps<typeof StyledMenuItem> & { icon?: ReactNode }
|
||||
Simplify<ComponentProps<typeof StyledMenuItem> & { icon?: ReactNode }>
|
||||
>(({ icon, children, withIndicator, ...props }, forwardedRef) =>
|
||||
icon ? (
|
||||
<StyledMenuItem
|
||||
|
||||
Generated
+3
@@ -1420,6 +1420,9 @@ importers:
|
||||
token-transformer:
|
||||
specifier: ^0.0.28
|
||||
version: 0.0.28
|
||||
type-fest:
|
||||
specifier: ^4.32.0
|
||||
version: 4.32.0
|
||||
use-debounce:
|
||||
specifier: ^10.0.4
|
||||
version: 10.0.4(react@18.3.0-canary-14898b6a9-20240318)
|
||||
|
||||
Reference in new issue
Block a user