@abyss-project/commons-front-core 9.1.0 → 9.2.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 (77) hide show
  1. package/README.md +43 -1
  2. package/catalog/catalog-entries.ts +7 -0
  3. package/catalog/pages/AvatarGroup.tsx +45 -0
  4. package/catalog/pages/CompanySearchField.tsx +113 -0
  5. package/catalog/pages/ConfirmDialog.tsx +43 -1
  6. package/catalog/pages/CopyField.tsx +24 -1
  7. package/catalog/pages/EntityImageField.tsx +124 -0
  8. package/catalog/pages/LineChart.tsx +118 -0
  9. package/catalog/pages/Pagination.tsx +52 -0
  10. package/catalog/pages/Section.tsx +16 -2
  11. package/catalog/pages/TeamAvatar.tsx +40 -0
  12. package/catalog/pages/UserPicker.tsx +124 -0
  13. package/catalog/pages/index.ts +14 -0
  14. package/dist/{AbyssCookieConsentProvider-B7-F6osY.js → AbyssCookieConsentProvider-DUYUEZaO.js} +94 -36
  15. package/dist/{CookieConsentSettings.component-DxQL4ULz.js → CookieConsentSettings.component-aVDMZLIs.js} +1 -1
  16. package/dist/components/data/LineChart/LineChart.d.ts +32 -0
  17. package/dist/components/data/LineChart/LineChart.spec.d.ts +1 -0
  18. package/dist/components/data/LineChart/line-chart-ticks.d.ts +31 -0
  19. package/dist/components/data/LineChart/line-chart-ticks.spec.d.ts +1 -0
  20. package/dist/components/data/Pagination/Pagination.d.ts +16 -0
  21. package/dist/components/data/Pagination/Pagination.spec.d.ts +1 -0
  22. package/dist/components/data/index.d.ts +2 -0
  23. package/dist/components/inputs/CompanySearchField/CompanySearchField.d.ts +24 -0
  24. package/dist/components/inputs/CompanySearchField/CompanySearchField.spec.d.ts +1 -0
  25. package/dist/components/inputs/CompanySearchField/pappers-company.d.ts +18 -0
  26. package/dist/components/inputs/CopyField/CopyField.d.ts +22 -6
  27. package/dist/components/inputs/EntityImageField/EntityImageField.d.ts +47 -0
  28. package/dist/components/inputs/EntityImageField/EntityImageField.spec.d.ts +1 -0
  29. package/dist/components/inputs/UserPicker/UserPicker.d.ts +50 -0
  30. package/dist/components/inputs/UserPicker/UserPicker.spec.d.ts +1 -0
  31. package/dist/components/inputs/index.d.ts +3 -0
  32. package/dist/components/layers/ConfirmDialog/useConfirmDialog.d.ts +21 -0
  33. package/dist/components/layers/ConfirmDialog/useConfirmDialog.spec.d.ts +1 -0
  34. package/dist/components/layers/index.d.ts +1 -0
  35. package/dist/components/meaning/AvatarGroup/AvatarGroup.d.ts +22 -0
  36. package/dist/components/meaning/AvatarGroup/AvatarGroup.spec.d.ts +1 -0
  37. package/dist/components/meaning/TeamAvatar/TeamAvatar.d.ts +14 -0
  38. package/dist/components/meaning/TeamAvatar/TeamAvatar.spec.d.ts +1 -0
  39. package/dist/components/meaning/index.d.ts +2 -0
  40. package/dist/components/structure/Section/Section.d.ts +10 -2
  41. package/dist/cookie-consent.js +2 -2
  42. package/dist/design-theme-DyWPbqXp.js +26515 -0
  43. package/dist/hooks/use-rich-intl.hook.d.ts +58 -0
  44. package/dist/{index-DflXNluH.js → index-77h-J0x1.js} +2 -2
  45. package/dist/{index-Bhhwuj39.js → index-BGIR-HuR.js} +2 -2
  46. package/dist/{index-DkcMwm_T.js → index-BM8u6b8w.js} +3 -3
  47. package/dist/{index-2zbZ2URx.js → index-BMgcTA0L.js} +3 -3
  48. package/dist/{index-C5OyIHCq.js → index-Bdv62RC2.js} +2 -2
  49. package/dist/{index-uGXjK3xv.js → index-BykEZXU6.js} +3 -3
  50. package/dist/{index-CsyAu658.js → index-C4plH_av.js} +2 -2
  51. package/dist/{index-uLh81uFU.js → index-C6YE_5pO.js} +2 -2
  52. package/dist/{index-BloHDADq.js → index-C6rmH0hM.js} +35694 -46623
  53. package/dist/{index-BuQlif6i.js → index-C9Vm0yCV.js} +2 -2
  54. package/dist/{index-BtzB7aR_.js → index-CAWnWyTQ.js} +2 -2
  55. package/dist/{index-CTP0QoCT.js → index-CX-2IC2j.js} +3 -3
  56. package/dist/{index-jhW25X__.js → index-CZj1-4RN.js} +2 -2
  57. package/dist/{index-RkXYGPSf.js → index-ChBWwrvm.js} +4 -4
  58. package/dist/{index-BMM6rvML.js → index-DKF4BSrW.js} +2 -2
  59. package/dist/{index-iQhqBjKY.js → index-DPsPJ7S5.js} +2 -2
  60. package/dist/{index-IooWxH41.js → index-LNg1bMNB.js} +2 -2
  61. package/dist/{index-Dew1ymFy.js → index-URaKKzT4.js} +2 -2
  62. package/dist/{index-BAcf97X_.js → index-UXYdJQT_.js} +2 -2
  63. package/dist/{index-BXtPl6pH.js → index-WINZVUn3.js} +1 -1
  64. package/dist/{index-CspGahq0.js → index-_kMEUAM7.js} +4 -4
  65. package/dist/{index-Waz6-pQr.js → index-elrbx7C5.js} +4 -4
  66. package/dist/index.js +5470 -4329
  67. package/dist/legacy/components/CompanySearchAutocomplete/CompanySearchAutocomplete.d.ts +2 -14
  68. package/dist/legacy/components/Layouts/AbyssFamilyBanner/AbyssFamilyBanner.component.d.ts +3 -7
  69. package/dist/legacy.js +61 -61
  70. package/dist/theme/tokens/semantic.d.ts +4 -1
  71. package/dist/translations/en.d.ts +29 -0
  72. package/dist/translations/fr.d.ts +29 -0
  73. package/dist/utils/family.d.ts +12 -0
  74. package/dist/utils/index.d.ts +1 -0
  75. package/package.json +1 -1
  76. package/test-results/catalog/.last-run.json +1 -1
  77. package/dist/design-theme-BztWe75w.js +0 -15490
