Files
wp-react-components/SpacingControl.js
Jeremy Rangel f1e7005b26 Initial
2025-11-30 00:50:56 -08:00

157 lines
4.5 KiB
JavaScript

import { useState } from '@wordpress/element';
import {
BaseControl,
Button,
RangeControl,
__experimentalHStack as HStack,
__experimentalVStack as VStack,
__experimentalUnitControl as UnitControl,
Icon
} from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { sidesHorizontal, sidesVertical, sidesTop, sidesBottom, sidesLeft, sidesRight, settings } from '@wordpress/icons';
export default function SpacingControl({ label, value = {}, onChange, themeSpacing = {} }) {
const [verticalAdvanced, setVerticalAdvanced] = useState(false);
const [horizontalAdvanced, setHorizontalAdvanced] = useState(false);
const [advancedMode, setAdvancedMode] = useState(false); // full advanced toggle
const parseValue = (val) => (val ? parseInt(val) : 0);
const handleChange = (sides, val) => {
const unit = val.toString().replace(/[0-9]/g, '') || 'px';
const newValue = { ...value };
sides.forEach((side) => {
newValue[side] = `${parseInt(val)}${unit}`;
});
onChange(newValue);
};
// --- Generate marks for RangeControl from theme.json spacing ---
const spacingMarks = themeSpacing?.spacingSizes?.map((s) => ({
value: parseInt(s.size), // you may need to strip "px" or clamp()
label: s.name,
})) || [];
// --- Advanced 4-row mode ---
if (advancedMode) {
const sides = [
{ key: 'top', sideIcon: sidesTop },
{ key: 'bottom', sideIcon: sidesBottom },
{ key: 'left', sideIcon: sidesLeft },
{ key: 'right', sideIcon: sidesRight },
];
return (
<BaseControl label={label}>
<VStack gap={16}>
<Button variant="secondary" onClick={() => setAdvancedMode(false)}>
{__('Back to Simple Mode', 'directory-listings')}
</Button>
{sides.map(({ key, sideIcon }) => (
<VStack key={key} gap={4}>
<HStack align="center" gap={8}>
<Icon icon={sideIcon} />
<div style={{ flex: 1 }}>
<RangeControl
value={parseValue(value[key] || 0)}
onChange={(val) => handleChange([key], val)}
min={0}
max={Math.max(...spacingMarks.map((m) => m.value))}
step={1}
marks={spacingMarks}
withInputField={false}
/>
</div>
<UnitControl
value={value[key] || '0px'}
onChange={(val) => handleChange([key], val)}
min={0}
max={Math.max(...spacingMarks.map((m) => m.value))}
/>
</HStack>
</VStack>
))}
</VStack>
</BaseControl>
);
}
// --- Simple vertical + horizontal mode ---
return (
<BaseControl label={label}>
<VStack gap={16}>
<Button variant="secondary" onClick={() => setAdvancedMode(true)}>
{__('Advanced Mode', 'directory-listings')}
</Button>
{/* Vertical */}
<VStack gap={4}>
<HStack align="center" gap={8}>
<Icon icon={sidesVertical} />
{verticalAdvanced && (
<UnitControl
value={value.top || '0px'}
onChange={(val) => handleChange(['top', 'bottom'], val)}
min={0}
max={Math.max(...spacingMarks.map((m) => m.value))}
/>
)}
<div style={{ flex: 1 }}>
<RangeControl
value={parseValue(value.top || 0)}
onChange={(val) => handleChange(['top', 'bottom'], val)}
min={0}
max={Math.max(...spacingMarks.map((m) => m.value))}
step={1}
marks={spacingMarks}
withInputField={false}
/>
</div>
<Button
icon={settings}
isSecondary
onClick={() => setVerticalAdvanced(!verticalAdvanced)}
aria-label={__('Toggle vertical spacing input', 'directory-listings')}
/>
</HStack>
</VStack>
{/* Horizontal */}
<VStack gap={4}>
<HStack align="center" gap={8}>
<Icon icon={sidesHorizontal} />
{horizontalAdvanced && (
<UnitControl
value={value.left || '0px'}
onChange={(val) => handleChange(['left', 'right'], val)}
min={0}
max={Math.max(...spacingMarks.map((m) => m.value))}
/>
)}
<div style={{ flex: 1 }}>
<RangeControl
value={parseValue(value.left || 0)}
onChange={(val) => handleChange(['left', 'right'], val)}
min={0}
max={Math.max(...spacingMarks.map((m) => m.value))}
step={1}
marks={spacingMarks}
withInputField={false}
/>
</div>
<Button
icon={settings}
isSecondary
onClick={() => setHorizontalAdvanced(!horizontalAdvanced)}
aria-label={__('Toggle horizontal spacing input', 'directory-listings')}
/>
</HStack>
</VStack>
</VStack>
</BaseControl>
);
}