@seniorsistemas/angular-components-mcp 1.0.0-beta.3 → 1.0.0-beta.4
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/dist/data/component-rules/button.md +37 -0
- package/dist/data/component-rules/checkbox.md +30 -0
- package/dist/data/component-rules/index.json +3 -0
- package/dist/data/component-technical/confirm-dialog.md +117 -0
- package/dist/data/component-technical/dialog.md +202 -0
- package/dist/data/component-technical/dynamic-form.md +292 -0
- package/dist/data/component-technical/index.json +4 -0
- package/dist/data/component-technical/kanban.md +116 -0
- package/dist/data/component-technical/loading-state.md +96 -0
- package/dist/data/component-technical/sidebar.md +82 -0
- package/dist/data/component-technical/table.md +19 -0
- package/dist/data/component-technical/toast.md +63 -0
- package/dist/data/components-metadata.json +3467 -0
- package/dist/data/dynamic-form-metadata.json +2272 -0
- package/dist/data/ux-rules.md +45 -0
- package/dist/handlers/handleDetectRequiredProviders.d.ts +12 -0
- package/dist/handlers/handleDetectRequiredProviders.d.ts.map +1 -0
- package/dist/handlers/handleGetComponentDetails.d.ts +13 -0
- package/dist/handlers/handleGetComponentDetails.d.ts.map +1 -0
- package/dist/handlers/handleGetComponentRules.d.ts +13 -0
- package/dist/handlers/handleGetComponentRules.d.ts.map +1 -0
- package/dist/handlers/handleGetComponentRulesIndex.d.ts +11 -0
- package/dist/handlers/handleGetComponentRulesIndex.d.ts.map +1 -0
- package/dist/handlers/handleGetComponentTechnicalGuide.d.ts +13 -0
- package/dist/handlers/handleGetComponentTechnicalGuide.d.ts.map +1 -0
- package/dist/handlers/handleGetComponentTechnicalIndex.d.ts +11 -0
- package/dist/handlers/handleGetComponentTechnicalIndex.d.ts.map +1 -0
- package/dist/handlers/handleGetComponents.d.ts +14 -0
- package/dist/handlers/handleGetComponents.d.ts.map +1 -0
- package/dist/handlers/handleGetDynamicFormTypes.d.ts +13 -0
- package/dist/handlers/handleGetDynamicFormTypes.d.ts.map +1 -0
- package/dist/handlers/handleGetUxRules.d.ts +10 -0
- package/dist/handlers/handleGetUxRules.d.ts.map +1 -0
- package/dist/handlers.d.ts +112 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/index.cjs +16703 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +16700 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Button — Regras de UX
|
|
2
|
+
|
|
3
|
+
**Seletor:** `s-button`
|
|
4
|
+
**Package:** `@seniorsistemas/angular-components/button`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Variantes
|
|
9
|
+
|
|
10
|
+
| Variante | Quando usar |
|
|
11
|
+
| ----------- | ---------------------------------------------------------------------------- |
|
|
12
|
+
| `primary` | Ação principal da página — máximo 1 por página/diálogo |
|
|
13
|
+
| `secondary` | Ações complementares à ação principal |
|
|
14
|
+
| `link` | Navegação secundária ou ações de baixa importância visual |
|
|
15
|
+
| `select` | Quando houver menu de ações — obrigatório usar ícone de chevron pré-definido |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Regras
|
|
20
|
+
|
|
21
|
+
### ⛔ Hierarquia (error)
|
|
22
|
+
|
|
23
|
+
Use no máximo 1 botão `primary` por página/diálogo. Os demais devem ser `secondary` ou `link`.
|
|
24
|
+
|
|
25
|
+
### ⚠️ Ícones (warning)
|
|
26
|
+
|
|
27
|
+
Use no máximo 1 ícone por botão (esquerda ou direita). Nunca 2 ícones no mesmo botão.
|
|
28
|
+
|
|
29
|
+
### ⛔ Tamanho `small` (error)
|
|
30
|
+
|
|
31
|
+
Permitido **somente** em células de tabela e section title.
|
|
32
|
+
Proibido em formulários, navegação primária ou qualquer outro contexto.
|
|
33
|
+
|
|
34
|
+
### ⛔ Labels
|
|
35
|
+
|
|
36
|
+
Use labels acionáveis e específicos. Prefira `"Salvar Cadastro"` a `"OK"`.
|
|
37
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Checkbox — Regras de UX
|
|
2
|
+
|
|
3
|
+
## Variantes
|
|
4
|
+
|
|
5
|
+
| Variante | Quando usar |
|
|
6
|
+
| -------------- | --------------------------------------------------------------- |
|
|
7
|
+
| **Horizontal** | Quando todos os itens possuem textos curtos ou palavras únicas. |
|
|
8
|
+
| **Vertical** | Quando um ou mais itens possuem textos longos. |
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Regras de uso
|
|
13
|
+
|
|
14
|
+
- **SEMPRE** use alinhamento vertical em telas pequenas.
|
|
15
|
+
- Cada checkbox deve possuir um label descrevendo a sua ação.
|
|
16
|
+
- Coloque opções importantes no topo.
|
|
17
|
+
|
|
18
|
+
## ⛔ Regras de erro
|
|
19
|
+
|
|
20
|
+
- Não use checkbox para escolhas mutuamente exclusivas. Nesses casos use Radio Button.
|
|
21
|
+
- Não use checkbox para ações que se aplicam imediatamente e alteram a interface em tempo real — isso é papel do Switch.
|
|
22
|
+
- Não apresente checkbox horizontalmente em dispositivos móveis.
|
|
23
|
+
- Não utilize checkbox sem um label claro e explícito.
|
|
24
|
+
- Não agrupe muitas opções horizontalmente. Neste caso use alinhamento vertical.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## ⚠️ Regras de recomendação
|
|
29
|
+
|
|
30
|
+
- Prefira usar checkbox em formulários ou contextos de escolha múltipla de forma persistente.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# ConfirmDialog — Guia Técnico
|
|
2
|
+
|
|
3
|
+
**Seletor:** `s-confirm-dialog`
|
|
4
|
+
**Package:** `@seniorsistemas/angular-components/confirm-dialog`
|
|
5
|
+
|
|
6
|
+
## Exports e Imports
|
|
7
|
+
|
|
8
|
+
| Tipo | Símbolo |
|
|
9
|
+
| ------- | ---------------------- |
|
|
10
|
+
| Service | `ConfirmDialogService` |
|
|
11
|
+
| Type | `ConfirmDialog` |
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { ConfirmDialogService } from '@seniorsistemas/angular-components/confirm-dialog';
|
|
15
|
+
import type { ConfirmDialog } from '@seniorsistemas/angular-components/confirm-dialog';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
> **Erro comum:** `Module has no exported member 'ConfirmDialogModule'` — use `ConfirmDialogService` diretamente; não existe `ConfirmDialogModule` público nesta versão.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
O ConfirmDialog é **SEMPRE** utilizado via `ConfirmDialogService`. O componente `s-confirm-dialog` **não deve ser instanciado diretamente** no template — ele é renderizado internamente pelo serviço.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Padrão único: Via `ConfirmDialogService` ✅ ÚNICO PADRÃO SUPORTADO
|
|
27
|
+
|
|
28
|
+
Abre um dialog de confirmação com mensagem, título e botões configuráveis. Retorna uma `Promise<boolean>` que resolve com `true` se o usuário confirmar e `false` se cancelar.
|
|
29
|
+
|
|
30
|
+
### Regras obrigatórias
|
|
31
|
+
|
|
32
|
+
- **NUNCA** adicionar `<s-confirm-dialog>` diretamente no template — o serviço gerencia isso automaticamente
|
|
33
|
+
- Importar `ConfirmDialogModule` no módulo ou componente raiz para registrar o componente
|
|
34
|
+
|
|
35
|
+
### Exemplo
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { ConfirmDialogModule, ConfirmDialogService } from '@seniorsistemas/angular-components/confirm-dialog';
|
|
39
|
+
|
|
40
|
+
@Component({
|
|
41
|
+
imports: [ConfirmDialogModule],
|
|
42
|
+
template: `
|
|
43
|
+
<s-button
|
|
44
|
+
label="Deletar"
|
|
45
|
+
(clicked)="delete()"
|
|
46
|
+
/>
|
|
47
|
+
`,
|
|
48
|
+
})
|
|
49
|
+
export class MyComponent {
|
|
50
|
+
private readonly confirmDialog = inject(ConfirmDialogService);
|
|
51
|
+
|
|
52
|
+
protected async delete(): Promise<void> {
|
|
53
|
+
const confirmed = await this.confirmDialog.open({
|
|
54
|
+
header: 'Confirmar exclusão',
|
|
55
|
+
message: 'Tem certeza que deseja excluir este registro? Esta ação não pode ser desfeita.',
|
|
56
|
+
acceptLabel: 'Excluir',
|
|
57
|
+
rejectLabel: 'Cancelar',
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (confirmed) {
|
|
61
|
+
// prosseguir com a exclusão
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Notas de integração
|
|
70
|
+
|
|
71
|
+
- `ConfirmDialogService.open()` retorna `Promise<boolean>`: use `async/await` ou `.then()` para aguardar a resposta do usuário.
|
|
72
|
+
- Personalize os labels dos botões via `acceptLabel` e `rejectLabel` para dar clareza à ação (ex: `'Excluir'` ao invés de `'Ok'`).
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Erros Comuns
|
|
77
|
+
|
|
78
|
+
### `Property 'open' does not exist on type 'ConfirmDialogService'`
|
|
79
|
+
|
|
80
|
+
**Tipo:** method
|
|
81
|
+
**Solução:** Use `confirm()` em vez de `open()`.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// ❌ Incorreto
|
|
85
|
+
await this.confirmDialogService.open({ ... })
|
|
86
|
+
|
|
87
|
+
// ✅ Correto
|
|
88
|
+
this.confirmDialogService.confirm({ ... })
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
> `ConfirmDialogService` desta versão expõe o método `confirm(confirmDialog)`.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### `Type 'Promise<boolean>' is not assignable...`
|
|
96
|
+
|
|
97
|
+
**Tipo:** return-type
|
|
98
|
+
**Solução:** Não use `await` no retorno de `confirm()`; o fluxo ocorre via callbacks `accept`/`reject`.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// ❌ Incorreto
|
|
102
|
+
const ok = await confirmDialogService.confirm(config);
|
|
103
|
+
|
|
104
|
+
// ✅ Correto
|
|
105
|
+
confirmDialogService.confirm({
|
|
106
|
+
...config,
|
|
107
|
+
accept: () => {
|
|
108
|
+
/* ação confirmada */
|
|
109
|
+
},
|
|
110
|
+
reject: () => {
|
|
111
|
+
/* ação cancelada */
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
> O método `confirm()` não retorna `Promise` nesta implementação.
|
|
117
|
+
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Dialog — Guia Técnico
|
|
2
|
+
|
|
3
|
+
**Seletor:** `s-dialog`
|
|
4
|
+
**Package:** `@seniorsistemas/angular-components/dialog`
|
|
5
|
+
|
|
6
|
+
## Exports e Imports
|
|
7
|
+
|
|
8
|
+
| Tipo | Símbolo |
|
|
9
|
+
| --------- | ---------------------------- |
|
|
10
|
+
| Component | `DialogComponent` |
|
|
11
|
+
| Service | `DialogService` |
|
|
12
|
+
| Class | `ActiveDialog` |
|
|
13
|
+
| Types | `DialogOptions`, `DialogRef` |
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { DialogComponent } from '@seniorsistemas/angular-components/dialog';
|
|
17
|
+
import { DialogService } from '@seniorsistemas/angular-components/dialog';
|
|
18
|
+
import { ActiveDialog } from '@seniorsistemas/angular-components/dialog';
|
|
19
|
+
import type { DialogOptions, DialogRef } from '@seniorsistemas/angular-components/dialog';
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> **Erros comuns:**
|
|
23
|
+
>
|
|
24
|
+
> - `Module has no exported member 'DialogModule'` → use `DialogComponent` standalone
|
|
25
|
+
> - `Module has no exported member 'DynamicDialogRef'` → use `DialogRef` e `ActiveDialog`
|
|
26
|
+
> - `Module has no exported member 'DIALOG_DATA'` → use padrão `ActiveDialog`/`DialogRef`
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
O Dialog pode ser controlado de forma declarativa via binding de propriedade ou de forma programática via `DialogService`. A forma de uso deve ser escolhida conforme o contexto de onde o dialog será aberto.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Padrão 1: Declarativo via `[(visible)]`
|
|
35
|
+
|
|
36
|
+
**Preferido:** Não (use apenas para casos simples)
|
|
37
|
+
|
|
38
|
+
O estado de abertura é controlado por uma propriedade booleana no componente pai, vinculada via two-way binding `[(visible)]`.
|
|
39
|
+
|
|
40
|
+
### Exemplo
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { DialogModule } from '@seniorsistemas/angular-components/dialog';
|
|
44
|
+
|
|
45
|
+
@Component({
|
|
46
|
+
imports: [DialogModule],
|
|
47
|
+
template: `
|
|
48
|
+
<s-button
|
|
49
|
+
label="Abrir"
|
|
50
|
+
(clicked)="isOpen = true"
|
|
51
|
+
/>
|
|
52
|
+
<s-dialog
|
|
53
|
+
[(visible)]="isOpen"
|
|
54
|
+
header="Título"
|
|
55
|
+
>
|
|
56
|
+
<ng-template sTemplate="body"> Conteúdo do dialog </ng-template>
|
|
57
|
+
<ng-template sTemplate="footer">
|
|
58
|
+
<s-button
|
|
59
|
+
label="Fechar"
|
|
60
|
+
(clicked)="isOpen = false"
|
|
61
|
+
/>
|
|
62
|
+
</ng-template>
|
|
63
|
+
</s-dialog>
|
|
64
|
+
`,
|
|
65
|
+
})
|
|
66
|
+
export class MyComponent {
|
|
67
|
+
protected isOpen = false;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Padrão 2: Programático via `DialogService` ✅ PREFERIDO
|
|
74
|
+
|
|
75
|
+
O dialog é aberto injetando o `DialogService`, que cria e renderiza o componente dinamicamente. Permite abrir dialogs de qualquer lugar da aplicação, inclusive de serviços.
|
|
76
|
+
|
|
77
|
+
### Exemplo
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { DialogComponent, DialogService } from '@seniorsistemas/angular-components/dialog';
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
// No componente ou serviço que abre o dialog
|
|
84
|
+
@Component({ ... })
|
|
85
|
+
export class MyComponent {
|
|
86
|
+
private readonly dialogService = inject(DialogService);
|
|
87
|
+
|
|
88
|
+
protected openDialog(): void {
|
|
89
|
+
this.dialogService.open(MyDialogContentComponent, {
|
|
90
|
+
header: 'Título do Dialog',
|
|
91
|
+
data: { id: 1 }
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Componente de conteúdo do dialog
|
|
97
|
+
@Component({
|
|
98
|
+
imports: [DialogComponent, TemplateModule]
|
|
99
|
+
template: `
|
|
100
|
+
<s-dialog header="Dialog Header">
|
|
101
|
+
<!-- TODO: Campo opcional, utilizar quando [header] não for provido, caso queira HTML Customizado para o header. -->
|
|
102
|
+
<ng-template sTemplate="header"></ng-template>
|
|
103
|
+
|
|
104
|
+
<p>Id recebido: {{ data.id }}</p>
|
|
105
|
+
<!-- TODO: Campo opcional. -->
|
|
106
|
+
<ng-template
|
|
107
|
+
sTemplate="footer"
|
|
108
|
+
let-activeDialog="activeDialog">
|
|
109
|
+
<div class="grid grid-cols-12">
|
|
110
|
+
<div class="col-span-12 flex justify-around">
|
|
111
|
+
<s-button
|
|
112
|
+
label="Ação terciária"
|
|
113
|
+
priority="link"
|
|
114
|
+
/>
|
|
115
|
+
<s-button
|
|
116
|
+
(clicked)="activeDialog.dismiss('Dismissed dialog demo footer')"
|
|
117
|
+
label="Ação secundária"
|
|
118
|
+
/>
|
|
119
|
+
<s-button
|
|
120
|
+
label="Ação primária"
|
|
121
|
+
(clicked)="activeDialog.close('Closed dialog demo footer')"
|
|
122
|
+
priority="primary"
|
|
123
|
+
/>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</ng-template>
|
|
127
|
+
|
|
128
|
+
</s-dialog>
|
|
129
|
+
|
|
130
|
+
`
|
|
131
|
+
})
|
|
132
|
+
export class MyDialogContentComponent {
|
|
133
|
+
readonly activeDialog = inject(ActiveDialog);
|
|
134
|
+
|
|
135
|
+
// Função opcional, caso utilize let-activeDialog no ng-template nao precisa fechar por aqui, ambos possuem o mesmo valor de activeDialog
|
|
136
|
+
protected close(): void {
|
|
137
|
+
this.activeDialog.close('resultado opcional');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Notas de integração
|
|
145
|
+
|
|
146
|
+
- O `<s-dialog>` deve existir no template para que o `DialogService` possa renderizar conteúdo nele. Coloque-o em um componente de layout de nível superior.
|
|
147
|
+
- Para receber dados no componente de conteúdo, use `inject(DIALOG_DATA)` importado de `@seniorsistemas/angular-components/dialog`.
|
|
148
|
+
- Para fechar o dialog de dentro do componente de conteúdo, injete `DynamicDialogRef` e chame `ref.close(resultado)`.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Erros Comuns
|
|
153
|
+
|
|
154
|
+
### `Module has no exported member 'DialogModule'`
|
|
155
|
+
|
|
156
|
+
**Tipo:** import
|
|
157
|
+
**Solução:** Use standalone import com `DialogComponent`.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// ❌ Incorreto
|
|
161
|
+
imports: [DialogModule];
|
|
162
|
+
|
|
163
|
+
// ✅ Correto
|
|
164
|
+
imports: [DialogComponent];
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
> Nesta versão, o public API expõe `DialogComponent` para uso declarativo.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### `Property 'onClose' does not exist on type 'DialogRef'`
|
|
172
|
+
|
|
173
|
+
**Tipo:** property
|
|
174
|
+
**Solução:** Use o observable `closed`.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
// ❌ Incorreto
|
|
178
|
+
ref.onClose.subscribe(...)
|
|
179
|
+
|
|
180
|
+
// ✅ Correto
|
|
181
|
+
ref.closed.subscribe(...)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
> `DialogRef` expõe `closed` e `dismissed`.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
### `Object literal may only specify known properties, and 'header' does not exist in type 'DialogOptions'`
|
|
189
|
+
|
|
190
|
+
**Tipo:** parameter
|
|
191
|
+
**Solução:** Use apenas as `DialogOptions` suportadas (`destroyClickOutside`, `escapeOnEsc`, `size`).
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// ❌ Incorreto
|
|
195
|
+
dialogService.open(MyDialogComponent, { header: 'Título' });
|
|
196
|
+
|
|
197
|
+
// ✅ Correto
|
|
198
|
+
dialogService.open(MyDialogComponent, { size: 'md' });
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
> `DialogOptions` nesta versão não inclui `header`/`data` no tipo público.
|
|
202
|
+
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# DynamicForm — Guia Técnico
|
|
2
|
+
|
|
3
|
+
**Seletor:** `s-dynamic-form`
|
|
4
|
+
**Package:** `@seniorsistemas/angular-components/dynamic-form`
|
|
5
|
+
|
|
6
|
+
## Exports e Imports
|
|
7
|
+
|
|
8
|
+
| Tipo | Símbolo |
|
|
9
|
+
| ---------- | ---------------------------------------------------------------------------------------- |
|
|
10
|
+
| Components | `DynamicFormComponent`, `DynamicFormDirective`, `FieldLabelComponent`, `LookupComponent` |
|
|
11
|
+
| Classes | `DynamicFormRegistry` |
|
|
12
|
+
| Types | `DynamicStructure`, `FieldConfig`, `DynamicType`, `FieldType`, `FieldTypeMap` |
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { DynamicFormComponent } from '@seniorsistemas/angular-components/dynamic-form';
|
|
16
|
+
import { DynamicFormModule } from '@seniorsistemas/angular-components/dynamic-form';
|
|
17
|
+
import type { DynamicStructure } from '@seniorsistemas/angular-components/dynamic-form';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Regra de estruturas
|
|
23
|
+
|
|
24
|
+
- Use `row` quando os campos não precisam de título visual agrupando-os. É o tipo padrão.
|
|
25
|
+
- **TODOS** os campos do formulário devem ir na **mesma `row`**. Só crie uma estrutura adicional se for necessário separar campos em um agrupamento com título próprio.
|
|
26
|
+
- `section` **OBRIGATORIAMENTE** deve ter `header`. Nunca use `section` sem `header`.
|
|
27
|
+
- Dentro de `section` **SEMPRE** deve haver outra `DynamicStructure` (`row` ou `fieldset`). Campos diretos em `section` não são permitidos.
|
|
28
|
+
- **NUNCA** aninhe `section` dentro de `section`.
|
|
29
|
+
- **Antes de agrupar campos**, pergunte ao usuário qual estrutura ele quer usar para organizar o formulário: `row` (sem título), `section` (nova seção com título), ou `fieldset` (agrupamento semântico com título).
|
|
30
|
+
|
|
31
|
+
### Exemplo comum (sem seção)
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
protected configs: DynamicStructure[] = [
|
|
35
|
+
{
|
|
36
|
+
type: 'row',
|
|
37
|
+
fields: [
|
|
38
|
+
{ name: 'name', type: 'string', label: 'Nome', required: () => true, size: { xl: 6 } },
|
|
39
|
+
{ name: 'email', type: 'string', label: 'E-mail', required: () => true, size: { xl: 6 } },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
> **OBRIGATÓRIO:** Para cada campo em `fields`, o `FormGroup` **deve ter um `FormControl` declarado explicitamente com o mesmo valor de `name`**. O DynamicForm usa esse nome internamente como `formControlName` para fazer o vínculo — ele NÃO cria os controles automaticamente.
|
|
46
|
+
> No exemplo acima, o `FormGroup` deve ser criado com: `this.fb.group({ name: [], email: [] })`.
|
|
47
|
+
|
|
48
|
+
### Exemplo com seção (quando há nova seção)
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
protected configs: DynamicStructure[] = [
|
|
52
|
+
{
|
|
53
|
+
type: 'section',
|
|
54
|
+
header: 'Dados Pessoais',
|
|
55
|
+
configs: [
|
|
56
|
+
{
|
|
57
|
+
type: 'row',
|
|
58
|
+
fields: [
|
|
59
|
+
{ name: 'name', type: 'string', label: 'Nome', required: () => true, size: { xl: 6 } },
|
|
60
|
+
{ name: 'email', type: 'string', label: 'E-mail', required: () => true, size: { xl: 6 } },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Exemplo com `fieldset` para agrupamento
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
protected configs: DynamicStructure[] = [
|
|
72
|
+
{
|
|
73
|
+
type: 'row',
|
|
74
|
+
fields: [
|
|
75
|
+
{ name: 'fullName', type: 'string', label: 'Nome completo', size: { xl: 12 } },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
type: 'fieldset',
|
|
80
|
+
header: 'Endereço',
|
|
81
|
+
configs: [
|
|
82
|
+
{
|
|
83
|
+
type: 'row',
|
|
84
|
+
fields: [
|
|
85
|
+
{ name: 'zipCode', type: 'string', label: 'CEP', size: { xl: 4 }, mask: '000000-000' },
|
|
86
|
+
{ name: 'street', type: 'string', label: 'Rua', size: { xl: 8 } },
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Padrão 1: Com `FormGroup` externo ✅ PADRÃO OBRIGATÓRIO
|
|
97
|
+
|
|
98
|
+
O DynamicForm **NÃO cria nem gerencia seu próprio FormGroup**. O `FormGroup` deve ser criado externamente no componente e passado via `[form]`. **Para cada campo declarado em `configs`, deve existir um `FormControl` no `FormGroup` com o mesmo valor de `name`** — o DynamicForm usa esse nome como `formControlName` internamente.
|
|
99
|
+
|
|
100
|
+
### Exemplo
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
104
|
+
import { DynamicFormModule, DynamicStructure } from '@seniorsistemas/angular-components/dynamic-form';
|
|
105
|
+
|
|
106
|
+
@Component({
|
|
107
|
+
imports: [DynamicFormModule, ReactiveFormsModule],
|
|
108
|
+
template: `
|
|
109
|
+
<s-dynamic-form
|
|
110
|
+
[form]="form"
|
|
111
|
+
[configs]="configs"
|
|
112
|
+
/>
|
|
113
|
+
<s-button
|
|
114
|
+
label="Salvar"
|
|
115
|
+
priority="primary"
|
|
116
|
+
[disabled]="form.invalid"
|
|
117
|
+
(clicked)="save()"
|
|
118
|
+
/>
|
|
119
|
+
`,
|
|
120
|
+
})
|
|
121
|
+
export class MyComponent implements OnInit {
|
|
122
|
+
private readonly fb = inject(FormBuilder);
|
|
123
|
+
|
|
124
|
+
protected form!: FormGroup;
|
|
125
|
+
protected configs: DynamicStructure[] = [
|
|
126
|
+
{
|
|
127
|
+
type: 'row',
|
|
128
|
+
fields: [
|
|
129
|
+
{ name: 'name', type: 'string', label: 'Nome', required: () => true, size: { xl: 6 } },
|
|
130
|
+
{ name: 'email', type: 'string', label: 'E-mail', required: () => true, size: { xl: 6 } },
|
|
131
|
+
{ name: 'birthDate', type: 'date', label: 'Data de Nascimento', size: { xl: 4 } },
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
public ngOnInit(): void {
|
|
137
|
+
// Cada FormControl deve ter o mesmo nome que o `name` do campo em configs
|
|
138
|
+
this.form = this.fb.group({
|
|
139
|
+
name: [],
|
|
140
|
+
email: [],
|
|
141
|
+
birthDate: [],
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
protected save(): void {
|
|
146
|
+
if (this.form.valid) {
|
|
147
|
+
console.log(this.form.value);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Notas de integração
|
|
156
|
+
|
|
157
|
+
- O `FormGroup` **DEVE** ser criado antes do DynamicForm renderizar, com um `FormControl` para cada campo. Exemplo: `this.fb.group({ fieldName: [], otherField: [] })`.
|
|
158
|
+
- Cada campo em `fields` **DEVE** ter `name` definido. Para cada `name`, **declare explicitamente um `FormControl` no `FormGroup` com o mesmo nome** — o DynamicForm usa esse nome como `formControlName` internamente e não cria controles automaticamente.
|
|
159
|
+
- Exemplo: campos com `name: 'name'` e `name: 'email'` exigem `this.fb.group({ name: [], email: [] })`.
|
|
160
|
+
- No array `fields`, a propriedade `required` deve ser uma função que retorna boolean (`() => boolean`).
|
|
161
|
+
- Para campos opcionais com providers específicos (ex: `editor`), use a tool `detect_required_providers` para identificar o provider necessário e onde adicioná-lo.
|
|
162
|
+
- Use `size` com breakpoints customizados (`xs`, `sm`, `md`, `lg`, `xl`, `xxl`, `big`) — **NÃO use breakpoints padrão do Tailwind**.
|
|
163
|
+
- **NUNCA** use `s-select`, `s-checkbox`, `s-text-area` ou outros campos individualmente — sempre prefira `s-dynamic-form`.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Criando um tipo de campo customizado
|
|
168
|
+
|
|
169
|
+
> ⚠️ **ATENÇÃO: uso pouco frequente.** Criar um tipo custom do zero é trabalhoso e só deve ser feito quando não existe nenhum tipo nativo que atenda à necessidade e nenhuma lib externa com provider já disponível. **Antes de criar, pergunte ao usuário:**
|
|
170
|
+
>
|
|
171
|
+
> 1. Já existe algum tipo custom implementado no projeto (ex: em `optional-fields/` ou equivalente)?
|
|
172
|
+
> 2. Existe alguma biblioteca/pacote que o projeto já usa que fornece um provider pronto para o DynamicForm?
|
|
173
|
+
|
|
174
|
+
### Passo 1 — Criar a interface de configuração e o provider
|
|
175
|
+
|
|
176
|
+
Crie um arquivo `<nome>-field.ts` no diretório do seu campo customizado.
|
|
177
|
+
A interface deve estender `FieldConfig`, fazer o module augmentation registrando o novo tipo em `FieldTypeMap`, e exportar uma função `provide<Nome>Field()` que registra o componente no `DynamicFormRegistry`.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// my-custom-field.ts
|
|
181
|
+
import { APP_INITIALIZER, EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';
|
|
182
|
+
import { FieldConfig, DynamicFormRegistry } from '@seniorsistemas/angular-components/dynamic-form';
|
|
183
|
+
import { MyCustomFieldComponent } from './my-custom-field/my-custom-field.component';
|
|
184
|
+
|
|
185
|
+
/** Configuração do campo customizado. */
|
|
186
|
+
export interface MyCustomFieldConfig extends FieldConfig {
|
|
187
|
+
type: 'myCustom';
|
|
188
|
+
// adicione aqui as propriedades específicas do seu campo
|
|
189
|
+
someOption?: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Augmentation: registra o novo tipo no mapa do DynamicForm
|
|
193
|
+
declare module '@seniorsistemas/angular-components/dynamic-form' {
|
|
194
|
+
export interface FieldTypeMap {
|
|
195
|
+
myCustom: MyCustomFieldConfig;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Registra o componente do campo customizado no DynamicForm. */
|
|
200
|
+
export function provideMyCustomField(): EnvironmentProviders {
|
|
201
|
+
return makeEnvironmentProviders([
|
|
202
|
+
{
|
|
203
|
+
provide: APP_INITIALIZER,
|
|
204
|
+
useFactory: () => () => {
|
|
205
|
+
DynamicFormRegistry.registerField(MyCustomFieldComponent, 'myCustom');
|
|
206
|
+
},
|
|
207
|
+
multi: true,
|
|
208
|
+
},
|
|
209
|
+
]);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Passo 2 — Criar o componente Angular
|
|
214
|
+
|
|
215
|
+
Crie um componente standalone que implementa `BaseFieldComponentConfig`.
|
|
216
|
+
Requisitos obrigatórios:
|
|
217
|
+
|
|
218
|
+
- Ser `standalone: true`
|
|
219
|
+
- Implementar `BaseFieldComponentConfig`
|
|
220
|
+
- Ter o `input` de `field` (tipado com a interface criada no passo 1)
|
|
221
|
+
- Ter o `input` de `formControl` (necessário quando o campo modifica o `FormGroup`, que é quase sempre)
|
|
222
|
+
- Importar e **renderizar `FieldLabelComponent` como primeira tag no template** — responsável por exibir o label, tooltip e infoSign do campo
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// my-custom-field/my-custom-field.component.ts
|
|
226
|
+
import { Component, input } from '@angular/core';
|
|
227
|
+
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
|
228
|
+
import { BaseFieldComponentConfig, FieldLabelComponent } from '@seniorsistemas/angular-components/dynamic-form';
|
|
229
|
+
import { MyCustomFieldConfig } from '../my-custom-field';
|
|
230
|
+
|
|
231
|
+
@Component({
|
|
232
|
+
standalone: true,
|
|
233
|
+
imports: [ReactiveFormsModule, FieldLabelComponent /* + outros imports necessários */],
|
|
234
|
+
templateUrl: './my-custom-field.component.html',
|
|
235
|
+
})
|
|
236
|
+
export class MyCustomFieldComponent implements BaseFieldComponentConfig {
|
|
237
|
+
/** Config do campo injetada pelo DynamicForm. */
|
|
238
|
+
field = input.required<MyCustomFieldConfig>();
|
|
239
|
+
|
|
240
|
+
/** FormControl associado ao campo no FormGroup pai. */
|
|
241
|
+
formControl = input.required<FormControl>();
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
```html
|
|
246
|
+
<!-- my-custom-field/my-custom-field.component.html -->
|
|
247
|
+
@let _field = field();
|
|
248
|
+
|
|
249
|
+
<!-- OBRIGATÓRIO: sempre a primeira tag do template -->
|
|
250
|
+
<s-field-label [field]="_field"></s-field-label>
|
|
251
|
+
|
|
252
|
+
<!-- Implemente aqui o input/controle visual do campo -->
|
|
253
|
+
<input
|
|
254
|
+
[formControl]="formControl()"
|
|
255
|
+
[placeholder]="_field.placeholder ?? ''"
|
|
256
|
+
/>
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Passo 3 — Registrar o provider no AppConfig ou AppModule
|
|
260
|
+
|
|
261
|
+
O provider gerado no Passo 1 deve ser adicionado ao `providers` do `app.config.ts` (projetos standalone) ou ao `providers` do `AppModule` (projetos com módulos).
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
// app.config.ts (projetos standalone)
|
|
265
|
+
import { provideMyCustomField } from './my-custom-field/my-custom-field';
|
|
266
|
+
|
|
267
|
+
export const appConfig: ApplicationConfig = {
|
|
268
|
+
providers: [
|
|
269
|
+
// ... outros providers
|
|
270
|
+
provideMyCustomField(),
|
|
271
|
+
],
|
|
272
|
+
};
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Após registrar o provider, o tipo `'myCustom'` estará disponível na configuração do DynamicForm:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
configs: DynamicStructure[] = [
|
|
279
|
+
{
|
|
280
|
+
type: 'section',
|
|
281
|
+
configs: [
|
|
282
|
+
{
|
|
283
|
+
type: 'row',
|
|
284
|
+
fields: [
|
|
285
|
+
{ type: 'myCustom', name: 'meuCampo', label: 'Meu Campo', someOption: 'valor' }
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
];
|
|
291
|
+
```
|
|
292
|
+
|