Autosave: 20260319-081925
This commit is contained in:
parent
afabb0cce1
commit
ce847e87d6
@ -84,6 +84,28 @@ type CanvasElement = {
|
||||
label: string;
|
||||
xPercent: number;
|
||||
yPercent: number;
|
||||
width?: string;
|
||||
height?: string;
|
||||
minWidth?: string;
|
||||
maxWidth?: string;
|
||||
minHeight?: string;
|
||||
maxHeight?: string;
|
||||
margin?: string;
|
||||
padding?: string;
|
||||
gap?: string;
|
||||
fontSize?: string;
|
||||
lineHeight?: string;
|
||||
fontWeight?: string;
|
||||
border?: string;
|
||||
borderRadius?: string;
|
||||
opacity?: string;
|
||||
boxShadow?: string;
|
||||
display?: string;
|
||||
position?: string;
|
||||
justifyContent?: string;
|
||||
alignItems?: string;
|
||||
textAlign?: string;
|
||||
zIndex?: string;
|
||||
appearDelaySec?: number;
|
||||
appearDurationSec?: number | null;
|
||||
iconUrl?: string;
|
||||
@ -193,6 +215,11 @@ const normalizeAppearDurationSec = (value: unknown) => {
|
||||
return Number(parsed);
|
||||
};
|
||||
|
||||
const getTrimmedCssValue = (value: unknown) => {
|
||||
if (value === null || value === undefined) return '';
|
||||
return String(value).trim();
|
||||
};
|
||||
|
||||
const createLocalId = () => {
|
||||
if (typeof window !== 'undefined' && window.crypto?.randomUUID) {
|
||||
return window.crypto.randomUUID();
|
||||
@ -529,12 +556,14 @@ const createDefaultElement = (
|
||||
const mergeElementWithDefaults = (
|
||||
element: CanvasElement,
|
||||
defaults?: Partial<CanvasElement>,
|
||||
options?: { preferElementValues?: boolean },
|
||||
): CanvasElement => {
|
||||
if (!defaults) return element;
|
||||
|
||||
const preferElementValues = Boolean(options?.preferElementValues);
|
||||
const merged: CanvasElement = {
|
||||
...element,
|
||||
...defaults,
|
||||
...(preferElementValues ? defaults : element),
|
||||
...(preferElementValues ? element : defaults),
|
||||
id: element.id,
|
||||
type: element.type,
|
||||
};
|
||||
@ -545,9 +574,13 @@ const mergeElementWithDefaults = (
|
||||
merged.appearDurationSec = normalizeAppearDurationSec(merged.appearDurationSec);
|
||||
|
||||
if (merged.type === 'gallery') {
|
||||
const cards = Array.isArray(defaults.galleryCards)
|
||||
? defaults.galleryCards
|
||||
: element.galleryCards || [];
|
||||
const cards = preferElementValues
|
||||
? Array.isArray(element.galleryCards)
|
||||
? element.galleryCards
|
||||
: defaults.galleryCards || []
|
||||
: Array.isArray(defaults.galleryCards)
|
||||
? defaults.galleryCards
|
||||
: element.galleryCards || [];
|
||||
merged.galleryCards = cards.map((card, cardIndex) => ({
|
||||
id: String(card?.id || createLocalId()),
|
||||
imageUrl: String(card?.imageUrl || ''),
|
||||
@ -557,9 +590,13 @@ const mergeElementWithDefaults = (
|
||||
}
|
||||
|
||||
if (merged.type === 'carousel') {
|
||||
const slides = Array.isArray(defaults.carouselSlides)
|
||||
? defaults.carouselSlides
|
||||
: element.carouselSlides || [];
|
||||
const slides = preferElementValues
|
||||
? Array.isArray(element.carouselSlides)
|
||||
? element.carouselSlides
|
||||
: defaults.carouselSlides || []
|
||||
: Array.isArray(defaults.carouselSlides)
|
||||
? defaults.carouselSlides
|
||||
: element.carouselSlides || [];
|
||||
merged.carouselSlides = slides.map((slide, slideIndex) => ({
|
||||
id: String(slide?.id || createLocalId()),
|
||||
imageUrl: String(slide?.imageUrl || ''),
|
||||
@ -582,7 +619,10 @@ const getElementButtonTitle = (element: CanvasElement) => {
|
||||
if (element.type === 'tooltip') return element.tooltipTitle ?? '';
|
||||
if (element.type === 'description') return element.descriptionTitle ?? '';
|
||||
if (element.type === 'navigation_next' || element.type === 'navigation_prev')
|
||||
return element.navLabel ?? '';
|
||||
return (
|
||||
element.navLabel?.trim() ||
|
||||
getNavigationButtonLabel(element.type as NavigationElementType)
|
||||
);
|
||||
if (
|
||||
(element.type === 'video_player' || element.type === 'audio_player') &&
|
||||
element.mediaUrl
|
||||
@ -593,6 +633,65 @@ const getElementButtonTitle = (element: CanvasElement) => {
|
||||
return element.label;
|
||||
};
|
||||
|
||||
const buildCanvasElementStyle = (
|
||||
element: CanvasElement,
|
||||
): React.CSSProperties => {
|
||||
const style: React.CSSProperties = {};
|
||||
const width = getTrimmedCssValue(element.width);
|
||||
if (width) style.width = width;
|
||||
const height = getTrimmedCssValue(element.height);
|
||||
if (height) style.height = height;
|
||||
const minWidth = getTrimmedCssValue(element.minWidth);
|
||||
if (minWidth) style.minWidth = minWidth;
|
||||
const maxWidth = getTrimmedCssValue(element.maxWidth);
|
||||
if (maxWidth) style.maxWidth = maxWidth;
|
||||
const minHeight = getTrimmedCssValue(element.minHeight);
|
||||
if (minHeight) style.minHeight = minHeight;
|
||||
const maxHeight = getTrimmedCssValue(element.maxHeight);
|
||||
if (maxHeight) style.maxHeight = maxHeight;
|
||||
const margin = getTrimmedCssValue(element.margin);
|
||||
if (margin) style.margin = margin;
|
||||
const padding = getTrimmedCssValue(element.padding);
|
||||
if (padding) style.padding = padding;
|
||||
const gap = getTrimmedCssValue(element.gap);
|
||||
if (gap) style.gap = gap;
|
||||
const fontSize = getTrimmedCssValue(element.fontSize);
|
||||
if (fontSize) style.fontSize = fontSize;
|
||||
const lineHeight = getTrimmedCssValue(element.lineHeight);
|
||||
if (lineHeight) style.lineHeight = lineHeight;
|
||||
const fontWeight = getTrimmedCssValue(element.fontWeight);
|
||||
if (fontWeight) style.fontWeight = fontWeight as React.CSSProperties['fontWeight'];
|
||||
const border = getTrimmedCssValue(element.border);
|
||||
if (border) style.border = border;
|
||||
const borderRadius = getTrimmedCssValue(element.borderRadius);
|
||||
if (borderRadius) style.borderRadius = borderRadius;
|
||||
const opacity = getTrimmedCssValue(element.opacity);
|
||||
if (opacity) {
|
||||
const parsed = Number(opacity);
|
||||
if (Number.isFinite(parsed)) style.opacity = parsed;
|
||||
}
|
||||
const boxShadow = getTrimmedCssValue(element.boxShadow);
|
||||
if (boxShadow) style.boxShadow = boxShadow;
|
||||
const display = getTrimmedCssValue(element.display);
|
||||
if (display) style.display = display;
|
||||
const position = getTrimmedCssValue(element.position);
|
||||
if (position) style.position = position as React.CSSProperties['position'];
|
||||
const justifyContent = getTrimmedCssValue(element.justifyContent);
|
||||
if (justifyContent)
|
||||
style.justifyContent = justifyContent as React.CSSProperties['justifyContent'];
|
||||
const alignItems = getTrimmedCssValue(element.alignItems);
|
||||
if (alignItems)
|
||||
style.alignItems = alignItems as React.CSSProperties['alignItems'];
|
||||
const textAlign = getTrimmedCssValue(element.textAlign);
|
||||
if (textAlign) style.textAlign = textAlign as React.CSSProperties['textAlign'];
|
||||
const zIndex = getTrimmedCssValue(element.zIndex);
|
||||
if (zIndex) {
|
||||
const parsed = Number(zIndex);
|
||||
if (Number.isFinite(parsed)) style.zIndex = parsed;
|
||||
}
|
||||
return style;
|
||||
};
|
||||
|
||||
const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
const router = useRouter();
|
||||
const canvasRef = useRef<HTMLDivElement>(null);
|
||||
@ -1104,86 +1203,98 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
(item) =>
|
||||
item && item.type && labelByType[item.type as CanvasElementType],
|
||||
)
|
||||
.map((item) => ({
|
||||
...item,
|
||||
id: String(item.id || createLocalId()),
|
||||
label: labelByType[item.type as CanvasElementType],
|
||||
xPercent: clamp(Number(item.xPercent || 0), 0, 100),
|
||||
yPercent: clamp(Number(item.yPercent || 0), 0, 100),
|
||||
appearDelaySec: normalizeAppearDelaySec(item.appearDelaySec),
|
||||
appearDurationSec: normalizeAppearDurationSec(item.appearDurationSec),
|
||||
galleryCards: Array.isArray(item.galleryCards)
|
||||
? item.galleryCards.map((card: any, index: number) => ({
|
||||
id: String(card?.id || createLocalId()),
|
||||
imageUrl: String(card?.imageUrl || ''),
|
||||
title: String(card?.title || `Card ${index + 1}`),
|
||||
description: String(card?.description || ''),
|
||||
}))
|
||||
: undefined,
|
||||
carouselSlides: Array.isArray(item.carouselSlides)
|
||||
? item.carouselSlides.map((slide: any, index: number) => ({
|
||||
id: String(slide?.id || createLocalId()),
|
||||
imageUrl: String(slide?.imageUrl || ''),
|
||||
caption: String(slide?.caption || `Slide ${index + 1}`),
|
||||
}))
|
||||
: undefined,
|
||||
iconUrl: typeof item.iconUrl === 'string' ? item.iconUrl : '',
|
||||
carouselPrevIconUrl:
|
||||
typeof item.carouselPrevIconUrl === 'string'
|
||||
? item.carouselPrevIconUrl
|
||||
: '',
|
||||
carouselNextIconUrl:
|
||||
typeof item.carouselNextIconUrl === 'string'
|
||||
? item.carouselNextIconUrl
|
||||
: '',
|
||||
tooltipTitle:
|
||||
typeof item.tooltipTitle === 'string' ? item.tooltipTitle : '',
|
||||
tooltipText:
|
||||
typeof item.tooltipText === 'string' ? item.tooltipText : '',
|
||||
descriptionTitle:
|
||||
typeof item.descriptionTitle === 'string'
|
||||
? item.descriptionTitle
|
||||
: '',
|
||||
descriptionText:
|
||||
typeof item.descriptionText === 'string'
|
||||
? item.descriptionText
|
||||
: '',
|
||||
navLabel: typeof item.navLabel === 'string' ? item.navLabel : '',
|
||||
navType:
|
||||
item.navType === 'back' || item.navType === 'forward'
|
||||
? item.navType
|
||||
: isNavigationElementType(item.type as CanvasElementType)
|
||||
? getNavigationButtonKind(item.type as NavigationElementType)
|
||||
: undefined,
|
||||
targetPageId:
|
||||
typeof item.targetPageId === 'string' ? item.targetPageId : '',
|
||||
transitionVideoUrl:
|
||||
typeof item.transitionVideoUrl === 'string'
|
||||
? item.transitionVideoUrl
|
||||
: '',
|
||||
transitionReverseMode:
|
||||
item.transitionReverseMode === 'separate_video'
|
||||
? 'separate_video'
|
||||
: ('auto_reverse' as const),
|
||||
reverseVideoUrl:
|
||||
typeof item.reverseVideoUrl === 'string'
|
||||
? item.reverseVideoUrl
|
||||
: '',
|
||||
transitionDurationSec: item.transitionDurationSec
|
||||
? Number(item.transitionDurationSec)
|
||||
: undefined,
|
||||
mediaUrl: typeof item.mediaUrl === 'string' ? item.mediaUrl : '',
|
||||
mediaAutoplay:
|
||||
typeof item.mediaAutoplay === 'boolean'
|
||||
? item.mediaAutoplay
|
||||
: true,
|
||||
mediaLoop:
|
||||
typeof item.mediaLoop === 'boolean' ? item.mediaLoop : true,
|
||||
mediaMuted:
|
||||
typeof item.mediaMuted === 'boolean'
|
||||
? item.mediaMuted
|
||||
: item.type === 'video_player',
|
||||
}))
|
||||
.map((item) => {
|
||||
const elementType = item.type as CanvasElementType;
|
||||
const normalizedElement: CanvasElement = {
|
||||
...item,
|
||||
id: String(item.id || createLocalId()),
|
||||
label:
|
||||
typeof item.label === 'string' && item.label.trim()
|
||||
? item.label
|
||||
: labelByType[elementType],
|
||||
xPercent: clamp(Number(item.xPercent || 0), 0, 100),
|
||||
yPercent: clamp(Number(item.yPercent || 0), 0, 100),
|
||||
appearDelaySec: normalizeAppearDelaySec(item.appearDelaySec),
|
||||
appearDurationSec: normalizeAppearDurationSec(item.appearDurationSec),
|
||||
galleryCards: Array.isArray(item.galleryCards)
|
||||
? item.galleryCards.map((card: any, index: number) => ({
|
||||
id: String(card?.id || createLocalId()),
|
||||
imageUrl: String(card?.imageUrl || ''),
|
||||
title: String(card?.title || `Card ${index + 1}`),
|
||||
description: String(card?.description || ''),
|
||||
}))
|
||||
: undefined,
|
||||
carouselSlides: Array.isArray(item.carouselSlides)
|
||||
? item.carouselSlides.map((slide: any, index: number) => ({
|
||||
id: String(slide?.id || createLocalId()),
|
||||
imageUrl: String(slide?.imageUrl || ''),
|
||||
caption: String(slide?.caption || `Slide ${index + 1}`),
|
||||
}))
|
||||
: undefined,
|
||||
iconUrl: typeof item.iconUrl === 'string' ? item.iconUrl : '',
|
||||
carouselPrevIconUrl:
|
||||
typeof item.carouselPrevIconUrl === 'string'
|
||||
? item.carouselPrevIconUrl
|
||||
: '',
|
||||
carouselNextIconUrl:
|
||||
typeof item.carouselNextIconUrl === 'string'
|
||||
? item.carouselNextIconUrl
|
||||
: '',
|
||||
tooltipTitle:
|
||||
typeof item.tooltipTitle === 'string' ? item.tooltipTitle : '',
|
||||
tooltipText:
|
||||
typeof item.tooltipText === 'string' ? item.tooltipText : '',
|
||||
descriptionTitle:
|
||||
typeof item.descriptionTitle === 'string'
|
||||
? item.descriptionTitle
|
||||
: '',
|
||||
descriptionText:
|
||||
typeof item.descriptionText === 'string'
|
||||
? item.descriptionText
|
||||
: '',
|
||||
navLabel: typeof item.navLabel === 'string' ? item.navLabel : '',
|
||||
navType:
|
||||
item.navType === 'back' || item.navType === 'forward'
|
||||
? item.navType
|
||||
: isNavigationElementType(elementType)
|
||||
? getNavigationButtonKind(elementType as NavigationElementType)
|
||||
: undefined,
|
||||
targetPageId:
|
||||
typeof item.targetPageId === 'string' ? item.targetPageId : '',
|
||||
transitionVideoUrl:
|
||||
typeof item.transitionVideoUrl === 'string'
|
||||
? item.transitionVideoUrl
|
||||
: '',
|
||||
transitionReverseMode:
|
||||
item.transitionReverseMode === 'separate_video'
|
||||
? 'separate_video'
|
||||
: ('auto_reverse' as const),
|
||||
reverseVideoUrl:
|
||||
typeof item.reverseVideoUrl === 'string'
|
||||
? item.reverseVideoUrl
|
||||
: '',
|
||||
transitionDurationSec: item.transitionDurationSec
|
||||
? Number(item.transitionDurationSec)
|
||||
: undefined,
|
||||
mediaUrl: typeof item.mediaUrl === 'string' ? item.mediaUrl : '',
|
||||
mediaAutoplay:
|
||||
typeof item.mediaAutoplay === 'boolean'
|
||||
? item.mediaAutoplay
|
||||
: true,
|
||||
mediaLoop:
|
||||
typeof item.mediaLoop === 'boolean' ? item.mediaLoop : true,
|
||||
mediaMuted:
|
||||
typeof item.mediaMuted === 'boolean'
|
||||
? item.mediaMuted
|
||||
: item.type === 'video_player',
|
||||
};
|
||||
|
||||
return mergeElementWithDefaults(
|
||||
normalizedElement,
|
||||
uiElementDefaultsByType[elementType],
|
||||
{ preferElementValues: true },
|
||||
);
|
||||
})
|
||||
: [];
|
||||
|
||||
setElements(normalizedElements);
|
||||
@ -1203,7 +1314,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
setBackgroundImageUrl(activePage.background_image_url || '');
|
||||
setBackgroundVideoUrl(activePage.background_video_url || '');
|
||||
setBackgroundAudioUrl(activePage.background_audio_url || '');
|
||||
}, [activePage, elementIdFromRoute]);
|
||||
}, [activePage, elementIdFromRoute, uiElementDefaultsByType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (allowedNavigationTypes.length !== 1) return;
|
||||
@ -1697,6 +1808,8 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
element.type === 'navigation_next' ||
|
||||
element.type === 'navigation_prev'
|
||||
) {
|
||||
const fallbackNavLabel = getNavigationButtonLabel(element.type);
|
||||
const navigationLabel = element.navLabel?.trim() || fallbackNavLabel;
|
||||
if (element.iconUrl) {
|
||||
return (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
@ -1714,7 +1827,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
return (
|
||||
<div className='flex flex-col items-start gap-1'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<span>{element.navLabel}</span>
|
||||
<span>{navigationLabel}</span>
|
||||
</div>
|
||||
{targetPageName ? (
|
||||
<span className='text-[10px] text-gray-500'>
|
||||
@ -2189,6 +2302,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
|
||||
: 'border-blue-200 bg-white/95'
|
||||
}`}
|
||||
style={{
|
||||
...buildCanvasElementStyle(element),
|
||||
left: `${element.xPercent}%`,
|
||||
top: `${element.yPercent}%`,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
|
||||
@ -47,6 +47,28 @@ type ConstructorElement = {
|
||||
label?: string;
|
||||
xPercent?: number;
|
||||
yPercent?: number;
|
||||
width?: string;
|
||||
height?: string;
|
||||
minWidth?: string;
|
||||
maxWidth?: string;
|
||||
minHeight?: string;
|
||||
maxHeight?: string;
|
||||
margin?: string;
|
||||
padding?: string;
|
||||
gap?: string;
|
||||
fontSize?: string;
|
||||
lineHeight?: string;
|
||||
fontWeight?: string;
|
||||
border?: string;
|
||||
borderRadius?: string;
|
||||
opacity?: string;
|
||||
boxShadow?: string;
|
||||
display?: string;
|
||||
position?: string;
|
||||
justifyContent?: string;
|
||||
alignItems?: string;
|
||||
textAlign?: string;
|
||||
zIndex?: string;
|
||||
appearDelaySec?: number;
|
||||
appearDurationSec?: number | null;
|
||||
iconUrl?: string;
|
||||
@ -113,6 +135,31 @@ const parseNullableNumber = (value: string) => {
|
||||
return parsed;
|
||||
};
|
||||
|
||||
const extractNumericValue = (value: unknown) => {
|
||||
const normalized = String(value ?? '').trim();
|
||||
if (!normalized) return '';
|
||||
const matched = normalized.match(/-?\d*\.?\d+/);
|
||||
return matched ? matched[0] : '';
|
||||
};
|
||||
|
||||
const normalizeNumberString = (value: string) => {
|
||||
const normalized = String(value || '').trim();
|
||||
if (!normalized) return '';
|
||||
const parsed = Number(normalized);
|
||||
if (!Number.isFinite(parsed)) return '';
|
||||
return String(parsed);
|
||||
};
|
||||
|
||||
const toOptionalTrimmed = (value: string) => {
|
||||
const normalized = String(value || '').trim();
|
||||
return normalized ? normalized : undefined;
|
||||
};
|
||||
|
||||
const toUnitValue = (value: string, unit: 'vw' | 'vh' | 'px') => {
|
||||
const normalized = normalizeNumberString(value);
|
||||
return normalized ? `${normalized}${unit}` : undefined;
|
||||
};
|
||||
|
||||
const createLocalId = () => {
|
||||
if (typeof window !== 'undefined' && window.crypto?.randomUUID) {
|
||||
return window.crypto.randomUUID();
|
||||
@ -141,6 +188,28 @@ const PageElementsProjectEditPage = () => {
|
||||
const [label, setLabel] = useState('');
|
||||
const [xPercent, setXPercent] = useState('0');
|
||||
const [yPercent, setYPercent] = useState('0');
|
||||
const [width, setWidth] = useState('');
|
||||
const [height, setHeight] = useState('');
|
||||
const [minWidth, setMinWidth] = useState('');
|
||||
const [maxWidth, setMaxWidth] = useState('');
|
||||
const [minHeight, setMinHeight] = useState('');
|
||||
const [maxHeight, setMaxHeight] = useState('');
|
||||
const [margin, setMargin] = useState('');
|
||||
const [padding, setPadding] = useState('');
|
||||
const [gap, setGap] = useState('');
|
||||
const [fontSize, setFontSize] = useState('');
|
||||
const [lineHeight, setLineHeight] = useState('');
|
||||
const [fontWeight, setFontWeight] = useState('');
|
||||
const [border, setBorder] = useState('');
|
||||
const [borderRadius, setBorderRadius] = useState('');
|
||||
const [opacity, setOpacity] = useState('');
|
||||
const [boxShadow, setBoxShadow] = useState('');
|
||||
const [display, setDisplay] = useState('');
|
||||
const [position, setPosition] = useState('');
|
||||
const [justifyContent, setJustifyContent] = useState('');
|
||||
const [alignItems, setAlignItems] = useState('');
|
||||
const [textAlign, setTextAlign] = useState('');
|
||||
const [zIndex, setZIndex] = useState('');
|
||||
const [appearDelaySec, setAppearDelaySec] = useState('0');
|
||||
const [appearDurationSec, setAppearDurationSec] = useState('');
|
||||
const [iconUrl, setIconUrl] = useState('');
|
||||
@ -168,6 +237,28 @@ const PageElementsProjectEditPage = () => {
|
||||
setLabel(String(item.label || ''));
|
||||
setXPercent(String(item.xPercent ?? 0));
|
||||
setYPercent(String(item.yPercent ?? 0));
|
||||
setWidth(extractNumericValue(item.width));
|
||||
setHeight(extractNumericValue(item.height));
|
||||
setMinWidth(extractNumericValue(item.minWidth));
|
||||
setMaxWidth(extractNumericValue(item.maxWidth));
|
||||
setMinHeight(extractNumericValue(item.minHeight));
|
||||
setMaxHeight(extractNumericValue(item.maxHeight));
|
||||
setMargin(String(item.margin || ''));
|
||||
setPadding(String(item.padding || ''));
|
||||
setGap(String(item.gap || ''));
|
||||
setFontSize(String(item.fontSize || ''));
|
||||
setLineHeight(String(item.lineHeight || ''));
|
||||
setFontWeight(String(item.fontWeight || ''));
|
||||
setBorder(extractNumericValue(item.border));
|
||||
setBorderRadius(extractNumericValue(item.borderRadius));
|
||||
setOpacity(item.opacity === '0' || item.opacity === 0 ? '0' : String(item.opacity || ''));
|
||||
setBoxShadow(String(item.boxShadow || ''));
|
||||
setDisplay(String(item.display || ''));
|
||||
setPosition(String(item.position || ''));
|
||||
setJustifyContent(String(item.justifyContent || ''));
|
||||
setAlignItems(String(item.alignItems || ''));
|
||||
setTextAlign(String(item.textAlign || ''));
|
||||
setZIndex(String(item.zIndex || ''));
|
||||
setAppearDelaySec(String(item.appearDelaySec ?? 0));
|
||||
setAppearDurationSec(item.appearDurationSec === null || item.appearDurationSec === undefined ? '' : String(item.appearDurationSec));
|
||||
setIconUrl(String(item.iconUrl || ''));
|
||||
@ -316,15 +407,62 @@ const PageElementsProjectEditPage = () => {
|
||||
setSuccessMessage('');
|
||||
|
||||
try {
|
||||
const borderWidthValue = toUnitValue(border, 'px');
|
||||
const nextElement: ConstructorElement = {
|
||||
...element,
|
||||
label: label.trim(),
|
||||
xPercent: clampPercent(xPercent),
|
||||
yPercent: clampPercent(yPercent),
|
||||
border: borderWidthValue ? `${borderWidthValue} solid currentColor` : 'none',
|
||||
borderRadius: toUnitValue(borderRadius, 'px') || '0px',
|
||||
appearDelaySec: Number(appearDelaySec) >= 0 ? Number(appearDelaySec) : 0,
|
||||
appearDurationSec: parseNullableNumber(appearDurationSec),
|
||||
};
|
||||
|
||||
const widthValue = toUnitValue(width, 'vw');
|
||||
const heightValue = toUnitValue(height, 'vh');
|
||||
const minWidthValue = toUnitValue(minWidth, 'vw');
|
||||
const maxWidthValue = toUnitValue(maxWidth, 'vw');
|
||||
const minHeightValue = toUnitValue(minHeight, 'vh');
|
||||
const maxHeightValue = toUnitValue(maxHeight, 'vh');
|
||||
|
||||
if (widthValue) nextElement.width = widthValue; else delete nextElement.width;
|
||||
if (heightValue) nextElement.height = heightValue; else delete nextElement.height;
|
||||
if (minWidthValue) nextElement.minWidth = minWidthValue; else delete nextElement.minWidth;
|
||||
if (maxWidthValue) nextElement.maxWidth = maxWidthValue; else delete nextElement.maxWidth;
|
||||
if (minHeightValue) nextElement.minHeight = minHeightValue; else delete nextElement.minHeight;
|
||||
if (maxHeightValue) nextElement.maxHeight = maxHeightValue; else delete nextElement.maxHeight;
|
||||
|
||||
const marginValue = toOptionalTrimmed(margin);
|
||||
const paddingValue = toOptionalTrimmed(padding);
|
||||
const gapValue = toOptionalTrimmed(gap);
|
||||
const fontSizeValue = toOptionalTrimmed(fontSize);
|
||||
const lineHeightValue = toOptionalTrimmed(lineHeight);
|
||||
const fontWeightValue = toOptionalTrimmed(fontWeight);
|
||||
const opacityValue = toOptionalTrimmed(opacity);
|
||||
const boxShadowValue = toOptionalTrimmed(boxShadow);
|
||||
const displayValue = toOptionalTrimmed(display);
|
||||
const positionValue = toOptionalTrimmed(position);
|
||||
const justifyContentValue = toOptionalTrimmed(justifyContent);
|
||||
const alignItemsValue = toOptionalTrimmed(alignItems);
|
||||
const textAlignValue = toOptionalTrimmed(textAlign);
|
||||
const zIndexValue = toOptionalTrimmed(zIndex);
|
||||
|
||||
if (marginValue) nextElement.margin = marginValue; else delete nextElement.margin;
|
||||
if (paddingValue) nextElement.padding = paddingValue; else delete nextElement.padding;
|
||||
if (gapValue) nextElement.gap = gapValue; else delete nextElement.gap;
|
||||
if (fontSizeValue) nextElement.fontSize = fontSizeValue; else delete nextElement.fontSize;
|
||||
if (lineHeightValue) nextElement.lineHeight = lineHeightValue; else delete nextElement.lineHeight;
|
||||
if (fontWeightValue) nextElement.fontWeight = fontWeightValue; else delete nextElement.fontWeight;
|
||||
if (opacityValue) nextElement.opacity = opacityValue; else delete nextElement.opacity;
|
||||
if (boxShadowValue) nextElement.boxShadow = boxShadowValue; else delete nextElement.boxShadow;
|
||||
if (displayValue) nextElement.display = displayValue; else delete nextElement.display;
|
||||
if (positionValue) nextElement.position = positionValue; else delete nextElement.position;
|
||||
if (justifyContentValue) nextElement.justifyContent = justifyContentValue; else delete nextElement.justifyContent;
|
||||
if (alignItemsValue) nextElement.alignItems = alignItemsValue; else delete nextElement.alignItems;
|
||||
if (textAlignValue) nextElement.textAlign = textAlignValue; else delete nextElement.textAlign;
|
||||
if (zIndexValue) nextElement.zIndex = zIndexValue; else delete nextElement.zIndex;
|
||||
|
||||
if (isNavigationType) {
|
||||
nextElement.iconUrl = iconUrl.trim();
|
||||
nextElement.navLabel = navLabel.trim();
|
||||
@ -515,6 +653,89 @@ const PageElementsProjectEditPage = () => {
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className='mb-4'>
|
||||
<h3 className='mb-3 text-sm font-semibold'>View & stylization</h3>
|
||||
<p className='mb-3 text-xs text-gray-500'>
|
||||
Fill numbers only: width is saved as vw, height as vh, border and radius as px.
|
||||
</p>
|
||||
<div className='grid gap-4 md:grid-cols-2'>
|
||||
<FormField label='Width (vw)'>
|
||||
<input type='number' step='0.1' min='0' value={width} onChange={(event) => setWidth(event.target.value)} disabled={!hasUpdatePermission} placeholder='e.g. 24' />
|
||||
</FormField>
|
||||
<FormField label='Height (vh)'>
|
||||
<input type='number' step='0.1' min='0' value={height} onChange={(event) => setHeight(event.target.value)} disabled={!hasUpdatePermission} placeholder='e.g. 8' />
|
||||
</FormField>
|
||||
<FormField label='Min width (vw)'><input type='number' step='0.1' min='0' value={minWidth} onChange={(event) => setMinWidth(event.target.value)} disabled={!hasUpdatePermission} /></FormField>
|
||||
<FormField label='Max width (vw)'><input type='number' step='0.1' min='0' value={maxWidth} onChange={(event) => setMaxWidth(event.target.value)} disabled={!hasUpdatePermission} /></FormField>
|
||||
<FormField label='Min height (vh)'><input type='number' step='0.1' min='0' value={minHeight} onChange={(event) => setMinHeight(event.target.value)} disabled={!hasUpdatePermission} /></FormField>
|
||||
<FormField label='Max height (vh)'><input type='number' step='0.1' min='0' value={maxHeight} onChange={(event) => setMaxHeight(event.target.value)} disabled={!hasUpdatePermission} /></FormField>
|
||||
<FormField label='Margin'><input value={margin} onChange={(event) => setMargin(event.target.value)} disabled={!hasUpdatePermission} placeholder='e.g. 0 auto / 0.5rem' /></FormField>
|
||||
<FormField label='Padding'><input value={padding} onChange={(event) => setPadding(event.target.value)} disabled={!hasUpdatePermission} placeholder='e.g. 0.5rem 0.75rem' /></FormField>
|
||||
<FormField label='Gap'><input value={gap} onChange={(event) => setGap(event.target.value)} disabled={!hasUpdatePermission} /></FormField>
|
||||
<FormField label='Font size'><input value={fontSize} onChange={(event) => setFontSize(event.target.value)} disabled={!hasUpdatePermission} placeholder='e.g. 0.875rem / clamp(...)' /></FormField>
|
||||
<FormField label='Line height'><input value={lineHeight} onChange={(event) => setLineHeight(event.target.value)} disabled={!hasUpdatePermission} /></FormField>
|
||||
<FormField label='Font weight'><input value={fontWeight} onChange={(event) => setFontWeight(event.target.value)} disabled={!hasUpdatePermission} placeholder='e.g. 500 / bold' /></FormField>
|
||||
<FormField label='Border width (px)'><input type='number' step='1' min='0' value={border} onChange={(event) => setBorder(event.target.value)} disabled={!hasUpdatePermission} placeholder='empty = none' /></FormField>
|
||||
<FormField label='Border radius (px)'><input type='number' step='1' min='0' value={borderRadius} onChange={(event) => setBorderRadius(event.target.value)} disabled={!hasUpdatePermission} placeholder='empty = 0' /></FormField>
|
||||
<FormField label='Opacity'><input value={opacity} onChange={(event) => setOpacity(event.target.value)} disabled={!hasUpdatePermission} placeholder='0..1' /></FormField>
|
||||
<FormField label='Shadow'><input value={boxShadow} onChange={(event) => setBoxShadow(event.target.value)} disabled={!hasUpdatePermission} placeholder='e.g. 0 4px 12px rgba(...)' /></FormField>
|
||||
<FormField label='Display'>
|
||||
<select value={display} onChange={(event) => setDisplay(event.target.value)} disabled={!hasUpdatePermission}>
|
||||
<option value=''>Not set</option>
|
||||
<option value='block'>block</option>
|
||||
<option value='inline-block'>inline-block</option>
|
||||
<option value='flex'>flex</option>
|
||||
<option value='inline-flex'>inline-flex</option>
|
||||
<option value='grid'>grid</option>
|
||||
<option value='none'>none</option>
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField label='Position'>
|
||||
<select value={position} onChange={(event) => setPosition(event.target.value)} disabled={!hasUpdatePermission}>
|
||||
<option value=''>Not set</option>
|
||||
<option value='static'>static</option>
|
||||
<option value='relative'>relative</option>
|
||||
<option value='absolute'>absolute</option>
|
||||
<option value='fixed'>fixed</option>
|
||||
<option value='sticky'>sticky</option>
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField label='Justify content'>
|
||||
<select value={justifyContent} onChange={(event) => setJustifyContent(event.target.value)} disabled={!hasUpdatePermission}>
|
||||
<option value=''>Not set</option>
|
||||
<option value='flex-start'>flex-start</option>
|
||||
<option value='center'>center</option>
|
||||
<option value='flex-end'>flex-end</option>
|
||||
<option value='space-between'>space-between</option>
|
||||
<option value='space-around'>space-around</option>
|
||||
<option value='space-evenly'>space-evenly</option>
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField label='Align items'>
|
||||
<select value={alignItems} onChange={(event) => setAlignItems(event.target.value)} disabled={!hasUpdatePermission}>
|
||||
<option value=''>Not set</option>
|
||||
<option value='stretch'>stretch</option>
|
||||
<option value='flex-start'>flex-start</option>
|
||||
<option value='center'>center</option>
|
||||
<option value='flex-end'>flex-end</option>
|
||||
<option value='baseline'>baseline</option>
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField label='Text align'>
|
||||
<select value={textAlign} onChange={(event) => setTextAlign(event.target.value)} disabled={!hasUpdatePermission}>
|
||||
<option value=''>Not set</option>
|
||||
<option value='left'>left</option>
|
||||
<option value='center'>center</option>
|
||||
<option value='right'>right</option>
|
||||
<option value='justify'>justify</option>
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField label='z-index'>
|
||||
<input value={zIndex} onChange={(event) => setZIndex(event.target.value)} disabled={!hasUpdatePermission} placeholder='e.g. 1 / 10' />
|
||||
</FormField>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
{isNavigationType ? (
|
||||
<CardBox className='mb-4'>
|
||||
<h3 className='mb-3 text-sm font-semibold'>Navigation</h3>
|
||||
|
||||
@ -77,6 +77,31 @@ const parseNullableNumber = (value: string) => {
|
||||
return parsed;
|
||||
};
|
||||
|
||||
const extractNumericValue = (value: unknown) => {
|
||||
const normalized = String(value ?? '').trim();
|
||||
if (!normalized) return '';
|
||||
const matched = normalized.match(/-?\d*\.?\d+/);
|
||||
return matched ? matched[0] : '';
|
||||
};
|
||||
|
||||
const normalizeNumberString = (value: string) => {
|
||||
const normalized = String(value || '').trim();
|
||||
if (!normalized) return '';
|
||||
const parsed = Number(normalized);
|
||||
if (!Number.isFinite(parsed)) return '';
|
||||
return String(parsed);
|
||||
};
|
||||
|
||||
const toOptionalTrimmed = (value: string) => {
|
||||
const normalized = String(value || '').trim();
|
||||
return normalized ? normalized : undefined;
|
||||
};
|
||||
|
||||
const toUnitValue = (value: string, unit: 'vw' | 'vh' | 'px') => {
|
||||
const normalized = normalizeNumberString(value);
|
||||
return normalized ? `${normalized}${unit}` : undefined;
|
||||
};
|
||||
|
||||
const createLocalId = () => {
|
||||
if (typeof window !== 'undefined' && window.crypto?.randomUUID) {
|
||||
return window.crypto.randomUUID();
|
||||
@ -101,6 +126,28 @@ const UiElementDetailsPage = () => {
|
||||
const [label, setLabel] = useState('');
|
||||
const [xPercent, setXPercent] = useState('0');
|
||||
const [yPercent, setYPercent] = useState('0');
|
||||
const [width, setWidth] = useState('');
|
||||
const [height, setHeight] = useState('');
|
||||
const [minWidth, setMinWidth] = useState('');
|
||||
const [maxWidth, setMaxWidth] = useState('');
|
||||
const [minHeight, setMinHeight] = useState('');
|
||||
const [maxHeight, setMaxHeight] = useState('');
|
||||
const [margin, setMargin] = useState('');
|
||||
const [padding, setPadding] = useState('');
|
||||
const [gap, setGap] = useState('');
|
||||
const [fontSize, setFontSize] = useState('');
|
||||
const [lineHeight, setLineHeight] = useState('');
|
||||
const [fontWeight, setFontWeight] = useState('');
|
||||
const [border, setBorder] = useState('');
|
||||
const [borderRadius, setBorderRadius] = useState('');
|
||||
const [opacity, setOpacity] = useState('');
|
||||
const [boxShadow, setBoxShadow] = useState('');
|
||||
const [display, setDisplay] = useState('');
|
||||
const [position, setPosition] = useState('');
|
||||
const [justifyContent, setJustifyContent] = useState('');
|
||||
const [alignItems, setAlignItems] = useState('');
|
||||
const [textAlign, setTextAlign] = useState('');
|
||||
const [zIndex, setZIndex] = useState('');
|
||||
const [appearDelaySec, setAppearDelaySec] = useState('0');
|
||||
const [appearDurationSec, setAppearDurationSec] = useState('');
|
||||
const [iconUrl, setIconUrl] = useState('');
|
||||
@ -135,6 +182,28 @@ const UiElementDetailsPage = () => {
|
||||
setLabel(String(settings.label || ''));
|
||||
setXPercent(String(settings.xPercent ?? 0));
|
||||
setYPercent(String(settings.yPercent ?? 0));
|
||||
setWidth(extractNumericValue(settings.width));
|
||||
setHeight(extractNumericValue(settings.height));
|
||||
setMinWidth(extractNumericValue(settings.minWidth));
|
||||
setMaxWidth(extractNumericValue(settings.maxWidth));
|
||||
setMinHeight(extractNumericValue(settings.minHeight));
|
||||
setMaxHeight(extractNumericValue(settings.maxHeight));
|
||||
setMargin(String(settings.margin || ''));
|
||||
setPadding(String(settings.padding || ''));
|
||||
setGap(String(settings.gap || ''));
|
||||
setFontSize(String(settings.fontSize || ''));
|
||||
setLineHeight(String(settings.lineHeight || ''));
|
||||
setFontWeight(String(settings.fontWeight || ''));
|
||||
setBorder(extractNumericValue(settings.border));
|
||||
setBorderRadius(extractNumericValue(settings.borderRadius));
|
||||
setOpacity(settings.opacity === 0 ? '0' : String(settings.opacity || ''));
|
||||
setBoxShadow(String(settings.boxShadow || ''));
|
||||
setDisplay(String(settings.display || ''));
|
||||
setPosition(String(settings.position || ''));
|
||||
setJustifyContent(String(settings.justifyContent || ''));
|
||||
setAlignItems(String(settings.alignItems || ''));
|
||||
setTextAlign(String(settings.textAlign || ''));
|
||||
setZIndex(String(settings.zIndex || ''));
|
||||
setAppearDelaySec(String(settings.appearDelaySec ?? 0));
|
||||
setAppearDurationSec(settings.appearDurationSec === null || settings.appearDurationSec === undefined ? '' : String(settings.appearDurationSec));
|
||||
setIconUrl(String(settings.iconUrl || ''));
|
||||
@ -271,14 +340,61 @@ const UiElementDetailsPage = () => {
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!id || !item) return;
|
||||
|
||||
const borderWidthValue = toUnitValue(border, 'px');
|
||||
const defaultSettings: Record<string, any> = {
|
||||
label: label.trim(),
|
||||
xPercent: clampPercent(xPercent),
|
||||
yPercent: clampPercent(yPercent),
|
||||
border: borderWidthValue ? `${borderWidthValue} solid currentColor` : 'none',
|
||||
borderRadius: toUnitValue(borderRadius, 'px') || '0px',
|
||||
appearDelaySec: Number(appearDelaySec) >= 0 ? Number(appearDelaySec) : 0,
|
||||
appearDurationSec: parseNullableNumber(appearDurationSec),
|
||||
};
|
||||
|
||||
const widthValue = toUnitValue(width, 'vw');
|
||||
const heightValue = toUnitValue(height, 'vh');
|
||||
const minWidthValue = toUnitValue(minWidth, 'vw');
|
||||
const maxWidthValue = toUnitValue(maxWidth, 'vw');
|
||||
const minHeightValue = toUnitValue(minHeight, 'vh');
|
||||
const maxHeightValue = toUnitValue(maxHeight, 'vh');
|
||||
|
||||
if (widthValue) defaultSettings.width = widthValue;
|
||||
if (heightValue) defaultSettings.height = heightValue;
|
||||
if (minWidthValue) defaultSettings.minWidth = minWidthValue;
|
||||
if (maxWidthValue) defaultSettings.maxWidth = maxWidthValue;
|
||||
if (minHeightValue) defaultSettings.minHeight = minHeightValue;
|
||||
if (maxHeightValue) defaultSettings.maxHeight = maxHeightValue;
|
||||
|
||||
const marginValue = toOptionalTrimmed(margin);
|
||||
const paddingValue = toOptionalTrimmed(padding);
|
||||
const gapValue = toOptionalTrimmed(gap);
|
||||
const fontSizeValue = toOptionalTrimmed(fontSize);
|
||||
const lineHeightValue = toOptionalTrimmed(lineHeight);
|
||||
const fontWeightValue = toOptionalTrimmed(fontWeight);
|
||||
const opacityValue = toOptionalTrimmed(opacity);
|
||||
const boxShadowValue = toOptionalTrimmed(boxShadow);
|
||||
const displayValue = toOptionalTrimmed(display);
|
||||
const positionValue = toOptionalTrimmed(position);
|
||||
const justifyContentValue = toOptionalTrimmed(justifyContent);
|
||||
const alignItemsValue = toOptionalTrimmed(alignItems);
|
||||
const textAlignValue = toOptionalTrimmed(textAlign);
|
||||
const zIndexValue = toOptionalTrimmed(zIndex);
|
||||
|
||||
if (marginValue) defaultSettings.margin = marginValue;
|
||||
if (paddingValue) defaultSettings.padding = paddingValue;
|
||||
if (gapValue) defaultSettings.gap = gapValue;
|
||||
if (fontSizeValue) defaultSettings.fontSize = fontSizeValue;
|
||||
if (lineHeightValue) defaultSettings.lineHeight = lineHeightValue;
|
||||
if (fontWeightValue) defaultSettings.fontWeight = fontWeightValue;
|
||||
if (opacityValue) defaultSettings.opacity = opacityValue;
|
||||
if (boxShadowValue) defaultSettings.boxShadow = boxShadowValue;
|
||||
if (displayValue) defaultSettings.display = displayValue;
|
||||
if (positionValue) defaultSettings.position = positionValue;
|
||||
if (justifyContentValue) defaultSettings.justifyContent = justifyContentValue;
|
||||
if (alignItemsValue) defaultSettings.alignItems = alignItemsValue;
|
||||
if (textAlignValue) defaultSettings.textAlign = textAlignValue;
|
||||
if (zIndexValue) defaultSettings.zIndex = zIndexValue;
|
||||
|
||||
if (isNavigationType) {
|
||||
defaultSettings.iconUrl = iconUrl.trim();
|
||||
defaultSettings.navLabel = navLabel.trim();
|
||||
@ -365,8 +481,13 @@ const UiElementDetailsPage = () => {
|
||||
carouselSlides,
|
||||
currentElementType,
|
||||
descriptionText,
|
||||
display,
|
||||
descriptionTitle,
|
||||
fontSize,
|
||||
fontWeight,
|
||||
gap,
|
||||
galleryCards,
|
||||
height,
|
||||
iconUrl,
|
||||
id,
|
||||
isActive,
|
||||
@ -377,25 +498,42 @@ const UiElementDetailsPage = () => {
|
||||
isNavigationType,
|
||||
isTooltipType,
|
||||
item,
|
||||
justifyContent,
|
||||
label,
|
||||
lineHeight,
|
||||
loadItem,
|
||||
margin,
|
||||
maxHeight,
|
||||
maxWidth,
|
||||
mediaAutoplay,
|
||||
mediaLoop,
|
||||
mediaMuted,
|
||||
mediaUrl,
|
||||
minHeight,
|
||||
minWidth,
|
||||
name,
|
||||
navLabel,
|
||||
navType,
|
||||
opacity,
|
||||
padding,
|
||||
position,
|
||||
reverseVideoUrl,
|
||||
sortOrder,
|
||||
textAlign,
|
||||
targetPageId,
|
||||
tooltipText,
|
||||
tooltipTitle,
|
||||
transitionDurationSec,
|
||||
transitionReverseMode,
|
||||
transitionVideoUrl,
|
||||
width,
|
||||
xPercent,
|
||||
yPercent,
|
||||
zIndex,
|
||||
alignItems,
|
||||
border,
|
||||
borderRadius,
|
||||
boxShadow,
|
||||
]);
|
||||
|
||||
return (
|
||||
@ -490,6 +628,83 @@ const UiElementDetailsPage = () => {
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
<CardBox className='border border-gray-200 dark:border-dark-700'>
|
||||
<h3 className='mb-3 text-sm font-semibold'>View & stylization</h3>
|
||||
<p className='mb-3 text-xs text-gray-500'>
|
||||
Fill numbers only: width is saved as vw, height as vh, border and radius as px.
|
||||
</p>
|
||||
<div className='grid gap-3 md:grid-cols-2'>
|
||||
<FormField label='Width (vw)'><input type='number' step='0.1' min='0' value={width} onChange={(event) => setWidth(event.target.value)} placeholder='e.g. 24' /></FormField>
|
||||
<FormField label='Height (vh)'><input type='number' step='0.1' min='0' value={height} onChange={(event) => setHeight(event.target.value)} placeholder='e.g. 8' /></FormField>
|
||||
<FormField label='Min width (vw)'><input type='number' step='0.1' min='0' value={minWidth} onChange={(event) => setMinWidth(event.target.value)} /></FormField>
|
||||
<FormField label='Max width (vw)'><input type='number' step='0.1' min='0' value={maxWidth} onChange={(event) => setMaxWidth(event.target.value)} /></FormField>
|
||||
<FormField label='Min height (vh)'><input type='number' step='0.1' min='0' value={minHeight} onChange={(event) => setMinHeight(event.target.value)} /></FormField>
|
||||
<FormField label='Max height (vh)'><input type='number' step='0.1' min='0' value={maxHeight} onChange={(event) => setMaxHeight(event.target.value)} /></FormField>
|
||||
<FormField label='Margin'><input value={margin} onChange={(event) => setMargin(event.target.value)} placeholder='e.g. 0 auto / 0.5rem' /></FormField>
|
||||
<FormField label='Padding'><input value={padding} onChange={(event) => setPadding(event.target.value)} placeholder='e.g. 0.5rem 0.75rem' /></FormField>
|
||||
<FormField label='Gap'><input value={gap} onChange={(event) => setGap(event.target.value)} /></FormField>
|
||||
<FormField label='Font size'><input value={fontSize} onChange={(event) => setFontSize(event.target.value)} placeholder='e.g. 0.875rem / clamp(...)' /></FormField>
|
||||
<FormField label='Line height'><input value={lineHeight} onChange={(event) => setLineHeight(event.target.value)} /></FormField>
|
||||
<FormField label='Font weight'><input value={fontWeight} onChange={(event) => setFontWeight(event.target.value)} placeholder='e.g. 500 / bold' /></FormField>
|
||||
<FormField label='Border width (px)'><input type='number' step='1' min='0' value={border} onChange={(event) => setBorder(event.target.value)} placeholder='empty = none' /></FormField>
|
||||
<FormField label='Border radius (px)'><input type='number' step='1' min='0' value={borderRadius} onChange={(event) => setBorderRadius(event.target.value)} placeholder='empty = 0' /></FormField>
|
||||
<FormField label='Opacity'><input value={opacity} onChange={(event) => setOpacity(event.target.value)} placeholder='0..1' /></FormField>
|
||||
<FormField label='Shadow'><input value={boxShadow} onChange={(event) => setBoxShadow(event.target.value)} placeholder='e.g. 0 4px 12px rgba(...)' /></FormField>
|
||||
<FormField label='Display'>
|
||||
<select value={display} onChange={(event) => setDisplay(event.target.value)}>
|
||||
<option value=''>Not set</option>
|
||||
<option value='block'>block</option>
|
||||
<option value='inline-block'>inline-block</option>
|
||||
<option value='flex'>flex</option>
|
||||
<option value='inline-flex'>inline-flex</option>
|
||||
<option value='grid'>grid</option>
|
||||
<option value='none'>none</option>
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField label='Position'>
|
||||
<select value={position} onChange={(event) => setPosition(event.target.value)}>
|
||||
<option value=''>Not set</option>
|
||||
<option value='static'>static</option>
|
||||
<option value='relative'>relative</option>
|
||||
<option value='absolute'>absolute</option>
|
||||
<option value='fixed'>fixed</option>
|
||||
<option value='sticky'>sticky</option>
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField label='Justify content'>
|
||||
<select value={justifyContent} onChange={(event) => setJustifyContent(event.target.value)}>
|
||||
<option value=''>Not set</option>
|
||||
<option value='flex-start'>flex-start</option>
|
||||
<option value='center'>center</option>
|
||||
<option value='flex-end'>flex-end</option>
|
||||
<option value='space-between'>space-between</option>
|
||||
<option value='space-around'>space-around</option>
|
||||
<option value='space-evenly'>space-evenly</option>
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField label='Align items'>
|
||||
<select value={alignItems} onChange={(event) => setAlignItems(event.target.value)}>
|
||||
<option value=''>Not set</option>
|
||||
<option value='stretch'>stretch</option>
|
||||
<option value='flex-start'>flex-start</option>
|
||||
<option value='center'>center</option>
|
||||
<option value='flex-end'>flex-end</option>
|
||||
<option value='baseline'>baseline</option>
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField label='Text align'>
|
||||
<select value={textAlign} onChange={(event) => setTextAlign(event.target.value)}>
|
||||
<option value=''>Not set</option>
|
||||
<option value='left'>left</option>
|
||||
<option value='center'>center</option>
|
||||
<option value='right'>right</option>
|
||||
<option value='justify'>justify</option>
|
||||
</select>
|
||||
</FormField>
|
||||
<FormField label='z-index'><input value={zIndex} onChange={(event) => setZIndex(event.target.value)} placeholder='e.g. 1 / 10' /></FormField>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
{isNavigationType ? (
|
||||
<>
|
||||
<FormField label='Icon URL'>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user