package/README.md CHANGED
@@ -90,7 +90,49 @@ export const buildRumOptions = () =>
90
90
 
91
91
  ## Changements visibles
92
92
 
93
- ### Prochaine version
93
+ ### 9.2.0
94
+
95
+ Organisation et projet du front principal sur le design system (carte #286, zone 3). Pièces nouvelles et évolutions,
96
+ chacune documentée dans le `README.md` de son dossier et montrée dans le catalogue.
97
+
98
+ - **`AvatarGroup`** (`meaning`) : pile d'avatars nommés, `max` au-delà duquel « +N » est nommé par les noms restants (`intl.formatList`) et les liste en infobulle ; aucun élément interactif. Remplace `UserAvatarList`.
99
+ - **`TeamAvatar`** (`meaning`) : initiale d'équipe sur sa couleur, encre `paper` ou `paper.on` la plus contrastée ; une couleur absente ou illisible (`CSS.supports`) donne la teinte neutre au lieu d'une exception (dette `DS-TEAM-COLOR`).
100
+ - **`Pagination`** (`data`) : précédent, numéros de page avec ellipses (sept places fixes), suivant ; page courante
101
+ en `aria-current="page"`, bornes désactivées, rien sous deux pages ; sous 768 px, précédent, « Page x sur y »,
102
+ suivant, cibles de 44 px.
103
+ - **`LineChart`** (`data`) : courbes Nivo sous `abyssNivoTheme`, échelle de temps ou linéaire, aire facultative ;
104
+ couleurs prises dans l'ordre de la palette de séries, jamais passées par l'hôte ; légende au-delà d'une série ;
105
+ figure nommée et tableau des valeurs pour les lecteurs d'écran ; « Aucune donnée à afficher » sans point.
106
+ Graduations de l'axe x selon la largeur mesurée du graphique (et non celle de l'écran) : pas le plus fin, jamais plus
107
+ fin que les données, dont les libellés mono gardent `space[8]` entre eux ; un libellé qui déborderait des marges
108
+ n'est pas affiché.
109
+ - **Palette de séries** (`theme`) : jetons `data.series.1` à `data.series.6` (et `--abyss-data-series-*`), ordre
110
+ exporté par `DATA_SERIES_TOKENS` ; luminosité et chroma égales, 3:1 au moins sur les fonds (ADR-0174, avenant).
111
+ - **`UserPicker`** (`inputs`) : choix simple ou multiple d'utilisateurs (`{ id, name, email, pictureUrl }`), avatar,
112
+ nom et e-mail par option ; filtre local sur le nom et l'e-mail, ou recherche serveur par `onSearch` avec `loading` ;
113
+ `disabledIds` ; en multiple, puces parcourues à la flèche gauche et retirées par Retour arrière ou Suppr ; sous
114
+ 768 px, 44 px par option, puces de 44 px de haut dont la croix de retrait est un carré tactile, et texte du champ
115
+ jamais plus étroit que 44 px quand les puces remplissent la ligne.
116
+ - **`CompanySearchField`** (`inputs`) : contrat de `CompanySearchAutocomplete` (Pappers, `onSelect(PappersCompany)`,
117
+ erreur distincte d'une réponse vide, date de création dans la langue de l'utilisateur) sur `FormField` et les jetons
118
+ du core ; temporisation annulée au démontage, réponse dépassée ignorée.
119
+ - **Types sortis de `legacy/`** : `PappersCompany` et `CurrentFamilyInfo` (avec `CurrentFamilyStatus`) sont exportés de
120
+ la racine ; `/legacy` les réexporte.
121
+ - **Traductions** : clés `ds.inputs.user-picker.*` et `ds.inputs.company-search-field.*` (FR et EN).
122
+ - **`EntityImageField`** (`inputs`) : image d'entité changée sur place, `EntityImageVariant.Avatar` (pastille d'appareil photo, `EntityImageShape.Round` ou `Square`) ou `Banner` (bouton en surimpression) ; sélecteur de l'appareil, ou menu d'`ImportButton` avec `cloudImport` ; envoi indiqué, annoncé, et bloquant.
123
+ - **`useConfirmDialog`** et **`ConfirmDialogProvider`** (`layers`) : `confirm(options)` renvoie `Promise<boolean>` et ouvre le `ConfirmDialog` ; une demande à la fois, les suivantes attendent ; hors du fournisseur, une erreur qui le nomme.
124
+ - **`CopyField.loadValue`** : la valeur n'est demandée qu'au premier « Afficher » ou « Copier », puis gardée ; chargement sur le bouton, échec annoncé et rejouable. `value` et `loadValue` sont exclusifs.
125
+ - **`Section tone`** : `SectionTone.Danger` met le titre et un filet en `status.danger` (zones de danger).
126
+ - **`DataTable`, liste mobile** : sous 768 px, chaque contrôle de la colonne `trailing` (lien, bouton, `role="button"`, `role="link"`, `role="menuitem"`) offre une cible de 44 × 44 (`densityValues.touchTarget`), même rendu en `size="small"` par l'hôte ; rien ne change à partir de 768 px.
127
+
128
+ ### 9.1.1
129
+
130
+ - **`useServiceTour`** : sous un `AbyssCookieConsentProvider`, une première visite attend que le bandeau de cookies
131
+ soit tranché. Sur mobile, le bandeau recouvrait la feuille de la visite et bloquait « Suivant ». `start()` n'attend pas.
132
+ - **`ProductTour`** : la découpe suit sa cible à chaque image tant que la visite est ouverte. Une cible déplacée sans
133
+ redimensionnement ni défilement (police web chargée au-dessus d'elle) laissait la découpe 20 px à côté.
134
+
135
+ ### 9.1.0
94
136
 
95
137
  - **`ProductTour`** (`layers`) : visite guidée d'un service (carte #285, spec §6.7). Bulle ancrée sur l'élément
96
138
  `data-tour`, page voilée autour d'une découpe, compteur et points d'étape, « Suivant » / « Terminer » et « Passer la
@@ -66,6 +66,8 @@ export const CATALOG_ENTRIES: CatalogEntry[] = [
66
66
  entry('KeyValueList', 'data', 'key-value-list'),
67
67
  entry('StatTile', 'data', 'stat-tile'),
68
68
  entry('Sparkline', 'data', 'sparkline'),
69
+ entry('Pagination', 'data', 'pagination', RESPONSIVE),
70
+ entry('LineChart', 'data', 'line-chart'),
69
71
  entry('ChartTheme', 'data', 'chart-theme'),
70
72
  entry('UsageMeter', 'data', 'usage-meter'),
71
73
  entry('StatusBadge', 'meaning', 'status-badge'),
@@ -75,6 +77,8 @@ export const CATALOG_ENTRIES: CatalogEntry[] = [
75
77
  entry('RoleBadge', 'meaning', 'role-badge'),
76
78
  entry('ToolIcon', 'meaning', 'tool-icon'),
77
79
  entry('Avatar', 'meaning', 'avatar'),
80
+ entry('AvatarGroup', 'meaning', 'avatar-group'),
81
+ entry('TeamAvatar', 'meaning', 'team-avatar'),
78
82
  entry('InfectedDownloadModal', 'business', 'infected-download-modal'),
79
83
  entry('CloudImport', 'business', 'cloud-import', RESPONSIVE),
80
84
  entry('ImportFromCloudModal', 'business', 'import-from-cloud-modal', {
@@ -97,6 +101,9 @@ export const CATALOG_ENTRIES: CatalogEntry[] = [
97
101
  entry('NumberField', 'inputs', 'number-field'),
98
102
  entry('SettingsRow', 'inputs', 'settings-row', RESPONSIVE),
99
103
  entry('CopyField', 'inputs', 'copy-field', RESPONSIVE),
104
+ entry('UserPicker', 'inputs', 'user-picker', RESPONSIVE),
105
+ entry('CompanySearchField', 'inputs', 'company-search-field', RESPONSIVE),
106
+ entry('EntityImageField', 'inputs', 'entity-image-field', RESPONSIVE),
100
107
  entry('ContextSelector', 'inputs', 'context-selector', RESPONSIVE),
101
108
  entry('DropZone', 'inputs', 'drop-zone', RESPONSIVE),
102
109
  entry('ImportButton', 'inputs', 'import-button'),
@@ -0,0 +1,45 @@
1
+ import { AvatarGroup } from '../../src/components/meaning/AvatarGroup/AvatarGroup';
2
+ import type { AvatarGroupPerson } from '../../src/components/meaning/AvatarGroup/AvatarGroup';
3
+ import { LogoAbyssSmall } from '../../src/assets';
4
+ import { Example, Showcase } from '../frame';
5
+
6
+ const PEOPLE: AvatarGroupPerson[] = [
7
+ { id: 'u1', name: 'Mehdi', pictureUrl: LogoAbyssSmall },
8
+ { id: 'u2', name: 'Camille', pictureUrl: null },
9
+ { id: 'u3', name: 'Lina', pictureUrl: null },
10
+ // A picture that cannot load: the initial stays.
11
+ { id: 'u4', name: 'Paul', pictureUrl: '/catalogue-introuvable/u4.png' },
12
+ { id: 'u5', name: 'Zoé', pictureUrl: null },
13
+ { id: 'u6', name: 'Hugo', pictureUrl: null },
14
+ { id: 'u7', name: 'Inès', pictureUrl: null },
15
+ ];
16
+
17
+ export const AvatarGroupPage = () => (
18
+ <Showcase
19
+ family="Sens"
20
+ name="AvatarGroup"
21
+ description="Personnes en pile : la photo ou l'initiale, chaque avatar nommé. Au-delà de max, « +N » compte les autres et les nomme dans une infobulle. Rien n'est interactif."
22
+ >
23
+ <Example title="Tout le monde (sans max)" framed>
24
+ <AvatarGroup people={PEOPLE.slice(0, 4)} label="Membres de Design" />
25
+ </Example>
26
+ <Example title="max = 3 : « +4 », noms au survol" framed>
27
+ <AvatarGroup people={PEOPLE} max={3} label="Membres de Ops" />
28
+ </Example>
29
+ <Example title="Tailles : 22, 32 (défaut), 40" framed>
30
+ <AvatarGroup
31
+ people={PEOPLE}
32
+ max={4}
33
+ size={22}
34
+ label="Membres de Support"
35
+ />
36
+ <AvatarGroup people={PEOPLE} max={4} label="Membres de Finance" />
37
+ <AvatarGroup
38
+ people={PEOPLE}
39
+ max={4}
40
+ size={40}
41
+ label="Membres de Ventes"
42
+ />
43
+ </Example>
44
+ </Showcase>
45
+ );
@@ -0,0 +1,113 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { CompanySearchField } from '../../src/components/inputs/CompanySearchField/CompanySearchField';
3
+ import type { PappersCompany } from '../../src/components/inputs/CompanySearchField/pappers-company';
4
+ import { Example, LastEvent, Showcase, useEventLog } from '../frame';
5
+
6
+ const COMPANIES: PappersCompany[] = [
7
+ {
8
+ siren: '901234567',
9
+ nom_entreprise: 'ABYSS PROJECT',
10
+ date_creation: '2021-09-14',
11
+ forme_juridique: 'SAS',
12
+ domaine_activite: 'Édition de logiciels applicatifs',
13
+ siege: {
14
+ adresse: '12 rue de la Paix',
15
+ code_postal: '75002',
16
+ ville: 'Paris',
17
+ },
18
+ },
19
+ {
20
+ siren: '812345678',
21
+ nom_entreprise:
22
+ 'ABYSSAL CONSEIL ET ACCOMPAGNEMENT DES ENTREPRISES DE LA RÉGION AUVERGNE-RHÔNE-ALPES',
23
+ date_creation: '2016-02-03',
24
+ forme_juridique: 'SARL',
25
+ siege: { ville: 'Lyon' },
26
+ },
27
+ {
28
+ siren: '723456789',
29
+ nom_entreprise: 'KARBON STUDIO',
30
+ date_creation: '2012-06-30',
31
+ forme_juridique: 'EURL',
32
+ domaine_activite: 'Activités photographiques',
33
+ siege: {
34
+ adresse: '4 quai des Chartrons',
35
+ code_postal: '33000',
36
+ ville: 'Bordeaux',
37
+ },
38
+ },
39
+ ];
40
+
41
+ /** The text that makes the stand-in for Pappers fail, to show the error state. */
42
+ const FAILING_QUERY = 'panne';
43
+
44
+ /**
45
+ * Pappers answered from memory while the page is shown: the catalog has no network. Typing
46
+ * « panne » shows the error state.
47
+ */
48
+ const usePappersFromMemory = () => {
49
+ useEffect(() => {
50
+ const realFetch = window.fetch;
51
+ window.fetch = async (input, init) => {
52
+ const url = new URL(
53
+ typeof input === 'string' || input instanceof URL ? input : input.url,
54
+ );
55
+ if (url.hostname !== 'suggestions.pappers.fr') {
56
+ return realFetch(input, init);
57
+ }
58
+ const query = (url.searchParams.get('q') ?? '').toLowerCase();
59
+ if (query.includes(FAILING_QUERY)) {
60
+ return new Response(null, { status: 503 });
61
+ }
62
+ const found = COMPANIES.filter((company) =>
63
+ company.nom_entreprise.toLowerCase().includes(query),
64
+ );
65
+ return new Response(JSON.stringify({ resultats_nom_entreprise: found }), {
66
+ status: 200,
67
+ headers: { 'Content-Type': 'application/json' },
68
+ });
69
+ };
70
+ return () => {
71
+ window.fetch = realFetch;
72
+ };
73
+ }, []);
74
+ };
75
+
76
+ export const CompanySearchFieldPage = () => {
77
+ const { log } = useEventLog();
78
+ const [name, setName] = useState('');
79
+ const [filled, setFilled] = useState('ABYSS PROJECT');
80
+ usePappersFromMemory();
81
+ return (
82
+ <Showcase
83
+ family="Saisie"
84
+ name="CompanySearchField"
85
+ description="Nom d'entreprise avec les suggestions de Pappers : forme juridique, SIREN, date de création, siège et activité. Une requête en échec met le champ en erreur, distincte d'une recherche sans résultat. Ici, Pappers répond de mémoire (« abyss », « karbon » ; « panne » simule une panne)."
86
+ >
87
+ <Example title="Recherche" maxWidth={640}>
88
+ <CompanySearchField
89
+ value={name}
90
+ onInputChange={setName}
91
+ onSelect={(company) => log(`entreprise ${company.siren}`)}
92
+ />
93
+ </Example>
94
+ <Example title="Nom déjà saisi" maxWidth={640}>
95
+ <CompanySearchField
96
+ value={filled}
97
+ onInputChange={setFilled}
98
+ onSelect={(company) => log(`entreprise ${company.siren}`)}
99
+ label="Raison sociale"
100
+ />
101
+ </Example>
102
+ <Example title="Inactif" maxWidth={640}>
103
+ <CompanySearchField
104
+ value="ABYSS PROJECT"
105
+ onInputChange={() => undefined}
106
+ onSelect={() => undefined}
107
+ disabled
108
+ />
109
+ </Example>
110
+ <LastEvent />
111
+ </Showcase>
112
+ );
113
+ };
@@ -4,7 +4,44 @@ import {
4
4
  ConfirmDialog,
5
5
  ConfirmDialogTone,
6
6
  } from '../../src/components/layers/ConfirmDialog/ConfirmDialog';
7
- import { Example, LastEvent, Showcase, useEventLog } from '../frame';
7
+ import {
8
+ ConfirmDialogProvider,
9
+ useConfirmDialog,
10
+ } from '../../src/components/layers/ConfirmDialog/useConfirmDialog';
11
+ import { Example, LastEvent, Row, Showcase, useEventLog } from '../frame';
12
+
13
+ /** Asks through `useConfirmDialog`, twice in a row: the second request waits for the first. */
14
+ const ImperativeConfirm = () => {
15
+ const { log } = useEventLog();
16
+ const { confirm } = useConfirmDialog();
17
+ const ask = async () => {
18
+ const removed = await confirm({
19
+ title: 'Supprimer l’équipe Design ?',
20
+ body: 'Ses 4 membres perdent les accès donnés par l’équipe.',
21
+ confirmLabel: 'Supprimer l’équipe',
22
+ tone: ConfirmDialogTone.Danger,
23
+ });
24
+ log(removed ? 'équipe supprimée' : 'suppression annulée');
25
+ };
26
+ const askTwice = () => {
27
+ void ask();
28
+ void confirm({
29
+ title: 'Quitter l’organisation ?',
30
+ body: 'Vous perdez l’accès à ses projets.',
31
+ confirmLabel: 'Quitter',
32
+ }).then((left) => log(left ? 'quitter' : 'rester'));
33
+ };
34
+ return (
35
+ <Row>
36
+ <Button variant="outlined" onClick={() => void ask()}>
37
+ Supprimer l’équipe
38
+ </Button>
39
+ <Button variant="outlined" onClick={askTwice}>
40
+ Deux demandes à la suite
41
+ </Button>
42
+ </Row>
43
+ );
44
+ };
8
45
 
9
46
  export const ConfirmDialogPage = () => {
10
47
  const { log } = useEventLog();
@@ -20,6 +57,11 @@ export const ConfirmDialogPage = () => {
20
57
  Supprimer Impôts 2026
21
58
  </Button>
22
59
  </Example>
60
+ <Example title="useConfirmDialog : confirm() renvoie une promesse">
61
+ <ConfirmDialogProvider>
62
+ <ImperativeConfirm />
63
+ </ConfirmDialogProvider>
64
+ </Example>
23
65
  <LastEvent />
24
66
  <ConfirmDialog
25
67
  open={open}
@@ -7,7 +7,7 @@ export const CopyFieldPage = () => {
7
7
  <Showcase
8
8
  family="Saisie"
9
9
  name="CopyField"
10
- description="Valeur à copier, masquable : bouton de copie, annonce du résultat dans une région vivante, bouton pour afficher une valeur masquée."
10
+ description="Valeur à copier, masquable : bouton de copie, annonce du résultat dans une région vivante, bouton pour afficher une valeur masquée. Avec loadValue, la valeur n'est demandée qu'au premier « Afficher » ou « Copier »."
11
11
  >
12
12
  <Example title="Clé d'API, masquée" maxWidth={640}>
13
13
  <CopyField
@@ -17,6 +17,29 @@ export const CopyFieldPage = () => {
17
17
  copyLabel="Copier la clé"
18
18
  />
19
19
  </Example>
20
+ <Example title="Jeton chargé au premier geste" maxWidth={640}>
21
+ <CopyField
22
+ loadValue={() =>
23
+ new Promise<string>((resolve) => {
24
+ log('jeton demandé');
25
+ setTimeout(() => resolve('abyss_env_tok_9c1e4b7a2f6d8e30'), 800);
26
+ })
27
+ }
28
+ masked
29
+ onReveal={() => log('afficher le jeton')}
30
+ copyLabel="Copier le jeton"
31
+ />
32
+ </Example>
33
+ <Example title="Jeton dont le chargement échoue" maxWidth={640}>
34
+ <CopyField
35
+ loadValue={() => {
36
+ log('jeton refusé');
37
+ return Promise.reject(new Error('forbidden'));
38
+ }}
39
+ masked
40
+ copyLabel="Copier le jeton"
41
+ />
42
+ </Example>
20
43
  <Example title="Identifiant de connexion IA" maxWidth={640}>
21
44
  <CopyField
22
45
  value="https://mcp.abyss-project.fr/u/7f3a9c2e"
@@ -0,0 +1,124 @@
1
+ import { styled } from '@mui/material/styles';
2
+ import {
3
+ EntityImageField,
4
+ EntityImageShape,
5
+ EntityImageVariant,
6
+ } from '../../src/components/inputs/EntityImageField/EntityImageField';
7
+ import {
8
+ OrganizationAvatar,
9
+ UserAvatar,
10
+ } from '../../src/components/meaning/Avatar/Avatar';
11
+ import { LogoAbyssSmall } from '../../src/assets';
12
+ import { px } from '../../src/theme/components/shared';
13
+ import { Example, LastEvent, Row, Showcase, useEventLog } from '../frame';
14
+ import { CATALOG_CLOUD_PICKER } from '../cloud-picker-fixture';
15
+
16
+ const AVATAR_SIZE = 72;
17
+
18
+ const picture = () => LogoAbyssSmall;
19
+
20
+ /** A stand-in banner image: the brand gradient over the width. */
21
+ const BannerPicture = styled('div')(({ theme }) => {
22
+ const { tokens, space } = theme.design;
23
+ return {
24
+ height: px(space[10] * 2),
25
+ backgroundImage: `linear-gradient(120deg, ${tokens['brand.gradientFrom']}, ${tokens['brand.gradientTo']})`,
26
+ };
27
+ });
28
+
29
+ export const EntityImageFieldPage = () => {
30
+ const { log } = useEventLog();
31
+ return (
32
+ <Showcase
33
+ family="Saisie"
34
+ name="EntityImageField"
35
+ description="L'image d'une entité, changée sur place : l'avatar est lui-même le bouton (pastille d'appareil photo), la bannière porte un bouton en surimpression. Sans AbyssCloud, le bouton ouvre le sélecteur de fichiers ; avec, le menu des sources."
36
+ >
37
+ <Example title="Avatars : photo ronde avec AbyssCloud, logo carré sans">
38
+ <Row>
39
+ <EntityImageField
40
+ variant={EntityImageVariant.Avatar}
41
+ picture={
42
+ <UserAvatar
43
+ userId="u1"
44
+ alias="Mehdi"
45
+ hasThumbnail
46
+ size={AVATAR_SIZE}
47
+ getProfilePictureUrl={picture}
48
+ />
49
+ }
50
+ label="Changer la photo"
51
+ accept={['image/*']}
52
+ onFile={(file) => log(`fichier ${file.name}`)}
53
+ uploading={false}
54
+ cloudImport={{
55
+ ...CATALOG_CLOUD_PICKER,
56
+ onImport: (_space, files) =>
57
+ log(`importé ${files.map((file) => file.name).join(', ')}`),
58
+ }}
59
+ />
60
+ <EntityImageField
61
+ variant={EntityImageVariant.Avatar}
62
+ shape={EntityImageShape.Square}
63
+ picture={
64
+ <OrganizationAvatar
65
+ orgId="o1"
66
+ name="Abyss"
67
+ hasThumbnail={false}
68
+ size={AVATAR_SIZE}
69
+ getLogoUrl={picture}
70
+ />
71
+ }
72
+ label="Changer le logo"
73
+ accept={['image/*']}
74
+ onFile={(file) => log(`fichier ${file.name}`)}
75
+ uploading={false}
76
+ />
77
+ </Row>
78
+ </Example>
79
+ <Example title="Envoi en cours">
80
+ <EntityImageField
81
+ variant={EntityImageVariant.Avatar}
82
+ shape={EntityImageShape.Square}
83
+ picture={
84
+ <OrganizationAvatar
85
+ orgId="o2"
86
+ name="Atlas"
87
+ hasThumbnail={false}
88
+ size={AVATAR_SIZE}
89
+ getLogoUrl={picture}
90
+ />
91
+ }
92
+ label="Changer le logo"
93
+ onFile={(file) => log(`fichier ${file.name}`)}
94
+ uploading
95
+ />
96
+ </Example>
97
+ <Example title="Bannière d'organisation" maxWidth={720}>
98
+ <EntityImageField
99
+ variant={EntityImageVariant.Banner}
100
+ picture={<BannerPicture />}
101
+ label="Changer la bannière"
102
+ accept={['image/*']}
103
+ onFile={(file) => log(`bannière ${file.name}`)}
104
+ uploading={false}
105
+ cloudImport={{
106
+ ...CATALOG_CLOUD_PICKER,
107
+ onImport: (_space, files) =>
108
+ log(`bannière ${files.map((file) => file.name).join(', ')}`),
109
+ }}
110
+ />
111
+ </Example>
112
+ <Example title="Bannière, envoi en cours" maxWidth={720}>
113
+ <EntityImageField
114
+ variant={EntityImageVariant.Banner}
115
+ picture={<BannerPicture />}
116
+ label="Changer la bannière"
117
+ onFile={(file) => log(`bannière ${file.name}`)}
118
+ uploading
119
+ />
120
+ </Example>
121
+ <LastEvent />
122
+ </Showcase>
123
+ );
124
+ };
@@ -0,0 +1,118 @@
1
+ import { LineChart } from '../../src/components/data/LineChart/LineChart';
2
+ import type {
3
+ LineChartSeries,
4
+ LineChartX,
5
+ } from '../../src/components/data/LineChart/LineChart';
6
+ import { Surface } from '../../src/components/structure/Surface/Surface';
7
+ import { Example, Showcase } from '../frame';
8
+
9
+ const START = new Date(2026, 9, 13, 0);
10
+
11
+ const atHour = (hour: number) =>
12
+ new Date(START.getTime() + hour * 60 * 60 * 1000);
13
+
14
+ const REQUESTS = [
15
+ 42, 31, 24, 19, 17, 22, 48, 96, 184, 236, 262, 248, 231, 244, 268, 281, 254,
16
+ 212, 168, 131, 104, 86, 67, 51,
17
+ ];
18
+ const ERRORS = [
19
+ 1, 0, 0, 0, 1, 0, 2, 4, 9, 6, 5, 4, 12, 18, 7, 5, 4, 3, 3, 2, 1, 1, 0, 1,
20
+ ];
21
+ const LATENCY = [
22
+ 88, 84, 81, 80, 79, 83, 94, 118, 141, 152, 158, 155, 149, 162, 171, 166, 150,
23
+ 133, 117, 104, 97, 93, 90, 89,
24
+ ];
25
+
26
+ const hourly = (
27
+ id: string,
28
+ label: string,
29
+ values: number[],
30
+ ): LineChartSeries => ({
31
+ id,
32
+ label,
33
+ points: values.map((y, hour) => ({ x: atHour(hour), y })),
34
+ });
35
+
36
+ const SIX_SERIES: LineChartSeries[] = [
37
+ 'Atlas',
38
+ 'Board',
39
+ 'Draw',
40
+ 'Beam',
41
+ 'Extract',
42
+ 'ShortLink',
43
+ ].map((label, index) => ({
44
+ id: label.toLowerCase(),
45
+ label,
46
+ points: Array.from({ length: 12 }, (_, month) => ({
47
+ x: month + 1,
48
+ y: 20 + index * 12 + Math.round(10 * Math.sin((month + index) / 2)),
49
+ })),
50
+ }));
51
+
52
+ const time = new Intl.DateTimeFormat('fr-FR', {
53
+ hour: '2-digit',
54
+ minute: '2-digit',
55
+ });
56
+ const count = new Intl.NumberFormat('fr-FR');
57
+
58
+ const formatHour = (x: LineChartX) =>
59
+ time.format(x instanceof Date ? x : new Date(x));
60
+ const formatMonth = (x: LineChartX) =>
61
+ `M${count.format(x instanceof Date ? x.getMonth() + 1 : x)}`;
62
+
63
+ export const LineChartPage = () => (
64
+ <Showcase
65
+ family="Données"
66
+ name="LineChart"
67
+ description="Courbes Nivo sous abyssNivoTheme : couleurs prises dans l'ordre de la palette data.series, légende au-delà d'une série, figure nommée et tableau des valeurs pour les lecteurs d'écran."
68
+ >
69
+ <Example title="Une série, aire">
70
+ <Surface padding={4}>
71
+ <LineChart
72
+ series={[hourly('requests', 'Requêtes', REQUESTS)]}
73
+ height={240}
74
+ area
75
+ formatX={formatHour}
76
+ formatY={(y) => count.format(y)}
77
+ label="Requêtes par heure, sur 24 heures"
78
+ />
79
+ </Surface>
80
+ </Example>
81
+ <Example title="Deux séries">
82
+ <Surface padding={4}>
83
+ <LineChart
84
+ series={[
85
+ hourly('latency', 'Latence p95', LATENCY),
86
+ hourly('errors', 'Erreurs', ERRORS),
87
+ ]}
88
+ height={240}
89
+ formatX={formatHour}
90
+ formatY={(y) => count.format(y)}
91
+ label="Latence et erreurs par heure"
92
+ />
93
+ </Surface>
94
+ </Example>
95
+ <Example title="Palette des six séries">
96
+ <Surface padding={4}>
97
+ <LineChart
98
+ series={SIX_SERIES}
99
+ height={240}
100
+ formatX={formatMonth}
101
+ formatY={(y) => count.format(y)}
102
+ label="Contenus créés par outil et par mois"
103
+ />
104
+ </Surface>
105
+ </Example>
106
+ <Example title="Aucune donnée">
107
+ <Surface padding={4}>
108
+ <LineChart
109
+ series={[{ id: 'requests', label: 'Requêtes', points: [] }]}
110
+ height={160}
111
+ formatX={formatHour}
112
+ formatY={(y) => count.format(y)}
113
+ label="Requêtes par heure"
114
+ />
115
+ </Surface>
116
+ </Example>
117
+ </Showcase>
118
+ );
@@ -0,0 +1,52 @@
1
+ import { useState } from 'react';
2
+
3
+ import { Pagination } from '../../src/components/data/Pagination/Pagination';
4
+ import { Example, LastEvent, Showcase, useEventLog } from '../frame';
5
+
6
+ /** A paged list the reader can walk through: the page is held here, as a host would. */
7
+ const LivePagination = ({
8
+ initialPage,
9
+ pageCount,
10
+ label,
11
+ }: {
12
+ initialPage: number;
13
+ pageCount: number;
14
+ label: string;
15
+ }) => {
16
+ const { log } = useEventLog();
17
+ const [page, setPage] = useState(initialPage);
18
+ return (
19
+ <Pagination
20
+ page={page}
21
+ pageCount={pageCount}
22
+ label={label}
23
+ onChange={(next) => {
24
+ setPage(next);
25
+ log(`page ${next}`);
26
+ }}
27
+ />
28
+ );
29
+ };
30
+
31
+ export const PaginationPage = () => (
32
+ <Showcase
33
+ family="Données"
34
+ name="Pagination"
35
+ description="Navigation entre les pages d'une liste : précédent, numéros avec ellipses, suivant ; sous 768 px, précédent, « Page x sur y », suivant."
36
+ >
37
+ <Example title="Cinq pages">
38
+ <LivePagination initialPage={2} pageCount={5} label="Pages des membres" />
39
+ </Example>
40
+ <Example title="Quarante pages, au milieu">
41
+ <LivePagination
42
+ initialPage={18}
43
+ pageCount={40}
44
+ label="Pages des permissions"
45
+ />
46
+ </Example>
47
+ <Example title="Quarante pages, près du début">
48
+ <LivePagination initialPage={3} pageCount={40} label="Pages du journal" />
49
+ </Example>
50
+ <LastEvent />
51
+ </Showcase>
52
+ );
@@ -1,6 +1,9 @@
1
1
  import Button from '@mui/material/Button';
2
2
  import ArrowForwardRounded from '@mui/icons-material/ArrowForwardRounded';
3
- import { Section } from '../../src/components/structure/Section/Section';
3
+ import {
4
+ Section,
5
+ SectionTone,
6
+ } from '../../src/components/structure/Section/Section';
4
7
  import { KeyValueList } from '../../src/components/data/KeyValueList/KeyValueList';
5
8
  import { Columns, Example, LastEvent, Showcase, useEventLog } from '../frame';
6
9
  import { ACCOUNTS, LONG_NAV_LABEL, formatEuro } from '../fixtures';
@@ -12,7 +15,7 @@ export const SectionPage = () => {
12
15
  <Showcase
13
16
  family="Structure"
14
17
  name="Section"
15
- description="Bloc titré d'une page : titre de 16 px, compteur en mono, actions alignées à droite."
18
+ description="Bloc titré d'une page : titre de 16 px, compteur en mono, actions alignées à droite. En ton danger, titre et filet en rouge."
16
19
  >
17
20
  <Example title="Avec compteur et action">
18
21
  <Section
@@ -67,6 +70,17 @@ export const SectionPage = () => {
67
70
  />
68
71
  </Section>
69
72
  </Example>
73
+ <Example title="Zone de danger">
74
+ <Section title="Zone de danger" tone={SectionTone.Danger}>
75
+ <Button
76
+ variant="outlined"
77
+ color="error"
78
+ onClick={() => log('supprimer l’organisation')}
79
+ >
80
+ Supprimer l’organisation
81
+ </Button>
82
+ </Section>
83
+ </Example>
70
84
  <Example title="Titre de 200 caractères sans espace">
71
85
  <Section title={LONG_NAV_LABEL} count={12}>
72
86
  <LastEvent />