feat(site-builder): HTML blocks get a code-only style panel

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-08 18:42:28 -07:00
co-authored by Claude Opus 5
parent 0d0d722dd7
commit 4ac57e1c4c
4 changed files with 79 additions and 3 deletions
+10 -3
View File
@@ -17,6 +17,7 @@ import {
PricingStylePanel, PricingStylePanel,
BackgroundSectionStylePanel, BackgroundSectionStylePanel,
GenericPropsEditor, GenericPropsEditor,
HtmlStylePanel,
} from './styles'; } from './styles';
/* ================================================================ /* ================================================================
@@ -75,8 +76,10 @@ export const GuidedStyles: React.FC = () => {
const isSocial = /^social links$|^icon$|^star rating$/i.test(typeName); const isSocial = /^social links$|^icon$|^star rating$/i.test(typeName);
const isPricing = /^pricing/i.test(typeName); const isPricing = /^pricing/i.test(typeName);
const isSection = /^accordion$|^tabs$|^testimonial|^countdown$|^number counter$|^cta section$|^call to action$|^features grid$/i.test(typeName); const isSection = /^accordion$|^tabs$|^testimonial|^countdown$|^number counter$|^cta section$|^call to action$|^features grid$/i.test(typeName);
// Utility types that need minimal controls const isHtml = /^html$/i.test(typeName);
const isUtility = /^divider$|^spacer$|^html$/i.test(typeName); // Utility types that need minimal controls. HTML is deliberately NOT here
// -- it gets HtmlStylePanel, which shows the code editor and nothing else.
const isUtility = /^divider$|^spacer$/i.test(typeName);
// Icon for the type badge // Icon for the type badge
const typeIcon = isText ? 'fa-font' const typeIcon = isText ? 'fa-font'
@@ -90,6 +93,7 @@ export const GuidedStyles: React.FC = () => {
: isForm ? 'fa-wpforms' : isForm ? 'fa-wpforms'
: isSocial ? 'fa-share-alt' : isSocial ? 'fa-share-alt'
: isSection ? 'fa-th-large' : isSection ? 'fa-th-large'
: isHtml ? 'fa-code'
: isUtility ? 'fa-ellipsis-h' : isUtility ? 'fa-ellipsis-h'
: 'fa-cube'; : 'fa-cube';
@@ -158,11 +162,14 @@ export const GuidedStyles: React.FC = () => {
{/* SECTION-TYPE (Accordion, Tabs, Testimonials, Countdown, Counter, CTA, Features) */} {/* SECTION-TYPE (Accordion, Tabs, Testimonials, Countdown, Counter, CTA, Features) */}
{isSection && <SectionTypePanel selectedId={selected} nodeProps={nodeProps} typeName={typeName} />} {isSection && <SectionTypePanel selectedId={selected} nodeProps={nodeProps} typeName={typeName} />}
{/* HTML -- code editor only */}
{isHtml && <HtmlStylePanel selectedId={selected} nodeProps={nodeProps} />}
{/* UTILITY (Divider, Spacer, HTML) -- use generic but it works well for these */} {/* UTILITY (Divider, Spacer, HTML) -- use generic but it works well for these */}
{isUtility && <GenericPropsEditor selectedId={selected} nodeProps={nodeProps} typeName={typeName} />} {isUtility && <GenericPropsEditor selectedId={selected} nodeProps={nodeProps} typeName={typeName} />}
{/* FALLBACK: Anything not matched above */} {/* FALLBACK: Anything not matched above */}
{!isText && !isButton && !isImage && !isBgSection && !isContainer && !isHero && !isNav && !isMedia && !isForm && !isSocial && !isPricing && !isSection && !isUtility && ( {!isText && !isButton && !isImage && !isBgSection && !isContainer && !isHero && !isNav && !isMedia && !isForm && !isSocial && !isPricing && !isSection && !isUtility && !isHtml && (
<GenericPropsEditor selectedId={selected} nodeProps={nodeProps} typeName={typeName} /> <GenericPropsEditor selectedId={selected} nodeProps={nodeProps} typeName={typeName} />
)} )}
</div> </div>
@@ -0,0 +1,38 @@
import { describe, test, expect, vi } from 'vitest';
import React from 'react';
import { createRoot, Root } from 'react-dom/client';
import { act } from 'react-dom/test-utils';
vi.mock('@craftjs/core', () => ({
useEditor: () => ({ actions: { setProp: vi.fn() }, query: {} }),
}));
import { HtmlStylePanel } from './HtmlStylePanel';
let container: HTMLDivElement;
let root: Root;
function render(ui: React.ReactElement) {
container = document.createElement('div');
document.body.appendChild(container);
act(() => {
root = createRoot(container);
root.render(ui);
});
}
describe('HtmlStylePanel', () => {
test('renders the Edit HTML control', () => {
render(<HtmlStylePanel selectedId="n1" nodeProps={{ code: '<p>x</p>', style: {} }} />);
expect(container.textContent).toContain('Edit HTML');
});
test('renders NO colour or style controls (they never reach the live page)', () => {
render(<HtmlStylePanel selectedId="n1" nodeProps={{ code: '<p>x</p>', style: {} }} />);
expect(container.querySelector('input[type="color"]')).toBeNull();
expect(container.textContent).not.toContain('Background');
expect(container.textContent).not.toContain('Text Color');
expect(container.textContent).not.toContain('Padding');
expect(container.textContent).not.toContain('Border Radius');
});
});
@@ -0,0 +1,30 @@
import React from 'react';
import { useNodeProp } from './shared';
import { HtmlCodeField } from './HtmlCodeField';
/**
* The HTML block's entire style panel.
*
* Deliberately ONLY the code editor. `HtmlBlock.toHtml()` emits nothing but
* the purified `code`, so every colour/padding/alignment control the generic
* editor used to offer here was dead on the published page. Styling an HTML
* block is done inside the user's own markup.
*/
export const HtmlStylePanel: React.FC<{ selectedId: string; nodeProps: Record<string, any> }> = ({
selectedId,
nodeProps,
}) => {
const { setProp: setPropValue } = useNodeProp(selectedId);
return (
<>
<HtmlCodeField
value={typeof nodeProps.code === 'string' ? nodeProps.code : ''}
onChange={(v) => setPropValue('code', v)}
/>
<p style={{ fontSize: 10, color: 'var(--color-text-dim)', lineHeight: 1.4, padding: '0 2px' }}>
Style this block inside your own markup a wrapper set here would show
in the editor but not on the published page.
</p>
</>
);
};
+1
View File
@@ -11,3 +11,4 @@ export { SectionTypePanel } from './SectionTypePanel';
export { PricingStylePanel } from './PricingStylePanel'; export { PricingStylePanel } from './PricingStylePanel';
export { BackgroundSectionStylePanel } from './BackgroundSectionStylePanel'; export { BackgroundSectionStylePanel } from './BackgroundSectionStylePanel';
export { GenericPropsEditor } from './GenericPropsEditor'; export { GenericPropsEditor } from './GenericPropsEditor';
export { HtmlStylePanel } from './HtmlStylePanel';