@iclips/ui 2.0.3 → 3.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.
Files changed (130) hide show
  1. package/DESIGN.md +246 -0
  2. package/MIGRATION-v3.md +244 -0
  3. package/README.md +4 -0
  4. package/dist/alert-theme.css +20 -79
  5. package/dist/components/ui/charts/donut-chart.cjs +1 -1
  6. package/dist/components/ui/charts/donut-chart.cjs.map +1 -1
  7. package/dist/components/ui/charts/donut-chart.js +39 -60
  8. package/dist/components/ui/charts/donut-chart.js.map +1 -1
  9. package/dist/components/ui/charts/kpi-card.cjs +1 -1
  10. package/dist/components/ui/charts/kpi-card.cjs.map +1 -1
  11. package/dist/components/ui/charts/kpi-card.js +19 -19
  12. package/dist/components/ui/charts/kpi-card.js.map +1 -1
  13. package/dist/components/ui/data/comment-system.cjs +1 -1
  14. package/dist/components/ui/data/comment-system.cjs.map +1 -1
  15. package/dist/components/ui/data/comment-system.js +67 -67
  16. package/dist/components/ui/data/comment-system.js.map +1 -1
  17. package/dist/components/ui/data/data-table.cjs +1 -1
  18. package/dist/components/ui/data/data-table.cjs.map +1 -1
  19. package/dist/components/ui/data/data-table.js +112 -101
  20. package/dist/components/ui/data/data-table.js.map +1 -1
  21. package/dist/components/ui/data/editorial-calendar.cjs +1 -1
  22. package/dist/components/ui/data/editorial-calendar.cjs.map +1 -1
  23. package/dist/components/ui/data/editorial-calendar.js +291 -291
  24. package/dist/components/ui/data/editorial-calendar.js.map +1 -1
  25. package/dist/components/ui/data/filter-builder.cjs +1 -1
  26. package/dist/components/ui/data/filter-builder.cjs.map +1 -1
  27. package/dist/components/ui/data/filter-builder.js +1 -1
  28. package/dist/components/ui/data/filter-builder.js.map +1 -1
  29. package/dist/components/ui/feedback/empty-state.cjs +1 -1
  30. package/dist/components/ui/feedback/empty-state.cjs.map +1 -1
  31. package/dist/components/ui/feedback/empty-state.js +14 -14
  32. package/dist/components/ui/feedback/empty-state.js.map +1 -1
  33. package/dist/components/ui/feedback/toast.cjs +1 -1
  34. package/dist/components/ui/feedback/toast.cjs.map +1 -1
  35. package/dist/components/ui/feedback/toast.js +53 -22
  36. package/dist/components/ui/feedback/toast.js.map +1 -1
  37. package/dist/components/ui/forms/calendar.cjs +1 -1
  38. package/dist/components/ui/forms/calendar.cjs.map +1 -1
  39. package/dist/components/ui/forms/calendar.js +16 -16
  40. package/dist/components/ui/forms/calendar.js.map +1 -1
  41. package/dist/components/ui/forms/combobox.cjs +1 -1
  42. package/dist/components/ui/forms/combobox.cjs.map +1 -1
  43. package/dist/components/ui/forms/combobox.js +78 -76
  44. package/dist/components/ui/forms/combobox.js.map +1 -1
  45. package/dist/components/ui/forms/file-attachment-list.cjs +1 -1
  46. package/dist/components/ui/forms/file-attachment-list.cjs.map +1 -1
  47. package/dist/components/ui/forms/file-attachment-list.js +4 -4
  48. package/dist/components/ui/forms/file-attachment-list.js.map +1 -1
  49. package/dist/components/ui/forms/file-upload-zone.cjs +2 -2
  50. package/dist/components/ui/forms/file-upload-zone.cjs.map +1 -1
  51. package/dist/components/ui/forms/file-upload-zone.js +55 -49
  52. package/dist/components/ui/forms/file-upload-zone.js.map +1 -1
  53. package/dist/components/ui/forms/form.cjs +1 -1
  54. package/dist/components/ui/forms/form.cjs.map +1 -1
  55. package/dist/components/ui/forms/form.js +11 -11
  56. package/dist/components/ui/forms/form.js.map +1 -1
  57. package/dist/components/ui/forms/input-validation.cjs +1 -1
  58. package/dist/components/ui/forms/input-validation.cjs.map +1 -1
  59. package/dist/components/ui/forms/input-validation.js +20 -20
  60. package/dist/components/ui/forms/input-validation.js.map +1 -1
  61. package/dist/components/ui/forms/multi-select.cjs +1 -1
  62. package/dist/components/ui/forms/multi-select.cjs.map +1 -1
  63. package/dist/components/ui/forms/multi-select.js +69 -67
  64. package/dist/components/ui/forms/multi-select.js.map +1 -1
  65. package/dist/components/ui/forms/radio-group.cjs +1 -1
  66. package/dist/components/ui/forms/radio-group.cjs.map +1 -1
  67. package/dist/components/ui/forms/radio-group.js +19 -19
  68. package/dist/components/ui/forms/radio-group.js.map +1 -1
  69. package/dist/components/ui/forms/social-button.cjs +1 -1
  70. package/dist/components/ui/forms/social-button.cjs.map +1 -1
  71. package/dist/components/ui/forms/social-button.js +6 -6
  72. package/dist/components/ui/forms/social-button.js.map +1 -1
  73. package/dist/components/ui/forms/toggle.cjs +1 -1
  74. package/dist/components/ui/forms/toggle.cjs.map +1 -1
  75. package/dist/components/ui/forms/toggle.js +7 -7
  76. package/dist/components/ui/forms/toggle.js.map +1 -1
  77. package/dist/components/ui/navigation/navigation-menu.cjs +1 -1
  78. package/dist/components/ui/navigation/navigation-menu.cjs.map +1 -1
  79. package/dist/components/ui/navigation/navigation-menu.js +14 -14
  80. package/dist/components/ui/navigation/navigation-menu.js.map +1 -1
  81. package/dist/components/ui/navigation/sidebar.cjs +1 -1
  82. package/dist/components/ui/navigation/sidebar.cjs.map +1 -1
  83. package/dist/components/ui/navigation/sidebar.js +44 -44
  84. package/dist/components/ui/navigation/sidebar.js.map +1 -1
  85. package/dist/components/ui/overlays/context-menu.cjs +1 -1
  86. package/dist/components/ui/overlays/context-menu.cjs.map +1 -1
  87. package/dist/components/ui/overlays/context-menu.js +1 -1
  88. package/dist/components/ui/overlays/context-menu.js.map +1 -1
  89. package/dist/components/ui/overlays/dropdown-menu.cjs +1 -1
  90. package/dist/components/ui/overlays/dropdown-menu.cjs.map +1 -1
  91. package/dist/components/ui/overlays/dropdown-menu.js +5 -5
  92. package/dist/components/ui/overlays/dropdown-menu.js.map +1 -1
  93. package/dist/components/ui/overlays/menubar.cjs +1 -1
  94. package/dist/components/ui/overlays/menubar.cjs.map +1 -1
  95. package/dist/components/ui/overlays/menubar.js +29 -29
  96. package/dist/components/ui/overlays/menubar.js.map +1 -1
  97. package/dist/components/ui/overlays/tooltip.cjs +1 -1
  98. package/dist/components/ui/overlays/tooltip.cjs.map +1 -1
  99. package/dist/components/ui/overlays/tooltip.js +12 -12
  100. package/dist/components/ui/overlays/tooltip.js.map +1 -1
  101. package/dist/components/ui/primitives/avatar.cjs +1 -1
  102. package/dist/components/ui/primitives/avatar.cjs.map +1 -1
  103. package/dist/components/ui/primitives/avatar.js +20 -27
  104. package/dist/components/ui/primitives/avatar.js.map +1 -1
  105. package/dist/components/ui/primitives/badge.cjs +1 -1
  106. package/dist/components/ui/primitives/badge.cjs.map +1 -1
  107. package/dist/components/ui/primitives/badge.js +21 -18
  108. package/dist/components/ui/primitives/badge.js.map +1 -1
  109. package/dist/components/ui/primitives/button.cjs +1 -1
  110. package/dist/components/ui/primitives/button.cjs.map +1 -1
  111. package/dist/components/ui/primitives/button.js +11 -8
  112. package/dist/components/ui/primitives/button.js.map +1 -1
  113. package/dist/index.cjs +1 -1
  114. package/dist/index.css +1 -1
  115. package/dist/index.d.cts +6 -6
  116. package/dist/index.d.ts +6 -6
  117. package/dist/index.js +34 -35
  118. package/dist/index.js.map +1 -1
  119. package/dist/preset.css +1 -2
  120. package/dist/toast-theme.css +100 -64
  121. package/dist/tokens.css +59 -159
  122. package/docs/README.md +44 -0
  123. package/docs/acoes.md +103 -0
  124. package/docs/escolha.md +171 -0
  125. package/docs/formularios.md +137 -0
  126. package/docs/mensagens.md +114 -0
  127. package/docs/overlays.md +192 -0
  128. package/docs/status.md +71 -0
  129. package/package.json +7 -3
  130. package/scripts/check-colors.mjs +208 -0
