import React, { CSSProperties } from 'react'; import { TEXT_COLORS, FONT_FAMILIES, TEXT_SIZES, FONT_WEIGHTS, LINE_HEIGHTS, LETTER_SPACINGS, SHADOW_PRESETS, } from '../../../constants/presets'; import { StylePanelProps, SectionLabel, ColorSwatchGrid, PresetButtonGrid, NumericUnitInput, CollapsibleSection, SpacingControl, SpacingSide, BorderControl, BorderValue, buildBorderShorthand, AnimationControl, VisibilityControl, sectionGap, labelStyle, useNodeProp, } from './shared'; /* Text-transform is a small fixed enum with no natural home in the shared foundation presets (constants/presets.ts is import-only for this package), so it lives here as a package-local preset list. */ const TEXT_TRANSFORMS: { label: string; value: string }[] = [ { label: 'None', value: 'none' }, { label: 'UPPER', value: 'uppercase' }, { label: 'lower', value: 'lowercase' }, { label: 'Capitalize', value: 'capitalize' }, ]; 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. */ 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] }; } /** style.opacity is a CSS-length-free numeric string ("0.8") or blank * (treated as fully opaque). Converts to a 0-100 integer for the UI. */ 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; } /* ---------- TEXT ---------- */ export const TextStylePanel: React.FC = ({ selectedId, nodeProps }) => { const style: CSSProperties = nodeProps.style || {}; const { setProp, setPropStyle } = useNodeProp(selectedId); const isItalic = style.fontStyle === 'italic'; const isUnderline = style.textDecoration === 'underline'; return ( <>
Text Color setPropStyle('color', v)} />
Font Family setPropStyle('fontFamily', v)} />
Text Size setPropStyle('fontSize', v)} />
p.value === style.fontSize) ? '' : ((style.fontSize as string) || '')} onChange={(v) => setPropStyle('fontSize', v)} units={['px', 'em', 'rem', '%']} placeholder="custom" testId="text-fontsize-custom" />
Font Weight setPropStyle('fontWeight', v)} />
Style
Text Transform setPropStyle('textTransform', v === 'none' ? '' : v)} />
Line Height setPropStyle('lineHeight', v)} />
Letter Spacing setPropStyle('letterSpacing', v)} />
Alignment
{(['left', 'center', 'right', 'justify'] as const).map((a) => ( ))}
{/* Box model + border/effects + animation/visibility rollout */} setPropStyle(`margin${capitalize(side)}`, v)} /> setPropStyle(`padding${capitalize(side)}`, v)} /> setPropStyle('border', buildBorderShorthand(v))} />
Shadow setPropStyle('boxShadow', v)} />
setPropStyle('opacity', String(Number(e.target.value) / 100))} style={{ width: '100%' }} />
{ setProp('animation', v.animation); setProp('animationDelay', v.animationDelay); }} /> { setProp('hideOnDesktop', !!v.hideOnDesktop); setProp('hideOnTablet', !!v.hideOnTablet); setProp('hideOnMobile', !!v.hideOnMobile); }} /> ); };