vite-react-stylesheet
Version:
A stylesheet component, which was created in the aim of finding a better solution to CSS in React Vite.
143 lines (121 loc) • 4.88 kB
JavaScript
;
const StyleSheet = {
// Responsive screens.
breakpoints: {
sm: 576, // Small devices
md: 768, // Medium devices
lg: 992, // Large devices
xl: 1200, // Extra large devices
xxl: 1400 // Extra extra large devices
},
// Classname
generateClassName(key) {
return `style-${key}-${Math.random().toString(36).substring(2, 8)}`;
},
// Create style
create(styles) {
let styleSheet = document.getElementById('react-stylesheet');
if (!styleSheet) {
styleSheet = document.createElement('style');
styleSheet.id = 'react-stylesheet';
document.head.appendChild(styleSheet);
}
const classMap = {};
Object.entries(styles).forEach(([styleName, styleObj]) => {
const baseStyles = {};
const responsiveStyles = {};
Object.entries(styleObj).forEach(([prop, value]) => {
if (prop.startsWith('@') || prop === 'media') {
responsiveStyles[prop] = value;
} else {
baseStyles[prop] = value;
}
});
const className = this.generateClassName(styleName);
classMap[styleName] = className;
if (Object.keys(baseStyles).length > 0) {
const cssRules = this.toCSSRules(baseStyles);
const cssText = `.${className} { ${Object.entries(cssRules)
.map(([prop, value]) => `${prop}: ${value};`)
.join(' ')} }`;
try {
styleSheet.sheet.insertRule(cssText, styleSheet.sheet.cssRules.length);
} catch (e) {
console.error("Error inserting CSS rule:", cssText, e);
}
}
Object.entries(responsiveStyles).forEach(([query, queryStyles]) => {
if (query === 'media') {
Object.entries(queryStyles).forEach(([mediaQuery, mediaStyles]) => {
const mediaCssRules = this.toCSSRules(mediaStyles);
const mediaCssText = `@media ${mediaQuery} { .${className} { ${
Object.entries(mediaCssRules)
.map(([prop, value]) => `${prop}: ${value};`)
.join(' ')
} } }`;
try {
styleSheet.sheet.insertRule(mediaCssText, styleSheet.sheet.cssRules.length);
} catch (e) {
console.error("Error inserting media CSS rule:", mediaCssText, e);
}
});
} else if (query.startsWith('@')) {
const breakpoint = query.substring(1);
if (this.breakpoints[breakpoint]) {
const mediaCssRules = this.toCSSRules(queryStyles);
const mediaCssText = `@media (min-width: ${this.breakpoints[breakpoint]}px) { .${className} { ${
Object.entries(mediaCssRules)
.map(([prop, value]) => `${prop}: ${value};`)
.join(' ')
} } }`;
try {
styleSheet.sheet.insertRule(mediaCssText, styleSheet.sheet.cssRules.length);
} catch (e) {
console.error("Error inserting breakpoint CSS rule:", mediaCssText, e);
}
}
}
});
});
return classMap;
},
createResponsive(baseStyles, mediaStyles) {
const result = {};
Object.entries(baseStyles).forEach(([styleName, styles]) => {
result[styleName] = { ...styles };
Object.entries(mediaStyles).forEach(([breakpoint, breakpointStyles]) => {
if (breakpointStyles[styleName]) {
result[styleName][`@${breakpoint}`] = breakpointStyles[styleName];
}
});
});
return this.create(result);
},
compose(...args) {
return args
.filter(Boolean)
.map(arg => {
if (typeof arg === 'string') return arg;
if (typeof arg === 'object') {
return Object.entries(arg)
.filter(([_, condition]) => Boolean(condition))
.map(([className]) => className);
}
return null;
})
.flat()
.filter(Boolean)
.join(' ');
},
toCSSRules(styles) {
return Object.entries(styles).reduce((acc, [prop, value]) => {
const cssProperty = prop.replace(/([A-Z])/g, "-$1").toLowerCase();
const cssValue = typeof value === 'number' &&
!['fontWeight', 'flex', 'opacity', 'zIndex', 'lineHeight'].includes(prop) ?
`${value}px` : value;
acc[cssProperty] = cssValue;
return acc;
}, {});
}
};
export default StyleSheet;