diff --git a/craft/src/ui/AdvancedTab.tsx b/craft/src/ui/AdvancedTab.tsx deleted file mode 100644 index 5c359b8..0000000 --- a/craft/src/ui/AdvancedTab.tsx +++ /dev/null @@ -1,233 +0,0 @@ -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 = ({ - 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 ( -
- {/* Margin */} - - - {/* Padding */} - - - {/* HTML Tag (for containers) */} - {showTagSelector && onTagChange && ( -
- - -
- )} - - {/* CSS ID */} - {onCssIdChange && ( -
- - onCssIdChange(e.target.value.replace(/\s/g, '-'))} - placeholder="my-element" - style={inputStyle} - /> -
- )} - - {/* CSS Class */} - {onCssClassChange && ( -
- - onCssClassChange(e.target.value)} - placeholder="class-one class-two" - style={inputStyle} - /> -
- )} - - {/* Responsive Visibility */} - {(onHideOnDesktopChange || onHideOnTabletChange || onHideOnMobileChange) && ( -
- -
- {([ - { 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 }) => ( - - ))} -
-
- )} - - {/* Entrance Animation */} - {onAnimationChange && ( -
- - - {animation && animation !== 'none' && onAnimationDelayChange && ( -
- - -
- )} -
- )} -
- ); -}; diff --git a/craft/src/ui/AnchorIdField.tsx b/craft/src/ui/AnchorIdField.tsx deleted file mode 100644 index 7aa16b2..0000000 --- a/craft/src/ui/AnchorIdField.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; -import { useNode, useEditor } from '@craftjs/core'; - -/** - * Reusable anchor-id input for any section/layout component. Lets the user - * set a stable URL fragment (e.g. #about) and auto-fills from the first - * heading found inside the node's subtree. - * - * Uses the editor query (more reliable than DOM lookup) to walk the Craft.js - * node tree and find the first Heading component's `text` prop. - */ -export const AnchorIdField: React.FC = () => { - const { id, actions: { setProp }, props, nodeName } = useNode((node) => ({ - props: node.data.props as { anchorId?: string }, - nodeName: node.data.displayName, - })); - const { query } = useEditor(); - const value = (props.anchorId ?? '').toString(); - - const slugify = (s: string) => - s.toLowerCase().trim().replace(/[^a-z0-9\s-]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-').slice(0, 60); - - // Walk the subtree via editor query looking for the first Heading's `text` prop. - const findFirstHeadingText = (): string | null => { - const walk = (nodeId: string): string | null => { - try { - const n = query.node(nodeId).get(); - if (n.data.displayName === 'Heading') { - return ((n.data.props as any).text as string | undefined) ?? null; - } - for (const childId of n.data.nodes ?? []) { - const r = walk(childId); - if (r) return r; - } - for (const childId of Object.values(n.data.linkedNodes ?? {})) { - const r = walk(childId as string); - if (r) return r; - } - } catch { - return null; - } - return null; - }; - try { return walk(id); } catch { return null; } - }; - - const autoFill = () => { - const txt = findFirstHeadingText(); - if (txt) setProp((p: any) => { p.anchorId = slugify(txt); }); - }; - - const labelStyle: React.CSSProperties = { display: 'block', fontSize: 11, color: 'var(--color-text-muted)', marginBottom: 4, fontWeight: 500 }; - - return ( -
- -
- # - setProp((p: any) => { p.anchorId = slugify(e.target.value); })} - placeholder="optional" - className="control-input" - style={{ flex: 1, fontSize: 12, fontFamily: 'monospace', borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }} - /> - -
-
- Link to this {nodeName?.toLowerCase() ?? 'block'} from anywhere with #{value || 'your-anchor'} -
-
- ); -}; diff --git a/craft/src/ui/BorderControl.tsx b/craft/src/ui/BorderControl.tsx deleted file mode 100644 index 942eb42..0000000 --- a/craft/src/ui/BorderControl.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import React, { useState } from 'react'; -import { CSSProperties } from 'react'; - -interface BorderControlProps { - style: CSSProperties; - onChange: (updates: CSSProperties) => void; -} - -const BORDER_STYLES = ['none', 'solid', 'dashed', 'dotted'] as const; - -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: '4px 6px', background: '#27272a', color: '#e4e4e7', - border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11, textAlign: 'center', -}; - -const btnStyle = (active: boolean): React.CSSProperties => ({ - padding: '4px 8px', fontSize: 11, borderRadius: 4, cursor: 'pointer', - border: '1px solid #3f3f46', - background: active ? '#3b82f6' : '#27272a', - color: active ? '#fff' : '#a1a1aa', - flex: 1, textTransform: 'capitalize', -}); - -const sideLabel: React.CSSProperties = { - fontSize: 9, color: '#71717a', textAlign: 'center', marginTop: 2, textTransform: 'uppercase', - letterSpacing: '0.5px', -}; - -interface FourSidedValue { - top: string; - right: string; - bottom: string; - left: string; -} - -function parseFourSided(val: string | undefined): FourSidedValue { - if (!val) return { top: '0', right: '0', bottom: '0', left: '0' }; - const parts = val.trim().split(/\s+/); - if (parts.length === 1) return { top: parts[0], right: parts[0], bottom: parts[0], left: parts[0] }; - if (parts.length === 2) return { top: parts[0], right: parts[1], bottom: parts[0], left: parts[1] }; - if (parts.length === 3) return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[1] }; - return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[3] }; -} - -function fourSidedToString(val: FourSidedValue): string { - const { top, right, bottom, left } = val; - if (top === right && right === bottom && bottom === left) return top || '0'; - if (top === bottom && right === left) return `${top} ${right}`; - if (right === left) return `${top} ${right} ${bottom}`; - return `${top} ${right} ${bottom} ${left}`; -} - -function getNumeric(val: string): string { - return val.replace(/[^0-9.]/g, '') || '0'; -} - -export const BorderControl: React.FC = ({ style, onChange }) => { - const [widthLinked, setWidthLinked] = useState(true); - const [radiusLinked, setRadiusLinked] = useState(true); - - const currentBorderStyle = (style.borderStyle as string) || 'none'; - const currentBorderColor = (style.borderColor as string) || '#3f3f46'; - - // Border width as 4-sided - const borderWidth = parseFourSided(style.borderWidth as string); - - // Border radius as 4 corners (TL, TR, BR, BL) - const borderRadius = parseFourSided(style.borderRadius as string); - - const handleWidthChange = (side: keyof FourSidedValue, raw: string) => { - const num = raw.replace(/[^0-9.]/g, ''); - const newVal = num ? `${num}px` : '0'; - let updated: FourSidedValue; - if (widthLinked) { - updated = { top: newVal, right: newVal, bottom: newVal, left: newVal }; - } else { - updated = { ...borderWidth, [side]: newVal }; - } - onChange({ borderWidth: fourSidedToString(updated) }); - }; - - const handleRadiusChange = (corner: keyof FourSidedValue, raw: string) => { - const num = raw.replace(/[^0-9.]/g, ''); - const newVal = num ? `${num}px` : '0'; - let updated: FourSidedValue; - if (radiusLinked) { - updated = { top: newVal, right: newVal, bottom: newVal, left: newVal }; - } else { - updated = { ...borderRadius, [corner]: newVal }; - } - onChange({ borderRadius: fourSidedToString(updated) }); - }; - - const widthSides: { key: keyof FourSidedValue; label: string }[] = [ - { key: 'top', label: 'T' }, - { key: 'right', label: 'R' }, - { key: 'bottom', label: 'B' }, - { key: 'left', label: 'L' }, - ]; - - const radiusCorners: { key: keyof FourSidedValue; label: string }[] = [ - { key: 'top', label: 'TL' }, - { key: 'right', label: 'TR' }, - { key: 'bottom', label: 'BR' }, - { key: 'left', label: 'BL' }, - ]; - - return ( -
- {/* Border Style */} -
- -
- {BORDER_STYLES.map((bs) => ( - - ))} -
-
- - {/* Border Width (4-sided) */} - {currentBorderStyle !== 'none' && ( -
-
- - -
-
- {widthSides.map((s) => ( -
- handleWidthChange(s.key, e.target.value)} - style={inputStyle} - /> -
{s.label}
-
- ))} -
-
- )} - - {/* Border Color */} - {currentBorderStyle !== 'none' && ( -
- -
- onChange({ borderColor: e.target.value })} - style={{ width: 32, height: 28, border: '1px solid #3f3f46', borderRadius: 4, background: 'none', cursor: 'pointer', padding: 0 }} - /> - onChange({ borderColor: e.target.value })} - style={{ ...inputStyle, flex: 1, textAlign: 'left' }} - placeholder="#000000" - /> -
-
- )} - - {/* Border Radius (4 corners) */} -
-
- - -
-
- {radiusCorners.map((c) => ( -
- handleRadiusChange(c.key, e.target.value)} - style={inputStyle} - /> -
{c.label}
-
- ))} -
-
-
- ); -}; diff --git a/craft/src/ui/SettingsTabs.tsx b/craft/src/ui/SettingsTabs.tsx deleted file mode 100644 index a964753..0000000 --- a/craft/src/ui/SettingsTabs.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useState } from 'react'; - -interface SettingsTabsProps { - general: React.ReactNode; - style: React.ReactNode; - advanced: React.ReactNode; -} - -export const SettingsTabs: React.FC = ({ general, style, advanced }) => { - const [tab, setTab] = useState<'general' | 'style' | 'advanced'>('general'); - - return ( -
-
- - - -
-
- {tab === 'general' && general} - {tab === 'style' && style} - {tab === 'advanced' && advanced} -
-
- ); -}; diff --git a/craft/src/ui/SpacingInput.tsx b/craft/src/ui/SpacingInput.tsx deleted file mode 100644 index 5eabdf3..0000000 --- a/craft/src/ui/SpacingInput.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import React, { useState } from 'react'; - -export interface SpacingValue { - top: string; - right: string; - bottom: string; - left: string; -} - -interface SpacingInputProps { - label: string; - value: SpacingValue; - onChange: (value: SpacingValue) => void; -} - -const UNITS = ['px', 'em', '%'] as const; - -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: '4px 6px', background: '#27272a', color: '#e4e4e7', - border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11, textAlign: 'center', -}; - -const sideLabel: React.CSSProperties = { - fontSize: 9, color: '#71717a', textAlign: 'center', marginTop: 2, textTransform: 'uppercase', - letterSpacing: '0.5px', -}; - -function parseValue(val: string): { num: string; unit: string } { - const match = val.match(/^(-?\d*\.?\d+)\s*(px|em|%|rem)?$/); - if (match) return { num: match[1], unit: match[2] || 'px' }; - return { num: val.replace(/[^0-9.-]/g, '') || '0', unit: 'px' }; -} - -export const SpacingInput: React.FC = ({ label, value, onChange }) => { - const [linked, setLinked] = useState(false); - const [unit, setUnit] = useState(() => parseValue(value.top).unit || 'px'); - - const handleSideChange = (side: keyof SpacingValue, raw: string) => { - const numericPart = raw.replace(/[^0-9.-]/g, ''); - const newVal = numericPart ? `${numericPart}${unit}` : '0'; - - if (linked) { - onChange({ top: newVal, right: newVal, bottom: newVal, left: newVal }); - } else { - onChange({ ...value, [side]: newVal }); - } - }; - - const handleUnitChange = (newUnit: string) => { - setUnit(newUnit); - // Re-apply current numeric values with new unit - const updated: SpacingValue = { top: '', right: '', bottom: '', left: '' }; - for (const side of ['top', 'right', 'bottom', 'left'] as const) { - const { num } = parseValue(value[side]); - updated[side] = num && num !== '0' ? `${num}${newUnit}` : '0'; - } - onChange(updated); - }; - - const sides: { key: keyof SpacingValue; label: string }[] = [ - { key: 'top', label: 'T' }, - { key: 'right', label: 'R' }, - { key: 'bottom', label: 'B' }, - { key: 'left', label: 'L' }, - ]; - - return ( -
-
- -
- {/* Unit selector */} -
- {UNITS.map((u) => ( - - ))} -
- {/* Link toggle */} - -
-
-
- {sides.map((s) => ( -
- handleSideChange(s.key, e.target.value)} - style={inputStyle} - /> -
{s.label}
-
- ))} -
-
- ); -}; - -/** Parse a CSS shorthand like "10px 20px 10px 20px" or "10px" into SpacingValue */ -export function parseSpacingShorthand(val: string | undefined): SpacingValue { - if (!val) return { top: '0', right: '0', bottom: '0', left: '0' }; - const parts = val.trim().split(/\s+/); - if (parts.length === 1) return { top: parts[0], right: parts[0], bottom: parts[0], left: parts[0] }; - if (parts.length === 2) return { top: parts[0], right: parts[1], bottom: parts[0], left: parts[1] }; - if (parts.length === 3) return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[1] }; - return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[3] }; -} - -/** Convert SpacingValue back to CSS shorthand */ -export function spacingToShorthand(val: SpacingValue): string { - const { top, right, bottom, left } = val; - if (top === right && right === bottom && bottom === left) return top || '0'; - if (top === bottom && right === left) return `${top} ${right}`; - if (right === left) return `${top} ${right} ${bottom}`; - return `${top} ${right} ${bottom} ${left}`; -} diff --git a/craft/src/ui/TypographyControl.tsx b/craft/src/ui/TypographyControl.tsx deleted file mode 100644 index f11c118..0000000 --- a/craft/src/ui/TypographyControl.tsx +++ /dev/null @@ -1,221 +0,0 @@ -import React from 'react'; -import { CSSProperties } from 'react'; - -interface TypographyControlProps { - style: CSSProperties; - onChange: (updates: CSSProperties) => void; -} - -const FONT_FAMILIES = [ - { label: 'Inter', value: 'Inter, sans-serif' }, - { label: 'Roboto', value: 'Roboto, sans-serif' }, - { label: 'Open Sans', value: 'Open Sans, sans-serif' }, - { label: 'Poppins', value: 'Poppins, sans-serif' }, - { label: 'Montserrat', value: 'Montserrat, sans-serif' }, - { label: 'Playfair', value: 'Playfair Display, serif' }, - { label: 'Merriweather', value: 'Merriweather, serif' }, - { label: 'Source Code', value: 'Source Code Pro, monospace' }, -]; - -const FONT_WEIGHTS = [ - { label: 'Light', value: '300' }, - { label: 'Normal', value: '400' }, - { label: 'Medium', value: '500' }, - { label: 'Semi', value: '600' }, - { label: 'Bold', value: '700' }, -]; - -const SIZE_UNITS = ['px', 'em', 'rem'] as const; - -const TEXT_TRANSFORMS: { label: string; value: string }[] = [ - { label: 'Aa', value: 'none' }, - { label: 'AA', value: 'uppercase' }, - { label: 'aa', value: 'lowercase' }, - { label: 'Aa', value: 'capitalize' }, -]; - -const TEXT_ALIGNS = ['left', 'center', 'right', 'justify'] as const; -const ALIGN_ICONS: Record = { - left: 'fa-align-left', - center: 'fa-align-center', - right: 'fa-align-right', - justify: 'fa-align-justify', -}; - -const labelStyle: React.CSSProperties = { - fontSize: 11, fontWeight: 600, color: '#a1a1aa', display: 'block', marginBottom: 6, - textTransform: 'uppercase', letterSpacing: '0.3px', -}; - -const selectStyle: React.CSSProperties = { - width: '100%', padding: '5px 8px', background: '#27272a', color: '#e4e4e7', - border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11, -}; - -const inputStyle: React.CSSProperties = { - width: '100%', padding: '5px 8px', background: '#27272a', color: '#e4e4e7', - border: '1px solid #3f3f46', borderRadius: 4, fontSize: 11, -}; - -const btnStyle = (active: boolean): React.CSSProperties => ({ - padding: '4px 8px', fontSize: 11, borderRadius: 4, cursor: 'pointer', - border: '1px solid #3f3f46', - background: active ? '#3b82f6' : '#27272a', - color: active ? '#fff' : '#a1a1aa', -}); - -function parseSizeValue(val: string | number | undefined): { num: string; unit: string } { - if (val === undefined || val === '') return { num: '', unit: 'px' }; - const s = String(val); - const match = s.match(/^(-?\d*\.?\d+)\s*(px|em|rem|%)?$/); - if (match) return { num: match[1], unit: match[2] || 'px' }; - return { num: s.replace(/[^0-9.-]/g, ''), unit: 'px' }; -} - -export const TypographyControl: React.FC = ({ style, onChange }) => { - const currentFamily = (style.fontFamily as string) || ''; - const currentWeight = String(style.fontWeight || ''); - const currentAlign = (style.textAlign as string) || ''; - const currentTransform = (style.textTransform as string) || 'none'; - const currentColor = (style.color as string) || '#1f2937'; - - const fontSize = parseSizeValue(style.fontSize); - const lineHeight = String(style.lineHeight || ''); - const letterSpacing = String(style.letterSpacing || ''); - - return ( -
- {/* Font Family */} -
- - -
- - {/* Font Weight */} -
- -
- {FONT_WEIGHTS.map((w) => ( - - ))} -
-
- - {/* Font Size + Line Height row */} -
-
- -
- { - const num = e.target.value.replace(/[^0-9.]/g, ''); - onChange({ fontSize: num ? `${num}${fontSize.unit}` : '' }); - }} - placeholder="16" - style={{ ...inputStyle, flex: 1 }} - /> - -
-
-
- - onChange({ lineHeight: e.target.value })} - placeholder="1.6" - style={inputStyle} - /> -
-
- - {/* Letter Spacing */} -
- - onChange({ letterSpacing: e.target.value })} - placeholder="0px" - style={inputStyle} - /> -
- - {/* Text Transform */} -
- -
- {TEXT_TRANSFORMS.map((t, i) => ( - - ))} -
-
- - {/* Text Align */} -
- -
- {TEXT_ALIGNS.map((a) => ( - - ))} -
-
- - {/* Color */} -
- -
- onChange({ color: e.target.value })} - style={{ width: 32, height: 28, border: '1px solid #3f3f46', borderRadius: 4, background: 'none', cursor: 'pointer', padding: 0 }} - /> - onChange({ color: e.target.value })} - style={{ ...inputStyle, flex: 1 }} - placeholder="#000000" - /> -
-
-
- ); -};