UNPKG

responsive-class-variants

Version:
208 lines 7.82 kB
import { clsx } from "clsx"; const isSingularValue = (value) => !isBreakpointsMap(value); const isBreakpointsMap = (value) => typeof value === "object" && value != null && !Array.isArray(value); /** * Maps a ResponsiveValue to a new ResponsiveValue using the provided mapper function. Singular values are passed through as is. * * @template V The type of the original value * @template T The type of the mapped value * @template B The type of breakpoints * @param {ResponsiveValue<V, B>} value - The original ResponsiveValue to be mapped * @param {function(V): T} mapper - A function that maps a ResponsiveValue to a new ResponsiveValue * @returns {ResponsiveValue<T, B>} A new ResponsiveValue with the mapped values * * * @example * const sizes = { * initial: 'md', * sm: 'lg', * } * * const output = mapResponsiveValue(sizes, size => { * switch (size) { * case 'initial': * return 'sm'; * case 'sm': * return 'md'; * } * }); * * // console.log(output) * { * initial: 'sm', * sm: 'md', * } */ export const mapResponsiveValue = (value, mapper) => { if (isSingularValue(value)) { return mapper(value); } const result = {}; for (const key of Object.keys(value)) { result[key] = mapper(value[key]); } return result; }; // Helper functions for slots const isSlotsConfig = (config) => { return "slots" in config; }; const normalizeClassValue = (value) => { if (Array.isArray(value)) { return value.join(" "); } if (typeof value === "string") { return value; } return undefined; }; const prefixClasses = (classes, prefix) => classes.replace(/(\S+)/g, `${prefix}:$1`); // Helper function to get variant value for a specific slot or base const getVariantValue = (variants, key, value, slotName) => { const variantValue = variants?.[key]?.[value]; // Early return if no variant value found if (variantValue == null) return undefined; // Handle string or array values directly if (typeof variantValue === "string" || Array.isArray(variantValue)) { return normalizeClassValue(variantValue); } // Handle slot-specific values (object with slot keys) if (slotName && slotName in variantValue) { return normalizeClassValue(variantValue[slotName]); } return undefined; }; // Helper function to process responsive values const processResponsiveValue = (variants, key, value, slotName) => { return Object.entries(value).map(([breakpoint, breakpointValue]) => { const variantValue = getVariantValue(variants, key, breakpointValue, slotName); if (!variantValue) return undefined; // If the breakpoint is initial, return without prefix if (breakpoint === "initial") { return variantValue; } // Otherwise, return with breakpoint prefix return prefixClasses(variantValue, breakpoint); }); }; // Helper function to process variant props into classes const processVariantProps = (props, variants, slotName) => { return Object.entries(props).map(([key, propValue]) => { const value = typeof propValue === "boolean" ? String(propValue) : propValue; // Handle undefined values if (!value) return undefined; // Handle singular values if (typeof value === "string") { return getVariantValue(variants, key, value, slotName); } // Handle responsive values return processResponsiveValue(variants, key, value, slotName); }); }; // Helper function to match compound variants const matchesCompoundVariant = (compound, props) => { return Object.entries(compound).every(([key, value]) => { const propValue = props[key]; // Direct comparison first, then try string conversion for boolean handling return propValue === value || propValue === String(value); }); }; // Helper function to extract class value from compound variant class prop const getCompoundVariantSlotClass = (classValue, slotName) => { if (!classValue) return undefined; if (typeof classValue === "object" && classValue[slotName]) { return normalizeClassValue(classValue[slotName]); } if (typeof classValue === "string") { return classValue; } return undefined; }; const createSlotFunction = (slotConfig, variants, compoundVariants, onComplete, slotName) => ({ className, class: classFromProps, ...props } = {}) => { const responsiveClasses = processVariantProps(props, variants, slotName); const compoundClasses = compoundVariants?.map(({ class: classFromCompound, className: classNameFromCompound, ...compound }) => { if (matchesCompoundVariant(compound, props)) { return [ getCompoundVariantSlotClass(classFromCompound, slotName), getCompoundVariantSlotClass(classNameFromCompound, slotName), ]; } return undefined; }); const classes = clsx(slotConfig, responsiveClasses, compoundClasses, className, classFromProps); return onComplete ? onComplete(classes) : classes; }; export function rcv(config) { // Check if config is a slots config if (isSlotsConfig(config)) { const { slots, variants, compoundVariants, onComplete } = config; return () => { const slotFunctions = {}; // Create slot functions for each slot - ensure all slots are always present for (const [slotName, slotConfig] of Object.entries(slots)) { const slotFunction = createSlotFunction(slotConfig, variants, compoundVariants, onComplete, slotName); slotFunctions[slotName] = slotFunction; } return slotFunctions; }; } // If config is not a slots config, create a base function const { base, variants, compoundVariants, onComplete } = config; return ({ className, class: classFromProps, ...props } = {}) => { const responsiveClasses = processVariantProps(props, variants); const compoundClasses = compoundVariants?.map(({ className: compoundClassName, ...compound }) => { if (matchesCompoundVariant(compound, props)) { return compoundClassName; } return undefined; }); const classes = clsx(base, responsiveClasses, compoundClasses, className, classFromProps); return onComplete ? onComplete(classes) : classes; }; } /** * Creates a custom rcv function with custom breakpoints and an optional onComplete callback * * @template B - The custom breakpoints type * @param breakpoints - Optional array of custom breakpoint names * @param onComplete - Optional callback function that receives the generated classes and returns the final classes * @returns A function that creates rcv with custom breakpoints * * @example * const customRcv = createRcv(['mobile', 'tablet', 'desktop']); * * const getButtonVariants = customRcv({ * base: "px-4 py-2 rounded", * variants: { * intent: { * primary: "bg-blue-500 text-white", * secondary: "bg-gray-200 text-gray-800" * } * } * }); * * // Usage with custom breakpoints: * getButtonVariants({ intent: { initial: "primary", mobile: "secondary", desktop: "primary" } }) */ export const createRcv = (_breakpoints, onComplete) => { function customRcv(config) { if (isSlotsConfig(config)) { return rcv({ ...config, onComplete: onComplete || config.onComplete, }); } else { return rcv({ ...config, onComplete: onComplete || config.onComplete, }); } } return customRcv; }; //# sourceMappingURL=index.js.map