@seniorsistemas/angular-components-mcp 1.0.0-beta.3 → 1.0.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/dist/data/component-rules/button.md +37 -0
- package/dist/data/component-rules/checkbox.md +30 -0
- package/dist/data/component-rules/confirm-dialog.md +86 -0
- package/dist/data/component-rules/index.json +3 -0
- package/dist/data/component-rules/radio-button.md +0 -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 +4402 -0
- package/dist/data/dynamic-form-metadata.json +2281 -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,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
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Kanban — Guia Técnico
|
|
2
|
+
|
|
3
|
+
**Seletor:** `s-kanban`
|
|
4
|
+
**Package:** `@seniorsistemas/angular-components/kanban`
|
|
5
|
+
|
|
6
|
+
## Exports e Imports
|
|
7
|
+
|
|
8
|
+
| Tipo | Símbolo |
|
|
9
|
+
| --------- | ---------------------------------------------------------------------------------------- |
|
|
10
|
+
| Component | `KanbanComponent` |
|
|
11
|
+
| Module | `KanbanModule` |
|
|
12
|
+
| Types | `KanbanData`, `KanbanColumn`, `KanbanItem`, `KanbanItemMovedData`, `KanbanTemplateTypes` |
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { KanbanComponent } from '@seniorsistemas/angular-components/kanban';
|
|
16
|
+
import { KanbanModule } from '@seniorsistemas/angular-components/kanban';
|
|
17
|
+
import type { KanbanData, KanbanColumn, KanbanItem } from '@seniorsistemas/angular-components/kanban';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Estruturas de dados
|
|
23
|
+
|
|
24
|
+
### `KanbanData`
|
|
25
|
+
|
|
26
|
+
Estrutura raiz passada ao input `[data]` do componente. Contém um array de colunas.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
const data: KanbanData = {
|
|
30
|
+
columns: [
|
|
31
|
+
{ id: 'todo', title: 'A Fazer', items: [] },
|
|
32
|
+
{ id: 'doing', title: 'Em Progresso', items: [] },
|
|
33
|
+
{ id: 'done', title: 'Concluído', items: [] },
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### `KanbanColumn`
|
|
39
|
+
|
|
40
|
+
| Propriedade | Tipo | Obrigatório | Descrição |
|
|
41
|
+
| ----------- | ---------------------- | ----------- | ------------------------------------- |
|
|
42
|
+
| `id` | `string` | Não | Identificador único da coluna |
|
|
43
|
+
| `title` | `string` | Sim | Título exibido no cabeçalho da coluna |
|
|
44
|
+
| `items` | `KanbanItem[]` | Sim | Array de itens dentro da coluna |
|
|
45
|
+
| `options` | `KanbanColumnOption[]` | Não | Menu de opções da coluna |
|
|
46
|
+
|
|
47
|
+
### `KanbanItem`
|
|
48
|
+
|
|
49
|
+
| Propriedade | Tipo | Obrigatório | Descrição |
|
|
50
|
+
| ----------- | -------------------- | ----------- | ------------------------------------------------------------------------------ |
|
|
51
|
+
| `data` | `any` | Sim | Dados customizados do item (acessados via `item.data.propriedade` no template) |
|
|
52
|
+
| `options` | `KanbanItemOption[]` | Não | Menu de opções do item |
|
|
53
|
+
| `disabled` | `boolean` | Não | Quando `true`, o item não pode ser arrastado |
|
|
54
|
+
| `frozen` | `boolean` | Não | Quando `true`, o item fica fixo no topo da coluna |
|
|
55
|
+
|
|
56
|
+
> O template do item acessa os dados via `item.data.propriedade` — ex: `{{ item.data.title }}`
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Templates (`KanbanTemplateTypes`)
|
|
61
|
+
|
|
62
|
+
O Kanban aceita **exatamente 5 tipos de template**, identificados pelo atributo `sTemplate`. Qualquer outro valor é silenciosamente ignorado.
|
|
63
|
+
|
|
64
|
+
| Tipo | Onde é renderizado |
|
|
65
|
+
| ---------------------- | ----------------------------------- |
|
|
66
|
+
| `column-header` | Cabeçalho de cada coluna |
|
|
67
|
+
| `column-empty-message` | Corpo da coluna quando não há itens |
|
|
68
|
+
| `item-header` | Cabeçalho do card (item) |
|
|
69
|
+
| `item-body` | Corpo do card (item) |
|
|
70
|
+
| `item-footer` | Rodapé do card (item) |
|
|
71
|
+
|
|
72
|
+
### Exemplo de uso
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<s-kanban [data]="kanbanData">
|
|
76
|
+
<!-- Cabeçalho customizado da coluna -->
|
|
77
|
+
<ng-template
|
|
78
|
+
sTemplate="column-header"
|
|
79
|
+
let-column
|
|
80
|
+
>
|
|
81
|
+
<strong>{{ column.title }}</strong>
|
|
82
|
+
</ng-template>
|
|
83
|
+
|
|
84
|
+
<!-- Mensagem quando a coluna está vazia -->
|
|
85
|
+
<ng-template sTemplate="column-empty-message">
|
|
86
|
+
<span>Nenhum item nesta coluna</span>
|
|
87
|
+
</ng-template>
|
|
88
|
+
|
|
89
|
+
<!-- Cabeçalho do card -->
|
|
90
|
+
<ng-template
|
|
91
|
+
sTemplate="item-header"
|
|
92
|
+
let-item
|
|
93
|
+
>
|
|
94
|
+
<span>{{ item.data.title }}</span>
|
|
95
|
+
</ng-template>
|
|
96
|
+
|
|
97
|
+
<!-- Corpo do card -->
|
|
98
|
+
<ng-template
|
|
99
|
+
sTemplate="item-body"
|
|
100
|
+
let-item
|
|
101
|
+
>
|
|
102
|
+
<p>{{ item.data.description }}</p>
|
|
103
|
+
</ng-template>
|
|
104
|
+
|
|
105
|
+
<!-- Rodapé do card -->
|
|
106
|
+
<ng-template
|
|
107
|
+
sTemplate="item-footer"
|
|
108
|
+
let-item
|
|
109
|
+
>
|
|
110
|
+
<small>{{ item.data.status }}</small>
|
|
111
|
+
</ng-template>
|
|
112
|
+
</s-kanban>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
> **ATENÇÃO:** Use **apenas** os 5 tipos listados acima como valor de `sTemplate`. Valores fora deste conjunto (`KanbanTemplateTypes`) são ignorados sem erro — o template simplesmente não será renderizado.
|
|
116
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# LoadingState — Guia Técnico
|
|
2
|
+
|
|
3
|
+
**Seletor:** `s-loading-state` / `[sLoadingState]`
|
|
4
|
+
**Package:** `@seniorsistemas/angular-components/loading-state`
|
|
5
|
+
|
|
6
|
+
O LoadingState pode ser usado de duas formas: como **componente wrapper** que envolve o conteúdo a ser bloqueado, ou como **diretiva estrutural** `[sLoadingState]` aplicada diretamente a um elemento existente.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Padrão 1: Como componente wrapper ✅ PREFERIDO
|
|
11
|
+
|
|
12
|
+
Envolve o conteúdo que deve ser bloqueado durante o carregamento. Quando `loading` é `true`, exibe o indicador animado sobre o conteúdo projetado.
|
|
13
|
+
|
|
14
|
+
### Exemplo
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { LoadingStateModule } from '@seniorsistemas/angular-components/loading-state';
|
|
18
|
+
|
|
19
|
+
@Component({
|
|
20
|
+
imports: [LoadingStateModule],
|
|
21
|
+
template: `
|
|
22
|
+
<s-loading-state [loading]="isLoading">
|
|
23
|
+
<s-card>
|
|
24
|
+
<!-- conteúdo que será bloqueado durante o carregamento -->
|
|
25
|
+
<p>{{ data }}</p>
|
|
26
|
+
</s-card>
|
|
27
|
+
</s-loading-state>
|
|
28
|
+
`
|
|
29
|
+
})
|
|
30
|
+
export class MyComponent {
|
|
31
|
+
protected isLoading = false;
|
|
32
|
+
protected data = '';
|
|
33
|
+
|
|
34
|
+
protected async loadData(): Promise<void> {
|
|
35
|
+
this.isLoading = true;
|
|
36
|
+
try {
|
|
37
|
+
this.data = await this.api.fetchData();
|
|
38
|
+
} finally {
|
|
39
|
+
this.isLoading = false; // SEMPRE usar finally para garantir reset
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Padrão 2: Como diretiva estrutural `[sLoadingState]`
|
|
48
|
+
|
|
49
|
+
Aplica o comportamento de loading diretamente a um elemento existente sem adicionar um elemento DOM extra ao layout.
|
|
50
|
+
|
|
51
|
+
### Exemplo
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { LoadingStateModule } from '@seniorsistemas/angular-components/loading-state';
|
|
55
|
+
|
|
56
|
+
@Component({
|
|
57
|
+
imports: [LoadingStateModule],
|
|
58
|
+
template: `
|
|
59
|
+
<div [sLoadingState]="isLoading">
|
|
60
|
+
<p>{{ data }}</p>
|
|
61
|
+
</div>
|
|
62
|
+
`
|
|
63
|
+
})
|
|
64
|
+
export class MyComponent {
|
|
65
|
+
protected isLoading = false;
|
|
66
|
+
protected data = '';
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Padrão 3: Bloqueio de tela inteira
|
|
73
|
+
|
|
74
|
+
Bloqueia a tela inteira durante operações globais.
|
|
75
|
+
|
|
76
|
+
### Exemplo
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
@Component({
|
|
80
|
+
imports: [LoadingStateModule],
|
|
81
|
+
template: `
|
|
82
|
+
<s-loading-state [loading]="isLoading" [blockWindow]="true">
|
|
83
|
+
<router-outlet />
|
|
84
|
+
</s-loading-state>
|
|
85
|
+
`
|
|
86
|
+
})
|
|
87
|
+
export class AppComponent {
|
|
88
|
+
protected isLoading = false;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Notas de integração
|
|
95
|
+
|
|
96
|
+
- **Sempre use `finally`** ao resetar `isLoading` para garantir que o loading seja removido mesmo em caso de erro.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Sidebar — Guia Técnico
|
|
2
|
+
|
|
3
|
+
**Seletor:** `s-sidebar`
|
|
4
|
+
**Package:** `@seniorsistemas/angular-components/sidebar`
|
|
5
|
+
|
|
6
|
+
O Sidebar é controlado via binding bidirecional `[(visible)]`. Suporta templates customizáveis para cabeçalho, corpo e rodapé, bloqueio de scroll automático e confirmação antes de fechar.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Padrão 1: Declarativo via `[(visible)]` ✅ PREFERIDO
|
|
11
|
+
|
|
12
|
+
Controla a abertura e fechamento do sidebar via two-way binding da propriedade `visible`.
|
|
13
|
+
|
|
14
|
+
### Exemplo
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { SidebarModule } from '@seniorsistemas/angular-components/sidebar';
|
|
18
|
+
|
|
19
|
+
@Component({
|
|
20
|
+
imports: [SidebarModule],
|
|
21
|
+
template: `
|
|
22
|
+
<s-button label="Abrir painel" (clicked)="isOpen = true" />
|
|
23
|
+
|
|
24
|
+
<s-sidebar [(visible)]="isOpen" header="Detalhes">
|
|
25
|
+
<ng-template sTemplate="body">
|
|
26
|
+
<!-- conteúdo do painel -->
|
|
27
|
+
</ng-template>
|
|
28
|
+
<ng-template sTemplate="footer">
|
|
29
|
+
<s-button label="Fechar" (clicked)="isOpen = false" />
|
|
30
|
+
<s-button label="Salvar" priority="primary" (clicked)="save()" />
|
|
31
|
+
</ng-template>
|
|
32
|
+
</s-sidebar>
|
|
33
|
+
`
|
|
34
|
+
})
|
|
35
|
+
export class MyComponent {
|
|
36
|
+
protected isOpen = false;
|
|
37
|
+
|
|
38
|
+
protected save(): void {
|
|
39
|
+
// lógica de salvamento
|
|
40
|
+
this.isOpen = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Padrão 2: Com confirmação de fechamento
|
|
48
|
+
|
|
49
|
+
Usa a propriedade `closeConfirmation` para exibir um dialog de confirmação antes de fechar o sidebar, evitando perda de dados não salvos.
|
|
50
|
+
|
|
51
|
+
### Exemplo
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { SidebarModule } from '@seniorsistemas/angular-components/sidebar';
|
|
55
|
+
|
|
56
|
+
@Component({
|
|
57
|
+
imports: [SidebarModule],
|
|
58
|
+
template: `
|
|
59
|
+
<s-sidebar
|
|
60
|
+
[(visible)]="isOpen"
|
|
61
|
+
header="Editar registro"
|
|
62
|
+
[closeConfirmation]="formDirty"
|
|
63
|
+
closeConfirmationMessage="Existem alterações não salvas. Deseja sair mesmo assim?"
|
|
64
|
+
>
|
|
65
|
+
<ng-template sTemplate="body">
|
|
66
|
+
<!-- formulário -->
|
|
67
|
+
</ng-template>
|
|
68
|
+
</s-sidebar>
|
|
69
|
+
`
|
|
70
|
+
})
|
|
71
|
+
export class MyComponent {
|
|
72
|
+
protected isOpen = false;
|
|
73
|
+
protected formDirty = false;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Notas de integração
|
|
80
|
+
|
|
81
|
+
- Use `sTemplate` com os valores `'header'`, `'body'` e `'footer'` para projetar conteúdo em cada seção.
|
|
82
|
+
- Para formulários no sidebar, sempre considere usar `closeConfirmation` quando o formulário estiver sujo (`dirty`).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Table — Guia Técnico
|
|
2
|
+
|
|
3
|
+
**Seletor:** `s-table` / `s-table-columns` / `s-table-paging`
|
|
4
|
+
**Package:** `@seniorsistemas/angular-components/table`
|
|
5
|
+
|
|
6
|
+
## Exports e Imports
|
|
7
|
+
|
|
8
|
+
| Tipo | Símbolo |
|
|
9
|
+
| ---------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
10
|
+
| Components | `TableColumnsComponent`, `TablePagingComponent` |
|
|
11
|
+
| Module | `TableModule` |
|
|
12
|
+
| Directives | `NavigationDirective`, `RowTogllerDirective` |
|
|
13
|
+
| Types | `Column`, `BadgeConfigs`, `BadgeColumn`, `ColumnValues`, `DynamicEditableGrid`, `LocaleOptions`, `NumberLocaleOptions` |
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { TableModule } from '@seniorsistemas/angular-components/table';
|
|
17
|
+
import type { Column } from '@seniorsistemas/angular-components/table';
|
|
18
|
+
```
|
|
19
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Toast — Guia Técnico
|
|
2
|
+
|
|
3
|
+
**Seletor:** `s-toast`
|
|
4
|
+
**Package:** `@seniorsistemas/angular-components/toast`
|
|
5
|
+
|
|
6
|
+
Toasts são **SEMPRE** exibidos via `ToastService`. O componente `s-toast` individual **não deve ser instanciado manualmente** para exibir notificações — o serviço gerencia a criação, empilhamento e remoção automaticamente.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Padrão único: Via `ToastService` ✅ PADRÃO RECOMENDADO
|
|
11
|
+
|
|
12
|
+
Exibe notificações temporárias na tela injetando `ToastService` de qualquer componente ou serviço.
|
|
13
|
+
|
|
14
|
+
### Exemplo
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { ToastModule, ToastService } from '@seniorsistemas/angular-components/toast';
|
|
18
|
+
|
|
19
|
+
// No componente raiz (AppComponent ou layout principal)
|
|
20
|
+
// O <s-toast> deve existir UMA VEZ na aplicação, no nível mais alto
|
|
21
|
+
@Component({
|
|
22
|
+
imports: [ToastModule],
|
|
23
|
+
template: `
|
|
24
|
+
<s-toast />
|
|
25
|
+
<router-outlet />
|
|
26
|
+
`,
|
|
27
|
+
})
|
|
28
|
+
export class AppComponent {}
|
|
29
|
+
|
|
30
|
+
// Em qualquer componente ou serviço
|
|
31
|
+
@Injectable({ providedIn: 'root' })
|
|
32
|
+
export class MyService {
|
|
33
|
+
private readonly toast = inject(ToastService);
|
|
34
|
+
|
|
35
|
+
async save(data: unknown): Promise<void> {
|
|
36
|
+
try {
|
|
37
|
+
await this.api.save(data);
|
|
38
|
+
this.toast.success('Registro salvo com sucesso!');
|
|
39
|
+
} catch {
|
|
40
|
+
this.toast.error('Falha ao salvar. Tente novamente.');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Métodos disponíveis no `ToastService`
|
|
49
|
+
|
|
50
|
+
| Método | Uso |
|
|
51
|
+
| -------------------- | ---------------------------------- |
|
|
52
|
+
| `toast.success(msg)` | Operação concluída com sucesso |
|
|
53
|
+
| `toast.error(msg)` | Falha ou erro |
|
|
54
|
+
| `toast.warning(msg)` | Aviso, situação que merece atenção |
|
|
55
|
+
| `toast.info(msg)` | Informação neutra |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Notas de integração
|
|
60
|
+
|
|
61
|
+
- O componente `<s-toast>` deve estar presente no template do componente raiz (`AppComponent`) para que os toasts sejam exibidos na aplicação.
|
|
62
|
+
- Por padrão os toasts são removidos automaticamente após alguns segundos. Use `sticky: true` para manter o toast até o usuário fechar manualmente.
|
|
63
|
+
|