I really like using utility first libraries such as Tailwind, since it allows me to move quickly, uses the idea of composition over inheritance and what's most important: I don't need to worry about being consistent, since it's taken care of by just exposing a few variants of each variable. :)
However, when I was looking over tutorials on how to use TailWind in conjunction with styled-components, I noticed to my horror:
It requires you to set up post-css (with all that extra re-render time when developing and config tweaking)
What I want to achieve
When writing styled-components, I'd like a function that let me parse Tailwind like syntax e.g:
parseUtilsString('bg-blue fc-blue-lighten p-large mt-xl')
which would translate into
background-color: {defined theme blue};
font-color: {defined theme blue, but two increments lighter};
padding: {defined theme large units};
margin-top: {defined theme extra large units};
margin-bottom: {defined theme extra large units};
I would also like to have the option to add extra CSS in the styled component and use the themes variables.
Introducing: tiny-util-first-like-tailwind-sort-of-setup
(I'll probably come up with a better name when this setup matures)
The setup
This is pretty straight-forward: You define your theme variables and import either just the themeParser
or/and the theme
to your component and use it there.
I know you can use a themeProvider in styled-components but writing
font-size: ${props => props.theme.fs.large}
is longer and more cumbersome, than just
font-size: ${theme.fs.large}
(Yeah, I'm lazy or cheap with my characters)
Usage
So how do we make this bird fly? you ask. We'll a snippet sais more than 1000 chars, so here goes:
import React from 'react'
import styled from 'styled-components';
import {themeParse} from '../Styles/theme'
const HeaderStyle = styled.header`
${themeParse('p-l ta-c')}
font-weight: bold;
`;
const TitleStyle = styled.div`
${themeParse('bg-primary-darkest fs-xl ff-primary fc-white')}
span{
${themeParse('fs-s ff-secondary d-b')}
transform-origin: bottom left;
transform: rotate(-10deg) translateY(4em);
}
`;
export default function Header() {
return (
<HeaderStyle>
<TitleStyle>
<span>Welcom to</span>
tiny-util-first-like-tailwind-sort-of-setup
</TitleStyle>
</HeaderStyle>
)
}
which renders into something like this
How to use it
- Copy this pretty snippet below and save it as a file in your project.
- Modify and/or add the properties of
themeStyles
(Perhaps you prefer full names instead of the bootstrap like shorts for all utilities. After alltext-center
is more descriptive thatta-c
). - Add polished to your node_modules (Or comment out the import and write your own shades of color)
- Import it to the component and hack away.
import { lighten, darken } from 'polished';
const units = {
xs: 5,
s: 10,
m: 15,
l: 30,
xl: 50,
};
const fonts = {
primary: 'Open Sans',
secondary: 'Cursive',
};
const fontSizes = {
xs: '.85rem',
s: '1rem',
m: '1.2rem',
l: '1.5rem',
xl: '2rem',
};
const colors = {
primary: _setColorMap('#80C565'),
secondary: _setColorMap('#002B55'),
white: _setColorMap('#ffffff'),
};
const theme = {
unit: units,
color: colors,
fontSize: fontSizes,
font: fonts,
};
// Exported for use of independent values
export default theme;
const displays = {
b: 'block',
i: 'inline',
ib: 'inline-block',
f: 'flex',
if: 'inline-flext',
g: 'grid',
};
const textAligns = {
c: 'center',
l: 'left',
r: 'right',
j: 'justify',
};
const themeStyles = {
fc: _renderVariationStyles('color', colors),
ff: _renderStyleSeries('font-family', fonts, false),
fs: _renderStyleSeries('font-size', fontSizes, false),
bg: _renderVariationStyles('background-color', colors, false),
br: _renderStyleSeries('border-radius', units),
p: _renderStyleSeries('padding', units),
py: _renderStyleSeries(['padding-top', 'padding-bottom'], units),
px: _renderStyleSeries(['padding-left', 'padding-right'], units),
m: _renderStyleSeries('margin', units),
my: _renderStyleSeries(['margin-top', 'margin-bottom'], units),
mx: _renderStyleSeries(['margin-left', 'margin-right'], units),
d: _renderStyleSeries('display', displays, false),
ta: _renderStyleSeries('text-align', textAligns, false),
};
/**
* Parser function for tailwind like syntax
*
* @param {String} atomicString A set of tailwind parameters as a string
*/
function themeParse(atomicString) {
var output = atomicString.split(' ').map((classString) => {
const [first, second, third] = classString.split('-');
// Handle "flat" colors
if (themeStyles[first][second].hasOwnProperty('base') && !third) {
return themeStyles[first][second]['base'];
}
return third
? themeStyles[first][second][third]
: themeStyles[first][second];
});
return output;
}
// Exported for use in components
export { themeParse };
/**
* Renders the styles for a property
*
* @param {Array} styles
* @param {Array} units
* @param {Boolean} isPixleValue
*/
function _renderStyleSeries(styles, units, isPixleValue = true) {
// Let us use either a string value or an array
if (!Array.isArray(styles)) styles = [styles];
let styleSerie = {};
let suffix = isPixleValue ? 'px' : '';
for (const unit in units) {
styleSerie[unit] = ``;
styles.forEach((style) => {
styleSerie[unit] += `${style}: ${units[unit]}${suffix};`;
});
}
return styleSerie;
}
/**
* Renders deep nested values as e.g. 'colors'
*
* @param {Array} styles
* @param {Array} units
*/
function _renderVariationStyles(styles, units) {
// Let us use either a string value or an array
if (!Array.isArray(styles)) styles = [styles];
let styleSerie = {};
for (const unit in units) {
styleSerie[unit] = {};
for (const subUnit in units[unit]) {
if (subUnit === 'toString') continue;
styleSerie[unit][subUnit] = ``;
styles.forEach((style) => {
styleSerie[unit][subUnit] += `${style}: ${units[unit][subUnit]};`;
});
}
}
return styleSerie;
}
/**
* Render a color in different variations; light, lighter, lightest and dark, darker, darkest
* Either just pass a mainColor or a set of preferred values
*
* @param {String} mainColor a color hex value for the standard color
* @param {String} dark
* @param {String} darker
* @param {String} darkest
* @param {String} light
* @param {String} lighter
* @param {String} lightest
*/
function _setColorMap(
mainColor,
dark,
darker,
darkest,
light,
lighter,
lightest
) {
if (!mainColor) throw Error('Main color must be provided');
return {
toString: () => mainColor,
base: mainColor,
dark: dark || darken(0.1, mainColor),
darker: darker || darken(0.2, mainColor),
darkest: darkest || darken(0.4, mainColor),
light: light || lighten(0.1, mainColor),
lighter: lighter || lighten(0.2, mainColor),
lightest: lightest || lighten(0.4, mainColor),
};
}
Ending notes
So, this is something I came up with, but I haven't given it much thought about performance and scaling.
If you have suggestions or opinions (did I just reinvent the wheel or do I did manage to break a working wheel?), - don't be a stranger! Add a comment. :)
Top comments (0)