@abyss-project/commons-front-core 8.2.0 → 8.3.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 (99) hide show
  1. package/README.md +171 -125
  2. package/catalog/catalog-entries.ts +17 -1
  3. package/catalog/cloud-picker-fixture.tsx +97 -0
  4. package/catalog/pages/Avatar.tsx +94 -0
  5. package/catalog/pages/Callout.tsx +67 -0
  6. package/catalog/pages/CloudImport.tsx +35 -0
  7. package/catalog/pages/ContextSelector.tsx +103 -0
  8. package/catalog/pages/CopyField.tsx +29 -0
  9. package/catalog/pages/DropZone.tsx +37 -0
  10. package/catalog/pages/FormField.tsx +54 -0
  11. package/catalog/pages/ImportButton.tsx +45 -0
  12. package/catalog/pages/ImportFromCloudModal.tsx +33 -0
  13. package/catalog/pages/Menu.tsx +99 -98
  14. package/catalog/pages/NumberField.tsx +46 -0
  15. package/catalog/pages/QRCodeModal.tsx +26 -0
  16. package/catalog/pages/SettingsRow.tsx +67 -0
  17. package/catalog/pages/Tabs.tsx +72 -0
  18. package/catalog/pages/UsageMeter.tsx +58 -0
  19. package/catalog/pages/index.ts +30 -2
  20. package/dist/{AbyssCookieConsentProvider-TE46ywnd.js → AbyssCookieConsentProvider-B-PlGbFf.js} +1096 -946
  21. package/dist/{CookieConsentSettings.component-BKsYgpMa.js → CookieConsentSettings.component-DEGCDEJW.js} +1 -1
  22. package/dist/components/business/CloudImport/CloudImport.d.ts +24 -0
  23. package/dist/components/business/CloudImport/cloud-import-tree.d.ts +22 -0
  24. package/dist/components/business/ImportFromCloudModal/ImportFromCloudModal.d.ts +21 -0
  25. package/dist/components/business/QRCodeModal/QRCodeModal.d.ts +18 -0
  26. package/dist/components/business/QRCodeModal/QRCodeModal.spec.d.ts +1 -0
  27. package/dist/components/business/index.d.ts +3 -0
  28. package/dist/components/data/DataTable/data-table.types.d.ts +10 -0
  29. package/dist/components/data/UsageMeter/UsageMeter.d.ts +31 -0
  30. package/dist/components/data/UsageMeter/UsageMeter.spec.d.ts +1 -0
  31. package/dist/components/data/index.d.ts +1 -0
  32. package/dist/components/inputs/ContextSelector/ContextSelector.d.ts +60 -0
  33. package/dist/components/inputs/ContextSelector/ContextSelector.spec.d.ts +1 -0
  34. package/dist/components/inputs/CopyField/CopyField.d.ts +19 -0
  35. package/dist/components/inputs/CopyField/CopyField.spec.d.ts +1 -0
  36. package/dist/components/inputs/DropZone/DropZone.d.ts +30 -0
  37. package/dist/components/inputs/DropZone/DropZone.spec.d.ts +1 -0
  38. package/dist/components/inputs/FormField/FormField.d.ts +29 -0
  39. package/dist/components/inputs/FormField/FormField.spec.d.ts +1 -0
  40. package/dist/components/inputs/ImportButton/ImportButton.d.ts +33 -0
  41. package/dist/components/inputs/ImportButton/ImportButton.spec.d.ts +1 -0
  42. package/dist/components/inputs/NumberField/NumberField.d.ts +21 -0
  43. package/dist/components/inputs/NumberField/NumberField.spec.d.ts +1 -0
  44. package/dist/components/inputs/NumberField/number-input.d.ts +13 -0
  45. package/dist/components/inputs/SettingsRow/SettingsRow.d.ts +16 -0
  46. package/dist/components/inputs/SettingsRow/SettingsRow.spec.d.ts +1 -0
  47. package/dist/components/inputs/index.d.ts +7 -0
  48. package/dist/components/layers/Menu/Menu.d.ts +2 -0
  49. package/dist/components/meaning/Avatar/Avatar.d.ts +41 -0
  50. package/dist/components/meaning/Avatar/Avatar.spec.d.ts +1 -0
  51. package/dist/components/meaning/index.d.ts +1 -0
  52. package/dist/components/states/Callout/Callout.d.ts +25 -0
  53. package/dist/components/states/Callout/Callout.spec.d.ts +1 -0
  54. package/dist/components/states/index.d.ts +1 -0
  55. package/dist/components/structure/Tabs/Tabs.d.ts +34 -0
  56. package/dist/components/structure/Tabs/Tabs.spec.d.ts +1 -0
  57. package/dist/components/structure/index.d.ts +1 -0
  58. package/dist/cookie-consent.js +2 -2
  59. package/dist/design-theme-CKTz_cOa.js +15490 -0
  60. package/dist/hooks/use-rich-intl.hook.d.ts +150 -0
  61. package/dist/{index-Dhz388yW.js → index-7N3J68EI.js} +2 -2
  62. package/dist/{index-BI8z_ZwK.js → index-B3rK_-T4.js} +4 -4
  63. package/dist/{index-BZfgg1Dp.js → index-B6qHo25Z.js} +2 -2
  64. package/dist/{index-D490I-Im.js → index-BMaAO_4e.js} +2 -2
  65. package/dist/{index-FS21eF3a.js → index-B_jSh70G.js} +3 -3
  66. package/dist/{index-B-Gq5Zeh.js → index-B_yywet5.js} +3 -3
  67. package/dist/{index-BR66kEMM.js → index-Bdtvzv-J.js} +2 -2
  68. package/dist/{index-C-bs2P06.js → index-Bh2_8FCK.js} +2 -2
  69. package/dist/{index-BXs_bBcm.js → index-Bpg9TBMX.js} +1 -1
  70. package/dist/{index-4BMCQ_6u.js → index-Bpnf8xKI.js} +2 -2
  71. package/dist/{index-DPxFwYmx.js → index-Bw2swxVq.js} +2 -2
  72. package/dist/{index-DjLWvtUt.js → index-BxxZgBFn.js} +2 -2
  73. package/dist/{index-C6ZLgLu-.js → index-Ca9tzEsn.js} +2 -2
  74. package/dist/{index-Bi3OW3pz.js → index-Csvh-AG6.js} +2 -2
  75. package/dist/{index-ltq6POQa.js → index-CwuklsUK.js} +3 -3
  76. package/dist/{index-csvcp4rP.js → index-DFC9xBY9.js} +2 -2
  77. package/dist/{index-C4VmhCn7.js → index-DfUcsAJv.js} +4 -4
  78. package/dist/{index-Bu5LDz63.js → index-DqedXp41.js} +3 -3
  79. package/dist/{index-CK9kVK6Y.js → index-DwXNHQIp.js} +4 -4
  80. package/dist/{index-Dm6584pR.js → index-_UY_fqvv.js} +28373 -34464
  81. package/dist/{index-DC-vg2P2.js → index-dVVflNge.js} +2 -2
  82. package/dist/{index-DistZiPf.js → index-sEWp0xIC.js} +2 -2
  83. package/dist/index.js +7214 -5051
  84. package/dist/legacy/components/QuotaOffer/AbyssQuotaOffer.component.d.ts +1 -1
  85. package/dist/legacy/components/QuotaOffer/index.d.ts +2 -2
  86. package/dist/legacy.js +142 -142
  87. package/dist/theme/tokens/semantic.d.ts +1 -1
  88. package/dist/translations/en.d.ts +75 -0
  89. package/dist/translations/fr.d.ts +75 -0
  90. package/dist/utils/cloud-accept.utils.spec.d.ts +1 -0
  91. package/dist/utils/index.d.ts +1 -0
  92. package/dist/utils/quota-offer-state.utils.spec.d.ts +1 -0
  93. package/package.json +1 -1
  94. package/test-results/catalog/.last-run.json +4 -0
  95. package/dist/design-theme-CN_6fRX3.js +0 -9344
  96. /package/dist/{legacy/components/Inputs/CloudImport/cloud-accept.spec.d.ts → components/business/CloudImport/CloudImport.spec.d.ts} +0 -0
  97. /package/dist/{legacy/components/QuotaOffer/quota-offer-state.spec.d.ts → components/business/ImportFromCloudModal/ImportFromCloudModal.spec.d.ts} +0 -0
  98. /package/dist/{legacy/components/Inputs/CloudImport/cloud-accept.d.ts → utils/cloud-accept.utils.d.ts} +0 -0
  99. /package/dist/{legacy/components/QuotaOffer/quota-offer-state.d.ts → utils/quota-offer-state.utils.d.ts} +0 -0
