UNPKG

@revenuecat/purchases-ui-js

Version:

Web components for Paywalls. Powered by RevenueCat

265 lines (264 loc) 10.5 kB
import { getTextComponentStyles } from "../text/text-utils"; import { getActiveStateProps, getComponentStyles, getDimensionStyle, getInsetStyles, getSizeStyle, prefixObject, stringifyStyles, } from "../../utils/style-utils"; /** * Generates comprehensive styles for stack components by combining component, dimension and size styles * @param props - Stack component properties including background, spacing, size, border etc. * @returns CSS style variables object with all stack-specific styles */ export const getStackComponentStyles = (props) => { const { background_color, margin, padding, spacing, size, border, shape, shadow, dimension, componentState, overrides, purchaseState, zStackChildStyles, } = props; const stackStyles = { "--flex": "0 1 auto", "--height": "initial", "--width": "initial", "--spacing": `${spacing || 0}px`, "--direction": "unset", "--alignment": "unset", "--distribution": "flex-start", "--margin-block-start": "0px", "--margin-inline-end": "0px", "--margin-block-end": "0px", "--margin-inline-start": "0px", "--padding-block-start": "0px", "--padding-inline-end": "0px", "--padding-block-end": "0px", "--padding-inline-start": "0px", "--background": "unset", "--text-color": "inherit", "--border": "none", "--border-end-start-radius": "0px", "--border-end-end-radius": "0px", "--border-start-start-radius": "0px", "--border-start-end-radius": "0px", "--shadow": "none", "--position": "relative", "--transform": "initial", "--inset": "initial", }; const activeStateProps = getActiveStateProps(overrides, componentState); Object.assign(stackStyles, getComponentStyles({ background_color, margin, padding, border, colorMode: purchaseState.colorMode, shape, shadow, ...activeStateProps, }), getDimensionStyle({ ...dimension, ...activeStateProps, }), getSizeStyle({ ...size, ...activeStateProps })); Object.assign(stackStyles, zStackChildStyles); const prefixedStyles = prefixObject(stackStyles, "stack"); return stringifyStyles(prefixedStyles); }; /** * Generates styles for badge component within a stack * @param props - Stack component properties containing badge configuration * @returns CSS style object with badge-specific styles including positioning, dimensions and appearance */ export function getBadgeStyles(props) { const { badge } = props; if (!badge) return ""; const styles = { "--inset": "unset", "--transform": "unset", "--margin-block-start": "0px", "--margin-inline-end": "0px", "--margin-block-end": "0px", "--margin-inline-start": "0px", "--padding-block-start": "0px", "--padding-inline-end": "0px", "--padding-block-end": "0px", "--padding-inline-start": "0px", "--background": "unset", "--text-color": "inherit", "--border": "none", "--border-end-start-radius": "0px", "--border-end-end-radius": "0px", "--border-start-start-radius": "0px", "--border-start-end-radius": "0px", "--shadow": "none", "--badge-width": "unset", "--z-index": "unset", }; // Calculating this as a zIndex alignment since it behaves in the same way // except there's no center alignment Object.assign(styles, getInsetStyles({ type: "zlayer", alignment: badge.alignment, }), getComponentStyles({ ...badge, background_color: badge.background_color, color: badge.color, colorMode: props.purchaseState.colorMode, })); if (badge.style === "overlay") { // Disable vertical margin for nested & center aligned badges styles["--margin-block-start"] = "0px"; styles["--margin-block-end"] = "0px"; // Vertically center the badge to the edge of the stack styles["--transform"] = getBadgeTransformStyles(badge, 0, -(props.border?.width || 0)); } if (badge.style === "edge_to_edge") { styles["--margin-block-start"] = "0px"; styles["--margin-block-end"] = "0px"; styles["--margin-inline-start"] = "0px"; styles["--margin-inline-end"] = "0px"; // Shared props between top and bottom if (badge.alignment === "top" || badge.alignment === "bottom") { styles["--badge-width"] = `calc(100% + ${(props.border?.width || 0) * 2}px)`; styles["--z-index"] = "-1"; const highestRadius = Math.max(props.shape?.corners?.top_leading || 0, props.shape?.corners?.top_trailing || 0); // one offs for top and bottom if (badge.alignment === "top") { styles["--padding-block-end"] = `${highestRadius + badge.padding.bottom}px`; styles["--transform"] = getBadgeTransformStyles(badge, highestRadius); } if (badge.alignment === "bottom") { styles["--padding-block-start"] = `${highestRadius + badge.padding.top}px`; styles["--transform"] = getBadgeTransformStyles(badge, -highestRadius); } } else { styles["--transform"] = getBadgeTransformStyles(badge); Object.assign(styles, getBadgeBorderRadiusStyles(props)); } } const prefixedStyles = prefixObject(styles, "stack-badge"); return stringifyStyles(prefixedStyles); } const oppositeCornerDictionary = { top_leading: "bottom_trailing", top_trailing: "bottom_leading", bottom_leading: "top_trailing", bottom_trailing: "top_leading", }; const propToCssPropDictionary = { top_leading: "--border-start-start-radius", top_trailing: "--border-start-end-radius", bottom_leading: "--border-end-start-radius", bottom_trailing: "--border-end-end-radius", }; function getBadgeBorderRadiusStyles(props) { const styles = { "--border-end-start-radius": "0px", "--border-end-end-radius": "0px", "--border-start-start-radius": "0px", "--border-start-end-radius": "0px", }; const { badge } = props; if (!badge) return styles; const borderWidth = props.border?.width || 0; const badgeAlignment = badge.alignment; // badge inner radius styles[propToCssPropDictionary[oppositeCornerDictionary[badgeAlignment]]] = `${badge.shape?.corners[oppositeCornerDictionary[badgeAlignment]]}px`; // badge outer radius styles[propToCssPropDictionary[badgeAlignment]] = `${props.shape?.corners[badgeAlignment] - borderWidth}px`; return styles; } /** * Generates CSS transform styles for badge positioning * @param badge - Badge configuration from StackProps * @param verticalOffset - Optional vertical offset in pixels for edge-to-edge badges * @returns CSS transform string for badge positioning */ function getBadgeTransformStyles(badge, verticalOffset = 0, horizontalOffset = 0) { if (!badge) return ""; if (badge.style === "overlay") { if (badge.alignment === "top_leading") { return `translate(${horizontalOffset}px, calc(-50% + ${verticalOffset}px))`; } if (badge.alignment === "top_trailing") { return `translate(calc(0% - ${horizontalOffset}px), calc(-50% + ${verticalOffset}px))`; } if (badge.alignment === "bottom_leading") { return `translate(${horizontalOffset}px, calc(50% + ${verticalOffset}px))`; } if (badge.alignment === "bottom_trailing") { return `translate(calc(0% - ${horizontalOffset}px), calc(50% + ${verticalOffset}px))`; } if (badge.alignment === "top") { return `translate(calc(-50% + ${horizontalOffset}px), calc(-50% + ${verticalOffset}px))`; } if (badge.alignment === "bottom") { return `translate(calc(50% + ${horizontalOffset}px), calc(50% + ${verticalOffset}px))`; } } if (badge.style === "edge_to_edge") { if (badge.alignment === "top") { return `translate(calc(-50% + ${horizontalOffset}px), calc(-100% - ${verticalOffset}px + 16px))`; } else if (badge.alignment === "bottom") { return `translate(calc(50% + ${horizontalOffset}px), calc(100% + ${verticalOffset}px))`; } } return ""; } /** * Generates text styles and HTML tag for a stack's badge component * @param props - Stack component properties containing badge configuration * @returns Object containing: * - tagToRender: HTML tag to use for the badge text * - textStyles: CSS styles string for the badge text */ export function getStackBadgeTextStyles(props) { const { badge } = props; if (!badge) return { tagToRender: "", textStyles: "" }; const { tagToRender, textStyles } = getTextComponentStyles({ id: props.id, labels: props.labels, purchaseState: props.purchaseState, ...badge, components: [], type: "text", size: { width: { type: "fit" }, height: { type: "fit" }, }, color: badge?.color, name: props.name, }); const resetSpacing = { "--text-margin-block-start": "0px", "--text-margin-inline-end": "0px", "--text-margin-block-end": "0px", "--text-margin-inline-start": "0px", "--text-padding-block-start": "0px", "--text-padding-inline-end": "0px", "--text-padding-block-end": "0px", "--text-padding-inline-start": "0px", }; const stylesObject = { ...textStyles, ...resetSpacing, }; const stringifiedStyles = stringifyStyles(stylesObject); return { tagToRender, textStyles: stringifiedStyles, }; } export function getZStackChildStyles(props) { const { dimension } = props; const baseStyles = { "--inset": "initial", "--transform": "initial", "--position": "relative", }; if (dimension.type !== "zlayer") return; const insetStyles = getInsetStyles({ ...dimension, type: "zlayer" }); Object.assign(baseStyles, { ...insetStyles, "--position": "absolute" }); return baseStyles; }