UNPKG

jacaranda

Version:

A lightweight styling library for React Native

222 lines (220 loc) 9.19 kB
/** * ISC License * Copyright 2025 Javier Diaz Chamorro * * Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby * granted, provided that the above copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, * DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE * OR PERFORMANCE OF THIS SOFTWARE. */ // Example of usage: // const buttonStyles = style({ // base: { // display: 'flex', // }, // variants: { // visual: { // solid: { backgroundColor: '#FC8181', color: 'white' }, // outline: { borderWidth: 1, borderColor: '#FC8181' }, // }, // size: { // sm: { padding: 4, fontSize: 12 }, // lg: { padding: 8, fontSize: 24 }, // }, // }, // // Optional: Add compound variants for specific combinations // compoundVariants: [ // { // variants: { visual: 'solid', size: ['lg'] }, // style: { fontWeight: 'bold' }, // }, // ], // defaultVariants: { // visual: 'solid', // size: 'sm', // } // }); // Example of using a styled component with a custom prop // const button = styled(Button)<{ otherProp: string }>((props) => { // position: props.otherProp ? 'absolute' : 'relative'; // }); import React from "react"; import { StyleSheet } from "react-native"; /** * Creates a function that generates styles based on variants */ function styles(config) { return (props)=>{ // We'll build up styles in the correct hierarchy: base → variants → compound variants // Base styles (lowest priority) let stylesObj = { ...config.base || {} }; // Merge default variants with provided props const mergedProps = { ...config.defaultVariants || {}, ...props }; // Apply variant styles (overrides base styles) for (const [propName, value] of Object.entries(mergedProps)){ const variantGroup = config.variants[propName]; if (variantGroup) { // Handle boolean variants if (typeof value === "boolean") { const booleanValue = value ? "true" : "false"; if (variantGroup[booleanValue]) { stylesObj = { ...stylesObj, ...variantGroup[booleanValue] }; } } else { var _config_defaultVariants; // Handle string/enum variants const variantValue = value || ((_config_defaultVariants = config.defaultVariants) === null || _config_defaultVariants === void 0 ? void 0 : _config_defaultVariants[propName]); if (variantValue && variantGroup[variantValue]) { stylesObj = { ...stylesObj, ...variantGroup[variantValue] }; } } } } // Apply compound variants (highest priority before inline styles) if (config.compoundVariants) { for (const compound of config.compoundVariants){ if (Object.entries(compound.variants).every((param)=>{ let [propName, value] = param; // Handle boolean values in compound variants if (typeof value === "boolean") { return mergedProps[propName] === value; } // Handle array of values if (Array.isArray(value)) { return value.includes(mergedProps[propName]); } // Handle single value (string/enum) return mergedProps[propName] === value; })) { stylesObj = { ...stylesObj, ...compound.style }; } } } // Create a StyleSheet for better performance return StyleSheet.create({ style: stylesObj }).style; }; } // Helper to resolve token references in style objects function resolveTokens(style, tokens) { return Object.entries(style).reduce((acc, param)=>{ let [key, value] = param; if (typeof value !== "string" || !value.startsWith("$")) { acc[key] = value; return acc; } const tokenPath = value.slice(1).split("."); const [category, token] = tokenPath; const tokenCategory = tokens[category]; const tokenValue = tokenCategory === null || tokenCategory === void 0 ? void 0 : tokenCategory[token]; if (tokenValue !== undefined) { acc[key] = tokenValue; } return acc; }, {}); } /** * Creates a token system and returns the styles function */ export function defineTokens(tokenConfig) { // Create the tokens object const tokens = Object.entries(tokenConfig).reduce((acc, param)=>{ let [category, values] = param; return { ...acc, [category]: values }; }, {}); // Create a wrapped styles function that resolves token references const sva = (config)=>{ var _config_compoundVariants; // Resolve tokens in base styles const resolvedBase = config.base ? resolveTokens(config.base, tokens) : config.base; // Resolve tokens in variants const resolvedVariants = config.variants ? Object.entries(config.variants).reduce((acc, param)=>{ let [key, variantGroup] = param; const resolvedGroup = Object.entries(variantGroup).reduce((groupAcc, param)=>{ let [variantKey, variantStyles] = param; return { ...groupAcc, [variantKey]: resolveTokens(variantStyles, tokens) }; }, {}); return { ...acc, [key]: resolvedGroup }; }, {}) : {}; // Resolve tokens in compound variants const resolvedCompoundVariants = (_config_compoundVariants = config.compoundVariants) === null || _config_compoundVariants === void 0 ? void 0 : _config_compoundVariants.map((compound)=>({ ...compound, style: resolveTokens(compound.style, tokens) })); // Return the styles function with resolved tokens return styles({ ...config, base: resolvedBase, variants: resolvedVariants, compoundVariants: resolvedCompoundVariants }); }; /** * Styled function for creating styled components with token-aware styles */ const styled = (Component)=>{ return (styleObject)=>{ // Create and return a new component that applies the resolved styles const StyledComponent = (props)=>{ const { children, style: propStyle, ...rest } = props; // If styleObject is a function, call it with props to get the style object const styleToResolve = typeof styleObject === "function" ? styleObject(props) : styleObject; // Resolve tokens in the style object const resolvedStyle = resolveTokens(styleToResolve, tokens); // Create StyleSheet for the resolved style const styles = StyleSheet.create({ style: resolvedStyle }); // Styles hierarchy: base styles → variants → compound variants → inline styles // Inline styles (propStyle) must have highest priority, so they come last in the array const mergedStyle = propStyle ? [ styles.style, ...Array.isArray(propStyle) ? propStyle : [ propStyle ] ] : styles.style; // We need to cast here to handle the component props correctly const componentProps = { ...rest, style: mergedStyle }; // Use createElement instead of JSX to avoid syntax issues return React.createElement(Component, componentProps, children); }; // Set display name for better debugging const componentName = Component.displayName || Component.name || "Component"; StyledComponent.displayName = `Styled(${componentName})`; return StyledComponent; }; }; return { sva, tokens, styled }; } //# sourceMappingURL=index.mjs.map