site-builder: dynamic CTAs, section anchors, edit-with-Sitesmith
Three related features: 1. Dynamic CTA buttons on HeroSimple, CTASection, CallToAction. New shared ctas[] array (text + href + variant + target) replaces the primary/secondary pair. Settings panel gets add/remove/reorder controls. Legacy fields stay readable for backwards compat — first user edit migrates the section onto the new array. 2. Anchor IDs on all layout/section components (Container, Section, BackgroundSection, ColumnLayout, plus 6 section blocks done by parallel subagent, plus Hero/CTA/CallToAction). Anchor input lives in the settings panel with an "auto from heading" button that walks the subtree for the first Heading.text. Renders as id="..." on the outermost element so #anchor URLs resolve. 3. Edit-with-Sitesmith targeted invocation. Right-click → "Ask Sitesmith" and a button at the top of the right-side settings panel both open the modal pre-targeted at the selected node. The node's serialized subtree is sent to the server; system prompt is augmented to require a patch with replace_node. Editor lifts modal state into a new SitesmithContext. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { cssPropsToString } from '../../utils/style-helpers';
|
||||
import { SettingsTabs } from '../../ui/SettingsTabs';
|
||||
import { BorderControl } from '../../ui/BorderControl';
|
||||
import { AdvancedTab } from '../../ui/AdvancedTab';
|
||||
import { AnchorIdField } from '../../ui/AnchorIdField';
|
||||
|
||||
interface ContainerProps {
|
||||
style?: CSSProperties;
|
||||
@@ -11,6 +12,7 @@ interface ContainerProps {
|
||||
children?: React.ReactNode;
|
||||
cssId?: string;
|
||||
cssClass?: string;
|
||||
anchorId?: string;
|
||||
hideOnDesktop?: boolean;
|
||||
hideOnTablet?: boolean;
|
||||
hideOnMobile?: boolean;
|
||||
@@ -37,6 +39,7 @@ export const Container: UserComponent<ContainerProps> = ({
|
||||
children,
|
||||
fullWidth = false,
|
||||
contentWidth = 'full',
|
||||
anchorId,
|
||||
}) => {
|
||||
const { connectors: { connect, drag } } = useNode();
|
||||
|
||||
@@ -56,6 +59,7 @@ export const Container: UserComponent<ContainerProps> = ({
|
||||
ref: (ref: HTMLElement | null): void => { if (ref) connect(drag(ref)); },
|
||||
style: outerStyle,
|
||||
'data-craft-container': 'true',
|
||||
id: anchorId || undefined,
|
||||
},
|
||||
needsBoxedWrapper
|
||||
? React.createElement('div', { style: { maxWidth: '1200px', margin: '0 auto', ...flexStyles } }, children)
|
||||
@@ -120,6 +124,7 @@ const ContainerSettings: React.FC = () => {
|
||||
<SettingsTabs
|
||||
general={
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<AnchorIdField />
|
||||
{/* Tag */}
|
||||
<div>
|
||||
<label style={cLabelStyle}>HTML Element</label>
|
||||
@@ -304,6 +309,7 @@ Container.craft = {
|
||||
tag: 'div',
|
||||
fullWidth: false,
|
||||
contentWidth: 'full',
|
||||
anchorId: '',
|
||||
},
|
||||
rules: {
|
||||
canDrag: () => true,
|
||||
@@ -318,6 +324,7 @@ Container.craft = {
|
||||
/* ---------- HTML export ---------- */
|
||||
|
||||
(Container as any).toHtml = (props: ContainerProps, childrenHtml: string) => {
|
||||
const esc = (s: any) => String(s ?? "").replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
const tag = props.tag || 'div';
|
||||
const isBoxed = props.contentWidth === 'boxed';
|
||||
const flexStyles = flexAlignFromTextAlign(props.style?.textAlign);
|
||||
@@ -333,11 +340,12 @@ Container.craft = {
|
||||
}
|
||||
|
||||
const styleStr = cssPropsToString(outerCss);
|
||||
const idAttr = props.anchorId ? ` id="${esc(props.anchorId)}"` : '';
|
||||
|
||||
if (isBoxed) {
|
||||
const innerStyle = cssPropsToString({ maxWidth: '1200px', margin: '0 auto', ...flexStyles });
|
||||
return { html: `<${tag}${styleStr ? ` style="${styleStr}"` : ''}><div${innerStyle ? ` style="${innerStyle}"` : ''}>${childrenHtml}</div></${tag}>` };
|
||||
return { html: `<${tag}${idAttr}${styleStr ? ` style="${styleStr}"` : ''}><div${innerStyle ? ` style="${innerStyle}"` : ''}>${childrenHtml}</div></${tag}>` };
|
||||
}
|
||||
|
||||
return { html: `<${tag}${styleStr ? ` style="${styleStr}"` : ''}>${childrenHtml}</${tag}>` };
|
||||
return { html: `<${tag}${idAttr}${styleStr ? ` style="${styleStr}"` : ''}>${childrenHtml}</${tag}>` };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user