UNPKG

responsive-class-variants

Version:
198 lines 7.22 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) => isSingularValue(value) ? mapper(value) : Object.fromEntries(Object.entries(value).map(([breakpoint, value]) => [ breakpoint, mapper(value), ])); // Helper functions for slots const isSlotsConfig = (config) => { return "slots" in config; }; const prefixClasses = (classes, prefix) => classes .split(" ") .map((className) => `${prefix}:${className}`) .join(" "); // Helper function to get variant value for a specific slot or base const getVariantValue = (variants, key, value, slotName) => { const variant = variants?.[key]; const variantValue = variant?.[value]; // Handle string values if (typeof variantValue === "string") { return variantValue; } // Handle object values (slot-specific classes) if (typeof variantValue === "object" && variantValue !== null && slotName && slotName in variantValue) { const slotSpecificValue = variantValue[slotName]; if (typeof slotSpecificValue === "string") { return slotSpecificValue; } } 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); }) .filter(Boolean) .join(" "); }; // 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); }) .filter(Boolean) .join(" "); }; // Helper function to match compound variants const matchesCompoundVariant = (compound, props) => { return Object.entries(compound).every(([key, value]) => props[key] === String(value) || props[key] === value); }; const createSlotFunction = (slotConfig, variants, compoundVariants, onComplete, slotName) => ({ className, ...props } = {}) => { const responsiveClasses = processVariantProps(props, variants, slotName); const compoundClasses = compoundVariants ?.map(({ class: slotClasses, className: compoundClassName, ...compound }) => { if (matchesCompoundVariant(compound, props)) { // If compound variant has slot-specific classes, use those for this slot if (slotClasses && typeof slotClasses === "object" && slotClasses[slotName]) { return slotClasses[slotName]; } // Otherwise use the general className return compoundClassName; } return undefined; }) .filter(Boolean); const classes = clsx(slotConfig, responsiveClasses, compoundClasses, className); 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; const slotFunctions = {}; // Create slot functions for each slot for (const [slotName, slotConfig] of Object.entries(slots)) { slotFunctions[slotName] = createSlotFunction(slotConfig, variants, compoundVariants, onComplete, slotName); } return slotFunctions; } // If config is not a slots config, create a base function const { base, variants, compoundVariants, onComplete } = config; return ({ className, ...props }) => { const responsiveClasses = processVariantProps(props, variants); const compoundClasses = compoundVariants ?.map(({ className: compoundClassName, ...compound }) => { if (matchesCompoundVariant(compound, props)) { return compoundClassName; } return undefined; }) .filter(Boolean); const classes = clsx(base, responsiveClasses, compoundClasses, className); 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