package/README.md CHANGED
@@ -1,125 +1,171 @@
1
- # Abyss Commons Front Core
2
-
3
- `@abyss-project/commons-front-core`
4
-
5
- Composants React, hooks, design system et traductions partagés par les frontends Abyss.
6
-
7
- _Shared React components, hooks, design system and translations for Abyss frontends._
8
-
9
- ## Installation
10
-
11
- ```bash
12
- npm install @abyss-project/commons-front-core
13
- # or
14
- pnpm add @abyss-project/commons-front-core
15
- ```
16
-
17
- ## Liens
18
-
19
- - **Documentation** : [https://docs.abyss-project.fr/abyss-project](https://docs.abyss-project.fr/abyss-project)
20
- - **NPM** : [@abyss-project/commons-front-core](https://www.npmjs.com/package/@abyss-project/commons-front-core)
21
- - **Application Abyss** : [app.abyss-project.fr](https://app.abyss-project.fr)
22
- - **Statut des services** : [status.abyss-project.fr](https://status.abyss-project.fr)
23
-
24
- ## Usage
25
-
26
- Voir la [documentation produit](https://docs.abyss-project.fr/abyss-project) pour les guides d'utilisation, la configuration du Core, les exemples d'appel et la référence des permissions.
27
-
28
- ## RUM
29
-
30
- Le RUM navigateur d'un front (SDK `@abyss-project/monitor-browser`, peer `^1.2.0`) se démarre par `AbyssRumProvider`, monté **sous** `AbyssCookieConsentProvider` et, quand le front en a un, sous `AuthProvider` :
31
-
32
- ```tsx
33
- <AbyssErrorBoundary appName="abyss-cloud">
34
- <AuthProvider client={authClient}>
35
- <AbyssCookieConsentProvider consentVersion={COOKIE_CONSENT_VERSION} isAuthenticated={isAuthenticated}>
36
- <AbyssRumProvider config={buildRumOptions()}>
37
- <App />
38
- </AbyssRumProvider>
39
- </AbyssCookieConsentProvider>
40
- </AuthProvider>
41
- </AbyssErrorBoundary>
42
- ```
43
-
44
- - **`config` est requise** (raison de la majeure 8.0.0 : aucun front ne peut oublier son RUM). `config={null}` désactive le RUM explicitement : rien n'est installé. Elle reçoit les options de `AbyssRum.init` (`apiUrl`, `proxyUrl`, `applicationId`, `token`, `release`, `instrumentedOrigins`, `routeResolver`…), sauf `consent`, que le provider fournit.
45
- - **Une seule initialisation.** `AbyssRum.init` est appelé au premier commit avec une `config` non nulle, une fois, StrictMode compris. Une `config` qui change ensuite est **ignorée** : le SDK vit autant que la page.
46
- - **Consentement.** Le replay exige la catégorie analytics de `AbyssCookieConsentProvider`, relue à chaque décision du SDK : un consentement donné ou retiré après le démarrage est suivi. Sans provider de consentement, analytics vaut refusé.
47
- - **Acteur.** Sous `AuthProvider`, l'acteur de la session est l'id de l'utilisateur connecté, `null` à la déconnexion, inchangé pendant le chargement de la session. Sans `AuthProvider`, aucun acteur.
48
- - **`feedbackTicket`** (`intakeSlug`, `internalTicketSectionId`, `appName`, `sessionUrl(sessionId, offsetMs)`, `locale?()`) : chaque retour utilisateur enregistré par Monitor devient un ticket Internal Tickets, par l'intake public d'abyss-console (`fileInternalTicketIntake`, peer `@abyss-project/console` `^12.4.0`, configuré par `AbyssConsoleCore.setConfig` du front). Titre `[<appName>] Retour utilisateur` ; description : le message, l'e-mail, la route et le lien vers la session. Le déclarant est l'e-mail saisi, sinon celui de l'utilisateur connecté ; un retour anonyme sans e-mail ne crée aucun ticket et reste dans Monitor. Un échec de création est journalisé (`console.warn`) et avalé. `locale()` est relue à chaque retour : sa sous-étiquette primaire (`en-GB` → `en`) part comme `locale` de l'intake, la langue des courriels de suivi ; une langue que l'intake ne parle pas (hors `UserLanguage`) n'est pas envoyée.
49
- - **`onFeedbackSent`** : rappel du front, appelé pour chaque retour enregistré, après le ticket (créé, sauté ou en échec).
50
- - **Erreurs de rendu.** `AbyssErrorBoundary` envoie l'erreur qu'il attrape à `AbyssRum.captureError(error, { source: 'error-boundary' })` ; sans RUM démarré, l'appel ne fait rien.
51
-
52
- ### Construire les options : `buildAbyssRumProviderConfig`
53
-
54
- Les fronts Abyss ne recopient pas leurs options : ils passent leurs valeurs de config brutes (chaîne vide = absente) au constructeur partagé, qui rend `AbyssRumProviderConfig | null`.
55
-
56
- ```ts
57
- import { buildAbyssRumProviderConfig } from '@abyss-project/commons-front-core';
58
-
59
- export const buildRumOptions = () =>
60
- buildAbyssRumProviderConfig({
61
- token: config.app.abyssMonitor.rumToken,
62
- applicationId: config.app.abyssMonitor.applicationId,
63
- monitorApiUrl: config.app.abyssMonitor.api,
64
- apiUrl: config.app.abyssCloud.api, // l'API du front : relais `/_t` et origine instrumentée
65
- mainApiUrl: config.app.abyss.api, // l'API d'abyss-main : origine instrumentée
66
- release: config.app.abyssMonitor.release,
67
- routeTemplates: CLOUD_ROUTE_TEMPLATES, // gabarits complets du routeur
68
- appName: 'AbyssCloud',
69
- mainFrontUrl: config.app.abyss.site, // la page de session vit dans le front d'abyss-main
70
- feedbackIntakeSlug: config.app.abyssMonitor.feedbackIntakeSlug,
71
- feedbackSectionId: config.app.abyssMonitor.feedbackSectionId,
72
- organizationId: config.app.abyssMonitor.organizationId,
73
- projectId: config.app.abyssMonitor.projectId,
74
- locale: () => currentLanguage(),
75
- });
76
- ```
77
-
78
- - **`null`** sans jeton, application, API Monitor, API du front ou API d'abyss-main (ou si l'une des trois URL n'en est pas une) : rien n'est installé.
79
- - **Relais et corrélation** : `proxyUrl` = origine de `apiUrl` + `/_t` ; `instrumentedOrigins` = origines de `apiUrl` et `mainApiUrl` (une seule fois si c'est la même), plus celle de Monitor avec `instrumentMonitorApi: true` (front d'abyss-main, qui appelle Monitor lui-même).
80
- - **Routes** : `routeResolver` = `createRouteResolver(routeTemplates)`.
81
- - **Ticket de retour** posé seulement quand l'intake, sa section, l'organisation, le projet et `mainFrontUrl` sont là ; son lien est `abyssMainRumSessionPath`, à l'instant du retour en secondes entières.
82
-
83
- ### Gabarits de route : `createRouteResolver(patterns)`
84
-
85
- `createRouteResolver(patterns: readonly string[]): (location: Location) => string` donne au SDK le gabarit de la route (`/org/:orgId/boards/:boardId`), que le SDK masque ensuite comme toute URL. Les gabarits sont **complets** (routes imbriquées comprises) et classés comme react-router les classe (`matchRoutes`) : un segment statique l'emporte sur un paramètre, un paramètre sur un `*`, quel que soit l'ordre de la liste. Sans correspondance : `location.pathname`.
86
-
87
- ### Page d'une session : `abyssMainRumSessionPath`
88
-
89
- `ABYSS_MAIN_RUM_SESSION_ROUTE` (`/org/:orgId/project/:projectId/application/:applicationId/monitor/rum/sessions/:sessionId`) est la seule source du chemin de la page d'une session dans le front d'abyss-main, qui le monte. `abyssMainRumSessionPath({ orgId, projectId, applicationId }, sessionId, t?)` le remplit (id de session encodé) et ajoute `?t=` en secondes entières, jamais négatif.
90
-
91
- ## Changements visibles
92
-
93
- ### 8.2.0
94
-
95
- Shell et tableaux de bord du front principal sur le design system (carte #286, zone 1).
96
-
97
- - **Nouveau** : `AppShell` prend `chromeless` (plein écran sans cadre), les entrées `external` et `action`,
98
- l'action de section (`NavSection.action`) et les entrées internes du pied ; `NotificationsMenu`, `AccountMenu`,
99
- `DesignCssVariables` et `focusRing` sont exportés ; `CommandPalette` gagne icônes, sous-titres, récents, sources
100
- asynchrones et ouverture filtrée.
101
- - **Paliers** : les composants du design system basculent à 768 et 1200 px (`designMedia`, `useDesignBreakpoint`)
102
- quel que soit le provider. Sous le thème legacy, l'`AppShell` montre le rail dès 768 px (et non plus à 900), la
103
- `Modal` une fenêtre dès 768 px, et la bannière MFA passe son bouton sous le texte quand il ne tient plus à côté.
104
- - **Largeur de `Drawer` et de `Toast` sous le thème legacy** : la largeur par défaut passe de 600 à 480 px (palier
105
- `sm` des jetons, et non plus le `sm` de MUI). Un hôte qui comptait sur 600 px passe `width` explicitement au
106
- `Drawer`.
107
- - **Largeur de `Modal` et de `CommandPalette` sous le thème legacy** : elle reste tirée des `theme.breakpoints` de
108
- MUI (prop `size` de la `Modal` passée à `maxWidth`, `sm` pour la palette). Sous le thème legacy, `md` vaut 900 px
109
- et `sm` 600 px, contre 768 et 480 sous `AbyssThemeProvider` : la même `Modal` est donc plus large chez un hôte
110
- legacy. Suivi : `DS-LEGACY-WIDTHS` dans `docs/internal/TECH-DEBT.md` d'abyss-main.
111
- - **Sélecteur d'espace** : un espace qui porte le nom de son type (« Espace personnel ») n'a plus de seconde ligne
112
- répétant ce type.
113
- - **Navigation** : la liste des sections garde une barre de défilement fine et visible quand elle dépasse la
114
- hauteur de la fenêtre.
115
- - **`PageState`** : `forbidden.message` remplace la description générique de l’accès refusé, pour nommer à qui
116
- demander l’accès (organisation, projet).
117
- - **`PageState`** : le bloc d’état passe son action sous le texte dès que son emplacement fait moins de 512 px
118
- (colonne latérale), et plus seulement sous 768 px d’écran.
119
- - **`PageHeader`** : de 768 px à la largeur du bureau, les actions passent sous le titre dès qu'elles ne tiennent
120
- plus à côté d'un titre de 256 px.
121
-
122
-
123
- ## Support
124
-
125
- Pour toute demande, ouvrir un ticket depuis [l'application Abyss](https://app.abyss-project.fr/dashboard/account/tickets/create).
1
+ # Abyss Commons Front Core
2
+
3
+ `@abyss-project/commons-front-core`
4
+
5
+ Composants React, hooks, design system et traductions partagés par les frontends Abyss.
6
+
7
+ _Shared React components, hooks, design system and translations for Abyss frontends._
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @abyss-project/commons-front-core
13
+ # or
14
+ pnpm add @abyss-project/commons-front-core
15
+ ```
16
+
17
+ ## Liens
18
+
19
+ - **Documentation** : [https://docs.abyss-project.fr/abyss-project](https://docs.abyss-project.fr/abyss-project)
20
+ - **NPM** : [@abyss-project/commons-front-core](https://www.npmjs.com/package/@abyss-project/commons-front-core)
21
+ - **Application Abyss** : [app.abyss-project.fr](https://app.abyss-project.fr)
22
+ - **Statut des services** : [status.abyss-project.fr](https://status.abyss-project.fr)
23
+
24
+ ## Usage
25
+
26
+ Voir la [documentation produit](https://docs.abyss-project.fr/abyss-project) pour les guides d'utilisation, la configuration du Core, les exemples d'appel et la référence des permissions.
27
+
28
+ ## RUM
29
+
30
+ Le RUM navigateur d'un front (SDK `@abyss-project/monitor-browser`, peer `^1.2.0`) se démarre par `AbyssRumProvider`, monté **sous** `AbyssCookieConsentProvider` et, quand le front en a un, sous `AuthProvider` :
31
+
32
+ ```tsx
33
+ <AbyssErrorBoundary appName="abyss-cloud">
34
+ <AuthProvider client={authClient}>
35
+ <AbyssCookieConsentProvider consentVersion={COOKIE_CONSENT_VERSION} isAuthenticated={isAuthenticated}>
36
+ <AbyssRumProvider config={buildRumOptions()}>
37
+ <App />
38
+ </AbyssRumProvider>
39
+ </AbyssCookieConsentProvider>
40
+ </AuthProvider>
41
+ </AbyssErrorBoundary>
42
+ ```
43
+
44
+ - **`config` est requise** (raison de la majeure 8.0.0 : aucun front ne peut oublier son RUM). `config={null}` désactive le RUM explicitement : rien n'est installé. Elle reçoit les options de `AbyssRum.init` (`apiUrl`, `proxyUrl`, `applicationId`, `token`, `release`, `instrumentedOrigins`, `routeResolver`…), sauf `consent`, que le provider fournit.
45
+ - **Une seule initialisation.** `AbyssRum.init` est appelé au premier commit avec une `config` non nulle, une fois, StrictMode compris. Une `config` qui change ensuite est **ignorée** : le SDK vit autant que la page.
46
+ - **Consentement.** Le replay exige la catégorie analytics de `AbyssCookieConsentProvider`, relue à chaque décision du SDK : un consentement donné ou retiré après le démarrage est suivi. Sans provider de consentement, analytics vaut refusé.
47
+ - **Acteur.** Sous `AuthProvider`, l'acteur de la session est l'id de l'utilisateur connecté, `null` à la déconnexion, inchangé pendant le chargement de la session. Sans `AuthProvider`, aucun acteur.
48
+ - **`feedbackTicket`** (`intakeSlug`, `internalTicketSectionId`, `appName`, `sessionUrl(sessionId, offsetMs)`, `locale?()`) : chaque retour utilisateur enregistré par Monitor devient un ticket Internal Tickets, par l'intake public d'abyss-console (`fileInternalTicketIntake`, peer `@abyss-project/console` `^12.4.0`, configuré par `AbyssConsoleCore.setConfig` du front). Titre `[<appName>] Retour utilisateur` ; description : le message, l'e-mail, la route et le lien vers la session. Le déclarant est l'e-mail saisi, sinon celui de l'utilisateur connecté ; un retour anonyme sans e-mail ne crée aucun ticket et reste dans Monitor. Un échec de création est journalisé (`console.warn`) et avalé. `locale()` est relue à chaque retour : sa sous-étiquette primaire (`en-GB` → `en`) part comme `locale` de l'intake, la langue des courriels de suivi ; une langue que l'intake ne parle pas (hors `UserLanguage`) n'est pas envoyée.
49
+ - **`onFeedbackSent`** : rappel du front, appelé pour chaque retour enregistré, après le ticket (créé, sauté ou en échec).
50
+ - **Erreurs de rendu.** `AbyssErrorBoundary` envoie l'erreur qu'il attrape à `AbyssRum.captureError(error, { source: 'error-boundary' })` ; sans RUM démarré, l'appel ne fait rien.
51
+
52
+ ### Construire les options : `buildAbyssRumProviderConfig`
53
+
54
+ Les fronts Abyss ne recopient pas leurs options : ils passent leurs valeurs de config brutes (chaîne vide = absente) au constructeur partagé, qui rend `AbyssRumProviderConfig | null`.
55
+
56
+ ```ts
57
+ import { buildAbyssRumProviderConfig } from '@abyss-project/commons-front-core';
58
+
59
+ export const buildRumOptions = () =>
60
+ buildAbyssRumProviderConfig({
61
+ token: config.app.abyssMonitor.rumToken,
62
+ applicationId: config.app.abyssMonitor.applicationId,
63
+ monitorApiUrl: config.app.abyssMonitor.api,
64
+ apiUrl: config.app.abyssCloud.api, // l'API du front : relais `/_t` et origine instrumentée
65
+ mainApiUrl: config.app.abyss.api, // l'API d'abyss-main : origine instrumentée
66
+ release: config.app.abyssMonitor.release,
67
+ routeTemplates: CLOUD_ROUTE_TEMPLATES, // gabarits complets du routeur
68
+ appName: 'AbyssCloud',
69
+ mainFrontUrl: config.app.abyss.site, // la page de session vit dans le front d'abyss-main
70
+ feedbackIntakeSlug: config.app.abyssMonitor.feedbackIntakeSlug,
71
+ feedbackSectionId: config.app.abyssMonitor.feedbackSectionId,
72
+ organizationId: config.app.abyssMonitor.organizationId,
73
+ projectId: config.app.abyssMonitor.projectId,
74
+ locale: () => currentLanguage(),
75
+ });
76
+ ```
77
+
78
+ - **`null`** sans jeton, application, API Monitor, API du front ou API d'abyss-main (ou si l'une des trois URL n'en est pas une) : rien n'est installé.
79
+ - **Relais et corrélation** : `proxyUrl` = origine de `apiUrl` + `/_t` ; `instrumentedOrigins` = origines de `apiUrl` et `mainApiUrl` (une seule fois si c'est la même), plus celle de Monitor avec `instrumentMonitorApi: true` (front d'abyss-main, qui appelle Monitor lui-même).
80
+ - **Routes** : `routeResolver` = `createRouteResolver(routeTemplates)`.
81
+ - **Ticket de retour** posé seulement quand l'intake, sa section, l'organisation, le projet et `mainFrontUrl` sont là ; son lien est `abyssMainRumSessionPath`, à l'instant du retour en secondes entières.
82
+
83
+ ### Gabarits de route : `createRouteResolver(patterns)`
84
+
85
+ `createRouteResolver(patterns: readonly string[]): (location: Location) => string` donne au SDK le gabarit de la route (`/org/:orgId/boards/:boardId`), que le SDK masque ensuite comme toute URL. Les gabarits sont **complets** (routes imbriquées comprises) et classés comme react-router les classe (`matchRoutes`) : un segment statique l'emporte sur un paramètre, un paramètre sur un `*`, quel que soit l'ordre de la liste. Sans correspondance : `location.pathname`.
86
+
87
+ ### Page d'une session : `abyssMainRumSessionPath`
88
+
89
+ `ABYSS_MAIN_RUM_SESSION_ROUTE` (`/org/:orgId/project/:projectId/application/:applicationId/monitor/rum/sessions/:sessionId`) est la seule source du chemin de la page d'une session dans le front d'abyss-main, qui le monte. `abyssMainRumSessionPath({ orgId, projectId, applicationId }, sessionId, t?)` le remplit (id de session encodé) et ajoute `?t=` en secondes entières, jamais négatif.
90
+
91
+ ## Changements visibles
92
+
93
+ ### 8.3.0
94
+
95
+ Compte et facturation du front principal sur le design system (carte #286, zone 2). Sept pièces nouvelles, chacune
96
+ documentée dans le `README.md` de son dossier et montrée dans le catalogue.
97
+
98
+ - **`Tabs`** (`structure`) : rangée d'onglets liés aux routes ou boutons, variantes `TabsVariant.Primary` et
99
+ `Secondary`, un seul arrêt de tabulation, flèches, Début et Fin ; défilement horizontal avec fondu tant qu'il reste
100
+ des onglets à droite (`useScrollOverflowCue`), 44 px de haut sous 768 px.
101
+ - **`Callout`** (`states`) : bandeau `CalloutSeverity.Info`, `Success`, `Warning`, `Danger`, avec titre, action,
102
+ fermeture et icône facultatifs ; l'action passe sous le texte sous 768 px.
103
+ - **`FormField`** (`inputs`) : libellé, aide et erreur autour d'un `OutlinedInput`, `InputBase` ou `Select` ; `id`,
104
+ `aria-describedby` et `labelId` reliés, erreur et obligation passées au contrôle.
105
+ - **`SettingsRow`** (`inputs`) : libellé, description et contrôle à droite ; empilée sous 768 px.
106
+ - **`Switch` du thème** : sous 768 px, la zone tactile de tout interrupteur (38 px dessinés) est portée à 44 px de
107
+ haut.
108
+ - **`UsageMeter`** (`data`) : consommation sur un plafond, seuils en parts du plafond, jauge `meter` ; sans plafond, la
109
+ valeur seule.
110
+ - **`CopyField`** (`inputs`) : valeur masquable et copiable, résultat de la copie annoncé dans une région vivante.
111
+ - **`NumberField`** (`inputs`) : nombre saisi et affiché avec les séparateurs de la langue, « , » ou « . » lus comme
112
+ marque décimale, borné (`min`) et arrondi (`decimals`) en sortie du champ ; remplace `AbyssNumberField` du legacy
113
+ pour le design system.
114
+ - **`DataTable`** : deux options de colonne, `wrap` (la cellule passe à la ligne au lieu d'être coupée) et
115
+ `hideHeader` (en-tête lu par les technologies d'assistance, non dessiné, pour une colonne d'actions en icône).
116
+ - **`Tabs`** : l'onglet choisi est ramené à l'écran aussi quand la rangée ou un onglet change de taille.
117
+ - **Traductions** : clés `ds.states.callout.*`, `ds.inputs.copy-field.*` et `ds.data.usage-meter.*` (FR et EN).
118
+
119
+ Pièces que les pages de compte importaient encore de `/legacy`, refaites dans le nouveau système (les copies legacy
120
+ restent pour les hôtes legacy) :
121
+
122
+ - **`UserAvatar`, `OrganizationAvatar`, `ProjectAvatar`** (`meaning`) : photo ou logo, sinon l'initiale ; taille
123
+ `size` en px. La fiche de profil au clic du legacy n'est pas reprise (sans appelant).
124
+ - **`ContextSelector`** (`inputs`) : mon espace, puis un groupe par organisation et ses projets ;
125
+ `ContextValue.kind` est l'énumération `ContextKind`.
126
+ - **`DropZone`** (`inputs`) : dépôt et bouton de choix, refus (taille, type, nombre) écrits sous la zone, entrée
127
+ AbyssCloud avec `cloudImport`.
128
+ - **`ImportButton`** (`inputs`) : menu appareil, lien (adresse vérifiée, échec dit), AbyssCloud.
129
+ - **`CloudImport`**, **`ImportFromCloudModal`**, **`QRCodeModal`** (`business`) : le sélecteur AbyssCloud, sa
130
+ modale (`open`, `onClose`, `onImport`), et un lien en QR code (`open` au lieu de `isOpen`).
131
+ - **Jetons `paper` et `paper.on`** : une page claire et son encre dans les deux thèmes, pour ce qui est dessiné à
132
+ l'encre sombre (code QR, signature en PNG transparent). ADR-0174 §3 complété.
133
+ - **Logique pure partagée** : `deriveQuotaOfferState`, `QuotaOfferState`, `offersAction`, `shouldSuggestModule`
134
+ (table de décision de l'offre de quota) sortent de `legacy/` et s'exportent de la racine ; `/legacy` les
135
+ réexporte toujours. `isAcceptedFile` (filtre `accept` du sélecteur cloud) passe aussi hors de `legacy/`.
136
+ - **Traductions** : `ds.inputs.context-selector.*`, `ds.inputs.drop-zone.*`, `ds.inputs.import-button.*`,
137
+ `ds.business.cloud-import.*`, `ds.business.qr-code.*` (FR et EN).
138
+
139
+ ### 8.2.0
140
+
141
+ Shell et tableaux de bord du front principal sur le design system (carte #286, zone 1).
142
+
143
+ - **Nouveau** : `AppShell` prend `chromeless` (plein écran sans cadre), les entrées `external` et `action`,
144
+ l'action de section (`NavSection.action`) et les entrées internes du pied ; `NotificationsMenu`, `AccountMenu`,
145
+ `DesignCssVariables` et `focusRing` sont exportés ; `CommandPalette` gagne icônes, sous-titres, récents, sources
146
+ asynchrones et ouverture filtrée.
147
+ - **Paliers** : les composants du design system basculent à 768 et 1200 px (`designMedia`, `useDesignBreakpoint`)
148
+ quel que soit le provider. Sous le thème legacy, l'`AppShell` montre le rail dès 768 px (et non plus à 900), la
149
+ `Modal` une fenêtre dès 768 px, et la bannière MFA passe son bouton sous le texte quand il ne tient plus à côté.
150
+ - **Largeur de `Drawer` et de `Toast` sous le thème legacy** : la largeur par défaut passe de 600 à 480 px (palier
151
+ `sm` des jetons, et non plus le `sm` de MUI). Un hôte qui comptait sur 600 px passe `width` explicitement au
152
+ `Drawer`.
153
+ - **Largeur de `Modal` et de `CommandPalette` sous le thème legacy** : elle reste tirée des `theme.breakpoints` de
154
+ MUI (prop `size` de la `Modal` passée à `maxWidth`, `sm` pour la palette). Sous le thème legacy, `md` vaut 900 px
155
+ et `sm` 600 px, contre 768 et 480 sous `AbyssThemeProvider` : la même `Modal` est donc plus large chez un hôte
156
+ legacy. Suivi : `DS-LEGACY-WIDTHS` dans `docs/internal/TECH-DEBT.md` d'abyss-main.
157
+ - **Sélecteur d'espace** : un espace qui porte le nom de son type (« Espace personnel ») n'a plus de seconde ligne
158
+ répétant ce type.
159
+ - **Navigation** : la liste des sections garde une barre de défilement fine et visible quand elle dépasse la
160
+ hauteur de la fenêtre.
161
+ - **`PageState`** : `forbidden.message` remplace la description générique de l’accès refusé, pour nommer à qui
162
+ demander l’accès (organisation, projet).
163
+ - **`PageState`** : le bloc d’état passe son action sous le texte dès que son emplacement fait moins de 512 px
164
+ (colonne latérale), et plus seulement sous 768 px d’écran.
165
+ - **`PageHeader`** : de 768 px à la largeur du bureau, les actions passent sous le titre dès qu'elles ne tiennent
166
+ plus à côté d'un titre de 256 px.
167
+
168
+
169
+ ## Support
170
+
171
+ Pour toute demande, ouvrir un ticket depuis [l'application Abyss](https://app.abyss-project.fr/dashboard/account/tickets/create).
@@ -20,7 +20,7 @@ export interface CatalogEntry {
20
20
  family: CatalogFamily;
21
21
  /** Base name of the screenshots: `<slug>-<theme>-<width>.png`. */
22
22
  slug: string;
23
- /** Checked by `responsive.spec.ts` at 360, 768 and 1440 px (structure, mobile, `DataTable`). */
23
+ /** Checked by `responsive.spec.ts` at 360, 768 and 1440 px: the pages that change form below 768 px. */
24
24
  responsive: boolean;
25
25
  /** Also captured at 390 px: the page changes form below 768 px. */
26
26
  mobileCapture: boolean;
@@ -51,6 +51,7 @@ export const CATALOG_ENTRIES: CatalogEntry[] = [
51
51
  entry('Toolbar', 'structure', 'toolbar', RESPONSIVE),
52
52
  entry('FilterBar', 'structure', 'filter-bar', RESPONSIVE),
53
53
  entry('SplitView', 'structure', 'split-view', RESPONSIVE),
54
+ entry('Tabs', 'structure', 'tabs', RESPONSIVE),
54
55
  entry('NotificationsMenu', 'structure', 'notifications-menu', {
55
56
  mobileCapture: true,
56
57
  }),
@@ -60,18 +61,26 @@ export const CATALOG_ENTRIES: CatalogEntry[] = [
60
61
  entry('ActionBar', 'mobile', 'action-bar', RESPONSIVE),
61
62
  entry('SwipeDeck', 'mobile', 'swipe-deck', RESPONSIVE),
62
63
  entry('PageState', 'states', 'page-state'),
64
+ entry('Callout', 'states', 'callout', RESPONSIVE),
63
65
  entry('DataTable', 'data', 'data-table', RESPONSIVE),
64
66
  entry('KeyValueList', 'data', 'key-value-list'),
65
67
  entry('StatTile', 'data', 'stat-tile'),
66
68
  entry('Sparkline', 'data', 'sparkline'),
67
69
  entry('ChartTheme', 'data', 'chart-theme'),
70
+ entry('UsageMeter', 'data', 'usage-meter'),
68
71
  entry('StatusBadge', 'meaning', 'status-badge'),
69
72
  entry('MethodBadge', 'meaning', 'method-badge'),
70
73
  entry('SeverityBadge', 'meaning', 'severity-badge'),
71
74
  entry('ScanBadge', 'meaning', 'scan-badge'),
72
75
  entry('RoleBadge', 'meaning', 'role-badge'),
73
76
  entry('ToolIcon', 'meaning', 'tool-icon'),
77
+ entry('Avatar', 'meaning', 'avatar'),
74
78
  entry('InfectedDownloadModal', 'business', 'infected-download-modal'),
79
+ entry('CloudImport', 'business', 'cloud-import', RESPONSIVE),
80
+ entry('ImportFromCloudModal', 'business', 'import-from-cloud-modal', {
81
+ mobileCapture: true,
82
+ }),
83
+ entry('QRCodeModal', 'business', 'qr-code-modal', { mobileCapture: true }),
75
84
  entry('Modal', 'layers', 'modal', { mobileCapture: true }),
76
85
  entry('ConfirmDialog', 'layers', 'confirm-dialog'),
77
86
  entry('Drawer', 'layers', 'drawer'),
@@ -83,6 +92,13 @@ export const CATALOG_ENTRIES: CatalogEntry[] = [
83
92
  entry('DateRangeField', 'inputs', 'date-range-field'),
84
93
  entry('ColorField', 'inputs', 'color-field'),
85
94
  entry('SegmentedControl', 'inputs', 'segmented-control'),
95
+ entry('FormField', 'inputs', 'form-field'),
96
+ entry('NumberField', 'inputs', 'number-field'),
97
+ entry('SettingsRow', 'inputs', 'settings-row', RESPONSIVE),
98
+ entry('CopyField', 'inputs', 'copy-field', RESPONSIVE),
99
+ entry('ContextSelector', 'inputs', 'context-selector', RESPONSIVE),
100
+ entry('DropZone', 'inputs', 'drop-zone', RESPONSIVE),
101
+ entry('ImportButton', 'inputs', 'import-button'),
86
102
  ];
87
103
 
88
104
  export const CATALOG_THEMES = [ThemeMode.Dark, ThemeMode.Light] as const;
@@ -0,0 +1,97 @@
1
+ import type {
2
+ IFile,
3
+ IFolder,
4
+ IGetRepositoryCloudData,
5
+ ISpace,
6
+ } from '@abyss-project/cloud';
7
+ import type { CloudPicker } from '../src/hooks/cloud-picker.types';
8
+
9
+ // An AbyssCloud picker that answers from memory: the catalog has no network. Three Spaces (mine,
10
+ // an organization's, one shared with me) and a small tree in the personal one.
11
+
12
+ const space = (id: string, overrides: Partial<ISpace>): ISpace => ({
13
+ id,
14
+ userId: 'user-1',
15
+ organizationId: null,
16
+ projectId: null,
17
+ createdByUserId: 'user-1',
18
+ archivedAt: null,
19
+ saveVersionDefault: false,
20
+ versionMaxCount: null,
21
+ versionMaxAgeDays: null,
22
+ ...overrides,
23
+ });
24
+
25
+ const PERSONAL = space('space-personal', {});
26
+ const ORGANIZATION = space('space-org', {
27
+ userId: null,
28
+ organizationId: 'org-1',
29
+ });
30
+ const SHARED = space('space-shared', { userId: 'user-2' });
31
+
32
+ const file = (name: string, mimeType: string, sizeOctet: number): IFile => ({
33
+ id: name,
34
+ name,
35
+ description: null,
36
+ sizeOctet,
37
+ isEncrypt: false,
38
+ thumbnailPublicAccessId: null,
39
+ thumbnailId: null,
40
+ saveVersion: false,
41
+ externalId: null,
42
+ folderId: null,
43
+ spaceId: 'space-personal',
44
+ createdByUserId: 'user-1',
45
+ checksum: null,
46
+ mimeType,
47
+ });
48
+
49
+ const folder = (name: string, childFile: IFile[]): IFolder => ({
50
+ id: name,
51
+ name,
52
+ description: null,
53
+ spaceId: 'space-personal',
54
+ folderId: null,
55
+ createdByUserId: 'user-1',
56
+ childFile,
57
+ childFolder: [],
58
+ });
59
+
60
+ const TREE: IGetRepositoryCloudData = {
61
+ folders: [
62
+ folder('Factures 2026', [
63
+ file('facture-septembre.pdf', 'application/pdf', 182_000),
64
+ file('facture-octobre.pdf', 'application/pdf', 176_000),
65
+ ]),
66
+ folder('Photos', [file('portrait.png', 'image/png', 1_240_000)]),
67
+ ],
68
+ files: [
69
+ file('signature.png', 'image/png', 24_000),
70
+ file('contrat-de-bail-appartement-lyon.pdf', 'application/pdf', 912_000),
71
+ file('notes.txt', 'text/plain', 2_000),
72
+ ],
73
+ size: 2_534_000,
74
+ };
75
+
76
+ export const CATALOG_CLOUD_PICKER: CloudPicker = {
77
+ abyssCloudUrl: 'https://cloud.abyss-project.fr',
78
+ getFileThumbnailUrl: () => '',
79
+ clouds: [PERSONAL, ORGANIZATION],
80
+ cloudShares: [SHARED],
81
+ isLoading: false,
82
+ hasError: false,
83
+ refresh: async () => true,
84
+ onCreateCloud: async () => true,
85
+ fetchCloudRepository: async (spaceId) =>
86
+ spaceId === PERSONAL.id ? TREE : { folders: [], files: [], size: 0 },
87
+ };
88
+
89
+ /** The same picker after a failed provisioning: nothing listed. */
90
+ export const CATALOG_FAILED_CLOUD_PICKER: CloudPicker = {
91
+ ...CATALOG_CLOUD_PICKER,
92
+ clouds: null,
93
+ cloudShares: null,
94
+ hasError: true,
95
+ refresh: async () => false,
96
+ onCreateCloud: async () => false,
97
+ };
@@ -0,0 +1,94 @@
1
+ import {
2
+ OrganizationAvatar,
3
+ ProjectAvatar,
4
+ UserAvatar,
5
+ } from '../../src/components/meaning/Avatar/Avatar';
6
+ import { LogoAbyssSmall } from '../../src/assets';
7
+ import { Example, Row, Showcase } from '../frame';
8
+
9
+ const picture = () => LogoAbyssSmall;
10
+ // A picture that cannot load: the initial stays.
11
+ const missing = (id: string) => `/catalogue-introuvable/${id}.png`;
12
+
13
+ export const AvatarPage = () => (
14
+ <Showcase
15
+ family="Sens"
16
+ name="Avatar"
17
+ description="Personne, organisation, projet : la photo ou le logo, sinon l'initiale (ou une icône sans nom). Une personne dans un disque, une organisation et un projet dans un carré arrondi."
18
+ >
19
+ <Example title="Avec image" framed>
20
+ <Row>
21
+ <UserAvatar
22
+ userId="u1"
23
+ alias="Mehdi"
24
+ hasThumbnail
25
+ getProfilePictureUrl={picture}
26
+ />
27
+ <OrganizationAvatar
28
+ orgId="o1"
29
+ name="Abyss"
30
+ hasThumbnail
31
+ getLogoUrl={picture}
32
+ />
33
+ <ProjectAvatar
34
+ projectId="p1"
35
+ title="Atlas"
36
+ hasThumbnail
37
+ getProjectPictureUrl={picture}
38
+ />
39
+ </Row>
40
+ </Example>
41
+ <Example title="Sans image, ou image introuvable" framed>
42
+ <Row>
43
+ <UserAvatar
44
+ userId="u2"
45
+ alias="camille"
46
+ hasThumbnail={false}
47
+ getProfilePictureUrl={picture}
48
+ />
49
+ <OrganizationAvatar
50
+ orgId="o2"
51
+ name="Karbon"
52
+ hasThumbnail
53
+ getLogoUrl={missing}
54
+ />
55
+ <ProjectAvatar
56
+ projectId="p2"
57
+ title="board"
58
+ hasThumbnail={false}
59
+ getProjectPictureUrl={picture}
60
+ />
61
+ <OrganizationAvatar
62
+ orgId="o3"
63
+ name=""
64
+ hasThumbnail={false}
65
+ getLogoUrl={picture}
66
+ />
67
+ </Row>
68
+ </Example>
69
+ <Example title="Tailles : 22, 32 (défaut), 64" framed>
70
+ <Row>
71
+ <UserAvatar
72
+ userId="u3"
73
+ alias="Lina"
74
+ hasThumbnail={false}
75
+ size={22}
76
+ getProfilePictureUrl={picture}
77
+ />
78
+ <UserAvatar
79
+ userId="u4"
80
+ alias="Lina"
81
+ hasThumbnail={false}
82
+ getProfilePictureUrl={picture}
83
+ />
84
+ <UserAvatar
85
+ userId="u5"
86
+ alias="Lina"
87
+ hasThumbnail
88
+ size={64}
89
+ getProfilePictureUrl={picture}
90
+ />
91
+ </Row>
92
+ </Example>
93
+ </Showcase>
94
+ );
@@ -0,0 +1,67 @@
1
+ import Button from '@mui/material/Button';
2
+ import GroupsRounded from '@mui/icons-material/GroupsRounded';
3
+ import {
4
+ Callout,
5
+ CalloutSeverity,
6
+ } from '../../src/components/states/Callout/Callout';
7
+ import { Example, LastEvent, Showcase, useEventLog } from '../frame';
8
+
9
+ export const CalloutPage = () => {
10
+ const { log } = useEventLog();
11
+ return (
12
+ <Showcase
13
+ family="États"
14
+ name="Callout"
15
+ description="Bandeau dans une page : information, succès, avertissement, danger ; titre, texte, action et fermeture facultatifs."
16
+ >
17
+ <Example title="Information, avec titre et fermeture">
18
+ <Callout
19
+ severity={CalloutSeverity.Info}
20
+ title="Essai gratuit"
21
+ onClose={() => log('fermer essai')}
22
+ >
23
+ Votre essai du plan Pro se termine dans 12 jours.
24
+ </Callout>
25
+ </Example>
26
+ <Example title="Succès">
27
+ <Callout severity={CalloutSeverity.Success}>
28
+ Votre moyen de paiement a été mis à jour.
29
+ </Callout>
30
+ </Example>
31
+ <Example title="Avertissement, avec action">
32
+ <Callout
33
+ severity={CalloutSeverity.Warning}
34
+ title="Quota bientôt atteint"
35
+ action={
36
+ <Button variant="outlined" onClick={() => log('augmenter')}>
37
+ Augmenter le stockage
38
+ </Button>
39
+ }
40
+ >
41
+ Vous avez utilisé 92 % de votre stockage.
42
+ </Callout>
43
+ </Example>
44
+ <Example title="Danger, avec action et fermeture">
45
+ <Callout
46
+ severity={CalloutSeverity.Danger}
47
+ title="Paiement refusé"
48
+ action={
49
+ <Button variant="contained" onClick={() => log('régler')}>
50
+ Régler la facture
51
+ </Button>
52
+ }
53
+ onClose={() => log('fermer paiement')}
54
+ >
55
+ Le prélèvement de la facture de septembre a échoué. Mettez à jour
56
+ votre carte pour éviter la suspension de votre abonnement.
57
+ </Callout>
58
+ </Example>
59
+ <Example title="Icône remplacée (famille)">
60
+ <Callout severity={CalloutSeverity.Info} icon={<GroupsRounded />}>
61
+ Votre abonnement est pris en charge par votre famille.
62
+ </Callout>
63
+ </Example>
64
+ <LastEvent />
65
+ </Showcase>
66
+ );
67
+ };