250 lines
8.3 KiB
TypeScript
250 lines
8.3 KiB
TypeScript
import type { CSSProperties } from "react";
|
|
import type { ButtonBlockProps } from "@/features/editor/state/editorStore";
|
|
|
|
export type ButtonAlign = "left" | "center" | "right" | null | undefined;
|
|
export type ButtonSize = "xs" | "sm" | "md" | "lg" | "xl" | null | undefined;
|
|
export type ButtonVariant = "solid" | "outline" | "ghost" | null | undefined;
|
|
export type ButtonColorPalette = "primary" | "muted" | "danger" | "success" | "neutral" | null | undefined;
|
|
export type ButtonRadius = "none" | "sm" | "md" | "lg" | "full" | null | undefined;
|
|
|
|
export interface ButtonStyleInput {
|
|
align?: ButtonAlign;
|
|
size?: ButtonSize;
|
|
variant?: ButtonVariant;
|
|
colorPalette?: ButtonColorPalette;
|
|
borderRadius?: ButtonRadius;
|
|
fullWidth?: boolean | null | undefined;
|
|
widthMode?: "auto" | "full" | "fixed" | null | undefined;
|
|
widthPx?: number | null | undefined;
|
|
paddingX?: number | null | undefined;
|
|
paddingY?: number | null | undefined;
|
|
fillColorCustom?: string | null | undefined;
|
|
strokeColorCustom?: string | null | undefined;
|
|
textColorCustom?: string | null | undefined;
|
|
}
|
|
|
|
export interface ButtonPbTokens {
|
|
alignClass: string;
|
|
sizeClass: string;
|
|
variantClass: string;
|
|
radiusClass: string;
|
|
inlineStyles: string[];
|
|
}
|
|
|
|
export function computeButtonPbTokens(input: ButtonStyleInput): ButtonPbTokens {
|
|
const align = input.align === "center" ? "center" : input.align === "right" ? "right" : "left";
|
|
const alignClass =
|
|
align === "center" ? "pb-text-center" : align === "right" ? "pb-text-right" : "pb-text-left";
|
|
|
|
const sizeToken = (input.size as ButtonSize | null) ?? "md";
|
|
const sizeMap: Record<Exclude<ButtonSize, null | undefined>, string> = {
|
|
xs: "pb-btn-size-xs",
|
|
sm: "pb-btn-size-sm",
|
|
md: "pb-btn-size-md",
|
|
lg: "pb-btn-size-lg",
|
|
xl: "pb-btn-size-xl",
|
|
};
|
|
const sizeClass = sizeMap[sizeToken as Exclude<ButtonSize, null | undefined>] ?? "pb-btn-size-md";
|
|
|
|
const variant = (input.variant as ButtonVariant | null) ?? "solid";
|
|
const palette = (input.colorPalette as ButtonColorPalette | null) ?? "primary";
|
|
const variantClass = `pb-btn-variant-${variant}-${palette}`;
|
|
|
|
const radiusToken = (input.borderRadius as ButtonRadius | null) ?? "md";
|
|
const radiusMap: Record<Exclude<ButtonRadius, null | undefined>, string> = {
|
|
none: "pb-btn-radius-none",
|
|
sm: "pb-btn-radius-sm",
|
|
md: "pb-btn-radius-md",
|
|
lg: "pb-btn-radius-lg",
|
|
full: "pb-btn-radius-full",
|
|
};
|
|
const radiusClass = radiusMap[radiusToken as Exclude<ButtonRadius, null | undefined>] ?? "";
|
|
|
|
const inlineStyles: string[] = [];
|
|
|
|
const widthMode = input.widthMode ?? (input.fullWidth ? "full" : "auto");
|
|
if (widthMode === "fixed" && typeof input.widthPx === "number" && input.widthPx > 0) {
|
|
inlineStyles.push(`width:${input.widthPx}px`);
|
|
}
|
|
|
|
if (typeof input.paddingX === "number") {
|
|
inlineStyles.push(`padding-left:${input.paddingX}px`);
|
|
inlineStyles.push(`padding-right:${input.paddingX}px`);
|
|
}
|
|
|
|
if (typeof input.paddingY === "number") {
|
|
inlineStyles.push(`padding-top:${input.paddingY}px`);
|
|
inlineStyles.push(`padding-bottom:${input.paddingY}px`);
|
|
}
|
|
|
|
if (input.fillColorCustom && input.fillColorCustom.trim() !== "") {
|
|
inlineStyles.push(`background-color:${input.fillColorCustom.trim()}`);
|
|
}
|
|
|
|
if (input.strokeColorCustom && input.strokeColorCustom.trim() !== "") {
|
|
inlineStyles.push(`border-color:${input.strokeColorCustom.trim()}`);
|
|
}
|
|
|
|
if (input.textColorCustom && input.textColorCustom.trim() !== "") {
|
|
inlineStyles.push(`color:${input.textColorCustom.trim()}`);
|
|
}
|
|
|
|
return {
|
|
alignClass,
|
|
sizeClass,
|
|
variantClass,
|
|
radiusClass,
|
|
inlineStyles,
|
|
};
|
|
}
|
|
|
|
// 퍼블릭 렌더러용 버튼 스타일 토큰
|
|
export interface ButtonPublicTokens {
|
|
alignClass: string;
|
|
widthClass: string;
|
|
styleOverrides: CSSProperties;
|
|
}
|
|
|
|
const pxToEm = (px: number, base = 16) => `${px / base}em`;
|
|
|
|
const convertPxStringToEm = (value?: string | null) => {
|
|
if (!value) return null;
|
|
const match = value.trim().match(/-?\d+(?:\.\d+)?/);
|
|
if (!match) return null;
|
|
const px = parseFloat(match[0]);
|
|
if (!Number.isFinite(px)) return null;
|
|
return pxToEm(px);
|
|
};
|
|
|
|
export function computeButtonPublicTokens(props: ButtonBlockProps): ButtonPublicTokens {
|
|
const align = props.align ?? "left";
|
|
const alignClass =
|
|
align === "center" ? "text-center" : align === "right" ? "text-right" : "text-left";
|
|
|
|
const styleOverrides: CSSProperties = {};
|
|
|
|
// 폰트 크기: fontSizeCustom(px)는 em 으로 변환, 그 외는 그대로 사용
|
|
const fontSizeEm = convertPxStringToEm(props.fontSizeCustom);
|
|
if (fontSizeEm) {
|
|
styleOverrides.fontSize = fontSizeEm;
|
|
}
|
|
|
|
// lineHeightCustom 은 px 단위든 아니든 그대로 사용 (기존 PublicPageRenderer 로직 유지)
|
|
if (props.lineHeightCustom && props.lineHeightCustom.trim() !== "") {
|
|
styleOverrides.lineHeight = props.lineHeightCustom;
|
|
}
|
|
|
|
// letterSpacingCustom 은 px 인 경우만 em 으로 변환, 그 외는 그대로 사용
|
|
if (props.letterSpacingCustom && props.letterSpacingCustom.trim() !== "") {
|
|
const letterSpacing = props.letterSpacingCustom.trim();
|
|
if (letterSpacing.endsWith("px")) {
|
|
const emValue = convertPxStringToEm(letterSpacing);
|
|
if (emValue) {
|
|
styleOverrides.letterSpacing = emValue;
|
|
}
|
|
} else {
|
|
styleOverrides.letterSpacing = letterSpacing;
|
|
}
|
|
}
|
|
|
|
const widthMode = props.widthMode ?? (props.fullWidth ? "full" : "auto");
|
|
if (widthMode === "fixed" && typeof props.widthPx === "number" && props.widthPx > 0) {
|
|
styleOverrides.width = pxToEm(props.widthPx);
|
|
}
|
|
|
|
if (typeof props.paddingX === "number" && props.paddingX >= 0) {
|
|
styleOverrides.paddingInline = pxToEm(props.paddingX);
|
|
}
|
|
|
|
if (typeof props.paddingY === "number" && props.paddingY >= 0) {
|
|
styleOverrides.paddingBlock = pxToEm(props.paddingY);
|
|
}
|
|
|
|
if (props.fillColorCustom && props.fillColorCustom.trim() !== "") {
|
|
styleOverrides.backgroundColor = props.fillColorCustom;
|
|
}
|
|
|
|
if (props.strokeColorCustom && props.strokeColorCustom.trim() !== "") {
|
|
styleOverrides.borderColor = props.strokeColorCustom;
|
|
styleOverrides.borderWidth = "1px";
|
|
styleOverrides.borderStyle = "solid";
|
|
}
|
|
|
|
if (props.textColorCustom && props.textColorCustom.trim() !== "") {
|
|
styleOverrides.color = props.textColorCustom;
|
|
}
|
|
|
|
const radiusToken = props.borderRadius ?? "md";
|
|
const radiusPx =
|
|
radiusToken === "none"
|
|
? 0
|
|
: radiusToken === "sm"
|
|
? 4
|
|
: radiusToken === "lg"
|
|
? 12
|
|
: radiusToken === "full"
|
|
? 9999
|
|
: 8;
|
|
styleOverrides.borderRadius = radiusPx === 9999 ? "9999px" : pxToEm(radiusPx);
|
|
|
|
const widthClass = widthMode === "full" ? "w-full" : "";
|
|
|
|
return {
|
|
alignClass,
|
|
widthClass,
|
|
styleOverrides,
|
|
};
|
|
}
|
|
|
|
export interface ButtonEditorTokens {
|
|
wrapperWidthClass: string;
|
|
buttonStyle: CSSProperties;
|
|
}
|
|
|
|
export function computeButtonEditorTokens(props: ButtonBlockProps): ButtonEditorTokens {
|
|
const baseFullWidth = props.fullWidth ?? false;
|
|
const widthMode = props.widthMode ?? (baseFullWidth ? "full" : "auto");
|
|
const isFullWidth = widthMode === "full" || baseFullWidth;
|
|
|
|
const wrapperWidthClass = isFullWidth ? "w-full" : "inline-block";
|
|
|
|
const buttonStyle: CSSProperties = {};
|
|
|
|
if (props.fontSizeCustom && props.fontSizeCustom.trim() !== "") {
|
|
buttonStyle.fontSize = props.fontSizeCustom;
|
|
}
|
|
if (props.lineHeightCustom && props.lineHeightCustom.trim() !== "") {
|
|
buttonStyle.lineHeight = props.lineHeightCustom;
|
|
}
|
|
if (props.letterSpacingCustom && props.letterSpacingCustom.trim() !== "") {
|
|
buttonStyle.letterSpacing = props.letterSpacingCustom;
|
|
}
|
|
|
|
if (props.fillColorCustom && props.fillColorCustom.trim() !== "") {
|
|
buttonStyle.backgroundColor = props.fillColorCustom;
|
|
}
|
|
if (props.strokeColorCustom && props.strokeColorCustom.trim() !== "") {
|
|
buttonStyle.borderColor = props.strokeColorCustom;
|
|
}
|
|
if (props.textColorCustom && props.textColorCustom.trim() !== "") {
|
|
buttonStyle.color = props.textColorCustom;
|
|
}
|
|
|
|
if (widthMode === "fixed" && typeof props.widthPx === "number" && props.widthPx > 0) {
|
|
buttonStyle.width = `${props.widthPx}px`;
|
|
}
|
|
|
|
if (typeof props.paddingX === "number" && props.paddingX >= 0) {
|
|
buttonStyle.paddingInline = `${props.paddingX}px`;
|
|
}
|
|
|
|
if (typeof props.paddingY === "number" && props.paddingY >= 0) {
|
|
buttonStyle.paddingBlock = `${props.paddingY}px`;
|
|
}
|
|
|
|
return {
|
|
wrapperWidthClass,
|
|
buttonStyle,
|
|
};
|
|
}
|