diff --git a/platform/wab/src/wab/client/components/insert-panel/InsertPanel.tsx b/platform/wab/src/wab/client/components/insert-panel/InsertPanel.tsx
index bc404c3d32a..4c6d34eb064 100644
--- a/platform/wab/src/wab/client/components/insert-panel/InsertPanel.tsx
+++ b/platform/wab/src/wab/client/components/insert-panel/InsertPanel.tsx
@@ -28,6 +28,7 @@ import {
createAddHostLessComponent,
createAddInsertableTemplate,
createAddTemplateComponent,
+ createAddTplCodeComponents,
createAddTplComponent,
createAddTplImage,
createFakeHostLessComponent,
@@ -77,9 +78,11 @@ import {
} from "@/wab/common";
import { HighlightBlinker } from "@/wab/commons/components/HighlightBlinker";
import {
+ CodeComponent,
getSubComponents,
getSuperComponents,
isCodeComponent,
+ isCodeComponentWithSection,
isComponentHiddenFromContentEditor,
isContextCodeComponent,
isDefaultComponentKind,
@@ -275,10 +278,13 @@ const AddDrawerContent = observer(function AddDrawerContent(props: {
let itemIndex = 0;
const isAtomicSection = atomicHostlessSections.includes(section);
+
const virtualItems: VirtualItem[] = groupedItems
.filter((group) => query || (group.sectionKey ?? group.key) === section)
.flatMap((group, index) => [
- ...(!isAtomicSection ? [{ type: "header", group } as const] : []),
+ ...(!isAtomicSection && !group.isHeaderLess
+ ? [{ type: "header", group } as const]
+ : []),
...group.items.map(
(item) =>
({ type: "item", item, group, itemIndex: itemIndex++ } as const)
@@ -936,6 +942,49 @@ function getLeafProjectIdForHostLessPackageMeta(pkg: HostLessPackageInfo) {
return last(ensureArray(pkg.projectId));
}
+function getCodeComponentsGroups(studioCtx: StudioCtx): AddItemGroup[] {
+ // All code components with studio UI will receive a dedicated section
+ // in the AddDrawer.
+ const components: CodeComponent[] = studioCtx.site.components.filter(
+ (c): c is CodeComponent => isCodeComponentWithSection(c)
+ );
+ const groups = groupBy(components, (c) => c.codeComponentMeta.section);
+ return sortBy(
+ Object.entries(groups)
+ .map(([section, sectionComponents]) => {
+ const subGroups = groupBy(sectionComponents, (c) => {
+ const meta = c.codeComponentMeta;
+ if (!meta.displayName) {
+ return "";
+ }
+ const parts = meta.displayName.split("/");
+ // Remove the last part to get the sub-section
+ return parts.length > 1 ? parts.slice(0, -1).join("/") : "";
+ });
+
+ return Object.entries(subGroups).map(
+ ([subSection, subSectionComponents]) => {
+ return {
+ key: `code-components-${section}${
+ subSection ? `-${subSection}` : ""
+ }`,
+ isHeaderLess: !subSection,
+ sectionKey: section,
+ sectionLabel: section,
+ label: subSection,
+ items: sortBy(
+ createAddTplCodeComponents(subSectionComponents),
+ (item) => item.label
+ ),
+ };
+ }
+ );
+ })
+ .flat(),
+ (itemGroup) => itemGroup.key
+ );
+}
+
export function buildAddItemGroups({
studioCtx,
includeFrames = true,
@@ -1155,6 +1204,9 @@ export function buildAddItemGroups({
}
),
+ // Code components groups
+ ...getCodeComponentsGroups(studioCtx),
+
includeFrames &&
canInsertAlias(uiConfig, "frame", canInsertContext) && {
key: "frames",
@@ -1166,6 +1218,7 @@ export function buildAddItemGroups({
],
},
+ // Custom components includes all the components from the project
{
key: "components",
label: "Custom components",
@@ -1174,6 +1227,7 @@ export function buildAddItemGroups({
(c) =>
isReusableComponent(c) &&
!isContextCodeComponent(c) &&
+ !isCodeComponentWithSection(c) &&
!(
contentEditorMode &&
isComponentHiddenFromContentEditor(c, studioCtx)
diff --git a/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.module.css b/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.module.css
new file mode 100644
index 00000000000..9230c97bdc4
--- /dev/null
+++ b/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.module.css
@@ -0,0 +1,8 @@
+.preview-image {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ min-width: 0;
+ min-height: 0;
+}
diff --git a/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.tsx b/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.tsx
index 8dac94b3bb7..292a408f922 100644
--- a/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.tsx
+++ b/platform/wab/src/wab/client/components/studio/add-drawer/AddDrawer.tsx
@@ -36,6 +36,7 @@ import {
getPlumeImage,
} from "@/wab/client/components/plume/plume-display-utils";
import { PlumyIcon } from "@/wab/client/components/plume/plume-markers";
+import sty from "@/wab/client/components/studio/add-drawer/AddDrawer.module.css";
import AddDrawerItem from "@/wab/client/components/studio/add-drawer/AddDrawerItem";
import { AddItemGroup } from "@/wab/client/components/studio/add-drawer/AddDrawerSection";
import { DraggableInsertable } from "@/wab/client/components/studio/add-drawer/DraggableInsertable";
@@ -58,6 +59,7 @@ import {
getScreenVariantToInsertableTemplate,
postInsertableTemplate,
} from "@/wab/client/insertable-templates";
+import ComponentIcon from "@/wab/client/plasmic/plasmic_kit/PlasmicIcon__Component";
import PlumeMarkIcon from "@/wab/client/plasmic/plasmic_kit_design_system/icons/PlasmicIcon__PlumeMark";
import { PlasmicAddDrawer } from "@/wab/client/plasmic/plasmic_kit_left_pane/PlasmicAddDrawer";
import { StudioCtx } from "@/wab/client/studio-ctx/StudioCtx";
@@ -65,6 +67,7 @@ import { ViewCtx } from "@/wab/client/studio-ctx/view-ctx";
import { trackEvent } from "@/wab/client/tracking";
import {
assert,
+ cx,
ensure,
ensureArray,
filterFalsy,
@@ -109,6 +112,7 @@ import {
} from "@/wab/shared/code-components/code-components";
import { isRenderableType } from "@/wab/shared/core/model-util";
import { isTagListContainer } from "@/wab/shared/core/rich-text-util";
+import { CSSProperties } from "@/wab/shared/element-repr/element-repr-v2";
import { InsertableTemplateExtraInfo } from "@/wab/shared/insertable-templates/types";
import { FRAMES_CAP } from "@/wab/shared/Labels";
import {
@@ -448,6 +452,51 @@ export function createAddTplComponent(component: Component): AddTplItem {
};
}
+function getAddTplItemPreviewImage(
+ url: string,
+ objectPosition: CSSProperties["objectPosition"]
+) {
+ return (
+
+ );
+}
+
+export function createAddTplCodeComponent(
+ component: CodeComponent
+): AddTplItem {
+ const thumbUrl = component.codeComponentMeta.thumbnailUrl;
+ return {
+ ...createAddTplComponent(component),
+ previewImage: thumbUrl ? (
+ getAddTplItemPreviewImage(thumbUrl, "center center")
+ ) : (
+
+ ),
+ isCompact: true,
+ };
+}
+
+export function createAddTplCodeComponents(
+ components: CodeComponent[]
+): AddTplItem[] {
+ return components.flatMap((component) => {
+ // It shouldn't be possible to have a sub component that is not a code component
+ // but we'll be safe and filter them out
+ const subComponents = getSubComponents(component).filter(isCodeComponent);
+ return [
+ createAddTplCodeComponent(component),
+ ...subComponents.map(createAddTplCodeComponent),
+ ];
+ });
+}
+
export function createAddComponentPreset(
studioCtx: StudioCtx,
component: CodeComponent,
@@ -481,22 +530,9 @@ export function createAddInsertableTemplate(
label: meta.componentName,
canWrap: false,
icon: COMBINATION_ICON,
- previewImage: meta.imageUrl ? (
-
- ) : null,
+ previewImage: meta.imageUrl
+ ? getAddTplItemPreviewImage(meta.imageUrl, "center top")
+ : null,
factory: (
vc: ViewCtx,
extraInfo: InsertableTemplateExtraInfo,
@@ -547,22 +583,9 @@ export function createAddTemplateComponent(
label: meta.displayName ?? meta.componentName,
canWrap: false,
icon: COMBINATION_ICON,
- previewImage: meta.imageUrl ? (
-
- ) : null,
+ previewImage: meta.imageUrl
+ ? getAddTplItemPreviewImage(meta.imageUrl, "center top")
+ : null,
factory: (
vc: ViewCtx,
extraInfo: InsertableTemplateComponentExtraInfo,
@@ -639,21 +662,7 @@ export function createAddHostLessComponent(
icon: COMBINATION_ICON,
gray: meta.gray,
previewImage: meta.imageUrl ? (
-
+ getAddTplItemPreviewImage(meta.imageUrl, "center center")
) : meta.videoUrl ? (
+ getAddTplItemPreviewImage(meta.imageUrl, "center center")
) : meta.videoUrl ? (
+ getAddTplItemPreviewImage(meta.imageUrl, "center center")
) : meta.videoUrl ? (