136 lines
5.3 KiB
TypeScript
136 lines
5.3 KiB
TypeScript
|
|
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<string, any>;
|
||
|
|
setPropStyle: (prop: string, value: string) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Margin + Padding, per-side, via the shared SpacingControl. */
|
||
|
|
export const BoxModelSection: React.FC<BoxModelSectionProps> = ({ style, setPropStyle }) => {
|
||
|
|
const sideSetter = (kind: 'margin' | 'padding') => (side: SpacingSide, value: string) =>
|
||
|
|
setPropStyle(`${kind}${capitalize(side)}`, value);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<CollapsibleSection title="Spacing" defaultOpen={false}>
|
||
|
|
<SpacingControl
|
||
|
|
label="Margin"
|
||
|
|
value={{ top: style.marginTop, right: style.marginRight, bottom: style.marginBottom, left: style.marginLeft }}
|
||
|
|
onChange={sideSetter('margin')}
|
||
|
|
/>
|
||
|
|
<SpacingControl
|
||
|
|
label="Padding"
|
||
|
|
value={{ top: style.paddingTop, right: style.paddingRight, bottom: style.paddingBottom, left: style.paddingLeft }}
|
||
|
|
onChange={sideSetter('padding')}
|
||
|
|
/>
|
||
|
|
</CollapsibleSection>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
/** 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<string, any>;
|
||
|
|
setPropStyle: (prop: string, value: string) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Border (width/style/color) + box-shadow preset + opacity slider. */
|
||
|
|
export const BorderEffectsSection: React.FC<BorderEffectsSectionProps> = ({ style, setPropStyle }) => (
|
||
|
|
<CollapsibleSection title="Border & Effects" defaultOpen={false}>
|
||
|
|
<BorderControl
|
||
|
|
value={parseBorderShorthand(style.border)}
|
||
|
|
onChange={(v) => setPropStyle('border', buildBorderShorthand(v))}
|
||
|
|
/>
|
||
|
|
<div className="guided-section">
|
||
|
|
<SectionLabel>Shadow</SectionLabel>
|
||
|
|
<PresetButtonGrid presets={SHADOW_PRESETS} activeValue={style.boxShadow} onSelect={(v) => setPropStyle('boxShadow', v)} />
|
||
|
|
</div>
|
||
|
|
<div style={sectionGap}>
|
||
|
|
<label style={labelStyle}>Opacity: {opacityPercent(style.opacity)}%</label>
|
||
|
|
<input
|
||
|
|
type="range"
|
||
|
|
min={0}
|
||
|
|
max={100}
|
||
|
|
value={opacityPercent(style.opacity)}
|
||
|
|
onChange={(e) => setPropStyle('opacity', String(Number(e.target.value) / 100))}
|
||
|
|
style={{ width: '100%' }}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</CollapsibleSection>
|
||
|
|
);
|
||
|
|
|
||
|
|
export interface AnimVisSectionProps {
|
||
|
|
nodeProps: Record<string, any>;
|
||
|
|
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<AnimVisSectionProps> = ({ nodeProps, setProp }) => (
|
||
|
|
<CollapsibleSection title="Animation & Visibility" defaultOpen={false}>
|
||
|
|
<AnimationControl
|
||
|
|
value={{ animation: nodeProps.animation || 'none', animationDelay: nodeProps.animationDelay }}
|
||
|
|
onChange={(v) => { setProp('animation', v.animation); setProp('animationDelay', v.animationDelay); }}
|
||
|
|
/>
|
||
|
|
<VisibilityControl
|
||
|
|
value={{
|
||
|
|
hideOnDesktop: nodeProps.hideOnDesktop,
|
||
|
|
hideOnTablet: nodeProps.hideOnTablet,
|
||
|
|
hideOnMobile: nodeProps.hideOnMobile,
|
||
|
|
}}
|
||
|
|
onChange={(v) => {
|
||
|
|
setProp('hideOnDesktop', !!v.hideOnDesktop);
|
||
|
|
setProp('hideOnTablet', !!v.hideOnTablet);
|
||
|
|
setProp('hideOnMobile', !!v.hideOnMobile);
|
||
|
|
}}
|
||
|
|
/>
|
||
|
|
</CollapsibleSection>
|
||
|
|
);
|