# Boas práticas com Next.js e Sass

> Organizando estilos em projetos Next.js: classes condicionais, design system com maps e funções, e mixins de media query.

- Autor: Marcelo Silva
- Publicado em: 2023-05-21
- Tema: Web
- URL: https://marcelxsilva.dev/artigos/boas-praticas-com-nextjs-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](https://vitejs.dev/) ou o [Next](https://nextjs.org/).

Construir aplicações React é bem diferente quando se comparada ao Next, aprendemos sobre [Server components](https://nextjs.org/docs/getting-started/react-essentials#server-components) e [Client components](https://nextjs.org/docs/getting-started/react-essentials#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](https://sass-lang.com/documentation/variables), [colors](https://sass-lang.com/documentation/values/colors), [funções](https://sass-lang.com/documentation/values/functions) e [map](https://sass-lang.com/documentation/values/maps), 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](https://sass-lang.com/documentation/modules)” 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 @mediadiretamente 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](https://sass-lang.com/documentation/at-rules/mixin) 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
