@cityway/bo-ui 1.1.0-beta015 → 1.1.0-beta017

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.
Binary file
@@ -151,7 +151,7 @@ filters: FilterFieldConfig[] = [
151
151
 
152
152
  ## 5. Notes d'intégration
153
153
 
154
- - **i18n** : `label`, `placeholder`, `applyLabel`, `resetLabel` passent par `translate` → fournir des **clés**, pas du texte en dur.
154
+ - **i18n** : `label`, `placeholder`, `applyLabel`, `resetLabel` passent par `translate` → fournir des **clés**, pas du texte en dur. La traduction est faite **une seule fois**, par `cw-form-field` et `cw-button` : la filter-bar leur transmet les clés telles quelles (jusqu'à la beta016 elle les traduisait aussi, d'où une double traduction, fausse quand un libellé traduit coïncidait avec une clé).
155
155
  - **`key`** sert à la fois de nom de `FormControl`, de clé dans `FilterValues` et de nom de query param. Pour une plage, l'URL utilise `key.start` / `key.end`.
156
156
  - **Mode auto vs manuel** : en auto, `filtersApplied` est émis à chaque frappe (anti-rebond 300 ms). En manuel, seuls *Appliquer* (et le *reset*) déclenchent l'émission.
157
157
  - **`syncUrl`** : nécessite que l'app fournisse le `Router` (sinon dégradation silencieuse) ; écriture en `replaceUrl: true` (l'historique n'est pas pollué) et `queryParamsHandling: 'merge'` (préserve les autres params, ex. pagination). Les valeurs vides sont retirées de l'URL.
@@ -1,238 +1,238 @@
1
- # `cw-page-header` — En-tête de page
2
-
3
- > Lib : `@cityway/bo-ui` · Sélecteur : `cw-page-header` · Standalone : oui
4
- > Import : `import { PageHeaderComponent } from '@cityway/bo-ui';`
5
-
6
- ---
7
-
8
- ## 1. Rôle & quand l'utiliser
9
-
10
- En-tête de page transverse du back-office. Couvre :
11
-
12
- - **titre** de page (`<h1>`) + **description** optionnelle ;
13
- - **bouton retour** (navigue via `Location.back()`) ;
14
- - une barre d'**actions normalisées** (Ajouter, Enregistrer, Réinitialiser) déclarées par configuration ;
15
- - deux **slots dédiés** pour l'import et l'export, aux positions attendues (§2 bis) ;
16
- - états **loading** et **disabled** par action ;
17
- - un emplacement de **contenu projeté** pour des actions personnalisées.
18
-
19
- À ne PAS utiliser pour : un titre de section interne (utiliser un simple `<h2>`), une barre d'outils de tableau (filtres, pagination — composants dédiés).
20
-
21
- ---
22
-
23
- ## 2. API
24
-
25
- ### Inputs
26
-
27
- | Input | Type | Défaut | Description |
28
- |-------|------|--------|-------------|
29
- | `title` | `string` | — (**requis**) | Titre de la page. Passé au pipe `translate` (fournir une **clé i18n**). |
30
- | `description` | `string` | — | Description sous le titre. Passée au pipe `translate`, rendue en `[innerHTML]` (HTML autorisé). |
31
- | `showBack` | `boolean` | `true` | Affiche le bouton retour (libellé `_COMMON._FORM.BACK`, icône `left1`). |
32
- | `actions` | `PageHeaderActionEnum[]` | `[]` | Liste des actions à afficher. Voir enum §3. L'ordre d'affichage est imposé par le template (cf. §5). |
33
- | `isLoading` | `PageHeaderActionState` | `{}` | Map action → `boolean` ; passe l'action en état chargement. |
34
- | `isDisabled` | `PageHeaderActionState` | `{}` | Map action → `boolean` ; désactive l'action. |
35
- | `columns` | `TableColumn[]` | `[]` | Colonnes du tableau associé. **Fourni → un menu déroulant « Colonnes » apparaît** (cases à cocher afficher/masquer). Voir §6 bis. |
36
- | `storageKey` | `string` | — | Identifiant **stable** du tableau pour persister l'état en localStorage (ex. `'users/list'`). Sans clé : le menu fonctionne mais l'état n'est pas mémorisé. |
37
- | `columnsLabel` | `string` | `_COMMON._TABLE.COLUMNS` | Clé i18n du libellé du bouton « Colonnes ». |
38
-
39
- ### Outputs
40
-
41
- | Output | Payload | Description |
42
- |--------|---------|-------------|
43
- | `action` | `PageHeaderActionEnum` | Émis au clic sur une action de la barre. Le bouton retour n'émet pas (géré en interne). |
44
- | `columnsChange` | `TableColumn[]` | Émis quand la visibilité des colonnes change (colonnes avec `hidden` à jour). À renvoyer dans le `signal`/state lié à la `cw-table`. |
45
-
46
- ---
47
-
48
- ## 2 bis. Slots de projection
49
-
50
- Trois emplacements, dans l'ordre du template :
51
-
52
- | Slot | Sélecteur | Position dans la barre | Usage |
53
- |------|-----------|------------------------|-------|
54
- | Import | `cwPageHeaderImport` | après *Colonnes*, avant *Réinitialiser* | `<cw-import-button>` |
55
- | Export | `cwPageHeaderExport` | après *Réinitialiser*, avant le slot libre | `<cw-export-button>` |
56
- | Libre | *(aucun)* | après l'export, avant *Ajouter* / *Enregistrer* | tout le reste |
57
-
58
- ```html
59
- <cw-page-header [title]="'DOMAIN.USERS.TITLE'" [actions]="[actionEnum.add]" (action)="onAction($event)">
60
- <cw-import-button cwPageHeaderImport [config]="importConfig" [modalTitle]="importTitle"
61
- (closed)="reload()" (completed)="onImported($event)" />
62
- <cw-export-button cwPageHeaderExport [options]="exportOptions" />
63
- <!-- Slot libre : action personnalisée, ou bouton d'export entièrement piloté par l'hôte. -->
64
- <cw-button [type]="boButtonType.secondary" [label]="'DOMAIN.USERS.INVITE'" (btnAction)="invite()" />
65
- </cw-page-header>
66
- ```
67
-
68
- **Pourquoi des slots et pas des inputs de configuration ?** Rendre l'import depuis
69
- `cw-page-header` l'obligerait à référencer `cw-import-wizard`, donc à embarquer l'assistant
70
- complet (avec `file-drop`, `accordion`, `card`, `progress-bar`) dans **toute** page affichant
71
- un titre. Un `@defer` n'y change rien : ng-packagr aplatit la lib en un **FESM unique**, la
72
- dépendance différée y est réécrite en `Promise.resolve()` sur un objet du même bundle — il
73
- n'y a aucun `import()` sur lequel un bundler pourrait découper. Par projection, une page qui
74
- n'importe ni n'exporte ne charge **rien** des deux composants, et une page qui exporte
75
- seulement ne charge rien de l'assistant d'import.
76
-
77
- **Le slot libre est l'échappatoire** : un hôte qui veut piloter lui-même son bouton
78
- d'export — sans passer par le contrat `ExportPayload` — y pose son propre `cw-button`.
79
-
80
- ---
81
-
82
- ## 3. Enums & Types
83
-
84
- ```ts
85
- enum PageHeaderActionEnum {
86
- add = 'add',
87
- save = 'save',
88
- reset = 'reset'
89
- }
90
-
91
- // Map partielle action → booléen, pour piloter loading / disabled par action
92
- type PageHeaderActionState = Partial<Record<PageHeaderActionEnum, boolean>>;
93
- ```
94
-
95
- > ⚠️ **Migration** — `import`, `exportCurrent` et `exportAll` **ont été retirés** de l'enum.
96
- > Ils ne rendaient qu'un bouton émettant un événement, tout le travail restant à l'hôte.
97
- > Remplacez-les par les composants dédiés, projetés dans les slots (§2 bis) :
98
- >
99
- > | Avant | Après |
100
- > |-------|-------|
101
- > | `actions: [actionEnum.import]` + `case import:` | `<cw-import-button cwPageHeaderImport …>` |
102
- > | `actions: [actionEnum.exportCurrent, actionEnum.exportAll]` + `case export…:` | `<cw-export-button cwPageHeaderExport [options]="…">` |
103
- > | un bouton d'export entièrement piloté par l'hôte | votre `cw-button` dans le slot libre |
104
- >
105
- > Il n'y a **pas** de règle de précédence : les deux chemins n'ont jamais coexisté, le retrait
106
- > est net et le compilateur signale chaque usage restant.
107
-
108
- ---
109
-
110
- ## 4. Exemples
111
-
112
- ### Page liste (Ajouter + Import + Export)
113
-
114
- ```html
115
- <cw-page-header
116
- [title]="'DOMAIN.USERS.TITLE'"
117
- [description]="'DOMAIN.USERS.SUBTITLE'"
118
- [actions]="[actionEnum.add]"
119
- (action)="onAction($event)">
120
- <cw-import-button cwPageHeaderImport [config]="importConfig" [modalTitle]="importTitle"
121
- (closed)="reload()" (completed)="onImported($event)" />
122
- <cw-export-button cwPageHeaderExport [options]="exportOptions" />
123
- </cw-page-header>
124
- ```
125
-
126
- ```ts
127
- import {
128
- ExportButtonComponent, ImportButtonComponent, PageHeaderActionEnum, PageHeaderComponent
129
- } from '@cityway/bo-ui';
130
-
131
- // dans le composant hôte
132
- actionEnum = PageHeaderActionEnum;
133
-
134
- onAction(action: PageHeaderActionEnum): void {
135
- switch (action) {
136
- case PageHeaderActionEnum.add: this.create(); break;
137
- }
138
- }
139
- ```
140
-
141
- Configuration de `importConfig` / `exportOptions` : voir `import-button.md` et
142
- `export-button.md`.
143
-
144
- ### Page édition (Enregistrer + Réinitialiser) avec états
145
-
146
- ```html
147
- <cw-page-header
148
- [title]="'DOMAIN.USERS.EDIT_TITLE'"
149
- [description]="'DOMAIN.USERS.EDIT_SUBTITLE'"
150
- [actions]="[actionEnum.reset, actionEnum.save]"
151
- [isLoading]="{ save: saving() }"
152
- [isDisabled]="{ reset: form.pristine }"
153
- (action)="onAction($event)">
154
- </cw-page-header>
155
- ```
156
-
157
- ### Titre seul (sans retour, sans action)
158
-
159
- ```html
160
- <cw-page-header [title]="'DOMAIN.DASHBOARD.TITLE'" [showBack]="false" />
161
- ```
162
-
163
- ### Action personnalisée projetée
164
-
165
- ```html
166
- <cw-page-header [title]="'DOMAIN.REPORTS.TITLE'" [actions]="[]">
167
- <cw-button [type]="boButtonType.secondary" [label]="'DOMAIN.REPORTS.SCHEDULE'" (btnAction)="schedule()" />
168
- </cw-page-header>
169
- ```
170
-
171
- ---
172
-
173
- ## 4 bis. Menu « Colonnes » (visibilité + persistance)
174
-
175
- Quand on passe `columns` au page-header, un bouton **Colonnes** apparaît : un dropdown de cases à cocher pour afficher/masquer chaque colonne du tableau associé. L'état (colonnes masquées) est **persisté en localStorage** par tableau.
176
-
177
- ```ts
178
- import { PageHeaderComponent, TableComponent, TableColumn } from '@cityway/bo-ui';
179
-
180
- @Component({
181
- standalone: true,
182
- imports: [PageHeaderComponent, TableComponent],
183
- template: `
184
- <cw-page-header
185
- [title]="'DOMAIN.USERS.TITLE'"
186
- [columns]="columns()"
187
- storageKey="users/list"
188
- (columnsChange)="columns.set($event)">
189
- </cw-page-header>
190
-
191
- <cw-table [columns]="columns()" [data]="rows()"></cw-table>
192
- `,
193
- })
194
- export class UsersPage {
195
- // Source de vérité unique, liée au header (contrôle) ET à la table (rendu).
196
- columns = signal<TableColumn[]>([
197
- { key: 'name', label: 'DOMAIN.USERS.NAME' },
198
- { key: 'email', label: 'DOMAIN.USERS.EMAIL' },
199
- { key: 'createdAt', label: 'DOMAIN.USERS.CREATED', hidden: true }, // masquée par défaut
200
- { key: 'actions', label: '', type: 'actions' }, // jamais masquable
201
- ]);
202
- }
203
- ```
204
-
205
- Règles :
206
- - **Activation** : le menu n'apparaît que si `columns` contient au moins une colonne masquable. Sans `columns`, pas de menu.
207
- - **Colonnes exclues** : `type='actions'` (jamais masquable) et toute colonne `hideable: false`.
208
- - **Garde-fou** : impossible de masquer la **dernière** colonne masquable visible (sa case est désactivée).
209
- - **Persistance** : une seule clé localStorage `cw-table-columns` → `{ [storageKey]: string[] }`, **on ne stocke que les clés des colonnes masquées**. Au chargement, l'état mémorisé prime sur les `hidden` du config ; **sans `storageKey`**, le menu fonctionne mais rien n'est mémorisé.
210
- - **Helpers** : `loadHiddenColumns(storageKey)` / `saveHiddenColumns(storageKey, keys)` exportés par `@cityway/bo-ui` (le page-header les utilise en interne).
211
-
212
- ---
213
-
214
- ## 5. Notes d'intégration
215
-
216
- - **i18n** : `title` et `description` passent par `translate` → fournir des **clés**, pas du texte en dur. `description` est rendue en `[innerHTML]` (peut contenir du HTML/markup traduit).
217
- - **Libellés des actions** : non paramétrables. Ils sont câblés sur les clés `_COMMON._ACTION.*` (`ADD`, `SAVE`, `RESET`) et `_COMMON._FORM.BACK` pour le retour. Ces clés doivent exister dans les bundles i18n de l'app.
218
- - **Ordre d'affichage** : imposé par le template, indépendant de l'ordre du tableau `actions` et de l'ordre d'écriture du contenu projeté → Colonnes, *slot import*, Réinitialiser, *slot export*, *slot libre*, Ajouter, Enregistrer.
219
- - **Bouton retour** : navigue via `Location.back()` (historique du navigateur). Aucune sortie ni cible configurable ; masquer avec `[showBack]="false"` si non pertinent.
220
- - **États par action** : `isLoading` / `isDisabled` sont des maps partielles ; une action absente de la map vaut `false`.
221
- - **Dépendance** : le composant projette des `cw-button` (`@cityway/basic-ui`) en interne — `basic-ui` doit être disponible.
222
-
223
- ---
224
-
225
- ## 6. Prompts-types (génération assistée)
226
-
227
- > Exemples de formulation pour demander à Claude de générer du code consommant ce composant.
228
-
229
- - « Ajoute un `cw-page-header` titre *Utilisateurs* (clé i18n) avec bouton retour et action Ajouter, qui route vers `onAction()`. »
230
- - « Sur ma page d'édition, mets un en-tête avec Enregistrer + Réinitialiser, où Enregistrer affiche un spinner pendant la sauvegarde et Réinitialiser est désactivé tant que le formulaire est vierge. »
231
- - « Ajoute un menu Export (tout + vue filtrée) dans l'en-tête de la page liste. »
232
-
233
- **Rappels pour Claude :**
234
- - Importer `PageHeaderComponent` (standalone) dans les `imports` du composant hôte.
235
- - Exposer `PageHeaderActionEnum` comme propriété du composant pour la binder dans le template (`actionEnum = PageHeaderActionEnum`).
236
- - Utiliser des clés i18n pour `title` et `description` ; les libellés des actions sont fixes (clés `_COMMON._ACTION.*`).
237
- - Piloter `isLoading` / `isDisabled` via des objets `{ [action]: boolean }` plutôt que des flags séparés.
238
- - **L'import et l'export ne sont pas des `actions`** : projeter `<cw-import-button cwPageHeaderImport>` et `<cw-export-button cwPageHeaderExport>` (cf. `import-button.md`, `export-button.md`). Ne jamais réintroduire ces valeurs dans `PageHeaderActionEnum`.
1
+ # `cw-page-header` — En-tête de page
2
+
3
+ > Lib : `@cityway/bo-ui` · Sélecteur : `cw-page-header` · Standalone : oui
4
+ > Import : `import { PageHeaderComponent } from '@cityway/bo-ui';`
5
+
6
+ ---
7
+
8
+ ## 1. Rôle & quand l'utiliser
9
+
10
+ En-tête de page transverse du back-office. Couvre :
11
+
12
+ - **titre** de page (`<h1>`) + **description** optionnelle ;
13
+ - **bouton retour** (navigue via `Location.back()`) ;
14
+ - une barre d'**actions normalisées** (Ajouter, Enregistrer, Réinitialiser) déclarées par configuration ;
15
+ - deux **slots dédiés** pour l'import et l'export, aux positions attendues (§2 bis) ;
16
+ - états **loading** et **disabled** par action ;
17
+ - un emplacement de **contenu projeté** pour des actions personnalisées.
18
+
19
+ À ne PAS utiliser pour : un titre de section interne (utiliser un simple `<h2>`), une barre d'outils de tableau (filtres, pagination — composants dédiés).
20
+
21
+ ---
22
+
23
+ ## 2. API
24
+
25
+ ### Inputs
26
+
27
+ | Input | Type | Défaut | Description |
28
+ |-------|------|--------|-------------|
29
+ | `title` | `string` | — (**requis**) | Titre de la page. Passé au pipe `translate` (fournir une **clé i18n**). |
30
+ | `description` | `string` | — | Description sous le titre. Passée au pipe `translate`, rendue en `[innerHTML]` (HTML autorisé). |
31
+ | `showBack` | `boolean` | `true` | Affiche le bouton retour (libellé `_COMMON._FORM.BACK`, icône `left1`). |
32
+ | `actions` | `PageHeaderActionEnum[]` | `[]` | Liste des actions à afficher. Voir enum §3. L'ordre d'affichage est imposé par le template (cf. §5). |
33
+ | `isLoading` | `PageHeaderActionState` | `{}` | Map action → `boolean` ; passe l'action en état chargement. |
34
+ | `isDisabled` | `PageHeaderActionState` | `{}` | Map action → `boolean` ; désactive l'action. |
35
+ | `columns` | `TableColumn[]` | `[]` | Colonnes du tableau associé. **Fourni → un menu déroulant « Colonnes » apparaît** (cases à cocher afficher/masquer). Voir §6 bis. |
36
+ | `storageKey` | `string` | — | Identifiant **stable** du tableau pour persister l'état en localStorage (ex. `'users/list'`). Sans clé : le menu fonctionne mais l'état n'est pas mémorisé. |
37
+ | `columnsLabel` | `string` | `_COMMON._TABLE.COLUMNS` | Clé i18n du libellé du bouton « Colonnes ». |
38
+
39
+ ### Outputs
40
+
41
+ | Output | Payload | Description |
42
+ |--------|---------|-------------|
43
+ | `action` | `PageHeaderActionEnum` | Émis au clic sur une action de la barre. Le bouton retour n'émet pas (géré en interne). |
44
+ | `columnsChange` | `TableColumn[]` | Émis quand la visibilité des colonnes change (colonnes avec `hidden` à jour). À renvoyer dans le `signal`/state lié à la `cw-table`. |
45
+
46
+ ---
47
+
48
+ ## 2 bis. Slots de projection
49
+
50
+ Trois emplacements, dans l'ordre du template :
51
+
52
+ | Slot | Sélecteur | Position dans la barre | Usage |
53
+ |------|-----------|------------------------|-------|
54
+ | Import | `cwPageHeaderImport` | après *Colonnes*, avant l'export | `<cw-import-button>` |
55
+ | Export | `cwPageHeaderExport` | après l'import, avant *Réinitialiser* | `<cw-export-button>` |
56
+ | Libre | *(aucun)* | après *Réinitialiser*, avant *Ajouter* / *Enregistrer* | tout le reste |
57
+
58
+ ```html
59
+ <cw-page-header [title]="'DOMAIN.USERS.TITLE'" [actions]="[actionEnum.add]" (action)="onAction($event)">
60
+ <cw-import-button cwPageHeaderImport [config]="importConfig" [modalTitle]="importTitle"
61
+ (closed)="reload()" (completed)="onImported($event)" />
62
+ <cw-export-button cwPageHeaderExport [options]="exportOptions" />
63
+ <!-- Slot libre : action personnalisée, ou bouton d'export entièrement piloté par l'hôte. -->
64
+ <cw-button [type]="boButtonType.secondary" [label]="'DOMAIN.USERS.INVITE'" (btnAction)="invite()" />
65
+ </cw-page-header>
66
+ ```
67
+
68
+ **Pourquoi des slots et pas des inputs de configuration ?** Rendre l'import depuis
69
+ `cw-page-header` l'obligerait à référencer `cw-import-wizard`, donc à embarquer l'assistant
70
+ complet (avec `file-drop`, `accordion`, `card`, `progress-bar`) dans **toute** page affichant
71
+ un titre. Un `@defer` n'y change rien : ng-packagr aplatit la lib en un **FESM unique**, la
72
+ dépendance différée y est réécrite en `Promise.resolve()` sur un objet du même bundle — il
73
+ n'y a aucun `import()` sur lequel un bundler pourrait découper. Par projection, une page qui
74
+ n'importe ni n'exporte ne charge **rien** des deux composants, et une page qui exporte
75
+ seulement ne charge rien de l'assistant d'import.
76
+
77
+ **Le slot libre est l'échappatoire** : un hôte qui veut piloter lui-même son bouton
78
+ d'export — sans passer par le contrat `ExportPayload` — y pose son propre `cw-button`.
79
+
80
+ ---
81
+
82
+ ## 3. Enums & Types
83
+
84
+ ```ts
85
+ enum PageHeaderActionEnum {
86
+ add = 'add',
87
+ save = 'save',
88
+ reset = 'reset'
89
+ }
90
+
91
+ // Map partielle action → booléen, pour piloter loading / disabled par action
92
+ type PageHeaderActionState = Partial<Record<PageHeaderActionEnum, boolean>>;
93
+ ```
94
+
95
+ > ⚠️ **Migration** — `import`, `exportCurrent` et `exportAll` **ont été retirés** de l'enum.
96
+ > Ils ne rendaient qu'un bouton émettant un événement, tout le travail restant à l'hôte.
97
+ > Remplacez-les par les composants dédiés, projetés dans les slots (§2 bis) :
98
+ >
99
+ > | Avant | Après |
100
+ > |-------|-------|
101
+ > | `actions: [actionEnum.import]` + `case import:` | `<cw-import-button cwPageHeaderImport …>` |
102
+ > | `actions: [actionEnum.exportCurrent, actionEnum.exportAll]` + `case export…:` | `<cw-export-button cwPageHeaderExport [options]="…">` |
103
+ > | un bouton d'export entièrement piloté par l'hôte | votre `cw-button` dans le slot libre |
104
+ >
105
+ > Il n'y a **pas** de règle de précédence : les deux chemins n'ont jamais coexisté, le retrait
106
+ > est net et le compilateur signale chaque usage restant.
107
+
108
+ ---
109
+
110
+ ## 4. Exemples
111
+
112
+ ### Page liste (Ajouter + Import + Export)
113
+
114
+ ```html
115
+ <cw-page-header
116
+ [title]="'DOMAIN.USERS.TITLE'"
117
+ [description]="'DOMAIN.USERS.SUBTITLE'"
118
+ [actions]="[actionEnum.add]"
119
+ (action)="onAction($event)">
120
+ <cw-import-button cwPageHeaderImport [config]="importConfig" [modalTitle]="importTitle"
121
+ (closed)="reload()" (completed)="onImported($event)" />
122
+ <cw-export-button cwPageHeaderExport [options]="exportOptions" />
123
+ </cw-page-header>
124
+ ```
125
+
126
+ ```ts
127
+ import {
128
+ ExportButtonComponent, ImportButtonComponent, PageHeaderActionEnum, PageHeaderComponent
129
+ } from '@cityway/bo-ui';
130
+
131
+ // dans le composant hôte
132
+ actionEnum = PageHeaderActionEnum;
133
+
134
+ onAction(action: PageHeaderActionEnum): void {
135
+ switch (action) {
136
+ case PageHeaderActionEnum.add: this.create(); break;
137
+ }
138
+ }
139
+ ```
140
+
141
+ Configuration de `importConfig` / `exportOptions` : voir `import-button.md` et
142
+ `export-button.md`.
143
+
144
+ ### Page édition (Enregistrer + Réinitialiser) avec états
145
+
146
+ ```html
147
+ <cw-page-header
148
+ [title]="'DOMAIN.USERS.EDIT_TITLE'"
149
+ [description]="'DOMAIN.USERS.EDIT_SUBTITLE'"
150
+ [actions]="[actionEnum.reset, actionEnum.save]"
151
+ [isLoading]="{ save: saving() }"
152
+ [isDisabled]="{ reset: form.pristine }"
153
+ (action)="onAction($event)">
154
+ </cw-page-header>
155
+ ```
156
+
157
+ ### Titre seul (sans retour, sans action)
158
+
159
+ ```html
160
+ <cw-page-header [title]="'DOMAIN.DASHBOARD.TITLE'" [showBack]="false" />
161
+ ```
162
+
163
+ ### Action personnalisée projetée
164
+
165
+ ```html
166
+ <cw-page-header [title]="'DOMAIN.REPORTS.TITLE'" [actions]="[]">
167
+ <cw-button [type]="boButtonType.secondary" [label]="'DOMAIN.REPORTS.SCHEDULE'" (btnAction)="schedule()" />
168
+ </cw-page-header>
169
+ ```
170
+
171
+ ---
172
+
173
+ ## 4 bis. Menu « Colonnes » (visibilité + persistance)
174
+
175
+ Quand on passe `columns` au page-header, un bouton **Colonnes** apparaît : un dropdown de cases à cocher pour afficher/masquer chaque colonne du tableau associé. L'état (colonnes masquées) est **persisté en localStorage** par tableau.
176
+
177
+ ```ts
178
+ import { PageHeaderComponent, TableComponent, TableColumn } from '@cityway/bo-ui';
179
+
180
+ @Component({
181
+ standalone: true,
182
+ imports: [PageHeaderComponent, TableComponent],
183
+ template: `
184
+ <cw-page-header
185
+ [title]="'DOMAIN.USERS.TITLE'"
186
+ [columns]="columns()"
187
+ storageKey="users/list"
188
+ (columnsChange)="columns.set($event)">
189
+ </cw-page-header>
190
+
191
+ <cw-table [columns]="columns()" [data]="rows()"></cw-table>
192
+ `,
193
+ })
194
+ export class UsersPage {
195
+ // Source de vérité unique, liée au header (contrôle) ET à la table (rendu).
196
+ columns = signal<TableColumn[]>([
197
+ { key: 'name', label: 'DOMAIN.USERS.NAME' },
198
+ { key: 'email', label: 'DOMAIN.USERS.EMAIL' },
199
+ { key: 'createdAt', label: 'DOMAIN.USERS.CREATED', hidden: true }, // masquée par défaut
200
+ { key: 'actions', label: '', type: 'actions' }, // jamais masquable
201
+ ]);
202
+ }
203
+ ```
204
+
205
+ Règles :
206
+ - **Activation** : le menu n'apparaît que si `columns` contient au moins une colonne masquable. Sans `columns`, pas de menu.
207
+ - **Colonnes exclues** : `type='actions'` (jamais masquable) et toute colonne `hideable: false`.
208
+ - **Garde-fou** : impossible de masquer la **dernière** colonne masquable visible (sa case est désactivée).
209
+ - **Persistance** : une seule clé localStorage `cw-table-columns` → `{ [storageKey]: string[] }`, **on ne stocke que les clés des colonnes masquées**. Au chargement, l'état mémorisé prime sur les `hidden` du config ; **sans `storageKey`**, le menu fonctionne mais rien n'est mémorisé.
210
+ - **Helpers** : `loadHiddenColumns(storageKey)` / `saveHiddenColumns(storageKey, keys)` exportés par `@cityway/bo-ui` (le page-header les utilise en interne).
211
+
212
+ ---
213
+
214
+ ## 5. Notes d'intégration
215
+
216
+ - **i18n** : `title` et `description` passent par `translate` → fournir des **clés**, pas du texte en dur. `description` est rendue en `[innerHTML]` (peut contenir du HTML/markup traduit).
217
+ - **Libellés des actions** : non paramétrables. Ils sont câblés sur les clés `_COMMON._ACTION.*` (`ADD`, `SAVE`, `RESET`) et `_COMMON._FORM.BACK` pour le retour. Ces clés doivent exister dans les bundles i18n de l'app.
218
+ - **Ordre d'affichage** : imposé par le template, indépendant de l'ordre du tableau `actions` et de l'ordre d'écriture du contenu projeté → Colonnes, *slot import*, *slot export*, Réinitialiser, *slot libre*, Ajouter, Enregistrer (échanges de données, puis actions sur le formulaire, puis action principale).
219
+ - **Bouton retour** : navigue via `Location.back()` (historique du navigateur). Aucune sortie ni cible configurable ; masquer avec `[showBack]="false"` si non pertinent.
220
+ - **États par action** : `isLoading` / `isDisabled` sont des maps partielles ; une action absente de la map vaut `false`.
221
+ - **Dépendance** : le composant projette des `cw-button` (`@cityway/basic-ui`) en interne — `basic-ui` doit être disponible.
222
+
223
+ ---
224
+
225
+ ## 6. Prompts-types (génération assistée)
226
+
227
+ > Exemples de formulation pour demander à Claude de générer du code consommant ce composant.
228
+
229
+ - « Ajoute un `cw-page-header` titre *Utilisateurs* (clé i18n) avec bouton retour et action Ajouter, qui route vers `onAction()`. »
230
+ - « Sur ma page d'édition, mets un en-tête avec Enregistrer + Réinitialiser, où Enregistrer affiche un spinner pendant la sauvegarde et Réinitialiser est désactivé tant que le formulaire est vierge. »
231
+ - « Ajoute un menu Export (tout + vue filtrée) dans l'en-tête de la page liste. »
232
+
233
+ **Rappels pour Claude :**
234
+ - Importer `PageHeaderComponent` (standalone) dans les `imports` du composant hôte.
235
+ - Exposer `PageHeaderActionEnum` comme propriété du composant pour la binder dans le template (`actionEnum = PageHeaderActionEnum`).
236
+ - Utiliser des clés i18n pour `title` et `description` ; les libellés des actions sont fixes (clés `_COMMON._ACTION.*`).
237
+ - Piloter `isLoading` / `isDisabled` via des objets `{ [action]: boolean }` plutôt que des flags séparés.
238
+ - **L'import et l'export ne sont pas des `actions`** : projeter `<cw-import-button cwPageHeaderImport>` et `<cw-export-button cwPageHeaderExport>` (cf. `import-button.md`, `export-button.md`). Ne jamais réintroduire ces valeurs dans `PageHeaderActionEnum`.