Guía
En esta sección, instalaremos EffCSS y veremos cómo usarlo.
Instalar
Escribe en tu terminal:
# npm
npm i effcss
# pnpm
pnpm add effcss
# yarn
yarn add effcssUsar
Para crear estilos con EffCSS, solo necesitas llamar a las utilidades. Afortunadamente, son muy pocas y su función se entiende fácilmente por su nombre.
Entre todas las utilidades, cabe destacar classNames y attributes. Estas requieren especificar un contrato en forma de un tipo TypeScript con el que se implementarán los selectores. Esto te permitirá controlar tanto la creación como el uso de los estilos. Un tipo de contrato es un objeto con cualquier nivel de anidamiento de propiedades.
/**
* Components stylesheet
*/
type Components = {
/**
* Is rounded
*/
rounded: true;
/**
* Height
*/
h: 'full' | 'half';
/**
* Card
*/
card: {
/**
* Card background
*/
bg: 'primary' | 'secondary';
/**
* Is card disabled
*/
disabled: boolean;
};
/**
* Spinner component
*/
spinner: {};
};
/**
* Utils stylesheet
*/
type Utils = {
/**
* Width
*/
w: 's' | 'm' | 'l';
/**
* Spacing
*/
spacing: 0 | 1 | 2;
/**
* Blink animation
*/
blink: true;
};Otras utilidades derivan los tipos de sus argumentos. Analicemos con más detalle el uso de cada una de ellas.
classNames
className crea una única regla CSS con el contenido especificado y devuelve el selector de clase como una cadena:
import { className } from 'effcss';
// create
const cls = className({
margin: 'auto',
'&:hover': {
outline: '2px solid black',
'.child': {
background: 'grey'
}
}
});
// apply
export const Component = () => {
return <div className={cls}>
Card
</div>
};classNames crea una hoja de estilos y devuelve una función para derivar nombres de clases:
import { classNames } from 'effcss';
// declare
type Card = {
w: 's' | 'm' | 'l';
blur: true;
card: {
variant: 1 | 2;
rounded: true;
};
}
// implement
const card = classNames<Card>((selectors) => {
const {w, card, blur} = selectors;
return {
[w.s]: {
width: '12px'
},
[w.m]: {
width: '24px'
},
[w.l]: {
width: '26px'
},
[blur.true]: {
filter: 'blur(5px)'
},
[card]: {
background: 'white',
border: 'none'
},
[card.variant[1]]: {
width: 'auto',
display: 'block',
padding: '12px',
'&:hover': {
cursor: 'pointer'
}
},
[card.variant[2]]: {
width: 'auto',
display: 'flex',
flexDirection: 'column',
padding: '16px',
'&:hover': {
outline: '2px solid black'
}
},
[card.rounded.true]: {
borderRadius: '1rem'
}
}
});
const cls = card({
card: {
rounded: true
},
w: 's'
});
// apply
export const Component = () => {
return <div className={cls}>
Card
</div>
};lazyClassNames se diferencia de classNames en que ejecuta la función pasada y crea una hoja de estilo después de que los selectores se derivan por primera vez:
import { lazyClassNames } from 'effcss';
// declare
type Card = {/* the same */};
// implement
const card = lazyClassNames<Card>(/* the same */);
// the stylesheet has not been created yet
const cls = card({
card: {
rounded: true
},
w: 's'
});
// the stylesheet has been created
// apply
export const Component = () => {
return <div className={cls}>
Card
</div>
};attributes
attribute crea una única regla CSS con el contenido especificado y devuelve el selector de atributo como un objeto:
import { attribute } from 'effcss';
// create
const attr = attribute({
margin: 'auto',
'&:hover': {
outline: '2px solid black',
'.child': {
background: 'grey'
}
}
});
// apply
export const Component = () => {
return <div {...attr}>
Card
</div>
};attributes crea una hoja de estilo y devuelve una función para derivar atributos:
import { attributes } from 'effcss';
// declare
type Card = {
w: 's' | 'm' | 'l';
blur: true;
card: {
variant: 1 | 2;
rounded: true;
};
}
// implement
const card = attributes<Card>((selectors) => {
const {w, card, blur} = selectors;
return {
[w.s]: {
width: '12px'
},
[w.m]: {
width: '24px'
},
[w.l]: {
width: '26px'
},
[blur.true]: {
filter: 'blur(5px)'
},
[card]: {
background: 'white',
border: 'none'
},
[card.variant[1]]: {
width: 'auto',
display: 'block',
padding: '12px',
'&:hover': {
cursor: 'pointer'
}
},
[card.variant[2]]: {
width: 'auto',
display: 'flex',
flexDirection: 'column',
padding: '16px',
'&:hover': {
outline: '2px solid black'
}
},
[card.rounded.true]: {
borderRadius: '1rem'
}
}
});
const attrs = card({
card: {
rounded: true
},
w: 's'
});
// apply
export const Component = () => {
return <div {...attrs}>
Card
</div>
};lazyAttributes se diferencia de attributes en que ejecuta la función pasada y crea una hoja de estilo después de que los selectores se derivan por primera vez:
import { lazyAttributes } from 'effcss';
// declare
type Card = {/* the same */};
// implement
const card = lazyAttributes<Card>(/* the same */);
// the stylesheet has not been created yet
const attrs = card({
card: {
rounded: true
},
w: 's'
});
// the stylesheet has been created
// apply
export const Component = () => {
return <div {...attrs}>
Card
</div>
};customStyles
customStyles crea una hoja de estilos sin selectores derivados:
import { customStyles } from 'effcss';
// implement
customStyles(() => ({
'.custom': {
background: 'transparent',
width: '100%',
'&:hover': {
outline: '2px solid black'
}
},
'@media screen and (max-width: 768px)': {
'.custom': {
width: '50%'
}
}
}));
// apply
export const Component = () => {
return <div className='custom'>
Card
</div>
};lazyCustomStyles se diferencia de customStyles en que ejecuta la función pasada y crea una hoja de estilos después de la primera llamada al resultado:
import { lazyCustomStyles } from 'effcss';
// implement
const applyStyles = lazyCustomStyles(/* the same */);
// the stylesheet has not been created yet
applyStyles();
// the stylesheet has been created
// apply
export const Component = () => {
return <div className='custom'>
Card
</div>
};variables
variable crea una única regla CSS @property, variables crea varias a la vez:
import { customStyles, variable, variables } from 'effcss';
// global
const offset = variable('10px');
const colors = variable({
primary: {
syntax: 'color',
inherits: false,
initialValue: '#2192a7'
},
secondary: '#425158'
});
customStyles(() => {
// local
const localOffset = variable({
inherits: true,
initialValue: '12px'
});
const localColors = variables({
primary: '#2192a7',
secondary: '#425158'
});
return {
'.global': {
background: colors.primary(),
// with fallback value
padding: offset('8px'),
},
'.local': {
// with fallback value
background: localColors.primary('grey'),
padding: localOffset()
},
'.override': {
[localColors.primary]: 'grey'
}
};
});Puedes obtener y establecer el valor inicial de una variable global utilizando los métodos correspondientes:
const offset = variable('10px');
const colors = variable({
primary: {
syntax: 'color',
inherits: false,
initialValue: '#2192a7'
},
secondary: '#425158'
});
offset.set('14px');
colors.primary.set('grey');
colors.secondary.set('green');
const actualOffsetValue = offset.get();
const actualPrimaryColorValue = colors.primary.get();animations
animation crea una única regla CSS @keyframes, animations crea varias a la vez:
import { customStyles, animation, animations } from 'effcss';
// global
const spin = animation({
from: {
transform: 'rotate(0deg)',
},
to: {
transform: 'rotate(360deg)',
},
});
const blink = animations({
simple: {
'50%': {
visibility: 'hidden'
}
},
smooth: {
'0%': {
opacity: 1
},
'50%': {
opacity: 0
},
'100%': {
opacity: 1
}
}
});
customStyles(() => {
// local
const localSpin = animation(/* the same */);
const localBlink = animations(/* the same */);
return {
'.global-spin': {
animation: `${spin} 6s infinite`
},
'.global-blink': {
animation: `${blink.smooth} 2s infinite`
},
'.local-spin': {
animation: `${localSpin} 6s infinite`
},
'.local-blink': {
animation: `${localBlink.smooth} 2s infinite`
},
};
});layers
layer crea una única regla CSS @layer, layers crea varias a la vez:
import { customStyles, layer, layers } from 'effcss';
// global
const single = layer();
const list = layers(['theme', 'layout', 'utilities']);
customStyles(() => {
// local
const localSingle = layer();
const localList = layers(['theme', 'layout', 'utilities']);
return {
[single]: {
'.global-layer': {
background: 'transparent'
}
},
[list.theme]: {
'.global-layer': {
background: '#425158'
}
},
[localSingle]: {
'.local-layer': {
background: 'white'
}
},
[localList.theme]: {
'.local-layer': {
background: 'grey'
}
}
};
});containers
container crea una única regla CSS @container, containers crea varias a la vez:
import { customStyles, container, containers } from 'effcss';
// global
const single = container();
const multiple = containers({
normal: '',
inline: 'inline-size',
scrollState: 'size scroll-state'
});
customStyles(() => {
// local
const localSingle = container();
const localMultiple = containers({
normal: '',
inline: 'inline-size',
scrollState: 'size scroll-state'
});
return {
'.global-container': {
container: single()
},
[single + ' not scroll-state(scrollable: none)']: {
'.inside-global-container': {
width: '100%'
}
},
'.local-container': {
container: localMultiple.inline()
},
[localMultiple.inline + ' (max-width: 768px)']: {
'.inside-local-container': {
width: '100%'
}
},
};
});fonts
font crea una única regla CSS @font-face, fonts crea varias a la vez:
import { customStyles, font, fonts } from 'effcss';
// global
const single = font({
src: `url("https://mdn.github.io/shared-assets/fonts/FiraSans-Regular.woff2")`,
genericName: 'sans-serif'
});
const multiple = fonts({
primary: {
src: `url("/fonts/roboto-regular.woff2") format("woff2"), url("/fonts/roboto-regular.woff") format("woff")`,
weight: 400,
style: 'normal',
display: 'swap'
},
secondary: {
src: `url("https://mdn.github.io/shared-assets/fonts/FiraSans-Regular.woff2")`
}
});
customStyles(() => {
// local
const localSingle = font(/* the same */);
const localMultiple = fonts(/* the same */);
return {
'.global-font': {
fontFamily: single()
},
'.global-primary-font': {
// `single` as fallback
fontFamily: multiple.primary(single)
},
'.local': {
fontFamily: localSingle()
},
'.local-primary-font': {
// `localSingle` as fallback
fontFamily: localMultiple.primary(localSingle)
}
};
});update
update actualiza el valor inicial de la(s) variable(s) global(es):
const offset = variable('10px');
const colors = variable({
primary: {
syntax: 'color',
inherits: false,
initialValue: '#2192a7'
},
secondary: '#425158'
});
update(offset, '14px');
update(colors, {
primary: 'grey',
secondary: 'green'
});Utilice el método set de la variable siempre que sea posible, ya que es más explícito.
stylesheet
stylesheet devuelve la hoja de estilos creada:
const custom = customStyles(() => {
return {
'.custom': {
padding: '1rem'
},
};
});
// specified stylesheet
const customStylesheet = stylesheet(custom);También existen utilidades que devuelven hojas de estilo especiales:
layersStylesheetdevuelve una hoja de estilo para capas globales,variablesStylesheetdevuelve una hoja de estilo para variables globales,animationsStylesheetdevuelve una hoja de estilo para animaciones globales,fontsStylesheetdevuelve una hoja de estilo para fuentes globales,sharedStylesheetdevuelve una hoja de estilo con reglas globales (creadas medianteclassNameyattribute).
configure
configure afecta a la generación de estilos si se llama antes de que se cree la primera hoja de estilos:
configure({
// custom prefix for ids
prefix: 'custom',
// disable minification
minify: false,
// emulate server-side mode
emulate: true
});serialize
serialize convierte su argumento o todas las hojas de estilo creadas en una cadena HTML:
const custom = customStyles(() => {
return {
'.custom': {
padding: '1rem'
},
};
});
// specified stylesheet
const customHTML = serialize(custom);
// all created stylesheets
const fullHTML = serialize();serializeMeta serializa los metadatos de su argumento o los metadatos de todas las hojas de estilo creadas en una cadena HTML:
import { classNames } from 'effcss';
type Card = {/* the same */};
const card = classNames<Card>(/* the same */);
// specified stylesheet metadata
const cardMetaHTML = serializeMeta(card);
// all created stylesheets metadata
const fullMetaHTML = serialize();De esta forma, los estilos y metadatos se pueden calcular en el servidor y reutilizar en el cliente. Esto resulta especialmente útil para SSG/SSR.