rootzz-layout
v0.1.9
Published
Shell de aplicação (Topbar + Sidebar + Content) da Eduzz para React. Leve, sem dependências de runtime e em CSS puro (sem Tailwind no consumidor).
Maintainers
Readme
rootzz-layout
Shell de aplicação da Eduzz para React: Topbar + Sidebar + área de conteúdo. Leve (zero dependências de runtime), com tema claro/escuro, drawer mobile, e sem conflitar com o Tailwind (ou qualquer CSS) do projeto consumidor.
Para agentes de IA: a API é composta. Use
<RootzzLayout>como casca eRootzzLayout.Topbar,RootzzLayout.Sidebar,RootzzLayout.Contentcomo filhos — cada um com suas próprias props. Copie o exemplo de Uso rápido, troque os dados. Todas as props têm JSDoc — confie no autocompletar/tipos.
Por que não quebra o seu projeto
- CSS já compilado e prefixado. Você importa um arquivo (
rootzz-layout/styles.css). Não precisa de Tailwind, PostCSS ou config no seu projeto. - Classes prefixadas com
rzl-→ não colidem comflex,bg-*, etc. do seu app. - Tudo escopado em
.rootzz-layout(inclusive os defaults internos do Tailwind). Nada vaza para fora do shell. O reset do Tailwind (preflight) não é incluído. - Customização por CSS variables (
--rzl-*) — sem precisar tocar no seu build.
Instalação
pnpm add rootzz-layout
# ou: npm i rootzz-layout / yarn add rootzz-layoutreact e react-dom (>= 17) são peer dependencies. Importe o CSS uma vez na raiz do app:
import 'rootzz-layout/styles.css';Builds suportados: ESM (.mjs), CJS (.cjs) e tipos (.d.ts). Funciona em Vite, Next.js (App e Pages Router), CRA, etc. Os componentes já vêm com a diretiva "use client" (Next App Router).
Uso rápido
import { RootzzLayout, SearchBar, IconButton } from 'rootzz-layout';
import 'rootzz-layout/styles.css';
export default function App() {
return (
<RootzzLayout>
<RootzzLayout.Topbar
logo={{ src: 'https://cdn.eduzzcdn.com/topbar/orbita-new.svg', alt: 'MyEduzz', href: '/' }}
apps // botão de apps (busca a lista oficial da Eduzz no CDN)
showThemeToggle // botão de tema claro/escuro
search={<SearchBar placeholder="Buscar…" shortcut="⌘K" />}
actions={<IconButton aria-label="Notificações" badge={8}>{/* ícone */}</IconButton>}
user={{
name: 'Designers da Eduzz',
initials: 'Dd',
menu: [
{ label: 'Meu perfil', onClick: () => {} },
{ type: 'divider' },
{ label: 'Sair', danger: true, onClick: () => {} },
],
}}
/>
<RootzzLayout.Sidebar
activeKey="resumo"
menu={[
{ key: 'resumo', label: 'Resumo', href: '/' },
{ key: 'compras', label: 'Minhas Compras', href: '/compras' },
{
type: 'group',
key: 'vendas',
label: 'Vendas',
defaultCollapsed: true,
items: [{ key: 'pedidos', label: 'Pedidos', href: '/pedidos' }],
},
]}
/>
<RootzzLayout.Content>
<h1>Novo produto</h1>
</RootzzLayout.Content>
</RootzzLayout>
);
}A ordem dos filhos não importa — a Topbar fica sempre no topo e a Sidebar à esquerda do conteúdo.
Cada subcomponente também é exportado individualmente (Topbar, Sidebar, Content).
API
<RootzzLayout> — casca (configs de nível de shell)
| Prop | Tipo | Padrão | Descrição |
| --- | --- | --- | --- |
| children | ReactNode | — | Os subcomponentes: .Topbar, .Sidebar, .Content. |
| theme | 'light' \| 'dark' | — | Tema controlado (você gerencia via onThemeChange). |
| defaultTheme | 'light' \| 'dark' | 'light' | Tema inicial quando não-controlado. |
| onThemeChange | (theme) => void | — | Disparado quando o tema muda. |
| linkComponent | ElementType | 'a' | Componente de link (topbar + sidebar). Passe o Link do seu router para SPA. |
| className / style / id | — | — | Aplicados ao elemento raiz .rootzz-layout. Use style para sobrescrever tokens. |
<RootzzLayout.Topbar>
| Prop | Tipo | Padrão | Descrição |
| --- | --- | --- | --- |
| logo | LogoConfig \| ReactNode | — | {src, alt?, href?, onClick?, height?} ou um nó React. |
| apps | boolean \| string \| AppItem[] \| {url?, items?} | — | true busca a lista oficial no CDN; array = lista estática; string/{url} busca de uma URL; false/omitido esconde. |
| search | ReactNode | — | Barra de busca opcional (centro). Use <SearchBar> ou um nó próprio. |
| actions | ReactNode | — | Ícones/ações custom à direita. Use <IconButton>. |
| user | UserConfig | — | {name, email?, avatarUrl?, initials?, menu?, onClick?}. |
| showThemeToggle | boolean | false | Exibe o botão de tema claro/escuro. |
| showMenuButton | boolean | true | Exibe o hambúrguer no mobile (desligue se não houver Sidebar). |
| appsLabel / menuButtonLabel | string | — | Rótulos acessíveis. |
<RootzzLayout.Sidebar>
| Prop | Tipo | Padrão | Descrição |
| --- | --- | --- | --- |
| menu | MenuItem[] | — | Itens (links, grupos colapsáveis, divisores). |
| activeKey | string | — | key do item ativo. |
| header / footer | ReactNode | — | Conteúdo fixo no topo / rodapé. |
| mobileTitle | ReactNode | — | Título exibido no topo do drawer mobile. |
| children | ReactNode | — | Conteúdo custom (substitui menu). |
| ariaLabel / className | — | — | Acessibilidade / classe extra. |
<RootzzLayout.Content>
| Prop | Tipo | Padrão | Descrição |
| --- | --- | --- | --- |
| children | ReactNode | — | Conteúdo da página. |
| maxWidth | number \| string \| false | token | Largura máx. false = 100%. Omitido usa --rzl-content-max-width. |
| noPadding | boolean | false | Remove o padding padrão. |
| className | string | — | Classe extra no wrapper interno. |
Tipos do menu
// Item simples (link ou botão)
{ key: string; label: ReactNode; icon?: ReactNode; href?: string; target?: string;
onClick?: (e) => void; active?: boolean; badge?: ReactNode; disabled?: boolean }
// Grupo colapsável
{ type: 'group'; key: string; label: ReactNode; icon?: ReactNode; items: MenuLink[];
defaultCollapsed?: boolean; collapsible?: boolean }
// Divisor
{ type: 'divider'; key?: string }O item ativo é resolvido por Sidebar.activeKey === item.key ou item.active === true.
Customização (tema)
Sobrescreva as CSS variables --rzl-* no seu CSS:
.rootzz-layout {
--rzl-primary: #6d2bcf;
--rzl-sidebar-width: 280px;
--rzl-radius: 12px;
--rzl-duration: 160ms;
--rzl-font: 'Inter', sans-serif;
}…ou pontualmente via style:
<RootzzLayout style={{ ['--rzl-primary' as string]: '#10b981' }}>…</RootzzLayout>Tokens disponíveis
| Token | Padrão (claro) | Uso |
| --- | --- | --- |
| --rzl-primary / --rzl-primary-hover / --rzl-primary-contrast | #2b4acf / #2540b5 / #fff | Cor de marca |
| --rzl-active-bg / --rzl-active-fg | #eef1fc / #2b4acf | Item ativo da sidebar |
| --rzl-bg / --rzl-surface | #fff / #fff | Conteúdo / topbar e sidebar |
| --rzl-fg / --rzl-fg-muted / --rzl-fg-subtle | #0f1324 / 65% / 50% | Textos |
| --rzl-border / --rzl-hover-bg | #e5e7eb / 4.5% | Linhas / hover |
| --rzl-danger / --rzl-danger-bg | #e11d48 / 8% | Ações destrutivas |
| --rzl-badge-bg / --rzl-badge-fg | #e11d48 / #fff | Badge (ex.: sino) |
| --rzl-overlay | rgba(15,19,36,.45) | Overlay mobile |
| --rzl-topbar-height / --rzl-sidebar-width / --rzl-content-max-width | 60px / 260px / 1200px | Dimensões |
| --rzl-radius / --rzl-radius-lg | 8px / 12px | Arredondamento |
| --rzl-duration / --rzl-ease | 200ms / cubic-bezier(.4,0,.2,1) | Movimento |
| --rzl-font | Google Sans + fallback | Fonte |
O tema escuro redefine esses tokens em .rootzz-layout[data-theme="dark"].
Animações respeitam prefers-reduced-motion.
Tema claro/escuro
O estado do tema fica no <RootzzLayout>; o botão de alternância (showThemeToggle) fica na Topbar.
// Não-controlado (a lib gerencia)
<RootzzLayout defaultTheme="light">
<RootzzLayout.Topbar showThemeToggle … />
…
</RootzzLayout>
// Controlado (você gerencia o estado)
const [theme, setTheme] = useState<'light' | 'dark'>('light');
<RootzzLayout theme={theme} onThemeChange={setTheme}>
<RootzzLayout.Topbar showThemeToggle … />
…
</RootzzLayout>Integração com router (SPA)
Por padrão os links são <a>. Para navegação SPA, passe o Link do seu router no <RootzzLayout>:
// Next.js
import Link from 'next/link';
<RootzzLayout linkComponent={Link}>…</RootzzLayout>
// React Router v6
import { Link } from 'react-router-dom';
const RouterLink = ({ href, ...p }) => <Link to={href} {...p} />;
<RootzzLayout linkComponent={RouterLink}>…</RootzzLayout>Menu de aplicativos
<RootzzLayout.Topbar apps /> // lista oficial da Eduzz (CDN)
<RootzzLayout.Topbar apps={[{ label: 'App', icon: '/i.svg', url: 'https://…' }]} /> // estática
<RootzzLayout.Topbar apps="https://meu-cdn/apps.json" /> // URL custom
<RootzzLayout.Topbar apps={false} /> // sem botão de appsFormato de AppItem: { application?, label, icon, description?, url }.
<SearchBar> e <IconButton>
Helpers para a Topbar (também exportados):
<RootzzLayout.Topbar
search={<SearchBar placeholder="Buscar…" shortcut="⌘K" value={q} onChange={e => setQ(e.target.value)} />}
actions={<IconButton aria-label="Notificações" badge={8}>{<MeuIconeSino />}</IconButton>}
/>Outros exports: LayoutProvider, hooks useLayout e useApps, util cx, e todos os tipos.
Mobile
Abaixo de 768px: a sidebar vira drawer com botão hambúrguer na topbar, overlay com fade, scroll travado e fechamento por clique no overlay, Esc ou ao navegar. Tudo automático.
Desenvolvimento
Requer Node 18+ e pnpm.
pnpm install
pnpm run example # playground em http://localhost:5183 — watch: TS e CSS recarregam ao vivo
pnpm run build # typecheck + build (ESM/CJS/d.ts/css) + testes Playwright
pnpm run test # testes e2e (Playwright)Os testes rodam automaticamente no build e no prepublishOnly — a publicação trava se algo quebrar.
Licença
MIT © Eduzz
