import React from 'react'; import { SHADOW_PRESETS } from '../../../constants/presets'; import { SectionLabel, PresetButtonGrid, CollapsibleSection, SpacingControl, SpacingSide, BorderControl, BorderValue, buildBorderShorthand, AnimationControl, VisibilityControl, sectionGap, labelStyle, } from './shared'; /* ========================================================================== Shared box-model / border+effects / animation+visibility sections for the CONTAINERS package's single shared panel (ContainerStylePanel, used for Container / Section / Columns). Kept local to this package (not in shared.tsx, which is foundation/import-only) since it's just DRY-ing the identical JSX block across those 3 components rather than a genuinely cross-package reusable control. Mirrors the equivalent helper in the media package (mediaBoxModel.tsx) -- same shape, independently duplicated per-package by design (packages are developed and merged in parallel). ========================================================================== */ function capitalize(s: string): string { return s.charAt(0).toUpperCase() + s.slice(1); } /** Parses a `border` shorthand string (e.g. "2px solid #ff0000") back into * the {width,style,color} shape BorderControl edits. Only needs to * round-trip values this same panel produced via buildBorderShorthand -- * not arbitrary author-supplied CSS. */ export function parseBorderShorthand(v: string | undefined): BorderValue { if (!v || v === 'none') return { width: '', style: 'none', color: '#000000' }; const m = String(v).trim().match(/^(\d+(?:\.\d+)?(?:px|em|rem)?)\s+(\w+)\s+(.+)$/); if (!m) return { width: '', style: 'none', color: '#000000' }; return { width: m[1], style: m[2], color: m[3] }; } export interface BoxModelSectionProps { style: Record; setPropStyle: (prop: string, value: string) => void; } /** Margin + Padding, per-side, via the shared SpacingControl. */ export const BoxModelSection: React.FC = ({ style, setPropStyle }) => { const sideSetter = (kind: 'margin' | 'padding') => (side: SpacingSide, value: string) => setPropStyle(`${kind}${capitalize(side)}`, value); return ( ); }; /** style.opacity is stored as a CSS-length-free numeric string ("0.8") or * may be blank/undefined (treated as fully opaque). Converts to a 0-100 * integer for the range input / label. */ function opacityPercent(v: unknown): number { if (v === undefined || v === null || v === '') return 100; const n = parseFloat(String(v)); return Number.isFinite(n) ? Math.round(n * 100) : 100; } export interface BorderEffectsSectionProps { style: Record; setPropStyle: (prop: string, value: string) => void; } /** Border (width/style/color) + box-shadow preset + opacity slider. */ export const BorderEffectsSection: React.FC = ({ style, setPropStyle }) => ( setPropStyle('border', buildBorderShorthand(v))} />
Shadow setPropStyle('boxShadow', v)} />
setPropStyle('opacity', String(Number(e.target.value) / 100))} style={{ width: '100%' }} />
); export interface AnimVisSectionProps { nodeProps: Record; setProp: (key: string, value: any) => void; } /** Entrance animation + responsive hide toggles -- top-level props consumed * directly by html-export.ts's buildDataAttrs (no toHtml change needed). */ export const AnimVisSection: React.FC = ({ nodeProps, setProp }) => ( { setProp('animation', v.animation); setProp('animationDelay', v.animationDelay); }} /> { setProp('hideOnDesktop', !!v.hideOnDesktop); setProp('hideOnTablet', !!v.hideOnTablet); setProp('hideOnMobile', !!v.hideOnMobile); }} /> );