video 블록 및 리펙터링
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
import type { CSSProperties } from "react";
|
||||
import type { TextBlockProps } from "@/features/editor/state/editorStore";
|
||||
|
||||
export type TextAlign = "left" | "center" | "right" | null | undefined;
|
||||
|
||||
export type TextFontSizeScale = "xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl";
|
||||
export type TextLineHeightScale = "tight" | "snug" | "normal" | "relaxed" | "loose";
|
||||
export type TextFontWeightScale = "normal" | "medium" | "semibold" | "bold";
|
||||
export type TextColorPalette =
|
||||
| "default"
|
||||
| "muted"
|
||||
| "strong"
|
||||
| "accent"
|
||||
| "danger"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "info"
|
||||
| "neutral";
|
||||
|
||||
export interface TextStyleInput {
|
||||
text?: string;
|
||||
align?: TextAlign;
|
||||
size?: "sm" | "base" | "lg" | null | undefined;
|
||||
fontSizeMode?: "scale" | "custom" | null | undefined;
|
||||
fontSizeScale?: TextFontSizeScale | null | undefined;
|
||||
fontSizeCustom?: string | null | undefined;
|
||||
lineHeightMode?: "scale" | "custom" | null | undefined;
|
||||
lineHeightScale?: TextLineHeightScale | null | undefined;
|
||||
lineHeightCustom?: string | null | undefined;
|
||||
fontWeightMode?: "scale" | "custom" | null | undefined;
|
||||
fontWeightScale?: TextFontWeightScale | null | undefined;
|
||||
fontWeightCustom?: string | number | null | undefined;
|
||||
colorMode?: "palette" | "custom" | null | undefined;
|
||||
colorPalette?: TextColorPalette | null | undefined;
|
||||
colorCustom?: string | null | undefined;
|
||||
backgroundColorCustom?: string | null | undefined;
|
||||
maxWidthMode?: "scale" | "custom" | null | undefined;
|
||||
maxWidthScale?: "none" | "prose" | "narrow" | null | undefined;
|
||||
underline?: boolean | null | undefined;
|
||||
strike?: boolean | null | undefined;
|
||||
italic?: boolean | null | undefined;
|
||||
}
|
||||
|
||||
export interface TextPbTokens {
|
||||
alignClass: string;
|
||||
sizeClass: string;
|
||||
leadingClass: string;
|
||||
weightClass: string;
|
||||
colorClass: string;
|
||||
maxWidthClass: string;
|
||||
extraClasses: string[];
|
||||
inlineStyles: string[];
|
||||
}
|
||||
|
||||
export function computeTextPbTokens(input: TextStyleInput): TextPbTokens {
|
||||
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 fontSizeMode = input.fontSizeMode ?? "scale";
|
||||
const fallbackScale: TextFontSizeScale = input.size === "sm" ? "sm" : input.size === "lg" ? "lg" : "base";
|
||||
const fontSizeScale: TextFontSizeScale = (input.fontSizeScale as TextFontSizeScale | null) ?? fallbackScale;
|
||||
|
||||
const fontSizeMap: Record<TextFontSizeScale, string> = {
|
||||
xs: "pb-text-xs",
|
||||
sm: "pb-text-sm",
|
||||
base: "pb-text-base",
|
||||
lg: "pb-text-lg",
|
||||
xl: "pb-text-xl",
|
||||
"2xl": "pb-text-2xl",
|
||||
"3xl": "pb-text-3xl",
|
||||
};
|
||||
|
||||
const sizeClass =
|
||||
fontSizeMode === "scale" && fontSizeScale && fontSizeMap[fontSizeScale]
|
||||
? fontSizeMap[fontSizeScale]
|
||||
: "";
|
||||
|
||||
const lineHeightMode = input.lineHeightMode ?? "scale";
|
||||
const lineHeightScale: TextLineHeightScale = (input.lineHeightScale as TextLineHeightScale | null) ?? "normal";
|
||||
const leadingMap: Record<TextLineHeightScale, string> = {
|
||||
tight: "pb-leading-tight",
|
||||
snug: "pb-leading-snug",
|
||||
normal: "pb-leading-normal",
|
||||
relaxed: "pb-leading-relaxed",
|
||||
loose: "pb-leading-loose",
|
||||
};
|
||||
|
||||
const leadingClass =
|
||||
lineHeightMode === "scale" && lineHeightScale && leadingMap[lineHeightScale]
|
||||
? leadingMap[lineHeightScale]
|
||||
: "";
|
||||
|
||||
const fontWeightMode = input.fontWeightMode ?? "scale";
|
||||
const fontWeightScale: TextFontWeightScale =
|
||||
(input.fontWeightScale as TextFontWeightScale | null) ?? "normal";
|
||||
const weightMap: Record<TextFontWeightScale, string> = {
|
||||
normal: "pb-font-normal",
|
||||
medium: "pb-font-medium",
|
||||
semibold: "pb-font-semibold",
|
||||
bold: "pb-font-bold",
|
||||
};
|
||||
|
||||
const weightClass =
|
||||
fontWeightMode === "scale" && fontWeightScale && weightMap[fontWeightScale]
|
||||
? weightMap[fontWeightScale]
|
||||
: "";
|
||||
|
||||
let colorClass = "pb-text-color-strong";
|
||||
const inlineStyles: string[] = [];
|
||||
|
||||
if (input.colorMode === "palette") {
|
||||
const palette: TextColorPalette = (input.colorPalette as TextColorPalette | null) ?? "default";
|
||||
const paletteMap: Record<TextColorPalette, string> = {
|
||||
default: "pb-text-color-default",
|
||||
muted: "pb-text-color-muted",
|
||||
strong: "pb-text-color-strong",
|
||||
accent: "pb-text-color-accent",
|
||||
danger: "pb-text-color-danger",
|
||||
success: "pb-text-color-success",
|
||||
warning: "pb-text-color-warning",
|
||||
info: "pb-text-color-info",
|
||||
neutral: "pb-text-color-neutral",
|
||||
};
|
||||
colorClass = paletteMap[palette] ?? colorClass;
|
||||
} else if (input.colorMode === "custom" && input.colorCustom && input.colorCustom.trim() !== "") {
|
||||
inlineStyles.push(`color:${input.colorCustom.trim()}`);
|
||||
}
|
||||
|
||||
if (input.backgroundColorCustom && input.backgroundColorCustom.trim() !== "") {
|
||||
inlineStyles.push(`background-color:${input.backgroundColorCustom.trim()}`);
|
||||
}
|
||||
|
||||
let maxWidthClass = "";
|
||||
const maxWidthMode = input.maxWidthMode ?? "scale";
|
||||
const maxWidthScale = input.maxWidthScale ?? "none";
|
||||
if (maxWidthMode === "scale") {
|
||||
if (maxWidthScale === "prose") {
|
||||
maxWidthClass = "pb-text-maxw-prose";
|
||||
} else if (maxWidthScale === "narrow") {
|
||||
maxWidthClass = "pb-text-maxw-narrow";
|
||||
}
|
||||
}
|
||||
|
||||
const extraClasses: string[] = ["pb-whitespace-pre-wrap"];
|
||||
if (input.underline) extraClasses.push("pb-underline");
|
||||
if (input.strike) extraClasses.push("pb-line-through");
|
||||
if (input.italic) extraClasses.push("pb-italic");
|
||||
|
||||
return {
|
||||
alignClass,
|
||||
sizeClass,
|
||||
leadingClass,
|
||||
weightClass,
|
||||
colorClass,
|
||||
maxWidthClass,
|
||||
extraClasses,
|
||||
inlineStyles,
|
||||
};
|
||||
}
|
||||
|
||||
export interface TextEditorTokens {
|
||||
alignClass: string;
|
||||
sizeClass: string;
|
||||
leadingClass: string;
|
||||
weightClass: string;
|
||||
colorClass: string;
|
||||
maxWidthClass: string;
|
||||
decorationClass: string;
|
||||
styleOverrides: CSSProperties;
|
||||
}
|
||||
|
||||
export function computeTextEditorTokens(props: TextBlockProps): TextEditorTokens {
|
||||
let alignClass = "";
|
||||
let sizeClass = "";
|
||||
let leadingClass = "";
|
||||
let weightClass = "";
|
||||
let colorClass = "";
|
||||
let maxWidthClass = "";
|
||||
let decorationClass = "";
|
||||
const styleOverrides: CSSProperties = {};
|
||||
|
||||
// 정렬: pb-text-*
|
||||
alignClass =
|
||||
props.align === "center"
|
||||
? "pb-text-center"
|
||||
: props.align === "right"
|
||||
? "pb-text-right"
|
||||
: "pb-text-left";
|
||||
|
||||
// 폰트 크기: scale/custom + 기존 size 값 fallback
|
||||
const fontSizeMode = props.fontSizeMode ?? "scale";
|
||||
const fallbackScale = props.size === "sm" ? "sm" : props.size === "lg" ? "lg" : "base";
|
||||
const fontSizeScale = props.fontSizeScale ?? fallbackScale;
|
||||
|
||||
// 스케일/커스텀 모드와 무관하게 pb-text-* 스케일 클래스는 항상 유지한다.
|
||||
sizeClass = `pb-text-${fontSizeScale}`;
|
||||
|
||||
// custom 모드에서는 inline 스타일로 실제 폰트 크기를 덮어쓴다.
|
||||
if (fontSizeMode === "custom" && props.fontSizeCustom && props.fontSizeCustom.trim() !== "") {
|
||||
styleOverrides.fontSize = props.fontSizeCustom;
|
||||
}
|
||||
|
||||
// 줄 간격: scale/custom
|
||||
const lineHeightMode = props.lineHeightMode ?? "scale";
|
||||
const lineHeightScale = props.lineHeightScale ?? "normal";
|
||||
if (lineHeightMode === "scale") {
|
||||
leadingClass = `pb-leading-${lineHeightScale}`;
|
||||
} else if (lineHeightMode === "custom" && props.lineHeightCustom && props.lineHeightCustom.trim() !== "") {
|
||||
styleOverrides.lineHeight = props.lineHeightCustom;
|
||||
}
|
||||
|
||||
// 굵기: scale/custom
|
||||
const fontWeightMode = props.fontWeightMode ?? "scale";
|
||||
const fontWeightScale = props.fontWeightScale ?? "normal";
|
||||
if (fontWeightMode === "scale") {
|
||||
weightClass = `pb-font-${fontWeightScale}`;
|
||||
} else if (fontWeightMode === "custom" && props.fontWeightCustom && String(props.fontWeightCustom).trim() !== "") {
|
||||
styleOverrides.fontWeight = props.fontWeightCustom as CSSProperties["fontWeight"];
|
||||
}
|
||||
|
||||
// 색상: colorCustom이 있으면 항상 우선 사용, 없으면 팔레트
|
||||
const colorPalette = props.colorPalette ?? "default";
|
||||
if (props.colorCustom && props.colorCustom.trim() !== "") {
|
||||
styleOverrides.color = props.colorCustom.trim();
|
||||
} else {
|
||||
colorClass = `pb-text-color-${colorPalette}`;
|
||||
}
|
||||
|
||||
// 블록 배경색: backgroundColorCustom 이 설정된 경우 편집기 텍스트 컨테이너 배경에도 반영한다.
|
||||
if (props.backgroundColorCustom && props.backgroundColorCustom.trim() !== "") {
|
||||
styleOverrides.backgroundColor = props.backgroundColorCustom.trim();
|
||||
}
|
||||
|
||||
// 최대 너비: 커스텀 값이 있으면 우선 사용, 없으면 scale 프리셋
|
||||
const maxWidthScale = props.maxWidthScale ?? "none";
|
||||
if (props.maxWidthCustom && props.maxWidthCustom.trim() !== "") {
|
||||
styleOverrides.maxWidth = props.maxWidthCustom.trim();
|
||||
} else if (maxWidthScale === "prose") {
|
||||
maxWidthClass = "pb-text-maxw-prose";
|
||||
} else if (maxWidthScale === "narrow") {
|
||||
maxWidthClass = "pb-text-maxw-narrow";
|
||||
}
|
||||
|
||||
// 글자 간격: 커스텀 값이 있으면 em 단위 그대로 사용
|
||||
if (props.letterSpacingCustom && props.letterSpacingCustom.trim() !== "") {
|
||||
styleOverrides.letterSpacing = props.letterSpacingCustom.trim();
|
||||
}
|
||||
|
||||
// 텍스트 장식: 밑줄/가운데줄/이탤릭
|
||||
if (props.underline) {
|
||||
decorationClass += " pb-underline";
|
||||
}
|
||||
if (props.strike) {
|
||||
decorationClass += " pb-line-through";
|
||||
}
|
||||
if (props.italic) {
|
||||
decorationClass += " pb-italic";
|
||||
}
|
||||
|
||||
return {
|
||||
alignClass,
|
||||
sizeClass,
|
||||
leadingClass,
|
||||
weightClass,
|
||||
colorClass,
|
||||
maxWidthClass,
|
||||
decorationClass,
|
||||
styleOverrides,
|
||||
};
|
||||
}
|
||||
|
||||
export interface TextPublicTokens {
|
||||
alignClass: string;
|
||||
sizeClass: string;
|
||||
styleOverrides: CSSProperties;
|
||||
}
|
||||
|
||||
export function computeTextPublicTokens(props: TextBlockProps): TextPublicTokens {
|
||||
const alignClass =
|
||||
props.align === "center"
|
||||
? "text-center"
|
||||
: props.align === "right"
|
||||
? "text-right"
|
||||
: "text-left";
|
||||
|
||||
const fontSizeMode = props.fontSizeMode ?? "scale";
|
||||
const fallbackScale = props.size === "sm" ? "sm" : props.size === "lg" ? "lg" : "base";
|
||||
const fontSizeScale = props.fontSizeScale ?? fallbackScale;
|
||||
|
||||
const fontSizeScaleToClass: Record<string, string> = {
|
||||
xs: "text-xs",
|
||||
sm: "text-sm",
|
||||
base: "text-base",
|
||||
lg: "text-lg",
|
||||
xl: "text-xl",
|
||||
"2xl": "text-2xl",
|
||||
"3xl": "text-3xl",
|
||||
};
|
||||
|
||||
let sizeClass = "";
|
||||
const styleOverrides: CSSProperties = {};
|
||||
|
||||
if (fontSizeMode === "scale" && fontSizeScale && fontSizeScaleToClass[fontSizeScale]) {
|
||||
sizeClass = fontSizeScaleToClass[fontSizeScale];
|
||||
} else if (
|
||||
fontSizeMode === "custom" &&
|
||||
props.fontSizeCustom &&
|
||||
props.fontSizeCustom.trim() !== ""
|
||||
) {
|
||||
styleOverrides.fontSize = props.fontSizeCustom;
|
||||
}
|
||||
|
||||
if (props.colorCustom && props.colorCustom.trim() !== "") {
|
||||
styleOverrides.color = props.colorCustom.trim();
|
||||
}
|
||||
|
||||
if (props.backgroundColorCustom && props.backgroundColorCustom.trim() !== "") {
|
||||
styleOverrides.backgroundColor = props.backgroundColorCustom.trim();
|
||||
}
|
||||
|
||||
return {
|
||||
alignClass,
|
||||
sizeClass,
|
||||
styleOverrides,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user