Files
site-builder/craft/src/ui/AdvancedTab.tsx
T
shadowdaoandClaude Opus 4.6 91a6b6f34b Add Craft.js site builder (v2) - complete rebuild from GrapesJS
Rebuilt the visual site builder from scratch using Craft.js, React 18,
and TypeScript. The new editor renders directly in the DOM (no iframe),
supports 40+ components, multi-page with shared header/footer, 16
templates, full-spectrum color/gradient controls, custom head code
injection, save/publish workflow, and auto-save.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:31:16 -07:00

234 lines
7.6 KiB
TypeScript

import React, { CSSProperties } from 'react';
import { SpacingInput, SpacingValue, parseSpacingShorthand, spacingToShorthand } from './SpacingInput';
interface AdvancedTabProps {
style: CSSProperties;
onStyleChange: (updates: CSSProperties) => void;
/** Optional: current CSS ID value */
cssId?: string;
onCssIdChange?: (id: string) => void;
/** Optional: current CSS class value */
cssClass?: string;
onCssClassChange?: (cls: string) => void;
/** Optional: show HTML tag selector (for containers) */
showTagSelector?: boolean;
tag?: string;
onTagChange?: (tag: string) => void;
/** Responsive visibility */
hideOnDesktop?: boolean;
onHideOnDesktopChange?: (hide: boolean) => void;
hideOnTablet?: boolean;
onHideOnTabletChange?: (hide: boolean) => void;
hideOnMobile?: boolean;
onHideOnMobileChange?: (hide: boolean) => void;
/** Entrance animation */
animation?: string;
onAnimationChange?: (anim: string) => void;
animationDelay?: string;
onAnimationDelayChange?: (delay: string) => void;
}
const labelStyle: React.CSSProperties = {
fontSize: 11, fontWeight: 600, color: '#a1a1aa', display: 'block', marginBottom: 6,
textTransform: 'uppercase', letterSpacing: '0.3px',
};
const inputStyle: React.CSSProperties = {
width: '100%', padding: '5px 8px', background: '#27272a', color: '#e4e4e7',
border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11,
};
const selectStyle: React.CSSProperties = {
width: '100%', padding: '5px 8px', background: '#27272a', color: '#e4e4e7',
border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11,
};
const TAG_OPTIONS = ['div', 'section', 'article', 'header', 'footer', 'main', 'aside', 'nav'];
export const AdvancedTab: React.FC<AdvancedTabProps> = ({
style,
onStyleChange,
cssId = '',
onCssIdChange,
cssClass = '',
onCssClassChange,
showTagSelector = false,
tag,
onTagChange,
hideOnDesktop = false,
onHideOnDesktopChange,
hideOnTablet = false,
onHideOnTabletChange,
hideOnMobile = false,
onHideOnMobileChange,
animation = 'none',
onAnimationChange,
animationDelay = '0',
onAnimationDelayChange,
}) => {
// Parse margin and padding from style
const margin: SpacingValue = {
top: (style.marginTop as string) || '0',
right: (style.marginRight as string) || '0',
bottom: (style.marginBottom as string) || '0',
left: (style.marginLeft as string) || '0',
};
// If there's a shorthand margin, parse it
const marginShorthand = style.margin as string | undefined;
const resolvedMargin = marginShorthand ? parseSpacingShorthand(marginShorthand) : margin;
const padding: SpacingValue = {
top: (style.paddingTop as string) || '0',
right: (style.paddingRight as string) || '0',
bottom: (style.paddingBottom as string) || '0',
left: (style.paddingLeft as string) || '0',
};
const paddingShorthand = style.padding as string | undefined;
const resolvedPadding = paddingShorthand ? parseSpacingShorthand(paddingShorthand) : padding;
const handleMarginChange = (val: SpacingValue) => {
onStyleChange({
margin: spacingToShorthand(val),
marginTop: undefined,
marginRight: undefined,
marginBottom: undefined,
marginLeft: undefined,
} as CSSProperties);
};
const handlePaddingChange = (val: SpacingValue) => {
onStyleChange({
padding: spacingToShorthand(val),
paddingTop: undefined,
paddingRight: undefined,
paddingBottom: undefined,
paddingLeft: undefined,
} as CSSProperties);
};
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
{/* Margin */}
<SpacingInput
label="Margin"
value={resolvedMargin}
onChange={handleMarginChange}
/>
{/* Padding */}
<SpacingInput
label="Padding"
value={resolvedPadding}
onChange={handlePaddingChange}
/>
{/* HTML Tag (for containers) */}
{showTagSelector && onTagChange && (
<div>
<label style={labelStyle}>HTML Tag</label>
<select
value={tag || 'div'}
onChange={(e) => onTagChange(e.target.value)}
style={selectStyle}
>
{TAG_OPTIONS.map((t) => (
<option key={t} value={t}>&lt;{t}&gt;</option>
))}
</select>
</div>
)}
{/* CSS ID */}
{onCssIdChange && (
<div>
<label style={labelStyle}>CSS ID</label>
<input
type="text"
value={cssId}
onChange={(e) => onCssIdChange(e.target.value.replace(/\s/g, '-'))}
placeholder="my-element"
style={inputStyle}
/>
</div>
)}
{/* CSS Class */}
{onCssClassChange && (
<div>
<label style={labelStyle}>CSS Class</label>
<input
type="text"
value={cssClass}
onChange={(e) => onCssClassChange(e.target.value)}
placeholder="class-one class-two"
style={inputStyle}
/>
</div>
)}
{/* Responsive Visibility */}
{(onHideOnDesktopChange || onHideOnTabletChange || onHideOnMobileChange) && (
<div>
<label style={labelStyle}>Visibility</label>
<div style={{ display: 'flex', gap: 8 }}>
{([
{ key: 'hideOnDesktop' as const, label: 'Desktop', icon: 'fa-desktop', value: hideOnDesktop, onChange: onHideOnDesktopChange },
{ key: 'hideOnTablet' as const, label: 'Tablet', icon: 'fa-tablet', value: hideOnTablet, onChange: onHideOnTabletChange },
{ key: 'hideOnMobile' as const, label: 'Mobile', icon: 'fa-mobile', value: hideOnMobile, onChange: onHideOnMobileChange },
] as const).map(({ key, label, icon, value, onChange }) => (
<label key={key} style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 11, color: '#a1a1aa', cursor: 'pointer' }}>
<input
type="checkbox"
checked={!value}
onChange={(e) => onChange && onChange(!e.target.checked)}
/>
<i className={`fa ${icon}`} />
<span>{label}</span>
</label>
))}
</div>
</div>
)}
{/* Entrance Animation */}
{onAnimationChange && (
<div>
<label style={labelStyle}>Entrance Animation</label>
<select
value={animation || 'none'}
onChange={(e) => onAnimationChange(e.target.value)}
style={selectStyle}
>
<option value="none">None</option>
<option value="fade-in">Fade In</option>
<option value="slide-up">Slide Up</option>
<option value="slide-left">Slide from Left</option>
<option value="slide-right">Slide from Right</option>
<option value="zoom-in">Zoom In</option>
<option value="bounce">Bounce</option>
</select>
{animation && animation !== 'none' && onAnimationDelayChange && (
<div style={{ marginTop: 6 }}>
<label style={labelStyle}>Delay</label>
<select
value={animationDelay || '0'}
onChange={(e) => onAnimationDelayChange(e.target.value)}
style={selectStyle}
>
<option value="0">None</option>
<option value="0.2s">0.2s</option>
<option value="0.4s">0.4s</option>
<option value="0.6s">0.6s</option>
<option value="0.8s">0.8s</option>
<option value="1s">1s</option>
</select>
</div>
)}
</div>
)}
</div>
);
};