jacaranda
Version:
A lightweight styling library for React Native
222 lines (220 loc) • 9.19 kB
JavaScript
/**
* 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