video 블록 및 리펙터링
CI / pr_and_merge (push) Has been skipped
CI / test (push) Failing after 46m46s
CI / test (pull_request) Failing after 43m8s
CI / pr_and_merge (pull_request) Has been skipped

This commit is contained in:
2025-11-27 10:07:59 +09:00
parent 672cca5271
commit 48e13d2a75
223 changed files with 8979 additions and 2125 deletions
+278
View File
@@ -0,0 +1,278 @@
import type { CSSProperties } from "react";
import type { SectionBlockProps } from "@/features/editor/state/editorStore";
export interface SectionExportTokens {
sectionClasses: string;
sectionStyleParts: string[];
backgroundVideoHtml: string;
}
export function computeSectionExportTokens(props: SectionBlockProps): SectionExportTokens {
const bgToken = props.background ?? "default";
const bgClassMap: Record<SectionBlockProps["background"], string> = {
default: "pb-section-bg-default",
muted: "pb-section-bg-muted",
primary: "pb-section-bg-primary",
};
const bgClass = bgClassMap[bgToken] ?? "pb-section-bg-default";
const pyToken = props.paddingY ?? "md";
const pyClassMap: Record<SectionBlockProps["paddingY"], string> = {
sm: "pb-section-py-sm",
md: "pb-section-py-md",
lg: "pb-section-py-lg",
};
const pyClass = pyClassMap[pyToken] ?? "pb-section-py-md";
const sectionClasses = ["pb-section", bgClass, pyClass].filter(Boolean).join(" ");
const sectionStyleParts: string[] = [];
if (typeof props.backgroundColorCustom === "string" && props.backgroundColorCustom.trim() !== "") {
sectionStyleParts.push(`background-color:${props.backgroundColorCustom.trim()}`);
}
const bgImgSrc = props.backgroundImageSrc;
if (typeof bgImgSrc === "string" && bgImgSrc.trim() !== "") {
const url = bgImgSrc.trim();
sectionStyleParts.push(`background-image:url(${url})`);
const size = props.backgroundImageSize ?? "cover";
sectionStyleParts.push(`background-size:${size}`);
const posMode = props.backgroundImagePositionMode ?? "preset";
if (posMode === "custom") {
const x = props.backgroundImagePositionXPercent;
const y = props.backgroundImagePositionYPercent;
const xSafe = typeof x === "number" ? x : 50;
const ySafe = typeof y === "number" ? y : 50;
sectionStyleParts.push(`background-position:${xSafe}% ${ySafe}%`);
} else {
const posToken = props.backgroundImagePosition ?? "center";
const position =
posToken === "top"
? "center top"
: posToken === "bottom"
? "center bottom"
: posToken === "left"
? "left center"
: posToken === "right"
? "right center"
: "center center";
sectionStyleParts.push(`background-position:${position}`);
}
const repeat = props.backgroundImageRepeat ?? "no-repeat";
sectionStyleParts.push(`background-repeat:${repeat}`);
}
const bgVideoSrc = props.backgroundVideoSrc;
let backgroundVideoHtml = "";
if (typeof bgVideoSrc === "string" && bgVideoSrc.trim() !== "") {
const url = bgVideoSrc.trim();
backgroundVideoHtml = `<video class="pb-section-bg-video" src="${url}" autoplay loop muted playsinline></video>`;
sectionStyleParts.push("position:relative");
sectionStyleParts.push("overflow:hidden");
}
return {
sectionClasses,
sectionStyleParts,
backgroundVideoHtml,
};
}
export interface SectionPublicTokens {
sectionStyle: CSSProperties;
innerWrapperStyle: CSSProperties;
columnsContainerStyle: CSSProperties;
hasBackgroundVideo: boolean;
backgroundVideoSrc: string | null;
}
const pxToEm = (px: number, base = 16) => `${px / base}em`;
export function computeSectionPublicTokens(props: SectionBlockProps): SectionPublicTokens {
const sectionStyle: CSSProperties = {};
if (props.backgroundColorCustom && props.backgroundColorCustom.trim() !== "") {
sectionStyle.backgroundColor = props.backgroundColorCustom.trim();
}
if (typeof props.paddingYPx === "number" && props.paddingYPx > 0) {
const paddingEm = pxToEm(props.paddingYPx);
sectionStyle.paddingTop = paddingEm;
sectionStyle.paddingBottom = paddingEm;
}
const rawBgVideoSrc = (props as any).backgroundVideoSrc;
let hasBackgroundVideo = false;
let backgroundVideoSrc: string | null = null;
if (typeof rawBgVideoSrc === "string" && rawBgVideoSrc.trim() !== "") {
hasBackgroundVideo = true;
backgroundVideoSrc = rawBgVideoSrc.trim();
sectionStyle.position = "relative";
sectionStyle.overflow = "hidden";
}
if (props.backgroundImageSrc && props.backgroundImageSrc.trim() !== "") {
const url = props.backgroundImageSrc.trim();
sectionStyle.backgroundImage = `url(${url})`;
const size = props.backgroundImageSize ?? "cover";
sectionStyle.backgroundSize = size;
const posMode = props.backgroundImagePositionMode ?? "preset";
if (
posMode === "custom" &&
typeof props.backgroundImagePositionXPercent === "number" &&
typeof props.backgroundImagePositionYPercent === "number"
) {
const x = props.backgroundImagePositionXPercent;
const y = props.backgroundImagePositionYPercent;
sectionStyle.backgroundPosition = `${x}% ${y}%`;
} else {
const posToken = props.backgroundImagePosition ?? "center";
const position =
posToken === "top"
? "center top"
: posToken === "bottom"
? "center bottom"
: posToken === "left"
? "left center"
: posToken === "right"
? "right center"
: "center center";
sectionStyle.backgroundPosition = position;
}
const repeat = props.backgroundImageRepeat ?? "no-repeat";
sectionStyle.backgroundRepeat = repeat;
}
const innerWrapperStyle: CSSProperties = {};
if (typeof props.maxWidthPx === "number" && props.maxWidthPx > 0) {
innerWrapperStyle.maxWidth = pxToEm(props.maxWidthPx);
}
const columnsContainerStyle: CSSProperties = {};
if (typeof props.gapXPx === "number" && props.gapXPx > 0) {
columnsContainerStyle.columnGap = pxToEm(props.gapXPx);
}
return {
sectionStyle,
innerWrapperStyle,
columnsContainerStyle,
hasBackgroundVideo,
backgroundVideoSrc,
};
}
export interface SectionEditorTokens {
bgClass: string;
pyClass: string;
alignItemsClass: string;
wrapperStyle: CSSProperties;
innerWrapperStyle: CSSProperties;
columnsContainerStyle: CSSProperties;
hasBackgroundVideo: boolean;
backgroundVideoSrc: string | null;
}
export function computeSectionEditorTokens(props: SectionBlockProps): SectionEditorTokens {
const bgClass =
props.background === "muted"
? "bg-slate-950/40"
: props.background === "primary"
? "bg-sky-950/40 border-sky-900/60"
: "bg-slate-900/60";
const pyClass =
props.paddingY === "sm"
? "py-4"
: props.paddingY === "lg"
? "py-10"
: "py-6";
const alignItemsClass =
props.alignItems === "center"
? "items-center"
: props.alignItems === "bottom"
? "items-end"
: "items-start";
const wrapperStyle: CSSProperties = {};
if (props.backgroundColorCustom && props.backgroundColorCustom.trim() !== "") {
wrapperStyle.backgroundColor = props.backgroundColorCustom;
}
if (typeof props.paddingYPx === "number" && props.paddingYPx > 0) {
wrapperStyle.paddingTop = `${props.paddingYPx}px`;
wrapperStyle.paddingBottom = `${props.paddingYPx}px`;
}
let hasBackgroundVideo = false;
let backgroundVideoSrc: string | null = null;
if (typeof props.backgroundVideoSrc === "string" && props.backgroundVideoSrc.trim() !== "") {
hasBackgroundVideo = true;
backgroundVideoSrc = props.backgroundVideoSrc.trim();
wrapperStyle.position = "relative";
wrapperStyle.overflow = "hidden";
}
if (props.backgroundImageSrc && props.backgroundImageSrc.trim() !== "") {
const url = props.backgroundImageSrc.trim();
wrapperStyle.backgroundImage = `url(${url})`;
const size = props.backgroundImageSize ?? "cover";
wrapperStyle.backgroundSize = size;
const posMode = props.backgroundImagePositionMode ?? "preset";
if (
posMode === "custom" &&
typeof props.backgroundImagePositionXPercent === "number" &&
typeof props.backgroundImagePositionYPercent === "number"
) {
const x = props.backgroundImagePositionXPercent;
const y = props.backgroundImagePositionYPercent;
wrapperStyle.backgroundPosition = `${x}% ${y}%`;
} else {
const posToken = props.backgroundImagePosition ?? "center";
const position =
posToken === "top"
? "center top"
: posToken === "bottom"
? "center bottom"
: posToken === "left"
? "left center"
: posToken === "right"
? "right center"
: "center center";
wrapperStyle.backgroundPosition = position;
}
const repeat = props.backgroundImageRepeat ?? "no-repeat";
wrapperStyle.backgroundRepeat = repeat;
}
const innerWrapperStyle: CSSProperties = {};
if (typeof props.maxWidthPx === "number" && props.maxWidthPx > 0) {
innerWrapperStyle.maxWidth = `${props.maxWidthPx}px`;
}
const columnsContainerStyle: CSSProperties = {};
if (typeof props.gapXPx === "number" && props.gapXPx > 0) {
columnsContainerStyle.columnGap = `${props.gapXPx}px`;
}
return {
bgClass,
pyClass,
alignItemsClass,
wrapperStyle,
innerWrapperStyle,
columnsContainerStyle,
hasBackgroundVideo,
backgroundVideoSrc,
};
}