Skip to content

Commit 02bcc55

Browse files
authored
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
1 parent 5ba9dc3 commit 02bcc55

File tree

7 files changed

+77
-25
lines changed

7 files changed

+77
-25
lines changed

apps/builder/app/builder/features/style-panel/sections/advanced/advanced.tsx

+19-7
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const AdvancedStyleSection = (props: {
6666
export const Section = () => {
6767
const styleMap = useStore($advancedStylesLonghands);
6868
const apiRef = useRef<CssEditorApi>();
69-
const properties = Array.from(styleMap.keys()) as Array<CssProperty>;
69+
const properties = Array.from(styleMap.keys());
7070
const selectedInstanceKey = useStore($selectedInstanceKey);
7171
// Memorizing recent properties by instance id, so that when user switches between instances and comes back
7272
// they are still in-place
@@ -90,16 +90,14 @@ export const Section = () => {
9090
setRecentPropertiesMap(newRecentPropertiesMap);
9191
};
9292

93-
const handleAddProperties = (styleMap: CssStyleMap) => {
93+
const handleAddDeclarations = (styleMap: CssStyleMap) => {
9494
const batch = createBatchUpdate();
9595
for (const [property, value] of styleMap) {
96-
batch.setProperty(property as CssProperty)(value);
96+
batch.setProperty(property)(value);
9797
}
9898
batch.publish({ listed: true });
9999

100-
const insertedProperties = Array.from(
101-
styleMap.keys()
102-
) as Array<CssProperty>;
100+
const insertedProperties = Array.from(styleMap.keys());
103101
updateRecentProperties([...recentProperties, ...insertedProperties]);
104102
};
105103

@@ -114,6 +112,19 @@ export const Section = () => {
114112
);
115113
};
116114

115+
const handleDeleteAllDeclarations = (styleMap: CssStyleMap) => {
116+
const batch = createBatchUpdate();
117+
for (const [property] of styleMap) {
118+
batch.deleteProperty(property);
119+
}
120+
batch.publish();
121+
updateRecentProperties(
122+
recentProperties.filter(
123+
(recentProperty) => styleMap.has(recentProperty) === false
124+
)
125+
);
126+
};
127+
117128
return (
118129
<AdvancedStyleSection
119130
label="Advanced"
@@ -126,7 +137,8 @@ export const Section = () => {
126137
styleMap={styleMap}
127138
onDeleteProperty={handleDeleteProperty}
128139
onSetProperty={setProperty}
129-
onAddProperties={handleAddProperties}
140+
onAddDeclarations={handleAddDeclarations}
141+
onDeleteAllDeclarations={handleDeleteAllDeclarations}
130142
apiRef={apiRef}
131143
recentProperties={recentProperties}
132144
/>

apps/builder/app/builder/shared/css-editor/copy-paste-menu.tsx apps/builder/app/builder/shared/css-editor/css-editor-context-menu.tsx

+38-10
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
} from "@webstudio-is/design-system";
99
import {
1010
generateStyleMap,
11-
hyphenateProperty,
1211
mergeStyles,
1312
toValue,
1413
type CssProperty,
@@ -17,54 +16,77 @@ import {
1716

1817
export const copyAttribute = "data-declaration";
1918

20-
export const CopyPasteMenu = ({
19+
export const CssEditorContextMenu = ({
2120
children,
2221
properties,
2322
styleMap,
2423
onPaste,
24+
onDeleteProperty,
25+
onDeleteAllDeclarations,
2526
}: {
2627
children: ReactNode;
27-
properties: Array<string>;
28+
properties: Array<CssProperty>;
2829
styleMap: CssStyleMap;
2930
onPaste: (cssText: string) => void;
31+
onDeleteProperty: (property: CssProperty) => void;
32+
onDeleteAllDeclarations: (styleMap: CssStyleMap) => void;
3033
}) => {
3134
const lastClickedProperty = useRef<string>();
3235

3336
const handlePaste = () => {
3437
navigator.clipboard.readText().then(onPaste);
3538
};
3639

37-
const handleCopyAll = () => {
40+
// Gets all currently visible declarations based on what's in the search or filters.
41+
const getAllDeclarations = () => {
3842
// We want to only copy properties that are currently in front of the user.
3943
// That includes search or any future filters.
4044
const currentStyleMap: CssStyleMap = new Map();
4145
for (const [property, value] of styleMap) {
4246
const isEmpty = toValue(value) === "";
4347
if (properties.includes(property) && isEmpty === false) {
44-
currentStyleMap.set(hyphenateProperty(property), value);
48+
currentStyleMap.set(property, value);
4549
}
4650
}
51+
return currentStyleMap;
52+
};
4753

48-
const css = generateStyleMap(mergeStyles(currentStyleMap));
54+
const handleCopyAll = () => {
55+
const styleMap = getAllDeclarations();
56+
const css = generateStyleMap(mergeStyles(styleMap));
4957
navigator.clipboard.writeText(css);
5058
};
5159

5260
const handleCopy = () => {
53-
const property = lastClickedProperty.current;
61+
const property = lastClickedProperty.current as CssProperty;
5462

5563
if (property === undefined) {
5664
return;
5765
}
58-
const value = styleMap.get(property as CssProperty);
66+
const value = styleMap.get(property);
5967

6068
if (value === undefined) {
6169
return;
6270
}
63-
const style = new Map([[property, value]]);
64-
const css = generateStyleMap(style);
71+
72+
const css = generateStyleMap(new Map([[property, value]]));
6573
navigator.clipboard.writeText(css);
6674
};
6775

76+
const handleDelete = () => {
77+
const property = lastClickedProperty.current as CssProperty;
78+
const value = styleMap.get(property);
79+
if (value === undefined) {
80+
return;
81+
}
82+
onDeleteProperty(property);
83+
};
84+
85+
const handleDeleteAllDeclarations = () => {
86+
const styleMap = getAllDeclarations();
87+
onDeleteAllDeclarations(styleMap);
88+
};
89+
6890
return (
6991
<ContextMenu>
7092
<ContextMenuTrigger
@@ -93,6 +115,12 @@ export const CopyPasteMenu = ({
93115
<ContextMenuItem onSelect={handlePaste}>
94116
Paste declarations
95117
</ContextMenuItem>
118+
<ContextMenuItem destructive onSelect={handleDelete}>
119+
Delete declaration
120+
</ContextMenuItem>
121+
<ContextMenuItem destructive onSelect={handleDeleteAllDeclarations}>
122+
Delete all declarations
123+
</ContextMenuItem>
96124
</ContextMenuContent>
97125
</ContextMenu>
98126
);

apps/builder/app/builder/shared/css-editor/css-editor.stories.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ export const CssEditor = () => {
5555
styleMap={styleMap}
5656
onDeleteProperty={() => undefined}
5757
onSetProperty={() => () => undefined}
58-
onAddProperties={() => undefined}
58+
onAddDeclarations={() => undefined}
59+
onDeleteAllDeclarations={() => undefined}
5960
/>
6061
);
6162
};

apps/builder/app/builder/shared/css-editor/css-editor.tsx

+11-6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
type CssProperty,
3232
type CssStyleMap,
3333
} from "@webstudio-is/css-engine";
34+
// @todo all style panel stuff needs to be moved to shared and/or decoupled from style panel
3435
import { CssValueInputContainer } from "../../features/style-panel/shared/css-value-input";
3536
import { styleConfigByName } from "../../features/style-panel/shared/configs";
3637
import {
@@ -40,7 +41,7 @@ import {
4041
import { PropertyInfo } from "../../features/style-panel/property-label";
4142
import { ColorPopover } from "../../features/style-panel/shared/color-picker";
4243
import { useClientSupports } from "~/shared/client-supports";
43-
import { CopyPasteMenu, copyAttribute } from "./copy-paste-menu";
44+
import { CssEditorContextMenu, copyAttribute } from "./css-editor-context-menu";
4445
import { AddStyleInput } from "./add-style-input";
4546
import { parseStyleInput } from "./parse-style-input";
4647
import type {
@@ -309,15 +310,17 @@ export type CssEditorApi = { showAddStyleInput: () => void } | undefined;
309310
export const CssEditor = ({
310311
onDeleteProperty,
311312
onSetProperty,
312-
onAddProperties,
313+
onAddDeclarations,
314+
onDeleteAllDeclarations,
313315
styleMap,
314316
apiRef,
315317
showSearch = true,
316318
recentProperties = [],
317319
}: {
318320
onDeleteProperty: DeleteProperty;
319321
onSetProperty: SetProperty;
320-
onAddProperties: (styleMap: CssStyleMap) => void;
322+
onAddDeclarations: (styleMap: CssStyleMap) => void;
323+
onDeleteAllDeclarations: (styleMap: CssStyleMap) => void;
321324
styleMap: CssStyleMap;
322325
apiRef?: RefObject<CssEditorApi>;
323326
showSearch?: boolean;
@@ -357,7 +360,7 @@ export const CssEditor = ({
357360
if (styleMap.size === 0) {
358361
return new Map();
359362
}
360-
onAddProperties(styleMap);
363+
onAddDeclarations(styleMap);
361364
return styleMap;
362365
};
363366

@@ -415,8 +418,10 @@ export const CssEditor = ({
415418
/>
416419
</Box>
417420
)}
418-
<CopyPasteMenu
421+
<CssEditorContextMenu
419422
onPaste={handleInsertStyles}
423+
onDeleteProperty={onDeleteProperty}
424+
onDeleteAllDeclarations={onDeleteAllDeclarations}
420425
styleMap={styleMap}
421426
properties={
422427
searchProperties ?? [...recentProperties, ...currentProperties]
@@ -496,7 +501,7 @@ export const CssEditor = ({
496501
})}
497502
</Flex>
498503
</Flex>
499-
</CopyPasteMenu>
504+
</CssEditorContextMenu>
500505
</>
501506
);
502507
};

packages/design-system/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"match-sorter": "^8.0.0",
6666
"react-hot-toast": "^2.5.1",
6767
"token-transformer": "^0.0.28",
68+
"type-fest": "^4.32.0",
6869
"use-debounce": "^10.0.4",
6970
"warn-once": "^0.1.1"
7071
},

packages/design-system/src/components/context-menu.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
MenuCheckedIcon,
2020
} from "./menu";
2121
export { DropdownMenuArrow } from "./menu";
22+
import type { Simplify } from "type-fest";
2223

2324
export const ContextMenu = ContextMenuPrimitive.Root;
2425

@@ -57,9 +58,10 @@ export const ContextMenuLabel = styled(ContextMenuPrimitive.Label, labelCss);
5758
const StyledMenuItem = styled(ContextMenuPrimitive.Item, menuItemCss, {
5859
defaultVariants: { withIndicator: true },
5960
});
61+
6062
export const ContextMenuItem = forwardRef<
6163
ElementRef<typeof StyledMenuItem>,
62-
ComponentProps<typeof StyledMenuItem> & { icon?: ReactNode }
64+
Simplify<ComponentProps<typeof StyledMenuItem> & { icon?: ReactNode }>
6365
>(({ icon, children, withIndicator, ...props }, forwardedRef) =>
6466
icon ? (
6567
<StyledMenuItem

pnpm-lock.yaml

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)