Skip to content

Commit 29e455a

Browse files
authored
fix: remove Text Content property from Image component (#5195)
Was accidentally added when rewriting content model. Also fixed two other issues - user can override text color in error boundary with global root - text content property now have tooltip with description and it can be reset <img width="320" alt="Screenshot 2025-05-10 at 18 14 19" src="https://github.com/user-attachments/assets/ffb16efc-e7b5-44d1-a214-0a9251e9eb9d" />
1 parent e4a6d3b commit 29e455a

File tree

6 files changed

+80
-35
lines changed

6 files changed

+80
-35
lines changed

apps/builder/app/builder/features/settings-panel/controls/text-content.tsx

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
import { useId, useMemo } from "react";
22
import { useStore } from "@nanostores/react";
33
import { computed } from "nanostores";
4-
import { TextArea } from "@webstudio-is/design-system";
4+
import { Flex, rawTheme, Text, TextArea } from "@webstudio-is/design-system";
55
import type { Instance } from "@webstudio-is/sdk";
6+
import { AlertIcon } from "@webstudio-is/icons";
67
import { $instances } from "~/shared/nano-states";
7-
import { serverSyncStore } from "~/shared/sync";
88
import {
99
BindingControl,
1010
BindingPopover,
1111
} from "~/builder/shared/binding-popover";
12+
import { updateWebstudioData } from "~/shared/instance-utils";
1213
import {
1314
type ControlProps,
1415
useLocalValue,
1516
VerticalLayout,
1617
$selectedInstanceScope,
17-
Label,
1818
updateExpressionValue,
1919
useBindingState,
20-
humanizeAttribute,
2120
} from "../shared";
21+
import { FieldLabel } from "../property-label";
2222

2323
const useInstance = (instanceId: Instance["id"]) => {
2424
const $store = useMemo(() => {
@@ -32,22 +32,20 @@ const updateChildren = (
3232
type: "text" | "expression",
3333
value: string
3434
) => {
35-
serverSyncStore.createTransaction([$instances], (instances) => {
36-
const instance = instances.get(instanceId);
37-
if (instance === undefined) {
38-
return;
35+
updateWebstudioData((data) => {
36+
const instance = data.instances.get(instanceId);
37+
if (instance) {
38+
instance.children = [{ type, value }];
3939
}
40-
instance.children = [{ type, value }];
4140
});
4241
};
4342

4443
export const TextContent = ({
4544
instanceId,
46-
meta,
47-
propName,
4845
computedValue,
4946
}: ControlProps<"textContent">) => {
5047
const instance = useInstance(instanceId);
48+
const hasChildren = (instance?.children.length ?? 0) > 0;
5149
// text content control is rendered only when empty or single child are present
5250
const child = instance?.children?.[0] ?? { type: "text", value: "" };
5351
const localValue = useLocalValue(String(computedValue ?? ""), (value) => {
@@ -58,7 +56,6 @@ export const TextContent = ({
5856
}
5957
});
6058
const id = useId();
61-
const label = humanizeAttribute(meta.label || propName);
6259

6360
const { scope, aliases } = useStore($selectedInstanceScope);
6461
let expression: undefined | string;
@@ -76,13 +73,37 @@ export const TextContent = ({
7673
return (
7774
<VerticalLayout
7875
label={
79-
<Label
80-
htmlFor={id}
81-
description={meta.description}
82-
readOnly={overwritable === false}
76+
<FieldLabel
77+
description={
78+
<>
79+
Plain text content that can be bound to either a variable or a
80+
resource value.
81+
{overwritable === false && (
82+
<Flex gap="1">
83+
<AlertIcon
84+
color={rawTheme.colors.backgroundAlertMain}
85+
style={{ flexShrink: 0 }}
86+
/>
87+
<Text>
88+
The value is controlled by an expression and cannot be
89+
changed.
90+
</Text>
91+
</Flex>
92+
)}
93+
</>
94+
}
95+
resettable={hasChildren}
96+
onReset={() => {
97+
updateWebstudioData((data) => {
98+
const instance = data.instances.get(instanceId);
99+
if (instance) {
100+
instance.children = [];
101+
}
102+
});
103+
}}
83104
>
84-
{label}
85-
</Label>
105+
Text Content
106+
</FieldLabel>
86107
}
87108
>
88109
<BindingControl>
@@ -102,7 +123,7 @@ export const TextContent = ({
102123
aliases={aliases}
103124
validate={(value) => {
104125
if (value !== undefined && typeof value !== "string") {
105-
return `${label} expects a string value`;
126+
return `Text Content expects a string value`;
106127
}
107128
}}
108129
variant={variant}

apps/builder/app/builder/features/settings-panel/property-label.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { micromark } from "micromark";
2-
import { useMemo, useState } from "react";
2+
import { useMemo, useState, type ReactNode } from "react";
33
import { computed } from "nanostores";
44
import { useStore } from "@nanostores/react";
55
import {
@@ -182,9 +182,9 @@ export const FieldLabel = ({
182182
children,
183183
}: {
184184
/**
185-
* Markdown text to show in tooltip
185+
* Markdown text to show in tooltip or react element
186186
*/
187-
description?: string;
187+
description?: string | ReactNode;
188188
/**
189189
* when true means field has value and colored true
190190
*/
@@ -193,6 +193,18 @@ export const FieldLabel = ({
193193
children: string;
194194
}) => {
195195
const [isOpen, setIsOpen] = useState(false);
196+
if (typeof description === "string") {
197+
description = (
198+
<Text
199+
css={{
200+
"> *": { marginTop: 0 },
201+
}}
202+
dangerouslySetInnerHTML={{ __html: micromark(description) }}
203+
></Text>
204+
);
205+
} else if (description) {
206+
description = <Text>{description}</Text>;
207+
}
196208
return (
197209
<Flex align="center" css={{ gap: theme.spacing[3] }}>
198210
{/* prevent label growing */}
@@ -221,16 +233,7 @@ export const FieldLabel = ({
221233
<Text variant="titles" css={{ textTransform: "none" }}>
222234
{children}
223235
</Text>
224-
{description && (
225-
<Text
226-
css={{
227-
"> *": {
228-
marginTop: 0,
229-
},
230-
}}
231-
dangerouslySetInnerHTML={{ __html: micromark(description) }}
232-
></Text>
233-
)}
236+
{description}
234237
{resettable && (
235238
<Button
236239
color="dark"

apps/builder/app/builder/features/settings-panel/props-section/use-props-logic.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,9 @@ export const usePropsLogic = ({
230230
systemProps.push({
231231
propName: textContentAttribute,
232232
meta: {
233-
label: "Text Content",
234233
required: false,
235234
control: "textContent",
236235
type: "string",
237-
defaultValue: "",
238236
},
239237
});
240238
}

apps/builder/app/shared/content-model.test.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,22 @@ describe("rich text tree", () => {
799799
})
800800
).toEqual(["divId", "bodyId"]);
801801
});
802+
803+
test("does not treat image component as rich text", () => {
804+
expect(
805+
findClosestRichText({
806+
...renderData(
807+
<ws.element ws:tag="body" ws:id="bodyId">
808+
<ws.element ws:tag="div" ws:id="divId">
809+
<$.Image ws:id="imgId" />
810+
</ws.element>
811+
</ws.element>
812+
),
813+
metas: defaultMetas,
814+
instanceSelector: ["imgId", "divId", "bodyId"],
815+
})
816+
).toEqual(undefined);
817+
});
802818
});
803819

804820
describe("closest container", () => {

apps/builder/app/shared/content-model.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,10 +462,15 @@ export const isRichTextTree = ({
462462
if (instance === undefined) {
463463
return false;
464464
}
465+
const tag = getTag({ instance, metas, props });
466+
const elementContentModel = tag ? elementsByTag[tag] : undefined;
465467
const componentContentModel = getComponentContentModel(
466468
metas.get(instance.component)
467469
);
468-
const isRichText = componentContentModel.children.includes("rich-text");
470+
const isRichText =
471+
(elementContentModel === undefined ||
472+
elementContentModel.children.length > 0) &&
473+
componentContentModel.children.includes("rich-text");
469474
// only empty instance with rich text content can be edited
470475
if (instance.children.length === 0) {
471476
return isRichText;

apps/builder/app/shared/error/error-message.client.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const pageStyle = css({
1616
inset: 0,
1717
background: theme.colors.brandBackgroundDashboard,
1818
paddingTop: "10vh",
19+
// prevent global root styles override error color
20+
color: theme.colors.black,
1921
});
2022

2123
export const ErrorMessage = ({

0 commit comments

Comments
 (0)