rootzz-layout 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eduzz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,287 @@
1
+ # rootzz-layout
2
+
3
+ Shell de aplicação da Eduzz para React: **Topbar + Sidebar + área de conteúdo**.
4
+ Leve (zero dependências de runtime), com tema claro/escuro, drawer mobile, e **sem conflitar com o Tailwind** (ou qualquer CSS) do projeto consumidor.
5
+
6
+ > **Para agentes de IA:** a API é **composta**. Use `<RootzzLayout>` como casca e
7
+ > `RootzzLayout.Topbar`, `RootzzLayout.Sidebar`, `RootzzLayout.Content` como filhos —
8
+ > cada um com suas próprias props. Copie o exemplo de [Uso rápido](#uso-rápido), troque os
9
+ > dados. Todas as props têm JSDoc — confie no autocompletar/tipos.
10
+
11
+ ---
12
+
13
+ ## Por que não quebra o seu projeto
14
+
15
+ - **CSS já compilado e prefixado.** Você importa **um** arquivo (`rootzz-layout/styles.css`). Não precisa de Tailwind, PostCSS ou config no seu projeto.
16
+ - **Classes prefixadas com `rzl-`** → não colidem com `flex`, `bg-*`, etc. do seu app.
17
+ - **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.
18
+ - **Customização por CSS variables** (`--rzl-*`) — sem precisar tocar no seu build.
19
+
20
+ ---
21
+
22
+ ## Instalação
23
+
24
+ ```bash
25
+ pnpm add rootzz-layout
26
+ # ou: npm i rootzz-layout / yarn add rootzz-layout
27
+ ```
28
+
29
+ `react` e `react-dom` (>= 17) são peer dependencies. Importe o CSS **uma vez** na raiz do app:
30
+
31
+ ```ts
32
+ import 'rootzz-layout/styles.css';
33
+ ```
34
+
35
+ 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).
36
+
37
+ ---
38
+
39
+ ## Uso rápido
40
+
41
+ ```tsx
42
+ import { RootzzLayout, SearchBar, IconButton } from 'rootzz-layout';
43
+ import 'rootzz-layout/styles.css';
44
+
45
+ export default function App() {
46
+ return (
47
+ <RootzzLayout>
48
+ <RootzzLayout.Topbar
49
+ logo={{ src: 'https://cdn.eduzzcdn.com/topbar/orbita-new.svg', alt: 'MyEduzz', href: '/' }}
50
+ apps // botão de apps (busca a lista oficial da Eduzz no CDN)
51
+ showThemeToggle // botão de tema claro/escuro
52
+ search={<SearchBar placeholder="Buscar…" shortcut="⌘K" />}
53
+ actions={<IconButton aria-label="Notificações" badge={8}>{/* ícone */}</IconButton>}
54
+ user={{
55
+ name: 'Designers da Eduzz',
56
+ initials: 'Dd',
57
+ menu: [
58
+ { label: 'Meu perfil', onClick: () => {} },
59
+ { type: 'divider' },
60
+ { label: 'Sair', danger: true, onClick: () => {} },
61
+ ],
62
+ }}
63
+ />
64
+
65
+ <RootzzLayout.Sidebar
66
+ activeKey="resumo"
67
+ menu={[
68
+ { key: 'resumo', label: 'Resumo', href: '/' },
69
+ { key: 'compras', label: 'Minhas Compras', href: '/compras' },
70
+ {
71
+ type: 'group',
72
+ key: 'vendas',
73
+ label: 'Vendas',
74
+ defaultCollapsed: true,
75
+ items: [{ key: 'pedidos', label: 'Pedidos', href: '/pedidos' }],
76
+ },
77
+ ]}
78
+ />
79
+
80
+ <RootzzLayout.Content>
81
+ <h1>Novo produto</h1>
82
+ </RootzzLayout.Content>
83
+ </RootzzLayout>
84
+ );
85
+ }
86
+ ```
87
+
88
+ A ordem dos filhos não importa — a Topbar fica sempre no topo e a Sidebar à esquerda do conteúdo.
89
+ Cada subcomponente também é exportado individualmente (`Topbar`, `Sidebar`, `Content`).
90
+
91
+ ---
92
+
93
+ ## API
94
+
95
+ ### `<RootzzLayout>` — casca (configs de nível de shell)
96
+
97
+ | Prop | Tipo | Padrão | Descrição |
98
+ | --- | --- | --- | --- |
99
+ | `children` | `ReactNode` | — | Os subcomponentes: `.Topbar`, `.Sidebar`, `.Content`. |
100
+ | `theme` | `'light' \| 'dark'` | — | Tema **controlado** (você gerencia via `onThemeChange`). |
101
+ | `defaultTheme` | `'light' \| 'dark'` | `'light'` | Tema inicial quando **não-controlado**. |
102
+ | `onThemeChange` | `(theme) => void` | — | Disparado quando o tema muda. |
103
+ | `linkComponent` | `ElementType` | `'a'` | Componente de link (topbar + sidebar). Passe o `Link` do seu router para SPA. |
104
+ | `className` / `style` / `id` | — | — | Aplicados ao elemento raiz `.rootzz-layout`. Use `style` para sobrescrever tokens. |
105
+
106
+ ### `<RootzzLayout.Topbar>`
107
+
108
+ | Prop | Tipo | Padrão | Descrição |
109
+ | --- | --- | --- | --- |
110
+ | `logo` | `LogoConfig \| ReactNode` | — | `{src, alt?, href?, onClick?, height?}` ou um nó React. |
111
+ | `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. |
112
+ | `search` | `ReactNode` | — | Barra de busca opcional (centro). Use [`<SearchBar>`](#searchbar) ou um nó próprio. |
113
+ | `actions` | `ReactNode` | — | Ícones/ações custom à direita. Use [`<IconButton>`](#iconbutton). |
114
+ | `user` | `UserConfig` | — | `{name, email?, avatarUrl?, initials?, menu?, onClick?}`. |
115
+ | `showThemeToggle` | `boolean` | `false` | Exibe o botão de tema claro/escuro. |
116
+ | `showMenuButton` | `boolean` | `true` | Exibe o hambúrguer no mobile (desligue se não houver Sidebar). |
117
+ | `appsLabel` / `menuButtonLabel` | `string` | — | Rótulos acessíveis. |
118
+
119
+ ### `<RootzzLayout.Sidebar>`
120
+
121
+ | Prop | Tipo | Padrão | Descrição |
122
+ | --- | --- | --- | --- |
123
+ | `menu` | `MenuItem[]` | — | Itens (links, grupos colapsáveis, divisores). |
124
+ | `activeKey` | `string` | — | `key` do item ativo. |
125
+ | `header` / `footer` | `ReactNode` | — | Conteúdo fixo no topo / rodapé. |
126
+ | `mobileTitle` | `ReactNode` | — | Título exibido no topo do drawer mobile. |
127
+ | `children` | `ReactNode` | — | Conteúdo custom (substitui `menu`). |
128
+ | `ariaLabel` / `className` | — | — | Acessibilidade / classe extra. |
129
+
130
+ ### `<RootzzLayout.Content>`
131
+
132
+ | Prop | Tipo | Padrão | Descrição |
133
+ | --- | --- | --- | --- |
134
+ | `children` | `ReactNode` | — | Conteúdo da página. |
135
+ | `maxWidth` | `number \| string \| false` | token | Largura máx. `false` = 100%. Omitido usa `--rzl-content-max-width`. |
136
+ | `noPadding` | `boolean` | `false` | Remove o padding padrão. |
137
+ | `className` | `string` | — | Classe extra no wrapper interno. |
138
+
139
+ ### Tipos do menu
140
+
141
+ ```ts
142
+ // Item simples (link ou botão)
143
+ { key: string; label: ReactNode; icon?: ReactNode; href?: string; target?: string;
144
+ onClick?: (e) => void; active?: boolean; badge?: ReactNode; disabled?: boolean }
145
+
146
+ // Grupo colapsável
147
+ { type: 'group'; key: string; label: ReactNode; icon?: ReactNode; items: MenuLink[];
148
+ defaultCollapsed?: boolean; collapsible?: boolean }
149
+
150
+ // Divisor
151
+ { type: 'divider'; key?: string }
152
+ ```
153
+
154
+ O item ativo é resolvido por `Sidebar.activeKey === item.key` **ou** `item.active === true`.
155
+
156
+ ---
157
+
158
+ ## Customização (tema)
159
+
160
+ Sobrescreva as **CSS variables** `--rzl-*` no seu CSS:
161
+
162
+ ```css
163
+ .rootzz-layout {
164
+ --rzl-primary: #6d2bcf;
165
+ --rzl-sidebar-width: 280px;
166
+ --rzl-radius: 12px;
167
+ --rzl-duration: 160ms;
168
+ --rzl-font: 'Inter', sans-serif;
169
+ }
170
+ ```
171
+
172
+ …ou pontualmente via `style`:
173
+
174
+ ```tsx
175
+ <RootzzLayout style={{ ['--rzl-primary' as string]: '#10b981' }}>…</RootzzLayout>
176
+ ```
177
+
178
+ ### Tokens disponíveis
179
+
180
+ | Token | Padrão (claro) | Uso |
181
+ | --- | --- | --- |
182
+ | `--rzl-primary` / `--rzl-primary-hover` / `--rzl-primary-contrast` | `#2b4acf` / `#2540b5` / `#fff` | Cor de marca |
183
+ | `--rzl-active-bg` / `--rzl-active-fg` | `#eef1fc` / `#2b4acf` | Item ativo da sidebar |
184
+ | `--rzl-bg` / `--rzl-surface` | `#fff` / `#fff` | Conteúdo / topbar e sidebar |
185
+ | `--rzl-fg` / `--rzl-fg-muted` / `--rzl-fg-subtle` | `#0f1324` / 65% / 50% | Textos |
186
+ | `--rzl-border` / `--rzl-hover-bg` | `#e5e7eb` / 4.5% | Linhas / hover |
187
+ | `--rzl-danger` / `--rzl-danger-bg` | `#e11d48` / 8% | Ações destrutivas |
188
+ | `--rzl-badge-bg` / `--rzl-badge-fg` | `#e11d48` / `#fff` | Badge (ex.: sino) |
189
+ | `--rzl-overlay` | `rgba(15,19,36,.45)` | Overlay mobile |
190
+ | `--rzl-topbar-height` / `--rzl-sidebar-width` / `--rzl-content-max-width` | `60px` / `260px` / `1200px` | Dimensões |
191
+ | `--rzl-radius` / `--rzl-radius-lg` | `8px` / `12px` | Arredondamento |
192
+ | `--rzl-duration` / `--rzl-ease` | `200ms` / `cubic-bezier(.4,0,.2,1)` | Movimento |
193
+ | `--rzl-font` | Google Sans + fallback | Fonte |
194
+
195
+ O tema **escuro** redefine esses tokens em `.rootzz-layout[data-theme="dark"]`.
196
+ Animações respeitam `prefers-reduced-motion`.
197
+
198
+ ---
199
+
200
+ ## Tema claro/escuro
201
+
202
+ O estado do tema fica no `<RootzzLayout>`; o botão de alternância (`showThemeToggle`) fica na Topbar.
203
+
204
+ ```tsx
205
+ // Não-controlado (a lib gerencia)
206
+ <RootzzLayout defaultTheme="light">
207
+ <RootzzLayout.Topbar showThemeToggle … />
208
+
209
+ </RootzzLayout>
210
+
211
+ // Controlado (você gerencia o estado)
212
+ const [theme, setTheme] = useState<'light' | 'dark'>('light');
213
+ <RootzzLayout theme={theme} onThemeChange={setTheme}>
214
+ <RootzzLayout.Topbar showThemeToggle … />
215
+
216
+ </RootzzLayout>
217
+ ```
218
+
219
+ ---
220
+
221
+ ## Integração com router (SPA)
222
+
223
+ Por padrão os links são `<a>`. Para navegação SPA, passe o `Link` do seu router no `<RootzzLayout>`:
224
+
225
+ ```tsx
226
+ // Next.js
227
+ import Link from 'next/link';
228
+ <RootzzLayout linkComponent={Link}>…</RootzzLayout>
229
+
230
+ // React Router v6
231
+ import { Link } from 'react-router-dom';
232
+ const RouterLink = ({ href, ...p }) => <Link to={href} {...p} />;
233
+ <RootzzLayout linkComponent={RouterLink}>…</RootzzLayout>
234
+ ```
235
+
236
+ ---
237
+
238
+ ## Menu de aplicativos
239
+
240
+ ```tsx
241
+ <RootzzLayout.Topbar apps /> // lista oficial da Eduzz (CDN)
242
+ <RootzzLayout.Topbar apps={[{ label: 'App', icon: '/i.svg', url: 'https://…' }]} /> // estática
243
+ <RootzzLayout.Topbar apps="https://meu-cdn/apps.json" /> // URL custom
244
+ <RootzzLayout.Topbar apps={false} /> // sem botão de apps
245
+ ```
246
+
247
+ Formato de `AppItem`: `{ application?, label, icon, description?, url }`.
248
+
249
+ ---
250
+
251
+ ## `<SearchBar>` e `<IconButton>`
252
+
253
+ Helpers para a Topbar (também exportados):
254
+
255
+ ```tsx
256
+ <RootzzLayout.Topbar
257
+ search={<SearchBar placeholder="Buscar…" shortcut="⌘K" value={q} onChange={e => setQ(e.target.value)} />}
258
+ actions={<IconButton aria-label="Notificações" badge={8}>{<MeuIconeSino />}</IconButton>}
259
+ />
260
+ ```
261
+
262
+ Outros exports: `LayoutProvider`, hooks `useLayout` e `useApps`, util `cx`, e todos os tipos.
263
+
264
+ ---
265
+
266
+ ## Mobile
267
+
268
+ 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.
269
+
270
+ ---
271
+
272
+ ## Desenvolvimento
273
+
274
+ Requer **Node 18+** e **pnpm**.
275
+
276
+ ```bash
277
+ pnpm install
278
+ pnpm run example # playground em http://localhost:5183 — watch: TS e CSS recarregam ao vivo
279
+ pnpm run build # typecheck + build (ESM/CJS/d.ts/css) + testes Playwright
280
+ pnpm run test # testes e2e (Playwright)
281
+ ```
282
+
283
+ Os testes rodam automaticamente no `build` e no `prepublishOnly` — a publicação trava se algo quebrar.
284
+
285
+ ## Licença
286
+
287
+ MIT © Eduzz