dwv-ui 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.
@@ -0,0 +1,305 @@
1
+ # Padrões de Componentes
2
+
3
+ Este documento define como componentes React devem ser criados para o projeto DWV.
4
+
5
+ ---
6
+
7
+ ## Estrutura de Arquivos
8
+
9
+ ### Componente Simples (sem variação estrutural)
10
+
11
+ ```
12
+ ComponentName/
13
+ ├── index.tsx # Type Props + Componente + Export
14
+ ├── styles/
15
+ │ └── variants.ts # CVA variants (se houver)
16
+ └── primitives/ # Componentes auxiliares (se houver)
17
+ └── ComponentIcon.tsx
18
+ ```
19
+
20
+ ### Componente com Variação Estrutural
21
+
22
+ Quando existem variações que mudam drasticamente a estrutura (ex: Select vs SearchableSelect):
23
+
24
+ ```
25
+ ComponentName/
26
+ ├── index.ts # Public API (apenas exports)
27
+ ├── mod/ # Variações estruturais
28
+ │ ├── ComponentName.tsx
29
+ │ └── SearchableComponentName.tsx
30
+ ├── styles/
31
+ │ └── variants.ts # CVA variants compartilhados
32
+ └── primitives/ # Partes reutilizáveis
33
+ ├── Trigger.tsx
34
+ └── Content.tsx
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Declaração de Componente
40
+
41
+ ### Padrão Obrigatório
42
+
43
+ ```typescript
44
+ // Arrow function, sem React.FC
45
+ const Button = ({ children, variant, className, ...props }: Props) => (
46
+ <button className={cn(buttonVariants({ variant }), className)} {...props}>
47
+ {children}
48
+ </button>
49
+ );
50
+
51
+ export { Button };
52
+ ```
53
+
54
+ ### Proibido
55
+
56
+ ```typescript
57
+ // React.FC - NÃO USAR
58
+ const Button: React.FC<Props> = ({ children }) => ...
59
+
60
+ // function declaration - NÃO USAR
61
+ function Button({ children }: Props) { ... }
62
+
63
+ // export default - NÃO USAR
64
+ export default Button
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Tipagem de Props
70
+
71
+ ### Props no index.tsx (nome simples)
72
+
73
+ ```typescript
74
+ type Props = ButtonHTMLAttributes<HTMLButtonElement> & {
75
+ children: ReactNode;
76
+ variant?: "primary" | "outline";
77
+ icon?: IconProps;
78
+ };
79
+ ```
80
+
81
+ ### Props em types.ts (nome completo)
82
+
83
+ Quando separar em arquivo próprio:
84
+
85
+ ```typescript
86
+ // types.ts
87
+ type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
88
+ children: ReactNode;
89
+ variant?: "primary" | "outline";
90
+ };
91
+
92
+ export type { ButtonProps };
93
+ ```
94
+
95
+ ### Intersecção com HTML Attributes
96
+
97
+ Herdar de HTML quando faz sentido:
98
+
99
+ ```typescript
100
+ // Input herda de InputHTMLAttributes
101
+ type Props = InputHTMLAttributes<HTMLInputElement> & {
102
+ label?: string;
103
+ error?: string;
104
+ };
105
+
106
+ // Div container herda de HTMLAttributes
107
+ type Props = HTMLAttributes<HTMLDivElement> & {
108
+ title: string;
109
+ };
110
+ ```
111
+
112
+ ### Acesso Externo Granular
113
+
114
+ Quando componente tem partes que precisam de customização:
115
+
116
+ ```typescript
117
+ type Props = InputHTMLAttributes<HTMLInputElement> & {
118
+ label?: string;
119
+ error?: string;
120
+ wrapperAttr?: HTMLAttributes<HTMLDivElement>;
121
+ errorAttr?: HTMLAttributes<HTMLParagraphElement>;
122
+ };
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Ref em Componentes
128
+
129
+ Componentes de ação (Button, Input, etc) devem aceitar ref.
130
+
131
+ ### React 19+ (ref como prop)
132
+
133
+ ```typescript
134
+ type Props = ButtonHTMLAttributes<HTMLButtonElement> & {
135
+ ref?: React.Ref<HTMLButtonElement>;
136
+ children: ReactNode;
137
+ };
138
+
139
+ const Button = ({ children, ref, ...props }: Props) => (
140
+ <button ref={ref} {...props}>
141
+ {children}
142
+ </button>
143
+ );
144
+ ```
145
+
146
+ ### React 18 (forwardRef)
147
+
148
+ ```typescript
149
+ const SelectTrigger = React.forwardRef<
150
+ React.ComponentRef<typeof SelectPrimitive.Trigger>,
151
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
152
+ >(({ className, ...props }, ref) => (
153
+ <SelectPrimitive.Trigger ref={ref} className={cn(...)} {...props} />
154
+ ))
155
+ SelectTrigger.displayName = 'SelectTrigger'
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Ordem dos Elementos
161
+
162
+ Dentro de um arquivo de componente:
163
+
164
+ ```typescript
165
+ // 1. Imports (externos primeiro, depois aliases internos, depois relativos)
166
+ import { useState, type ReactNode } from "react";
167
+ import { cva, type VariantProps } from "class-variance-authority";
168
+ import { cn } from "@shared/lib";
169
+ import { buttonVariants } from "./styles/variants";
170
+ import { ButtonIcon } from "./primitives/ButtonIcon";
171
+
172
+ // 2. Types
173
+ type Props = VariantProps<typeof buttonVariants> & {
174
+ children: ReactNode;
175
+ icon?: IconType;
176
+ className?: string;
177
+ };
178
+
179
+ // 3. Componente principal
180
+ const Button = ({
181
+ children,
182
+ variant,
183
+ size,
184
+ icon,
185
+ className,
186
+ ...props
187
+ }: Props) => (
188
+ <button
189
+ className={cn(buttonVariants({ variant, size }), className)}
190
+ {...props}
191
+ >
192
+ {icon && <ButtonIcon type={icon} position="left" />}
193
+ {children}
194
+ </button>
195
+ );
196
+
197
+ // 4. Export nomeado
198
+ export { Button };
199
+ export type { Props as ButtonProps };
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Componentes Auxiliares (Primitives)
205
+
206
+ Quando um componente tem partes reutilizáveis:
207
+
208
+ ```typescript
209
+ // primitives/ButtonIcon.tsx
210
+ const buttonIcons = {
211
+ download: FiDownload,
212
+ add: FiPlus,
213
+ edit: FiEdit,
214
+ };
215
+
216
+ type Props = {
217
+ type: keyof typeof buttonIcons | React.ComponentType;
218
+ position: "left" | "right";
219
+ };
220
+
221
+ const ButtonIcon = ({ type, ...props }: Props) => {
222
+ const Icon = typeof type === "string" ? buttonIcons[type] : type;
223
+ return <Icon {...props} />;
224
+ };
225
+
226
+ export { ButtonIcon };
227
+ ```
228
+
229
+ ---
230
+
231
+ ## Barrel Files (index.ts)
232
+
233
+ ### Componente simples (tudo no index.tsx)
234
+
235
+ O próprio `index.tsx` já exporta o componente.
236
+
237
+ ### Componente com variações (index.ts separado)
238
+
239
+ ```typescript
240
+ // index.ts
241
+ export { Select } from "./mod/Select";
242
+ export { SearchableSelect } from "./mod/SearchableSelect";
243
+ export type { SelectProps, SelectOption } from "./types";
244
+ ```
245
+
246
+ ---
247
+
248
+ ## Exemplos Completos
249
+
250
+ ### Badge
251
+
252
+ ```typescript
253
+ // Badge/index.tsx
254
+ import { cn } from "@shared/lib";
255
+ import { badgeVariants } from "./styles/variants.ts";
256
+
257
+ type Props = VariantProps<typeof badgeVariants> & {
258
+ children: ReactNode;
259
+ className?: string;
260
+ };
261
+
262
+ const Badge = ({ children, variant, className }: Props) => (
263
+ <span className={cn(badgeVariants({ variant }), className)}>{children}</span>
264
+ );
265
+
266
+ export { Badge };
267
+ ```
268
+
269
+ ### Button
270
+
271
+ ```typescript
272
+ // Button/index.tsx
273
+ import { ButtonHTMLAttributes, ReactNode } from "react";
274
+ import { IconType } from "react-icons";
275
+ import { buttonVariants } from "./styles/variants.ts";
276
+ import { ButtonIcon, buttonIcons } from "./primitives";
277
+
278
+ type IconProps = {
279
+ type: keyof typeof buttonIcons | IconType;
280
+ position: "left" | "right";
281
+ size?: number;
282
+ color?: string;
283
+ className?: string;
284
+ };
285
+
286
+ type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> &
287
+ VariantProps<typeof buttonVariants> & {
288
+ children: ReactNode;
289
+ icon?: IconProps;
290
+ };
291
+
292
+ const Button = ({
293
+ children,
294
+ variant,
295
+ className,
296
+ icon,
297
+ ...props
298
+ }: ButtonProps) => (
299
+ <button className={buttonVariants({ variant, className })} {...props}>
300
+ {!!icon && icon.position === "left" && <ButtonIcon {...icon} />}
301
+ {children}
302
+ {!!icon && icon.position === "right" && <ButtonIcon {...icon} />}
303
+ </button>
304
+ );
305
+ ```
@@ -0,0 +1,154 @@
1
+ # Sistema de Estilização
2
+
3
+ Este documento define como componentes devem ser estilizados no projeto DWV.
4
+
5
+ ---
6
+
7
+ ## Stack de Estilização
8
+
9
+ | Ferramenta | Propósito |
10
+ | ---------------------------------- | ----------------------------------- |
11
+ | **Tailwind CSS** | Utility-first CSS framework |
12
+ | **CVA** (class-variance-authority) | Gerenciar variantes de componentes |
13
+ | **tailwind-merge** | Resolver conflitos de classes |
14
+ | **cn()** | Função helper para merge de classes |
15
+
16
+ ---
17
+
18
+ ## Função cn()
19
+
20
+ Sempre usar `cn()` para combinar classes Tailwind. Ela faz merge inteligente e resolve conflitos.
21
+
22
+ ### Implementação
23
+
24
+ ```typescript
25
+ import { cx } from "class-variance-authority";
26
+ import { twMerge } from "tailwind-merge";
27
+
28
+ type ClassValue = string | number | boolean | undefined | null | ClassValue[];
29
+
30
+ export const cn = (...classes: ClassValue[]) => twMerge(cx(classes));
31
+ ```
32
+
33
+ ### Uso
34
+
35
+ ```typescript
36
+ // Sempre usar cn() para classes
37
+ <div className={cn('flex gap-4', isActive && 'bg-brand-100', className)} />
38
+
39
+ // Nunca concatenar strings diretamente
40
+ <div className={`flex gap-4 ${isActive ? 'bg-brand-100' : ''}`} /> // ERRADO
41
+ ```
42
+
43
+ ---
44
+
45
+ ## CVA (Class Variance Authority)
46
+
47
+ Usar CVA para componentes com variantes (variant, size, etc).
48
+
49
+ ### Estrutura Básica
50
+
51
+ ```typescript
52
+ import { cva, type VariantProps } from "class-variance-authority";
53
+
54
+ const buttonVariants = cva(
55
+ // Classes base (sempre aplicadas)
56
+ "inline-flex items-center justify-center rounded-md font-medium transition-colors",
57
+ {
58
+ variants: {
59
+ // Cada variante é uma prop do componente
60
+ variant: {
61
+ primary: "bg-brand-600 text-white hover:bg-brand-700",
62
+ outline: "border border-neutral-300 bg-transparent hover:bg-neutral-50",
63
+ link: "text-brand-600 underline-offset-4 hover:underline",
64
+ },
65
+ size: {
66
+ sm: "h-8 px-3 text-sm",
67
+ md: "h-10 px-4 text-sm",
68
+ lg: "h-12 px-6 text-base",
69
+ },
70
+ },
71
+ // Valores padrão
72
+ defaultVariants: {
73
+ variant: "primary",
74
+ size: "md",
75
+ },
76
+ }
77
+ );
78
+ ```
79
+
80
+ ### Organização e uso
81
+
82
+ ```
83
+ Button/
84
+ ├── index.tsx
85
+ └── styles/
86
+ └── variants.ts
87
+ ```
88
+
89
+ ```typescript
90
+ // styles/variants.ts
91
+ import { cva } from 'class-variance-authority'
92
+ const buttonVariants = cva(...)
93
+ export { buttonVariants }
94
+ ```
95
+
96
+ ```typescript
97
+ // index.tsx
98
+ import { buttonVariants } from "./styles/variants.ts";
99
+
100
+ type Props = VariantProps<typeof buttonVariants> & {
101
+ children: ReactNode;
102
+ className?: string;
103
+ };
104
+
105
+ const Button = ({ children, variant, size, className, ...props }: Props) => (
106
+ <button
107
+ className={cn(buttonVariants({ variant, size }), className)}
108
+ {...props}
109
+ >
110
+ {children}
111
+ </button>
112
+ );
113
+ ```
114
+
115
+ ## Estilos condicionais
116
+
117
+ **NUNCA usar condicionais diretas no className:**
118
+
119
+ ```typescript
120
+ // ❌ Proibido
121
+ <div className={isActive ? 'text-white' : 'text-brand'} />
122
+ <div className={isHidden && 'hidden'} />
123
+ <div className={`${isOpen ? 'opacity-100' : 'opacity-0'}`} />
124
+ ```
125
+
126
+ **SEMPRE usar `data-` attributes:**
127
+
128
+ ```typescript
129
+ // ✅ Correto
130
+ <Button
131
+ data-hidden={!!isButtonHidden}
132
+ className={cn(
133
+ 'flex flex-1 text-white bg-brand',
134
+ 'data-[hidden=true]:hidden'
135
+ )}
136
+ />
137
+
138
+ <div
139
+ data-active={!!isActive}
140
+ className={cn(
141
+ 'px-4 py-2 rounded',
142
+ 'data-[active=true]:bg-brand data-[active=true]:text-white',
143
+ 'data-[active=false]:bg-neutral-100 data-[active=false]:text-neutral-600'
144
+ )}
145
+ />
146
+ ```
147
+
148
+ **Regras:**
149
+
150
+ | Regra | Descrição |
151
+ | ------------ | -------------------------------------------------------------------- |
152
+ | Prefixo `!!` | Sempre usar para garantir valor booleano |
153
+ | Separação | Estilos condicionais separados dos base via `cn()` |
154
+ | Nomenclatura | `data-[nome]` descritivo: `hidden`, `active`, `selected`, `disabled` |
@@ -0,0 +1,204 @@
1
+ # Tema do Projeto
2
+
3
+ Este documento define os tokens de design do projeto DWV, na maioria dos casos o próprio design dos componentes no Figma informa qual token correto usar, mas em outros casos será necessário mapear o token pela cor hexadecimal.
4
+
5
+ ---
6
+
7
+ ## Cores
8
+
9
+ ### Brand (Cor Primária - Vermelho)
10
+
11
+ | Token | Hex | Uso |
12
+ | ----------- | ------- | ---------------------- |
13
+ | `brand-100` | #ffdddd | Backgrounds sutis |
14
+ | `brand-200` | #ffc0c0 | Backgrounds hover |
15
+ | `brand-300` | #ff9494 | Borders |
16
+ | `brand-400` | #ff5757 | Estados intermediários |
17
+ | `brand-500` | #ff2323 | Cor principal |
18
+ | `brand-600` | #d70000 | **Botões primários** |
19
+ | `brand-700` | #b10303 | Hover de botões |
20
+ | `brand-800` | #920A0A | Estados ativos |
21
+ | `brand-900` | #500000 | Textos de ênfase |
22
+
23
+ ### Neutral (Cinzas)
24
+
25
+ | Token | Hex | Uso |
26
+ | -------------- | ------- | ------------------ |
27
+ | `neutral-0` | #ffffff | Background branco |
28
+ | `neutral-50` | #F9F9FA | Background sutil |
29
+ | `neutral-100` | #F5F5F6 | Background cards |
30
+ | `neutral-200` | #E6E6E7 | Borders leves |
31
+ | `neutral-300` | #CFCFD2 | Borders |
32
+ | `neutral-400` | #AEADB3 | Placeholders |
33
+ | `neutral-500` | #85848C | Textos secundários |
34
+ | `neutral-600` | #6A6971 | Textos |
35
+ | `neutral-700` | #5B5A60 | Textos importantes |
36
+ | `neutral-800` | #4D4C51 | Títulos |
37
+ | `neutral-900` | #444347 | Títulos fortes |
38
+ | `neutral-1000` | #363538 | Textos escuros |
39
+
40
+ ### Dark (Modo Escuro)
41
+
42
+ | Token | Hex | Uso |
43
+ | ---------- | ------- | ---------------------- |
44
+ | `dark-100` | #2C2C2E | Cards dark mode |
45
+ | `dark-200` | #202021 | Background dark |
46
+ | `dark-300` | #161618 | Background mais escuro |
47
+
48
+ ### Green (Sucesso)
49
+
50
+ | Token | Hex | Uso |
51
+ | ----------- | ------- | ----------------- |
52
+ | `green-100` | #DFF9E2 | Background sutil |
53
+ | `green-200` | #C1F1C7 | Background |
54
+ | `green-300` | #91E49B | Borders |
55
+ | `green-400` | #5ACE69 | Estados |
56
+ | `green-500` | #33B444 | **Cor principal** |
57
+ | `green-600` | #28A138 | Hover |
58
+ | `green-700` | #20752C | Textos |
59
+ | `green-800` | #1B4C22 | - |
60
+ | `green-900` | #092A0F | - |
61
+
62
+ ### Yellow (Aviso)
63
+
64
+ | Token | Hex | Uso |
65
+ | ------------ | ------- | ----------------- |
66
+ | `yellow-100` | #FFFDC5 | Background sutil |
67
+ | `yellow-200` | #FFFA87 | Background |
68
+ | `yellow-300` | #FFF148 | Borders |
69
+ | `yellow-400` | #FFE31E | Estados |
70
+ | `yellow-500` | #FCC404 | **Cor principal** |
71
+ | `yellow-600` | #E69D00 | Hover |
72
+ | `yellow-700` | #b96d04 | Textos |
73
+
74
+ ### Orange (Alerta e erros)
75
+
76
+ | Token | Hex | Uso |
77
+ | ------------ | ------- | ----------------- |
78
+ | `orange-100` | #FFEDD3 | Background sutil |
79
+ | `orange-200` | #FFD8A5 | Background |
80
+ | `orange-300` | #FFBB6D | Borders |
81
+ | `orange-400` | #FF9232 | Estados |
82
+ | `orange-500` | #FF720A | **Cor principal** |
83
+ | `orange-600` | #F05906 | Hover |
84
+ | `orange-700` | #C74207 | Textos |
85
+
86
+ ### Blue (Informação)
87
+
88
+ | Token | Hex | Uso |
89
+ | ---------- | ------- | ----------------- |
90
+ | `blue-100` | #E0EFFE | Background sutil |
91
+ | `blue-200` | #BADFFD | Background |
92
+ | `blue-300` | #7CC6FD | Borders |
93
+ | `blue-400` | #37AAF9 | Estados |
94
+ | `blue-500` | #0D8FEA | **Cor principal** |
95
+ | `blue-600` | #0172CB | Hover |
96
+ | `blue-700` | #0259A2 | Textos |
97
+ | `blue-800` | #0B406F | - |
98
+ | `blue-900` | #082849 | - |
99
+
100
+ ### Pink
101
+
102
+ | Token | Hex |
103
+ | ---------- | ------- |
104
+ | `pink-100` | #FDE4F8 |
105
+ | `pink-500` | #F479D8 |
106
+ | `pink-700` | #D025B7 |
107
+
108
+ ### Overlay
109
+
110
+ ```typescript
111
+ "bg-overlay-100"; // #0A0A0CD9 - overlay escuro com transparência
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Espaçamentos
117
+
118
+ Base de 4px. Escala completa:
119
+
120
+ | Token | Valor | Pixels |
121
+ | ----- | ------- | ------ |
122
+ | `0` | 0 | 0px |
123
+ | `px` | 1px | 1px |
124
+ | `1` | 0.25rem | 4px |
125
+ | `2` | 0.5rem | 8px |
126
+ | `3` | 0.75rem | 12px |
127
+ | `4` | 1rem | 16px |
128
+ | `5` | 1.25rem | 20px |
129
+ | `6` | 1.5rem | 24px |
130
+ | `7` | 1.75rem | 28px |
131
+ | `8` | 2rem | 32px |
132
+ | `9` | 2.25rem | 36px |
133
+ | `10` | 2.5rem | 40px |
134
+ | `11` | 2.75rem | 44px |
135
+ | `12` | 3rem | 48px |
136
+ | `14` | 3.5rem | 56px |
137
+ | `16` | 4rem | 64px |
138
+ | `20` | 5rem | 80px |
139
+ | `24` | 6rem | 96px |
140
+
141
+ ---
142
+
143
+ ## Border Radius
144
+
145
+ | Token | Valor | Pixels |
146
+ | -------------- | --------- | ------- |
147
+ | `rounded-none` | 0 | 0px |
148
+ | `rounded-sm` | 0.3125rem | 5px |
149
+ | `rounded-md` | 0.625rem | 10px |
150
+ | `rounded-lg` | 0.9375rem | 15px |
151
+ | `rounded-full` | 50% | Círculo |
152
+
153
+ ---
154
+
155
+ ## Tipografia
156
+
157
+ ### Font Family
158
+
159
+ ```typescript
160
+ "font-roboto"; // Roboto, sans-serif (fonte principal)
161
+ ```
162
+
163
+ ### Line Height
164
+
165
+ | Token | Valor |
166
+ | ---------------- | ----- |
167
+ | `leading-none` | 1 |
168
+ | `leading-tight` | 1.2 |
169
+ | `leading-normal` | 1.5 |
170
+
171
+ ---
172
+
173
+ ## Breakpoints
174
+
175
+ | Token | Valor | Pixels |
176
+ | ----- | ----- | ------ |
177
+ | `sm` | 30em | 480px |
178
+ | `md` | 48em | 768px |
179
+ | `lg` | 62em | 992px |
180
+ | `xl` | 80em | 1280px |
181
+ | `2xl` | 96em | 1536px |
182
+ | `3xl` | 120em | 1920px |
183
+
184
+ ---
185
+
186
+ ## Animações
187
+
188
+ ### Keyframes disponíveis
189
+
190
+ | Nome | Descrição |
191
+ | ----------------------- | -------------------------- |
192
+ | `fade-in` | Aparece com fade |
193
+ | `fade-out` | Desaparece com fade |
194
+ | `scale-in` | Aparece com zoom |
195
+ | `scale-out` | Desaparece com zoom |
196
+ | `slide-in-from-bottom` | Entra de baixo |
197
+ | `slide-out-from-bottom` | Sai para baixo |
198
+ | `slide-in-from-top` | Entra de cima |
199
+ | `slide-out-from-top` | Sai para cima |
200
+ | `slide-in-from-left` | Entra da esquerda |
201
+ | `slide-out-from-left` | Sai para esquerda |
202
+ | `slide-in-from-right` | Entra da direita |
203
+ | `slide-out-from-right` | Sai para direita |
204
+ | `infinite-scroll` | Scroll infinito horizontal |