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,
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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';
|
||||||
|
|||||||
Reference in New Issue
Block a user