- ColumnLayout: exposes style.alignItems on its flex ROW (aligns uneven columns) -- render/toHtml already spread `style` onto the row div, so this is a craft.props default + panel control addition, no structural change. - Container/Section: root element is now unconditionally display:flex; flex-direction:column (both editor render and toHtml), so the new Vertical Alignment control maps to style.justifyContent, paired with a Min Height (NumericUnitInput) control on style.minHeight. Default justify-content/align-items reproduce ordinary block-flow stacking, so this is a visual no-op for existing published content. Works in both normal and "boxed" (contentWidth) modes -- the boxed inner wrapper's own margin:0-auto horizontal centering is preserved via flex auto-margin override semantics. - ContainerStylePanel (serves Container/Section/Columns) distinguishes the Columns case from Container/Section via nodeProps.columns/split presence (no typeName plumbing needed) to pick align-items vs justify-content for the shared Vertical Alignment control. - All 3 owned components: added margin/padding (per-side)/border/box-shadow/ opacity style defaults + AnimationControl/VisibilityControl-backed animation/animationDelay/hideOnDesktop/hideOnTablet/hideOnMobile props. New containerBoxModel.tsx (package-local, not shared.tsx) DRYs the box-model + border/effects + animation/visibility panel sections across the single shared ContainerStylePanel, mirroring the sibling media package's mediaBoxModel.tsx. - Tests: extended all 3 *.toHtml.test.ts files (align-items/justify-content/ min-height emission, box-model style emission, craft.props presence). 673 tests green, tsc + vite build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
146 lines
5.1 KiB
TypeScript
146 lines
5.1 KiB
TypeScript
import React, { CSSProperties } from 'react';
|
|
import {
|
|
BG_COLORS,
|
|
SPACING_PRESETS,
|
|
RADIUS_PRESETS,
|
|
} from '../../../constants/presets';
|
|
import {
|
|
StylePanelProps,
|
|
SectionLabel,
|
|
ColorSwatchGrid,
|
|
GradientSwatchGrid,
|
|
PresetButtonGrid,
|
|
NumericUnitInput,
|
|
labelStyle,
|
|
inputStyle,
|
|
sectionGap,
|
|
useNodeProp,
|
|
} from './shared';
|
|
import { BoxModelSection, BorderEffectsSection, AnimVisSection } from './containerBoxModel';
|
|
|
|
// Vertical Alignment options shown to the user identically regardless of
|
|
// which CSS property they end up mapped to (align-items for the Columns
|
|
// flex ROW vs. justify-content for Container/Section's flex COLUMN root --
|
|
// see the per-type branch below).
|
|
const VERTICAL_ALIGN_OPTIONS: { label: string; value: string }[] = [
|
|
{ label: 'Top', value: 'flex-start' },
|
|
{ label: 'Center', value: 'center' },
|
|
{ label: 'Bottom', value: 'flex-end' },
|
|
{ label: 'Stretch', value: 'stretch' },
|
|
];
|
|
|
|
/* ---------- CONTAINER / SECTION / COLUMNS ---------- */
|
|
export const ContainerStylePanel: React.FC<StylePanelProps> = ({ selectedId, nodeProps }) => {
|
|
const style: CSSProperties = nodeProps.style || {};
|
|
|
|
const { setProp, setPropStyle } = useNodeProp(selectedId);
|
|
|
|
// ColumnLayout only ever carries `columns`/`split` props -- Container and
|
|
// Section never set them -- so checking either alone distinguishes the
|
|
// flex-ROW case (align its columns via align-items, aligning uneven
|
|
// column heights) from the flex-COLUMN case (Container/Section, which
|
|
// vertically center/position their OWN content via justify-content,
|
|
// paired with a Min Height control so centering is meaningful).
|
|
const isColumns = nodeProps.columns !== undefined || nodeProps.split !== undefined;
|
|
const vAlignKey = isColumns ? 'alignItems' : 'justifyContent';
|
|
|
|
return (
|
|
<>
|
|
{nodeProps.cssId !== undefined && (
|
|
<div style={sectionGap}>
|
|
<label style={labelStyle}>CSS ID</label>
|
|
<input
|
|
type="text"
|
|
value={nodeProps.cssId || ''}
|
|
onChange={(e) => setProp('cssId', e.target.value)}
|
|
placeholder="my-element-id"
|
|
style={inputStyle}
|
|
/>
|
|
</div>
|
|
)}
|
|
{nodeProps.cssClass !== undefined && (
|
|
<div style={sectionGap}>
|
|
<label style={labelStyle}>CSS Class</label>
|
|
<input
|
|
type="text"
|
|
value={nodeProps.cssClass || ''}
|
|
onChange={(e) => setProp('cssClass', e.target.value)}
|
|
placeholder="my-custom-class"
|
|
style={inputStyle}
|
|
/>
|
|
</div>
|
|
)}
|
|
<div className="guided-section">
|
|
<SectionLabel>Background Color</SectionLabel>
|
|
<ColorSwatchGrid
|
|
colors={BG_COLORS}
|
|
activeValue={style.backgroundColor as string}
|
|
onSelect={(v) => setPropStyle('backgroundColor', v)}
|
|
/>
|
|
</div>
|
|
<div className="guided-section">
|
|
<SectionLabel>Background Gradient</SectionLabel>
|
|
<GradientSwatchGrid
|
|
activeValue={style.background as string}
|
|
onSelect={(v) => setPropStyle('background', v)}
|
|
/>
|
|
</div>
|
|
<div className="guided-section">
|
|
<SectionLabel>Padding</SectionLabel>
|
|
<PresetButtonGrid
|
|
presets={SPACING_PRESETS}
|
|
activeValue={style.padding as string}
|
|
onSelect={(v) => setPropStyle('padding', v)}
|
|
/>
|
|
</div>
|
|
<div className="guided-section">
|
|
<SectionLabel>Border Radius</SectionLabel>
|
|
<PresetButtonGrid
|
|
presets={RADIUS_PRESETS}
|
|
activeValue={style.borderRadius as string}
|
|
onSelect={(v) => setPropStyle('borderRadius', v)}
|
|
/>
|
|
</div>
|
|
<div className="guided-section">
|
|
<SectionLabel>Alignment</SectionLabel>
|
|
<div className="preset-grid align-grid">
|
|
{(['left', 'center', 'right', 'justify'] as const).map((a) => (
|
|
<button
|
|
key={a}
|
|
className={`preset-btn ${style.textAlign === a ? 'active' : ''}`}
|
|
onClick={() => setPropStyle('textAlign', a)}
|
|
title={a}
|
|
>
|
|
<i className={`fa fa-align-${a}`} />
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
<div className="guided-section">
|
|
<SectionLabel>Vertical Alignment</SectionLabel>
|
|
<PresetButtonGrid
|
|
presets={VERTICAL_ALIGN_OPTIONS}
|
|
activeValue={style[vAlignKey] as string}
|
|
onSelect={(v) => setPropStyle(vAlignKey, v)}
|
|
/>
|
|
</div>
|
|
{!isColumns && (
|
|
<div className="guided-section">
|
|
<SectionLabel>Min Height</SectionLabel>
|
|
<NumericUnitInput
|
|
value={(style.minHeight as string) || ''}
|
|
onChange={(v) => setPropStyle('minHeight', v)}
|
|
units={['px', 'vh', '%']}
|
|
placeholder="auto"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Box model + border/effects + animation/visibility rollout */}
|
|
<BoxModelSection style={style} setPropStyle={setPropStyle} />
|
|
<BorderEffectsSection style={style} setPropStyle={setPropStyle} />
|
|
<AnimVisSection nodeProps={nodeProps} setProp={setProp} />
|
|
</>
|
|
);
|
|
};
|