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:
@@ -17,6 +17,7 @@ import {
|
||||
PricingStylePanel,
|
||||
BackgroundSectionStylePanel,
|
||||
GenericPropsEditor,
|
||||
HtmlStylePanel,
|
||||
} from './styles';
|
||||
|
||||
/* ================================================================
|
||||
@@ -75,8 +76,10 @@ export const GuidedStyles: React.FC = () => {
|
||||
const isSocial = /^social links$|^icon$|^star rating$/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);
|
||||
// Utility types that need minimal controls
|
||||
const isUtility = /^divider$|^spacer$|^html$/i.test(typeName);
|
||||
const isHtml = /^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
|
||||
const typeIcon = isText ? 'fa-font'
|
||||
@@ -90,6 +93,7 @@ export const GuidedStyles: React.FC = () => {
|
||||
: isForm ? 'fa-wpforms'
|
||||
: isSocial ? 'fa-share-alt'
|
||||
: isSection ? 'fa-th-large'
|
||||
: isHtml ? 'fa-code'
|
||||
: isUtility ? 'fa-ellipsis-h'
|
||||
: 'fa-cube';
|
||||
|
||||
@@ -158,11 +162,14 @@ export const GuidedStyles: React.FC = () => {
|
||||
{/* SECTION-TYPE (Accordion, Tabs, Testimonials, Countdown, Counter, CTA, Features) */}
|
||||
{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 */}
|
||||
{isUtility && <GenericPropsEditor selectedId={selected} nodeProps={nodeProps} typeName={typeName} />}
|
||||
|
||||
{/* 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} />
|
||||
)}
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -11,3 +11,4 @@ export { SectionTypePanel } from './SectionTypePanel';
|
||||
export { PricingStylePanel } from './PricingStylePanel';
|
||||
export { BackgroundSectionStylePanel } from './BackgroundSectionStylePanel';
|
||||
export { GenericPropsEditor } from './GenericPropsEditor';
|
||||
export { HtmlStylePanel } from './HtmlStylePanel';
|
||||
|
||||
Reference in New Issue
Block a user