diff --git a/apps/builder/app/builder/features/command-panel/command-panel.tsx b/apps/builder/app/builder/features/command-panel/command-panel.tsx index 2d819e3bdf5e..3e4bd4f899b8 100644 --- a/apps/builder/app/builder/features/command-panel/command-panel.tsx +++ b/apps/builder/app/builder/features/command-panel/command-panel.tsx @@ -25,13 +25,16 @@ import { collectionComponent, parseComponentName, elementComponent, + tags, } from "@webstudio-is/sdk"; -import type { Breakpoint, Page } from "@webstudio-is/sdk"; +import type { Breakpoint, Instance, Page } from "@webstudio-is/sdk"; import type { TemplateMeta } from "@webstudio-is/template"; import { $breakpoints, $editingPageId, + $instances, $pages, + $props, $registeredComponentMetas, $registeredTemplates, $selectedBreakpoint, @@ -44,7 +47,11 @@ import { } from "~/shared/instance-utils"; import { humanizeString } from "~/shared/string-utils"; import { setCanvasWidth } from "~/builder/features/breakpoints"; -import { $selectedPage, selectPage } from "~/shared/awareness"; +import { + $selectedInstancePath, + $selectedPage, + selectPage, +} from "~/shared/awareness"; import { mapGroupBy } from "~/shared/shim"; import { setActiveSidebarPanel } from "~/builder/shared/nano-states"; import { $commandMetas } from "~/shared/commands-emitter"; @@ -54,6 +61,7 @@ import { getInstanceLabel, InstanceIcon, } from "~/builder/shared/instance-label"; +import { isTreeSatisfyingContentModel } from "~/shared/content-model"; const $commandPanel = atom< | undefined @@ -233,6 +241,101 @@ const ComponentOptionsGroup = ({ options }: { options: ComponentOption[] }) => { ); }; +type TagOption = { + tokens: string[]; + type: "tag"; + tag: string; +}; + +const $tagOptions = computed( + [$selectedInstancePath, $instances, $props, $registeredComponentMetas], + (instancePath, instances, props, metas) => { + const tagOptions: TagOption[] = []; + if (instancePath === undefined) { + return tagOptions; + } + const [{ instance, instanceSelector }] = instancePath; + const childInstance: Instance = { + type: "instance", + id: "new_instance", + component: elementComponent, + children: [], + }; + const newInstances = new Map(instances); + newInstances.set(childInstance.id, childInstance); + newInstances.set(instance.id, { + ...instance, + children: [...instance.children, { type: "id", value: childInstance.id }], + }); + for (const tag of tags) { + childInstance.tag = tag; + const isSatisfying = isTreeSatisfyingContentModel({ + instances: newInstances, + props, + metas, + instanceSelector, + }); + if (isSatisfying) { + tagOptions.push({ + tokens: ["tags", tag, `<${tag}>`], + type: "tag", + tag, + }); + } + } + return tagOptions; + } +); + +const TagOptionsGroup = ({ options }: { options: TagOption[] }) => { + return ( + Tags} + actions={["add"]} + > + {options.map(({ tag }) => { + return ( + { + closeCommandPanel(); + const newInstance: Instance = { + type: "instance", + id: "new_instance", + component: elementComponent, + tag, + children: [], + }; + insertWebstudioFragmentAt({ + children: [{ type: "id", value: newInstance.id }], + instances: [newInstance], + props: [], + dataSources: [], + styleSourceSelections: [], + styleSources: [], + styles: [], + breakpoints: [], + assets: [], + resources: [], + }); + }} + > + + + + + {`<${tag}>`} + + + ); + })} + + ); +}; + type BreakpointOption = { tokens: string[]; type: "breakpoint"; @@ -423,12 +526,25 @@ const ShortcutOptionsGroup = ({ options }: { options: ShortcutOption[] }) => { }; const $options = computed( - [$componentOptions, $breakpointOptions, $pageOptions, $shortcutOptions], - (componentOptions, breakpointOptions, pageOptions, commandOptions) => [ + [ + $componentOptions, + $breakpointOptions, + $pageOptions, + $shortcutOptions, + $tagOptions, + ], + ( + componentOptions, + breakpointOptions, + pageOptions, + commandOptions, + tagOptions + ) => [ ...componentOptions, ...breakpointOptions, ...pageOptions, ...commandOptions, + ...tagOptions, ] ); @@ -461,6 +577,14 @@ const CommandDialogContent = () => { /> ); } + if (group === "tag") { + return ( + + ); + } if (group === "breakpoint") { return (