Skip to content

Commit 3777d4b

Browse files
committed
feat: update dropzone for custom content
1 parent 724e93b commit 3777d4b

9 files changed

Lines changed: 390 additions & 50 deletions

File tree

packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorConfig.ts

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
ContainerProps,
44
StructurePreviewProps,
55
structurePreviewPalette,
6+
dropzone,
67
container
78
} from "@mendix/widget-plugin-platform/preview/structure-preview-api";
89
import { SelectionControlsPreviewProps } from "../typings/SelectionControlsProps";
@@ -139,29 +140,104 @@ function getIconPreview(_isDarkMode: boolean): ContainerProps {
139140

140141
export function getPreview(_values: SelectionControlsPreviewProps, isDarkMode: boolean): StructurePreviewProps {
141142
const palette = structurePreviewPalette[isDarkMode ? "dark" : "light"];
143+
const structurePreviewChildren: StructurePreviewProps[] = [];
144+
let readOnly = _values.readOnly;
142145

146+
// Handle custom content dropzones when enabled
147+
if (_values.optionsSourceCustomContentType !== "no") {
148+
if (_values.source === "context" && _values.optionsSourceType === "association") {
149+
structurePreviewChildren.push(
150+
dropzone(
151+
dropzone.placeholder("Configure the selection controls: Place widgets here"),
152+
dropzone.hideDataSourceHeaderIf(false)
153+
)(_values.optionsSourceAssociationCustomContent)
154+
);
155+
} else if (_values.source === "database") {
156+
structurePreviewChildren.push(
157+
dropzone(
158+
dropzone.placeholder("Configure the selection controls: Place widgets here"),
159+
dropzone.hideDataSourceHeaderIf(false)
160+
)(_values.optionsSourceDatabaseCustomContent)
161+
);
162+
} else if (_values.source === "static") {
163+
_values.optionsSourceStaticDataSource.forEach(value => {
164+
structurePreviewChildren.push(
165+
container({
166+
borders: true,
167+
borderWidth: 1,
168+
borderRadius: 2
169+
})(
170+
dropzone(
171+
dropzone.placeholder(
172+
`Configure the selection controls: Place widgets for option ${value.staticDataSourceCaption} here`
173+
),
174+
dropzone.hideDataSourceHeaderIf(false)
175+
)(value.staticDataSourceCustomContent)
176+
)
177+
);
178+
});
179+
}
180+
}
181+
182+
// Handle database-specific read-only logic
183+
if (_values.source === "database" && _values.databaseAttributeString.length === 0) {
184+
readOnly = _values.customEditability === "never";
185+
}
186+
187+
// If no custom content dropzones, show default preview
188+
if (structurePreviewChildren.length === 0) {
189+
return {
190+
type: "RowLayout",
191+
columnSize: "fixed",
192+
backgroundColor: readOnly ? palette.background.containerDisabled : palette.background.containerFill,
193+
children: [
194+
{
195+
type: "RowLayout",
196+
columnSize: "grow",
197+
children: [
198+
getIconPreview(isDarkMode),
199+
{
200+
type: "Container",
201+
padding: 4,
202+
children: [
203+
{
204+
type: "Text",
205+
content: "Selection Controls",
206+
fontColor: palette.text.primary,
207+
fontSize: 10
208+
}
209+
]
210+
}
211+
]
212+
}
213+
]
214+
};
215+
}
216+
217+
// Return container with dropzones
143218
return {
144-
type: "RowLayout",
145-
columnSize: "fixed",
146-
backgroundColor: palette.background.containerFill,
219+
type: "Container",
147220
children: [
148221
{
149222
type: "RowLayout",
150223
columnSize: "grow",
224+
borders: true,
225+
borderWidth: 1,
226+
borderRadius: 2,
227+
backgroundColor: readOnly ? palette.background.containerDisabled : palette.background.container,
151228
children: [
152-
getIconPreview(isDarkMode),
153229
{
154230
type: "Container",
231+
grow: 1,
155232
padding: 4,
156-
children: [
157-
{
158-
type: "Text",
159-
content: "Selection Controls",
160-
fontColor: palette.text.primary,
161-
fontSize: 10
162-
}
163-
]
164-
}
233+
children: structurePreviewChildren
234+
},
235+
readOnly && _values.readOnlyStyle === "text"
236+
? container({ grow: 0, padding: 4 })()
237+
: {
238+
...getIconPreview(isDarkMode),
239+
...{ grow: 0, padding: 4 }
240+
}
165241
]
166242
}
167243
]

packages/pluggableWidgets/selection-controls-web/src/SelectionControls.editorPreview.tsx

Lines changed: 10 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,11 @@ import { SelectionControlsPreviewProps } from "../typings/SelectionControlsProps
44
import { RadioSelection } from "./components/RadioSelection/RadioSelection";
55
import { dynamic } from "@mendix/widget-plugin-test-utils";
66
import { SingleSelector, SelectionBaseProps } from "./helpers/types";
7+
import { StaticPreviewSelector } from "./helpers/Static/Preview/StaticPreviewSelector";
8+
import { DatabasePreviewSelector } from "./helpers/Database/Preview/DatabasePreviewSelector";
9+
import { AssociationPreviewSelector } from "./helpers/Association/Preview/AssociationPreviewSelector";
710
import "./ui/SelectionControls.scss";
811

9-
// Preview selector implementation - simplified for preview
10-
class PreviewSelector implements SingleSelector {
11-
type = "single" as const;
12-
status = "available" as const;
13-
readOnly = false;
14-
validation = undefined;
15-
clearable = false;
16-
currentId = null;
17-
customContentType = "no" as const;
18-
19-
constructor(_props: SelectionControlsPreviewProps) {}
20-
21-
updateProps() {}
22-
setValue() {}
23-
onEnterEvent() {}
24-
onLeaveEvent() {}
25-
26-
options = {
27-
status: "available" as const,
28-
searchTerm: "",
29-
getAll: () => ["Option 1", "Option 2", "Option 3"],
30-
setSearchTerm: () => {},
31-
onAfterSearchTermChange: () => {},
32-
isLoading: false,
33-
_updateProps: () => {},
34-
_optionToValue: () => undefined,
35-
_valueToOption: () => null
36-
};
37-
38-
caption = {
39-
get: (value: string | null) => value || "Preview Option",
40-
render: (value: string | null) => value || "Preview Option",
41-
emptyCaption: "Select an option"
42-
};
43-
}
44-
4512
export const preview = (props: SelectionControlsPreviewProps): ReactElement => {
4613
const id = generateUUID().toString();
4714
const commonProps: Omit<SelectionBaseProps<null>, "selector"> = {
@@ -65,7 +32,13 @@ export const preview = (props: SelectionControlsPreviewProps): ReactElement => {
6532

6633
// eslint-disable-next-line react-hooks/rules-of-hooks
6734
const selector: SingleSelector = useMemo(() => {
68-
return new PreviewSelector(props);
35+
if (props.source === "static") {
36+
return new StaticPreviewSelector(props);
37+
}
38+
if (props.source === "database") {
39+
return new DatabasePreviewSelector(props);
40+
}
41+
return new AssociationPreviewSelector(props);
6942
}, [props]);
7043

7144
return (
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { SingleSelector, Status, CaptionsProvider, OptionsProvider } from "../../types";
2+
import {
3+
SelectionControlsPreviewProps,
4+
OptionsSourceCustomContentTypeEnum
5+
} from "../../../../typings/SelectionControlsProps";
6+
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
7+
import { PreviewCaptionsProvider } from "../../Preview/PreviewCaptionsProvider";
8+
import { PreviewOptionsProvider } from "../../Preview/PreviewOptionsProvider";
9+
10+
export class AssociationPreviewSelector implements SingleSelector {
11+
type = "single" as const;
12+
status: Status = "available";
13+
attributeType?: "string" | "boolean" | "big" | "date" | undefined;
14+
selectorType?: "context" | "database" | "static" | undefined;
15+
// type: "single";
16+
readOnly: boolean;
17+
validation?: string | undefined;
18+
clearable: boolean = false;
19+
currentId: string | null;
20+
customContentType: OptionsSourceCustomContentTypeEnum;
21+
caption: CaptionsProvider;
22+
options: OptionsProvider;
23+
24+
constructor(props: SelectionControlsPreviewProps) {
25+
this.readOnly = props.readOnly;
26+
this.currentId = `single-${generateUUID()}`;
27+
this.customContentType = props.optionsSourceCustomContentType;
28+
this.readOnly = props.readOnly;
29+
this.caption = new PreviewCaptionsProvider(new Map());
30+
this.options = new PreviewOptionsProvider(this.caption, new Map());
31+
(this.caption as PreviewCaptionsProvider).updatePreviewProps({
32+
customContentRenderer: props.optionsSourceAssociationCustomContent?.renderer,
33+
customContentType: props.optionsSourceCustomContentType
34+
});
35+
}
36+
37+
updateProps() {}
38+
setValue() {}
39+
onEnterEvent() {}
40+
onLeaveEvent() {}
41+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { SingleSelector, Status, CaptionsProvider, OptionsProvider } from "../../types";
2+
import {
3+
SelectionControlsPreviewProps,
4+
OptionsSourceCustomContentTypeEnum
5+
} from "../../../../typings/SelectionControlsProps";
6+
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
7+
import { PreviewCaptionsProvider } from "../../Preview/PreviewCaptionsProvider";
8+
import { PreviewOptionsProvider } from "../../Preview/PreviewOptionsProvider";
9+
10+
export class DatabasePreviewSelector implements SingleSelector {
11+
type = "single" as const;
12+
status: Status = "available";
13+
attributeType?: "string" | "boolean" | "big" | "date" | undefined;
14+
selectorType?: "context" | "database" | "static" | undefined;
15+
// type: "single";
16+
readOnly: boolean;
17+
validation?: string | undefined;
18+
clearable: boolean = false;
19+
currentId: string | null;
20+
customContentType: OptionsSourceCustomContentTypeEnum;
21+
caption: CaptionsProvider;
22+
options: OptionsProvider;
23+
24+
constructor(props: SelectionControlsPreviewProps) {
25+
this.currentId = `single-${generateUUID()}`;
26+
this.customContentType = props.optionsSourceCustomContentType;
27+
this.readOnly = props.readOnly;
28+
this.caption = new PreviewCaptionsProvider(new Map());
29+
this.options = new PreviewOptionsProvider(this.caption, new Map());
30+
(this.caption as PreviewCaptionsProvider).updatePreviewProps({
31+
customContentRenderer: props.optionsSourceDatabaseCustomContent?.renderer,
32+
customContentType: props.optionsSourceCustomContentType
33+
});
34+
// Show dropzones in design mode when custom content is enabled
35+
}
36+
37+
updateProps() {}
38+
setValue() {}
39+
onEnterEvent() {}
40+
onLeaveEvent() {}
41+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { OptionsSourceCustomContentTypeEnum } from "../../../typings/SelectionControlsProps";
2+
import { SimpleCaptionsProvider } from "./SimpleCaptionsProvider";
3+
import { createElement, ReactNode, ComponentType } from "react";
4+
interface PreviewProps {
5+
customContentRenderer:
6+
| ComponentType<{ children: ReactNode; caption?: string }>
7+
| Array<ComponentType<{ children: ReactNode; caption?: string }>>;
8+
customContentType: OptionsSourceCustomContentTypeEnum;
9+
}
10+
11+
export class PreviewCaptionsProvider extends SimpleCaptionsProvider {
12+
emptyCaption = "Combo box";
13+
private customContentRenderer: ComponentType<{ children: ReactNode; caption?: string }> = () => <div></div>;
14+
get(value: string | null): string {
15+
return value || this.emptyCaption;
16+
}
17+
18+
getCustomContent(value: string | null): ReactNode | null {
19+
if (value === null) {
20+
return null;
21+
}
22+
if (this.customContentType !== "no") {
23+
return (
24+
<this.customContentRenderer caption={"CUSTOM CONTENT"}>
25+
<div />
26+
</this.customContentRenderer>
27+
);
28+
}
29+
}
30+
31+
updatePreviewProps(props: PreviewProps): void {
32+
this.customContentRenderer = props.customContentRenderer as ComponentType<{
33+
children: ReactNode;
34+
caption?: string | undefined;
35+
}>;
36+
this.customContentType = props.customContentType;
37+
}
38+
39+
render(value: string | null, htmlFor?: string): ReactNode {
40+
return super.render(value, htmlFor);
41+
}
42+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { ObjectItem } from "mendix";
2+
import { BaseOptionsProvider } from "../BaseOptionsProvider";
3+
import { CaptionsProvider, OptionsProvider, Status } from "../types";
4+
5+
export class PreviewOptionsProvider implements OptionsProvider<ObjectItem, BaseOptionsProvider> {
6+
hasMore?: boolean | undefined = undefined;
7+
searchTerm: string = "";
8+
status: Status = "available";
9+
isLoading: boolean = false;
10+
11+
constructor(
12+
protected caption: CaptionsProvider,
13+
protected valuesMap: Map<string, ObjectItem>
14+
) {}
15+
onAfterSearchTermChange(_callback: () => void): void {}
16+
setSearchTerm(_value: string): void {}
17+
loadMore?(): void {
18+
throw new Error("Method not implemented.");
19+
}
20+
_updateProps(_: BaseOptionsProvider): void {
21+
throw new Error("Method not implemented.");
22+
}
23+
_optionToValue(_value: string | null): ObjectItem | undefined {
24+
throw new Error("Method not implemented.");
25+
}
26+
_valueToOption(_value: ObjectItem | undefined): string | null {
27+
throw new Error("Method not implemented.");
28+
}
29+
getAll(): string[] {
30+
return ["..."];
31+
}
32+
}

0 commit comments

Comments
 (0)