Boas práticas com Next.Js e Sass
Houve mudanças na forma como iniciamos nossos projetos React e a própria documentação do mesmo recomenda o uso do Next.Js para a criação de novos projetos e com essas mudanças muitos desenvolvedores que estavam habituados com o desenvolvimento React tiveram que utilizar o Vite ou o Next.
Construir aplicações React é bem diferente quando se comparada ao Next, aprendemos sobre Server components e Client components além de outras formas de estilização, este artigo se trata disso. Estilizações com Sass.
Habituado a utilizar React é comum usarmos o styled components para definir todo o nosso design system globalmente e consumi-los em cada pagina ou componente que construimos e é possível utiliza-lo com Next, porém como forma de aprender algo novo procurei aplicar boa parte daquilo que ja temos com o styled components com Sass, mas fique tranquilo não vamos recriar a roda será apenas dicas de como deixar o seu Sass mais limpo e fácil de utiliza-lo.
Classes
A primeira coisa que me incomodou foi aquele amontoado de classes em uma tag HTML.
<div className='content section-animation'>
<h1 className='text headline-plain-text headline-text-wrapper'>Hello</h1>
<button className='sign-in sign-in-text-style'>Entrar</button>
</div>
Me pareceu que estava retrocedendo aquele modelo antigo que criávamos HTML e CSS, e é claro que iria se tornar muito difícil encontrar classes especificas no olho, além de visualmente não ficar nada agradável.
E quando precisarmos aplicar alguma condicional ? “caso o botão for pressionado altere a cor do titulo”, imagino que ficaria mais ou menos assim:
<div className='content section-animation'>
<h1
className={`text headline-plain-text headline-text-wrapper ${
buttonPressed
? 'headline-plan-text-dark'
: 'headline-plan-text-yellow'
}`}>
Hello
</h1>
<button
type='button'
onClick={onPress}
className='sign-in sign-in-text-style'>
Entrar
</button>
</div>
Para ajudar na visualização e condicionais criei um método chamado injectClass
Com o uso dele deixamos o nosso código mais limpo:
import style from './home.module.sass'; // Primeiro importamos o modulo sass
import { injectClass } from '~/utils'; // Helper
const classes = injectClass(style); // Passamos o style para o inject.
Nosso código anterior ficará assim:
<div {...classes(['content', 'section-animation'])}>
<h1
{...classes(['text', 'headline-plain-text', 'headline-text-wrapper'], {
condition: buttonPressed,
whenIsFalse: ['headline-plan-text-yellow'],
whenIsTrue: ['headline-plan-text-dark'],
})}>
Hello
</h1>
<button
type='button'
onClick={onPress}
{...classes(['sign-in', 'sign-in-text-style'])}>
Entrar
</button>
</div>
Além de cada uma das classes se tornar um item em um array o segundo parâmetros do método classes se torna uma condicional onde possui o condition do tipo boleano e os outros dois parâmetros whenIsFalse, whenIsTrue do tipo array que recebem outras classes.
Código do Inject
type InjectClass = {
condition: boolean;
whenIsTrue: string[];
whenIsFalse: string[];
};
export const parsedClass = (classes: string[]) => classes.join(' ');
export const mapClass = (style: Record<string, string>, classes: string[]) => {
return classes.map(it => style[it] || it);
};
export const injectClass = (style: Record<string, string>) => {
return (classes: string[], arg?: InjectClass) => {
const conditionClass = arg?.condition
? arg?.whenIsTrue
: arg?.whenIsFalse || [];
const mappedNormalClass = mapClass(style, classes);
const mappedConditionClass = mapClass(style, conditionClass);
return {
className: parsedClass([...mappedNormalClass, ...mappedConditionClass]),
};
};
};
Se olharmos no quesito quantidade de código escrito é claro que não valeria muito a pena adotar esse formato, porém de acordo com que algumas aplicações crescem e o seu nível de complexidade exigir novas classes e condicionais. Utilizar o inject seria uma solução para deixar seu código mais limpo, e esteticamente agradável.
Definição de Design System
É muito comum em aplicações definirmos o seu design system antes de iniciar o desenvolvimento, definindo cores, espaçamentos, tamanhos e radius. No styled-components temos o Provider, que adiciona essas configurações globalmente e pode ser acessadas via props na própria estilização, algo como:
const Button = styled.button`
color: ${props.colors.button.primary}`;
`;
Com Sass podemos fazer isso utilizando quatro recursos, variáveis, colors, funções e map, resultando em:
@use '~/theme/theme.module'
.button
color: theme.getColor(black)
Então você pode criar um módulo específico para cores, espaçamentos entre outras configurações e consumir de forma bem simples ao estilizar sua aplicação.
Com cores podemos alterar a sua opacidade
@use '~/theme/theme.module'
.button
color: theme.getColor(black, 65%)
Olhando em detalhes como essa função é implementada, primeiro temos o nosso modulo Sass com as cores.
colors.module.sass
$colors: (white: #FAFAFA,yellow: #FFE47C,black: #222222)
Em seguida nosso módulo theme
theme.module.sass
@use 'sass:map'
@use 'sass:color'
@use './colors.module'
@function getColor($color, $lightness: 0%)
@return color.adjust(map.get(colors.$colors, $color), $lightness: $lightness)
Se você conhece bem o Sass sabe que todos esses “Módulos embutidos” são do próprio Sass, como o map, colors e function.
Seguindo esse mesmo modelo é possível aplica-lo a espaçamentos, radius e sizes.
Sizes
Complementando a seção de Design System podemos também utilizar os operadores do Sass para unidades de medida dentro da nossa aplicação, um exemplo bem simples é a conversão de px para rem, apenas para fins de exemplo nossa função ficaria assim:
@function unity($value)
@return #{$value * 0.0625}rem
Para utilizar na estilização ficaria algo como:
@use '~/theme/theme.module'
.button
color: theme.getColor(black, 65%)
width: theme.unity(80)
height: theme.unity(48)
Media Query
Media query é essencial para transformar nossas aplicações responsivas e geralmente utilizamos definindo a @media
diretamente em nosso código para cada dimensões que buscamos atender, repetir as diversas dimensões toda vez que necessário é bem complicado ainda mais quando atendemos mobile, tablet e desktop.
Geralmente o uso se da assim:
.button
color: theme.getColor(black, 65%)
// Mobile
@media (min-width: 328px) and (max-width: 768px)
width: theme.unity(40)
height: theme.unity(28)
// Tablet
@media (min-width: 768px) and (max-width: 1024)
width: theme.unity(60)
height: theme.unity(38)
// Desktop
@media min-width: 1024)
width: theme.unity(80)
height: theme.unity(48)
Já imaginou repetir isso inúmeras vezes em toda a sua aplicação ? e para piorar e se mudar alguma regra na media query de mobile ? teria que alterar em cada uma dessas declarações.
Com os módulos embutidos do Sass podemos construir algo bem simples para evitar repetição e transformar a declaração mais enxutas e caso for necessário mudar alguma regra, será alterado em um único ponto.
Mudando a declaração anterior
@use '~/theme/theme.module'
@use '~/theme/md.module'
.button
color: theme.getColor(black, 65%)
@include md.Mobile
width: theme.unity(40)
height: theme.unity(28)
@include md.Tablet
width: theme.unity(60)
height: theme.unity(38)
@include md.Desktop
width: theme.unity(80)
height: theme.unity(48)
Para quem ainda não domina o intervalo de media query fica muito mais fácil olhar e saber exatamente para qual tipo de device estamos aplicando nosso estilo.
Agora olhando nosso modulo md (Media)
md.module.sass
@use 'sass:map'
$mobile: 328px
$tablet: 768px
$desktop: 1024px
@mixin Mobile
@media (min-width: $mobile) and (max-width: $tablet)
@content
@mixin Tablet
@media (min-width: $tablet) and (max-width: $desktop)
@content
@mixin Desktop
@media (min-width: $desktop)
@content
Algo diferente dos exemplos anteriores foi o uso do mixins que permite a criação de blocos de código que podem ser reutilizados alem do uso do @content
que recebe o conteúdo do bloco.
Essas são apenas algumas dicas que me ajudam muito no dia a dia, espero que seja útil para você também.
Grande abraço
Minhas redes: https://linktr.ee/marcelxsilva