@@ -0,0 +1,137 @@
1
+ # Montando um formulário
2
+
3
+ Um formulário bom é **previsível**: label sempre acima do campo, erro sempre logo abaixo, ação primária sempre no mesmo canto. As decisões abaixo são o que muda de tela pra tela.
4
+
5
+ ## Anatomia de um campo
6
+
7
+ A ordem é sempre esta, de cima para baixo:
8
+
9
+ 1. **Label** acima, sempre visível (nunca só placeholder).
10
+ 2. **Campo**.
11
+ 3. **Ajuda** (opcional), ligada por `aria-describedby`.
12
+ 4. **Erro** no lugar da ajuda quando houver, com `aria-invalid` no campo.
13
+
14
+ ### Campo completo
15
+
16
+ ```tsx
17
+ import { Input, Label } from "@iclips/ui";
18
+
19
+ <div className="grid w-full max-w-xs gap-1.5">
20
+ <Label htmlFor="site">
21
+ Site <span className="text-muted-foreground">(opcional)</span>
22
+ </Label>
23
+ <Input id="site" placeholder="https://" aria-describedby="site-ajuda" />
24
+ <p id="site-ajuda" className="text-sm text-muted-foreground">
25
+ Aparece no rodapé das propostas.
26
+ </p>
27
+ </div>
28
+ ```
29
+
30
+ ### Com erro
31
+
32
+ ```tsx
33
+ import { Input, Label } from "@iclips/ui";
34
+
35
+ <div className="grid w-full max-w-xs gap-1.5">
36
+ <Label htmlFor="email">E-mail</Label>
37
+ <Input id="email" defaultValue="ana@" aria-invalid aria-describedby="email-erro" />
38
+ <p id="email-erro" className="text-sm text-destructive-emphasis">Informe um e-mail válido.</p>
39
+ </div>
40
+ ```
41
+
42
+ ## Obrigatório ou opcional?
43
+
44
+ Marque a minoria. Nunca os dois.
45
+
46
+ | Caso | Escolha | Evite |
47
+ | --- | --- | --- |
48
+ | maioria obrigatória | marque só os opcionais com "(opcional)" em texto ao lado do label. | asterisco em quase todos os campos — vira ruído e ninguém lê. |
49
+ | maioria opcional | marque os obrigatórios com "*" (ou "obrigatório") e explique o "*" uma vez no topo. | marcar "(opcional)" em 15 campos. |
50
+
51
+ ## Quando é validação inline vs no envio
52
+
53
+ 1. **O erro só pode ser sabido pelo servidor (e-mail já existe, saldo insuficiente)?** Sim: valide no envio (ou no blur com debounce, se a API aguentar). Mostre no campo. Não: continue.
54
+ 2. **É formato/obrigatoriedade que dá pra checar no cliente?** Sim: valide no blur (`mode: "onTouched"`) — erra cedo, sem incomodar quem ainda está digitando. Não: continue.
55
+ 3. **O formulário é curto (1–3 campos, ex: login)?** Sim: pode validar só no envio — o usuário vê tudo de uma vez. Não: blur + um resumo no topo (`Alert`) ao tentar enviar com erros. Foque o primeiro campo inválido.
56
+
57
+ ## Layout
58
+
59
+ | Layout | Escolha quando | Evite quando |
60
+ | --- | --- | --- |
61
+ | uma coluna | o padrão. Fluxo de leitura vertical, funciona em qualquer largura, não confunde a ordem de tab. | quase nunca — só saia disso com motivo. |
62
+ | duas colunas | pares que andam juntos e são curtos: cidade/UF, início/fim, DDD/telefone. Agrupe visualmente. | campos longos ou não relacionados lado a lado — a leitura zigue-zagueia. |
63
+ | seções (fieldset) | formulário longo com blocos temáticos: Dados da conta / Endereço / Preferências. Título por bloco. | wizard de várias telas quando tudo cabe numa página com rolagem. |
64
+
65
+ ## Botões de ação
66
+
67
+ #### Faça
68
+
69
+ - Ação primária à direita (ou sozinha, largura total no mobile). Uma só.
70
+ - Rótulo com o verbo da ação: "Criar projeto", "Salvar alterações" — não "Enviar" / "OK".
71
+ - Cancelar/Voltar como variante `outline` ou `ghost`, à esquerda da primária.
72
+ - Durante o envio: `isLoading` no botão (trava e mostra spinner) — não um overlay na tela toda.
73
+
74
+ #### Evite
75
+
76
+ - Dois botões com o mesmo peso visual (dois primários).
77
+ - "Limpar" ao lado de "Salvar" — o acidente é caro; se precisar, deixe longe e peça confirmação.
78
+ - Desabilitar o botão de envio enquanto há erro — o usuário não descobre o que falta. Deixe habilitado e valide no clique.
79
+
80
+ ## react-hook-form + os componentes
81
+
82
+ Use `FormField` para amarrar tudo pelos ids certos automaticamente.
83
+
84
+ ### FormField
85
+
86
+ `FormControl` injeta `id`/`aria-invalid`/`aria-describedby`; `FormMessage` some quando não há erro.
87
+
88
+ ```tsx
89
+ import { useForm } from "react-hook-form";
90
+ import {
91
+ Button, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, Input, Textarea,
92
+ } from "@iclips/ui";
93
+
94
+ type Campos = { nome: string; obs: string };
95
+
96
+ function NovoProjeto() {
97
+ const form = useForm<Campos>({ mode: "onTouched", defaultValues: { nome: "", obs: "" } });
98
+ return (
99
+ <Form {...form}>
100
+ <form onSubmit={form.handleSubmit(salvar)} className="grid w-full max-w-sm gap-4" noValidate>
101
+ <FormField
102
+ control={form.control}
103
+ name="nome"
104
+ rules={{ required: "Informe o nome." }}
105
+ render={({ field }) => (
106
+ <FormItem>
107
+ <FormLabel>Nome do projeto</FormLabel>
108
+ <FormControl><Input placeholder="Rebranding Aurora" {...field} /></FormControl>
109
+ <FormMessage />
110
+ </FormItem>
111
+ )}
112
+ />
113
+ <FormField
114
+ control={form.control}
115
+ name="obs"
116
+ render={({ field }) => (
117
+ <FormItem>
118
+ <FormLabel>Observações <span className="text-muted-foreground">(opcional)</span></FormLabel>
119
+ <FormControl><Textarea rows={3} {...field} /></FormControl>
120
+ <FormDescription>Visível só para a equipe.</FormDescription>
121
+ <FormMessage />
122
+ </FormItem>
123
+ )}
124
+ />
125
+ <div className="flex justify-end gap-2">
126
+ <Button type="button" variant="outline">Cancelar</Button>
127
+ <Button type="submit">Criar projeto</Button>
128
+ </div>
129
+ </form>
130
+ </Form>
131
+ );
132
+ }
133
+ ```
134
+
135
+ **Qual input para cada dado?** Está em [Escolha em lista](escolha.md) (Select vs Combobox vs Radio…). Para valores: `NumberInput`/`CurrencyInput` para números, `DatePicker`/`DateRangePicker` para datas, `PasswordInput` para senha, `InputOTP` para código, `InputGroup` quando o campo tem prefixo/sufixo (R$, .com, botão).
136
+
137
+ **Acessibilidade:** todo campo com `Label htmlFor` apontando pro `id` — clicar no texto foca o campo e o leitor de tela anuncia o rótulo. `aria-describedby` liga ajuda e erro. Ao enviar com erros, mova o foco para o resumo ou para o primeiro campo inválido.
@@ -0,0 +1,114 @@
1
+ # Onde a mensagem aparece
2
+
3
+ Três eixos decidem: **é resposta a uma ação** ou uma condição persistente? **Precisa de confirmação** ou só informa? **É sobre um campo** ou sobre a tela toda?
4
+
5
+ ## Tabela de decisão
6
+
7
+ | Mensagem | Escolha quando | Evite quando |
8
+ | --- | --- | --- |
9
+ | Erro de campo | a mensagem é sobre um input específico: formato inválido, obrigatório, indisponível. Fica abaixo do campo, com `aria-invalid`. | o problema é do formulário inteiro (ex: "falha de conexão ao salvar") → `Alert` no topo do form. |
10
+ | Alert (inline) | uma condição que permanece enquanto o usuário olha a tela: aviso de plano expirando, banner de modo somente-leitura, resumo de erros de validação. | é reação efêmera a um clique que acabou de acontecer → `Toast`. |
11
+ | Toast | confirmação passageira de uma ação: "Salvo", "E-mail enviado", "Falha ao publicar". Some sozinho. Pode ter ação "Desfazer". | a informação é importante e não pode passar batida, ou exige decisão → `Alert` ou `AlertDialog`. |
12
+ | AlertDialog | a ação é destrutiva e você precisa que o usuário pare e confirme antes. Trava a tela. (Ver [Overlays](overlays.md).) | a ação é desfazível → execute e ofereça "Desfazer" num `Toast`. |
13
+
14
+ ## Trilha rápida
15
+
16
+ 1. **A mensagem é sobre um campo específico do formulário?** Sim: erro de campo abaixo do input + `aria-invalid` no input. Não: continue.
17
+ 2. **Você precisa bloquear a tela até o usuário decidir?** Sim: `AlertDialog`. Não: continue.
18
+ 3. **É reação imediata a um clique e pode desaparecer em segundos?** Sim: Toast (`toast.success` / `toast.error` / …). Se a ação for reversível, inclua "Desfazer". Não: continue.
19
+ 4. **A condição continua verdadeira enquanto a tela estiver aberta?** Sim: `Alert` inline, ancorado onde a condição importa. Não: provavelmente não precisa de mensagem — reavalie.
20
+
21
+ ## Exemplos
22
+
23
+ ### Toast — confirmação efêmera
24
+
25
+ Dispare no callback da ação. Prefira `toast.success`/`toast.error` a texto neutro. Monte um único `<Toaster />` na raiz do app.
26
+
27
+ ```tsx
28
+ import { Button, toast } from "@iclips/ui";
29
+
30
+ <Button variant="outline" onClick={() => toast.success("Campanha salva")}>Salvar</Button>
31
+ <Button
32
+ variant="outline"
33
+ onClick={() =>
34
+ toast("Item removido", {
35
+ action: { label: "Desfazer", onClick: () => toast("Restaurado") },
36
+ })
37
+ }
38
+ >
39
+ Remover (com desfazer)
40
+ </Button>
41
+ ```
42
+
43
+ ### Alert — condição persistente
44
+
45
+ Fica na tela. Título curto + o que fazer a respeito.
46
+
47
+ ```tsx
48
+ import { Alert, AlertDescription, AlertTitle } from "@iclips/ui";
49
+ import { TriangleAlert } from "lucide-react";
50
+
51
+ <Alert variant="warning" className="max-w-sm">
52
+ <TriangleAlert />
53
+ <AlertTitle>Plano expira em 3 dias</AlertTitle>
54
+ <AlertDescription>Renove para não perder o acesso às automações.</AlertDescription>
55
+ </Alert>
56
+ ```
57
+
58
+ ### Erro de campo
59
+
60
+ A mensagem vive com o input, não no topo da tela. Em formulário com react-hook-form, `FormMessage` faz isso sozinho (ver [Formulários](formularios.md)).
61
+
62
+ ```tsx
63
+ import { Input, Label } from "@iclips/ui";
64
+
65
+ <div className="grid w-full max-w-xs gap-1.5">
66
+ <Label htmlFor="email">E-mail</Label>
67
+ <Input id="email" defaultValue="ana@" aria-invalid aria-describedby="email-erro" />
68
+ <p id="email-erro" className="text-sm text-destructive-emphasis">
69
+ Informe um e-mail válido.
70
+ </p>
71
+ </div>
72
+ ```
73
+
74
+ ### Alert — resumo de validação
75
+
76
+ Erros do formulário inteiro sobem para um `Alert` no topo, com foco.
77
+
78
+ ```tsx
79
+ import { Alert, AlertDescription, AlertTitle } from "@iclips/ui";
80
+ import { Info } from "lucide-react";
81
+
82
+ <Alert variant="destructive" className="max-w-sm" role="alert">
83
+ <Info />
84
+ <AlertTitle>Não foi possível salvar</AlertTitle>
85
+ <AlertDescription>Revise os 2 campos destacados abaixo.</AlertDescription>
86
+ </Alert>
87
+ ```
88
+
89
+ ## Faça / evite
90
+
91
+ #### Faça
92
+
93
+ - Monte um único `<Toaster />` na raiz do app. Chame `toast(...)` de qualquer lugar.
94
+ - Toast de erro: diga o que falhou e o próximo passo ("Falha ao publicar — verifique a conexão").
95
+ - Ação reversível → execute já e ofereça "Desfazer" no toast, em vez de um `AlertDialog` antes.
96
+ - Toast de erro fica até o usuário fechar (padrão do `toast.error`); sucesso some em 4s, aviso em 6s, informação em 5s.
97
+ - Escuro = ação com prazo: `toast(msg, { action })` sem tipo vem em superfície invertida e dura 8s. Use só para Desfazer.
98
+ - Progresso: `toast.promise` — o toast de processando se transforma em sucesso ou erro, em vez de somar outro na pilha.
99
+ - Ação em lote gera um toast agregado ("12 jobs atualizados"), não doze.
100
+ - Erro de campo: ligue input e mensagem com `aria-describedby` + `aria-invalid`.
101
+ - Aviso: `text-warning-emphasis`, nunca `text-warning`, e sempre com ícone — amarelo sozinho não identifica um aviso.
102
+
103
+ #### Evite
104
+
105
+ - Toast para algo que o usuário precisa ler com atenção — ele some antes.
106
+ - Alert de sucesso permanente que nunca some e vira ruído visual.
107
+ - `AlertDialog` "Salvo com sucesso! [OK]" — é o caso clássico de Toast.
108
+ - Empilhar 5 toasts de uma vez; agrupe ou resuma.
109
+
110
+ **Toast:** `toast` (sonner) é a única API — `toast.success` / `toast.error` / `toast.promise`, `action`, `duration`, `toast.dismiss`. Monte um `<Toaster />` na raiz do app. Superfície neutra sempre, cor só no ícone; o único toast roxo é o de processando.
111
+
112
+ **Cor de status em texto:** texto e ícone sobre superfície neutra usam a camada `-emphasis` (`text-destructive-emphasis`, `text-success-emphasis`…), que passa AA nos dois temas. Nunca `text-destructive` cru para texto. Idioma: `bg-warning/10 text-warning-emphasis border-warning/20`.
113
+
114
+ **Acessibilidade:** o `Toaster` já anuncia via `aria-live`. Para um `Alert` que *aparece* em resposta a uma ação (resumo de validação), adicione `role="alert"` e mova o foco para ele.
@@ -0,0 +1,192 @@
1
+ # Qual overlay usar
2
+
3
+ Todos flutuam acima da página, mas resolvem problemas diferentes. A pergunta que separa é: **o conteúdo exige uma ação do usuário** (então prende o foco) ou **só complementa** (então some ao clicar fora)?
4
+
5
+ ## Tabela de decisão
6
+
7
+ | Overlay | Escolha quando | Evite quando |
8
+ | --- | --- | --- |
9
+ | Tooltip | rótulo curto para um ícone ou controle sem texto — aparece no hover/foco, some sozinho. | o conteúdo tem link, botão ou texto que o usuário precise ler com calma → `Popover`. |
10
+ | HoverCard | prévia rica e não-essencial de algo já visível (card de usuário, resumo de projeto). | a informação é necessária para a tarefa, ou o gatilho é em mobile (não há hover) → `Popover`. |
11
+ | Popover | um pequeno painel ancorado a um botão: filtro, seletor de cor, mini-formulário, menu de ações. | há mais de ~6 campos ou o fluxo tem etapas → `Dialog` ou `Sheet`. |
12
+ | Dialog | uma tarefa focada que interrompe o fluxo: editar um registro, confirmar com contexto, wizard curto. | é só um aviso de sim/não destrutivo → `AlertDialog`. Formulário longo → `Sheet`. |
13
+ | AlertDialog | confirmação de ação destrutiva ou irreversível (excluir, cancelar assinatura). Trava até o usuário decidir. | a ação não é perigosa e é desfazível → use um Toast com "Desfazer". |
14
+ | Sheet | painel lateral para formulário longo, detalhes ou configurações sem perder o contexto da lista atrás. | a tarefa é curta e centrada → `Dialog`. Navegação primária do app → não é overlay. |
15
+ | Drawer | o mesmo papel do Sheet, mas puxado de baixo — padrão em telas de toque/mobile. | desktop com espaço de sobra → `Sheet` lateral. |
16
+
17
+ ## Trilha rápida
18
+
19
+ Responda de cima para baixo e pare na primeira que servir.
20
+
21
+ 1. **A ação é destrutiva/irreversível e precisa de confirmação?** Sim: `AlertDialog`. Não: continue.
22
+ 2. **É só um rótulo de texto para um ícone?** Sim: `Tooltip`. Não: continue.
23
+ 3. **É uma prévia opcional de algo que já está na tela?** Sim: `HoverCard` (desktop) — mas garanta um caminho por clique no mobile. Não: continue.
24
+ 4. **Cabe em um painelzinho de até ~6 campos ancorado ao gatilho?** Sim: `Popover`. Não: continue.
25
+ 5. **O usuário precisa ver a lista/tela de trás enquanto preenche?** Sim: `Sheet` (desktop) ou `Drawer` (toque). Não: `Dialog`.
26
+
27
+ ## Exemplos
28
+
29
+ ### Tooltip — rótulo de ícone
30
+
31
+ Some ao tirar o mouse ou o foco. Sem conteúdo interativo dentro. Balão neutro invertido: é informação, não ação. Precisa de um `TooltipProvider` acima (uma vez, na raiz).
32
+
33
+ ```tsx
34
+ import { Button, Tooltip, TooltipContent, TooltipTrigger } from "@iclips/ui";
35
+ import { Archive } from "lucide-react";
36
+
37
+ <Tooltip>
38
+ <TooltipTrigger asChild>
39
+ <Button variant="outline" size="icon" aria-label="Arquivar">
40
+ <Archive className="size-4" />
41
+ </Button>
42
+ </TooltipTrigger>
43
+ <TooltipContent>Arquivar</TooltipContent>
44
+ </Tooltip>
45
+ ```
46
+
47
+ ### Popover — mini-formulário
48
+
49
+ Fecha ao clicar fora. Bom para 1–6 campos.
50
+
51
+ ```tsx
52
+ import { Button, Input, Label, Popover, PopoverContent, PopoverTrigger } from "@iclips/ui";
53
+
54
+ <Popover>
55
+ <PopoverTrigger asChild>
56
+ <Button variant="outline">Renomear</Button>
57
+ </PopoverTrigger>
58
+ <PopoverContent className="w-64 space-y-2">
59
+ <Label htmlFor="novo-nome">Novo nome</Label>
60
+ <Input id="novo-nome" defaultValue="Campanha Q3" />
61
+ </PopoverContent>
62
+ </Popover>
63
+ ```
64
+
65
+ ### Dialog — tarefa focada
66
+
67
+ Prende o foco. Título + descrição + ações.
68
+
69
+ ```tsx
70
+ import {
71
+ Button, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle,
72
+ DialogTrigger, Input, Label,
73
+ } from "@iclips/ui";
74
+
75
+ <Dialog>
76
+ <DialogTrigger asChild>
77
+ <Button>Editar perfil</Button>
78
+ </DialogTrigger>
79
+ <DialogContent>
80
+ <DialogHeader>
81
+ <DialogTitle>Editar perfil</DialogTitle>
82
+ <DialogDescription>As alterações são salvas ao confirmar.</DialogDescription>
83
+ </DialogHeader>
84
+ <div className="grid gap-1.5 py-2">
85
+ <Label htmlFor="nome">Nome</Label>
86
+ <Input id="nome" defaultValue="Ana Souza" />
87
+ </div>
88
+ <DialogFooter>
89
+ <DialogClose asChild><Button variant="outline">Cancelar</Button></DialogClose>
90
+ <Button>Salvar</Button>
91
+ </DialogFooter>
92
+ </DialogContent>
93
+ </Dialog>
94
+ ```
95
+
96
+ ### AlertDialog — confirmação destrutiva
97
+
98
+ Sem clicar-fora-para-fechar. Uma escolha explícita.
99
+
100
+ ```tsx
101
+ import {
102
+ AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription,
103
+ AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, Button,
104
+ } from "@iclips/ui";
105
+
106
+ <AlertDialog>
107
+ <AlertDialogTrigger asChild>
108
+ <Button variant="destructive">Excluir projeto</Button>
109
+ </AlertDialogTrigger>
110
+ <AlertDialogContent>
111
+ <AlertDialogHeader>
112
+ <AlertDialogTitle>Excluir “Campanha Q3”?</AlertDialogTitle>
113
+ <AlertDialogDescription>
114
+ Esta ação não pode ser desfeita. Todos os itens do projeto serão removidos.
115
+ </AlertDialogDescription>
116
+ </AlertDialogHeader>
117
+ <AlertDialogFooter>
118
+ <AlertDialogCancel>Cancelar</AlertDialogCancel>
119
+ <AlertDialogAction>Excluir</AlertDialogAction>
120
+ </AlertDialogFooter>
121
+ </AlertDialogContent>
122
+ </AlertDialog>
123
+ ```
124
+
125
+ ### Sheet — formulário longo
126
+
127
+ A lista de trás continua visível como contexto.
128
+
129
+ ```tsx
130
+ import {
131
+ Button, Input, Label, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle,
132
+ SheetTrigger,
133
+ } from "@iclips/ui";
134
+
135
+ <Sheet>
136
+ <SheetTrigger asChild>
137
+ <Button variant="outline">Nova tarefa</Button>
138
+ </SheetTrigger>
139
+ <SheetContent className="w-full sm:max-w-md">
140
+ <SheetHeader>
141
+ <SheetTitle>Nova tarefa</SheetTitle>
142
+ <SheetDescription>Preencha os campos e salve.</SheetDescription>
143
+ </SheetHeader>
144
+ <div className="grid gap-3 px-4">
145
+ <div className="grid gap-1.5"><Label htmlFor="titulo">Título</Label><Input id="titulo" /></div>
146
+ <div className="grid gap-1.5"><Label htmlFor="responsavel">Responsável</Label><Input id="responsavel" /></div>
147
+ <div className="grid gap-1.5"><Label htmlFor="prazo">Prazo</Label><Input id="prazo" type="date" /></div>
148
+ </div>
149
+ <SheetFooter>
150
+ <Button>Criar tarefa</Button>
151
+ <SheetClose asChild><Button variant="outline">Cancelar</Button></SheetClose>
152
+ </SheetFooter>
153
+ </SheetContent>
154
+ </Sheet>
155
+ ```
156
+
157
+ ### HoverCard — prévia opcional
158
+
159
+ Só complementa. Nunca coloque aqui algo que a tarefa exige.
160
+
161
+ ```tsx
162
+ import { Button, HoverCard, HoverCardContent, HoverCardTrigger } from "@iclips/ui";
163
+
164
+ <HoverCard>
165
+ <HoverCardTrigger asChild>
166
+ <Button variant="link">@ana.souza</Button>
167
+ </HoverCardTrigger>
168
+ <HoverCardContent className="text-sm">
169
+ <p className="font-medium">Ana Souza</p>
170
+ <p className="text-muted-foreground">Designer · entrou em 2023</p>
171
+ </HoverCardContent>
172
+ </HoverCard>
173
+ ```
174
+
175
+ ## Faça / evite
176
+
177
+ #### Faça
178
+
179
+ - Dê a todo `Dialog`/`Sheet`/`AlertDialog` um título — é o rótulo que o leitor de tela anuncia.
180
+ - Reserve o `AlertDialog` para o que dói desfazer; para o resto, aja e ofereça "Desfazer" num Toast.
181
+ - No mobile, troque `Sheet` lateral por `Drawer` e `HoverCard` por `Popover` (toque não tem hover).
182
+ - Empilhe no máximo um overlay. Se precisar de outro, provavelmente o fluxo deveria ser uma página.
183
+
184
+ #### Evite
185
+
186
+ - Colocar um formulário de 15 campos num `Popover`.
187
+ - Usar `Tooltip` para conteúdo que tem link ou botão — o usuário não consegue alcançar.
188
+ - Pintar o `Tooltip` com a cor da marca (`bg-primary`) — overlay informativo não se veste de ação.
189
+ - Abrir um `Dialog` dentro de outro `Dialog`.
190
+ - Trocar toast de sucesso por `Dialog` "OK" — interrompe sem motivo.
191
+
192
+ **Acessibilidade:** `Dialog`, `AlertDialog`, `Sheet` e `Popover` vêm do Radix — prendem o foco, fecham no Esc e devolvem o foco ao gatilho de graça. O que você precisa garantir: um `Title` em todo painel modal, e `aria-label` em gatilhos que são só ícone.
package/docs/status.md ADDED
@@ -0,0 +1,71 @@
1
+ # Status
2
+
3
+ Status é **estado**: feedback, nunca decoração. Vermelho, verde, amarelo e azul só aparecem para dizer algo sobre um item. Status nunca é roxo (roxo é ação) e sempre vem com ícone ou rótulo — cor sozinha não identifica nada.
4
+
5
+ A receita é sempre suave: `bg-<status>/10 text-<status>-emphasis border-<status>/20`. Os componentes já a aplicam — use o componente em vez de montar as classes.
6
+
7
+ | Status | `variant` | Quando |
8
+ | --- | --- | --- |
9
+ | Sucesso | `success` | aprovado, pago, concluído, ativo |
10
+ | Aviso | `warning` | pendente, aguardando, vence em breve |
11
+ | Informação | `info` | em análise, rascunho enviado, novidade |
12
+ | Erro | `destructive` | recusado, vencido, falhou, cancelado |
13
+
14
+ ## Status de um item
15
+
16
+ Use `Badge` com a variante do status e um ícone ou rótulo claro.
17
+
18
+ ```tsx
19
+ import { Badge } from "@iclips/ui";
20
+ import { Check, CircleAlert, Clock, Info } from "lucide-react";
21
+
22
+ <Badge variant="success"><Check /> Aprovado</Badge>
23
+ <Badge variant="warning"><Clock /> Aguardando</Badge>
24
+ <Badge variant="info"><Info /> Em análise</Badge>
25
+ <Badge variant="destructive"><CircleAlert /> Recusado</Badge>
26
+ ```
27
+
28
+ Status que vem da API vira um mapa, não um `if` por status:
29
+
30
+ ```tsx
31
+ import { Badge } from "@iclips/ui";
32
+
33
+ const STATUS = {
34
+ pago: { variant: "success", rotulo: "Pago" },
35
+ pendente: { variant: "warning", rotulo: "Pendente" },
36
+ vencido: { variant: "destructive", rotulo: "Vencido" },
37
+ } as const;
38
+
39
+ function StatusParcela({ status }: { status: keyof typeof STATUS }) {
40
+ const { variant, rotulo } = STATUS[status];
41
+ return <Badge variant={variant}>{rotulo}</Badge>;
42
+ }
43
+ ```
44
+
45
+ Status neutro (sem juízo de bom ou ruim, como "Arquivado" ou "Inativo") usa `variant="muted"` ou `variant="outline"`, não uma cor de estado.
46
+
47
+ ## Valor positivo ou negativo
48
+
49
+ Número que sobe ou desce (variação, saldo, meta) usa o texto `-emphasis` do status, com sinal ou ícone — nunca só a cor.
50
+
51
+ ```tsx
52
+ import { TrendingDown, TrendingUp } from "lucide-react";
53
+
54
+ <span className="inline-flex items-center gap-1 text-success-emphasis">
55
+ <TrendingUp className="size-4" aria-hidden="true" /> +12,4%
56
+ </span>
57
+ <span className="inline-flex items-center gap-1 text-destructive-emphasis">
58
+ <TrendingDown className="size-4" aria-hidden="true" /> −3,1%
59
+ </span>
60
+ ```
61
+
62
+ ## Condição que vale para a tela toda
63
+
64
+ Se o status é da página e não de um item (plano expirando, modo somente-leitura), é um `Alert` — ver [Mensagens](mensagens.md).
65
+
66
+ ## Regras
67
+
68
+ - Status **nunca é roxo** e nunca é `text-<status>` cru em texto: use `text-<status>-emphasis`, que passa contraste AA nos dois temas.
69
+ - Não pinte **fundo de seção** ou card com cor de estado — o badge carrega o estado, não o card.
70
+ - Nunca fundo sólido de estado (`bg-success` sem `/10`): a cor cheia fica só no ícone e na borda de campo inválido.
71
+ - Mais regras de cor em [Linguagem visual](../DESIGN.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iclips/ui",
3
- "version": "2.0.3",
3
+ "version": "3.1.0",
4
4
  "description": "Design system da iClips — componentes React sobre Radix UI + Tailwind v4, paleta #7F26BF",
5
5
  "author": "iclipsbr",
6
6
  "license": "MIT",
@@ -47,7 +47,11 @@
47
47
  "scripts/codemod-v2-toast.mjs",
48
48
  "README.md",
49
49
  "LICENSE",
50
- "MIGRATION-v2.md"
50
+ "MIGRATION-v2.md",
51
+ "MIGRATION-v3.md",
52
+ "DESIGN.md",
53
+ "docs",
54
+ "scripts/check-colors.mjs"
51
55
  ],
52
56
  "peerDependencies": {
53
57
  "react": "^18.0.0 || ^19.0.0",
@@ -169,7 +173,7 @@
169
173
  "test:watch": "vitest",
170
174
  "test:coverage": "vitest run --coverage",
171
175
  "typecheck": "tsc --noEmit -p src/tsconfig.json",
172
- "lint": "eslint . --suppressions-location=eslint-suppressions.json && node scripts/check-forwardref.mjs",
176
+ "lint": "eslint . --suppressions-location=eslint-suppressions.json && node scripts/check-forwardref.mjs && node scripts/check-colors.mjs",
173
177
  "lint:fix": "eslint . --fix --suppressions-location=eslint-suppressions.json",
174
178
  "lint:prune-suppressions": "eslint . --prune-suppressions --suppressions-location=eslint-suppressions.json",
175
179
  "format": "prettier --write .",