@abyss-project/commons-front-core 9.4.1 → 9.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/catalog/captures-plan.ts +5 -1
- package/catalog/catalog-entries.ts +8 -1
- package/catalog/pages/AnchoredPanel.tsx +67 -0
- package/catalog/pages/ExpressionSearch.tsx +65 -0
- package/catalog/pages/FacetPanel.tsx +81 -0
- package/catalog/pages/PageState.tsx +17 -0
- package/catalog/pages/PreviewDrawer.tsx +120 -0
- package/catalog/pages/ProductTour.tsx +9 -1
- package/catalog/pages/Section.tsx +10 -0
- package/catalog/pages/StatTile.tsx +13 -0
- package/catalog/pages/ValueMenu.tsx +40 -0
- package/catalog/pages/index.ts +10 -0
- package/catalog/specs/captures.spec.ts +7 -0
- package/catalog/specs/expression-search-scroll.spec.ts +33 -0
- package/catalog/specs/preview-drawer-trail.spec.ts +38 -0
- package/catalog/specs/write-capture-index.ts +1 -0
- package/dist/{AbyssCookieConsentProvider-L42RPb5f.js → AbyssCookieConsentProvider-jMv5bvVK.js} +412 -312
- package/dist/{CookieConsentSettings.component-CkhtQuGU.js → CookieConsentSettings.component-B6i65LtJ.js} +1 -1
- package/dist/components/data/FacetPanel/FacetPanel.d.ts +30 -0
- package/dist/components/data/FacetPanel/FacetPanel.spec.d.ts +1 -0
- package/dist/components/data/FacetPanel/facet-terms.d.ts +16 -0
- package/dist/components/data/StatTile/StatTile.d.ts +4 -1
- package/dist/components/data/index.d.ts +1 -0
- package/dist/components/inputs/ExpressionSearch/ExpressionSearch.d.ts +30 -0
- package/dist/components/inputs/ExpressionSearch/ExpressionSearch.spec.d.ts +1 -0
- package/dist/components/inputs/ExpressionSearch/suggestion-context.d.ts +20 -0
- package/dist/components/inputs/index.d.ts +1 -0
- package/dist/components/layers/AnchoredPanel/AnchoredPanel.d.ts +25 -0
- package/dist/components/layers/AnchoredPanel/AnchoredPanel.spec.d.ts +1 -0
- package/dist/components/layers/PreviewDrawer/PreviewDrawer.d.ts +18 -0
- package/dist/components/layers/PreviewDrawer/PreviewDrawer.spec.d.ts +1 -0
- package/dist/components/layers/PreviewDrawer/usePreviewStack.d.ts +32 -0
- package/dist/components/layers/ProductTour/ProductTour.d.ts +3 -3
- package/dist/components/layers/ProductTour/product-tour-geometry.d.ts +9 -3
- package/dist/components/layers/index.d.ts +4 -0
- package/dist/components/layers/layers-kbd.spec.d.ts +1 -0
- package/dist/components/meaning/ValueMenu/ValueMenu.d.ts +25 -0
- package/dist/components/meaning/ValueMenu/ValueMenu.spec.d.ts +1 -0
- package/dist/components/meaning/index.d.ts +1 -0
- package/dist/components/states/PageState/PageState.d.ts +9 -2
- package/dist/components/structure/Section/Section.d.ts +3 -1
- package/dist/cookie-consent.js +2 -2
- package/dist/{design-theme-C3DcD-kA.js → design-theme-CykIb-Gz.js} +1 -1
- package/dist/hooks/use-rich-intl.hook.d.ts +102 -2
- package/dist/{index-sI8PQFKF.js → index-7S_OHK4f.js} +3 -3
- package/dist/{index-BowQxHAl.js → index-B4xjMC7O.js} +2 -2
- package/dist/{index-DrSNFgIl.js → index-B9kzX70T.js} +2 -2
- package/dist/{index-BhRgGLuD.js → index-BEQgBOwt.js} +2 -2
- package/dist/{index-CvT5zdz7.js → index-BOm9JgH7.js} +2 -2
- package/dist/{index-5hIRSHTc.js → index-BPGHypMd.js} +2 -2
- package/dist/{index-BLN9Vvdr.js → index-BS7CgEyj.js} +2 -2
- package/dist/{index-C0jOpBPq.js → index-Bd0oB0x3.js} +2 -2
- package/dist/{index-Dyp_SF7H.js → index-Bgrp5kC3.js} +2 -2
- package/dist/{index-BAO0d8HL.js → index-BveBtqZP.js} +4 -4
- package/dist/{index-gVg-FLmC.js → index-CKD84DUx.js} +1 -1
- package/dist/{index-Bi1ITcfD.js → index-CLxMgDwa.js} +2 -2
- package/dist/{index-DZ5svQvp.js → index-Cqn_LfL2.js} +2 -2
- package/dist/{index-Y46rQdc3.js → index-Cy8fNdNL.js} +2 -2
- package/dist/{index-Cny_hr56.js → index-DXYvHlFM.js} +2 -2
- package/dist/{index-yTDpH9la.js → index-DebUlRoE.js} +2 -2
- package/dist/{index-DyhinvD5.js → index-IhTCIvj6.js} +3 -3
- package/dist/{index-CuOhAUX-.js → index-Lub7dhvT.js} +28 -28
- package/dist/{index-B-D0LQjw.js → index-_nt9c97v.js} +3 -3
- package/dist/{index-DBKoCefj.js → index-f_oWcs8w.js} +4 -4
- package/dist/{index-CMTzmSRJ.js → index-mNiLojvx.js} +4 -4
- package/dist/{index-DXLyOn3D.js → index-rfJiqJxI.js} +3 -3
- package/dist/index.js +5523 -4455
- package/dist/legacy.js +4 -4
- package/dist/theme/tokens/choice-swatches.d.ts +8 -0
- package/dist/translations/en.d.ts +51 -1
- package/dist/translations/fr.d.ts +51 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -90,6 +90,24 @@ export const buildRumOptions = () =>
|
|
|
90
90
|
|
|
91
91
|
## Changements visibles
|
|
92
92
|
|
|
93
|
+
### 9.5.0
|
|
94
|
+
|
|
95
|
+
Éditeurs des outils du front principal sur le design system (carte #286, zone 4b). Pièces nouvelles, chacune documentée
|
|
96
|
+
dans le `README.md` de son dossier et montrée dans le catalogue.
|
|
97
|
+
|
|
98
|
+
- **`AnchoredPanel`** (`layers`) : petit formulaire ou sélecteur posé contre son déclencheur (`anchorEl`, `open`,
|
|
99
|
+
`onClose`, `title`, `placement`, `width`). À partir de 768 px, un popover au style de `Menu` (surface `bg.surface`,
|
|
100
|
+
filet `border.subtle`, ombre `elevations.overlay`) avec titre et bouton « Fermer » ; en dessous, une `BottomSheet`.
|
|
101
|
+
Focus piégé, `Escape` et clic hors du panneau ferment, le focus revient au déclencheur. Il porte des champs, pas
|
|
102
|
+
des actions (`Menu`).
|
|
103
|
+
- **`Kbd`** (`layers`) : l'indication de touche de `Menu` et de `CommandPalette` est exportée de la racine, rendu
|
|
104
|
+
inchangé.
|
|
105
|
+
- **Couleurs de création d'un canevas** (`theme`) : `CANVAS_NEUTRAL_INK`, `CANVAS_COMMENT_FILL`, `CANVAS_COMMENT_INK` et
|
|
106
|
+
`CANVAS_SHAPE_STROKE`, écrites en dur à côté de `CHOICE_SWATCHES`. Ce qu'un canevas donne à une forme ou à un texte
|
|
107
|
+
qu'un utilisateur crée : la couleur est enregistrée avec l'objet, identique pour tous les lecteurs et lisible sur
|
|
108
|
+
le fond `bg.canvas` clair comme sur le sombre (3:1 au moins ; 4,5:1 pour l'encre du commentaire sur son fond).
|
|
109
|
+
- **Traductions** : clé `ds.layers.anchored-panel.close` (FR et EN).
|
|
110
|
+
|
|
93
111
|
### 9.4.0
|
|
94
112
|
|
|
95
113
|
Tickets du programme Abyss Founders (carte #285, livraison D). Détail dans [`src/legacy/components/Support/README.md`](src/legacy/components/Support/README.md).
|
package/catalog/captures-plan.ts
CHANGED
|
@@ -34,7 +34,8 @@ export type CaptureState =
|
|
|
34
34
|
| 'sheet-open'
|
|
35
35
|
| 'list-open'
|
|
36
36
|
| 'calendar-open'
|
|
37
|
-
| 'panel-open'
|
|
37
|
+
| 'panel-open'
|
|
38
|
+
| 'menu-open';
|
|
38
39
|
|
|
39
40
|
/** File name suffix of each state. */
|
|
40
41
|
const STATE_SUFFIX: Record<CaptureState, string> = {
|
|
@@ -43,15 +44,18 @@ const STATE_SUFFIX: Record<CaptureState, string> = {
|
|
|
43
44
|
'list-open': '-liste',
|
|
44
45
|
'calendar-open': '-calendrier',
|
|
45
46
|
'panel-open': '-ouvert',
|
|
47
|
+
'menu-open': '-ouvert',
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
/** The states shown beyond arrival: what a closed control hides (options, calendar, sheet). */
|
|
49
51
|
const EXTRA_STATES: Record<string, CaptureState> = {
|
|
50
52
|
SceneMobile: 'sheet-open',
|
|
51
53
|
RichSelect: 'list-open',
|
|
54
|
+
ExpressionSearch: 'list-open',
|
|
52
55
|
DateField: 'calendar-open',
|
|
53
56
|
NotificationsMenu: 'panel-open',
|
|
54
57
|
AccountMenu: 'panel-open',
|
|
58
|
+
ValueMenu: 'menu-open',
|
|
55
59
|
};
|
|
56
60
|
|
|
57
61
|
export interface Capture {
|
|
@@ -63,7 +63,7 @@ export const CATALOG_ENTRIES: CatalogEntry[] = [
|
|
|
63
63
|
entry('BottomSheet', 'mobile', 'bottom-sheet', RESPONSIVE),
|
|
64
64
|
entry('ActionBar', 'mobile', 'action-bar', RESPONSIVE),
|
|
65
65
|
entry('SwipeDeck', 'mobile', 'swipe-deck', RESPONSIVE),
|
|
66
|
-
entry('PageState', 'states', 'page-state'),
|
|
66
|
+
entry('PageState', 'states', 'page-state', { mobileCapture: true }),
|
|
67
67
|
entry('Callout', 'states', 'callout', RESPONSIVE),
|
|
68
68
|
entry('DataTable', 'data', 'data-table', RESPONSIVE),
|
|
69
69
|
entry('KeyValueList', 'data', 'key-value-list'),
|
|
@@ -74,12 +74,14 @@ export const CATALOG_ENTRIES: CatalogEntry[] = [
|
|
|
74
74
|
entry('BarChart', 'data', 'bar-chart'),
|
|
75
75
|
entry('ChartTheme', 'data', 'chart-theme'),
|
|
76
76
|
entry('UsageMeter', 'data', 'usage-meter'),
|
|
77
|
+
entry('FacetPanel', 'data', 'facet-panel', { mobileCapture: true }),
|
|
77
78
|
entry('StatusBadge', 'meaning', 'status-badge'),
|
|
78
79
|
entry('MethodBadge', 'meaning', 'method-badge'),
|
|
79
80
|
entry('SeverityBadge', 'meaning', 'severity-badge'),
|
|
80
81
|
entry('ScanBadge', 'meaning', 'scan-badge'),
|
|
81
82
|
entry('RoleBadge', 'meaning', 'role-badge'),
|
|
82
83
|
entry('ToolIcon', 'meaning', 'tool-icon'),
|
|
84
|
+
entry('ValueMenu', 'meaning', 'value-menu', { mobileCapture: true }),
|
|
83
85
|
entry('Avatar', 'meaning', 'avatar'),
|
|
84
86
|
entry('AvatarGroup', 'meaning', 'avatar-group'),
|
|
85
87
|
entry('TeamAvatar', 'meaning', 'team-avatar'),
|
|
@@ -93,7 +95,9 @@ export const CATALOG_ENTRIES: CatalogEntry[] = [
|
|
|
93
95
|
entry('ConfirmDialog', 'layers', 'confirm-dialog'),
|
|
94
96
|
entry('ConfirmDialogWarning', 'layers', 'confirm-dialog-warning'),
|
|
95
97
|
entry('Drawer', 'layers', 'drawer'),
|
|
98
|
+
entry('PreviewDrawer', 'layers', 'preview-drawer', { mobileCapture: true }),
|
|
96
99
|
entry('Menu', 'layers', 'menu'),
|
|
100
|
+
entry('AnchoredPanel', 'layers', 'anchored-panel', { mobileCapture: true }),
|
|
97
101
|
entry('Toast', 'layers', 'toast'),
|
|
98
102
|
entry('CommandPalette', 'layers', 'command-palette'),
|
|
99
103
|
entry('ProductTour', 'layers', 'product-tour', { mobileCapture: true }),
|
|
@@ -112,6 +116,9 @@ export const CATALOG_ENTRIES: CatalogEntry[] = [
|
|
|
112
116
|
entry('ContextSelector', 'inputs', 'context-selector', RESPONSIVE),
|
|
113
117
|
entry('DropZone', 'inputs', 'drop-zone', RESPONSIVE),
|
|
114
118
|
entry('ImportButton', 'inputs', 'import-button'),
|
|
119
|
+
entry('ExpressionSearch', 'inputs', 'expression-search', {
|
|
120
|
+
mobileCapture: true,
|
|
121
|
+
}),
|
|
115
122
|
];
|
|
116
123
|
|
|
117
124
|
export const CATALOG_THEMES = [ThemeMode.Dark, ThemeMode.Light] as const;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import Button from '@mui/material/Button';
|
|
3
|
+
import OutlinedInput from '@mui/material/OutlinedInput';
|
|
4
|
+
import LabelOutlined from '@mui/icons-material/LabelOutlined';
|
|
5
|
+
import { AnchoredPanel } from '../../src/components/layers/AnchoredPanel/AnchoredPanel';
|
|
6
|
+
import { ColorField } from '../../src/components/inputs/ColorField/ColorField';
|
|
7
|
+
import { FormField } from '../../src/components/inputs/FormField/FormField';
|
|
8
|
+
import { CHOICE_SWATCHES } from '../../src/theme/tokens/choice-swatches';
|
|
9
|
+
import { Example, LastEvent, Showcase, useEventLog } from '../frame';
|
|
10
|
+
|
|
11
|
+
export const AnchoredPanelPage = () => {
|
|
12
|
+
const { log } = useEventLog();
|
|
13
|
+
const trigger = useRef<HTMLButtonElement>(null);
|
|
14
|
+
const [anchor, setAnchor] = useState<HTMLElement | null>(null);
|
|
15
|
+
const [name, setName] = useState('Urgent');
|
|
16
|
+
const [color, setColor] = useState(CHOICE_SWATCHES[4]);
|
|
17
|
+
|
|
18
|
+
// Opened on arrival, so the page shows the panel without a click.
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
setAnchor(trigger.current);
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Showcase
|
|
25
|
+
family="Couches"
|
|
26
|
+
name="AnchoredPanel"
|
|
27
|
+
description="Petit formulaire posé contre son déclencheur : un popover à partir de 768 px, une feuille du bas en dessous. Il porte des champs, pas des actions."
|
|
28
|
+
>
|
|
29
|
+
<Example title="Étiquette d'une carte">
|
|
30
|
+
<Button
|
|
31
|
+
ref={trigger}
|
|
32
|
+
variant="outlined"
|
|
33
|
+
startIcon={<LabelOutlined />}
|
|
34
|
+
onClick={(event) => setAnchor(event.currentTarget)}
|
|
35
|
+
>
|
|
36
|
+
Étiquettes
|
|
37
|
+
</Button>
|
|
38
|
+
</Example>
|
|
39
|
+
<LastEvent />
|
|
40
|
+
<AnchoredPanel
|
|
41
|
+
anchorEl={anchor}
|
|
42
|
+
open={anchor !== null}
|
|
43
|
+
onClose={() => {
|
|
44
|
+
setAnchor(null);
|
|
45
|
+
log('panneau fermé');
|
|
46
|
+
}}
|
|
47
|
+
title="Étiquettes de la carte"
|
|
48
|
+
>
|
|
49
|
+
<FormField label="Nom">
|
|
50
|
+
<OutlinedInput
|
|
51
|
+
size="small"
|
|
52
|
+
value={name}
|
|
53
|
+
onChange={(event) => setName(event.target.value)}
|
|
54
|
+
/>
|
|
55
|
+
</FormField>
|
|
56
|
+
<ColorField
|
|
57
|
+
label="Couleur"
|
|
58
|
+
value={color}
|
|
59
|
+
onChange={(hex) => {
|
|
60
|
+
setColor(hex);
|
|
61
|
+
log(`couleur ${hex}`);
|
|
62
|
+
}}
|
|
63
|
+
/>
|
|
64
|
+
</AnchoredPanel>
|
|
65
|
+
</Showcase>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { ExpressionSearch } from '../../src/components/inputs/ExpressionSearch/ExpressionSearch';
|
|
3
|
+
import { Example, LastEvent, Showcase, useEventLog } from '../frame';
|
|
4
|
+
|
|
5
|
+
const KEYS = [
|
|
6
|
+
{ name: 'level', count: 18420 },
|
|
7
|
+
{ name: 'env', count: 18420 },
|
|
8
|
+
{ name: 'release', count: 17983 },
|
|
9
|
+
{ name: 'status', count: 9214 },
|
|
10
|
+
{ name: '@user.plan', count: 4120 },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const VALUES: Record<string, { value: string; count: number }[]> = {
|
|
14
|
+
level: [
|
|
15
|
+
{ value: 'ERROR', count: 1204 },
|
|
16
|
+
{ value: 'WARN', count: 3310 },
|
|
17
|
+
{ value: 'INFO', count: 13906 },
|
|
18
|
+
],
|
|
19
|
+
env: [
|
|
20
|
+
{ value: 'prod', count: 15012 },
|
|
21
|
+
{ value: 'staging', count: 3408 },
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const fetchKeys = async (prefix: string) =>
|
|
26
|
+
KEYS.filter((key) => key.name.startsWith(prefix));
|
|
27
|
+
|
|
28
|
+
const fetchValues = async (field: string, prefix: string) =>
|
|
29
|
+
(VALUES[field] ?? []).filter((item) => item.value.startsWith(prefix));
|
|
30
|
+
|
|
31
|
+
export const ExpressionSearchPage = () => {
|
|
32
|
+
const { log } = useEventLog();
|
|
33
|
+
const [typed, setTyped] = useState('');
|
|
34
|
+
const [broken, setBroken] = useState('level:ERROR (env:prod OR env:staging');
|
|
35
|
+
return (
|
|
36
|
+
<Showcase
|
|
37
|
+
family="Saisie"
|
|
38
|
+
name="ExpressionSearch"
|
|
39
|
+
description="Champ de filtre en expression (level:ERROR -env:staging) : suggestions de clés puis de valeurs, erreur de syntaxe soulignée à sa position sans perdre la saisie."
|
|
40
|
+
>
|
|
41
|
+
<Example
|
|
42
|
+
title="Saisie avec suggestions de clés puis de valeurs"
|
|
43
|
+
maxWidth={720}
|
|
44
|
+
>
|
|
45
|
+
<ExpressionSearch
|
|
46
|
+
value={typed}
|
|
47
|
+
onChange={setTyped}
|
|
48
|
+
onSubmit={(value) => log(`filtrer : ${value || 'aucun filtre'}`)}
|
|
49
|
+
fetchKeys={fetchKeys}
|
|
50
|
+
fetchValues={fetchValues}
|
|
51
|
+
/>
|
|
52
|
+
</Example>
|
|
53
|
+
<Example title="Expression incomplète" maxWidth={720}>
|
|
54
|
+
<ExpressionSearch
|
|
55
|
+
value={broken}
|
|
56
|
+
onChange={setBroken}
|
|
57
|
+
onSubmit={(value) => log(`filtrer : ${value}`)}
|
|
58
|
+
fetchKeys={fetchKeys}
|
|
59
|
+
fetchValues={fetchValues}
|
|
60
|
+
/>
|
|
61
|
+
</Example>
|
|
62
|
+
<LastEvent />
|
|
63
|
+
</Showcase>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { FacetPanel } from '../../src/components/data/FacetPanel/FacetPanel';
|
|
3
|
+
import type { Facet } from '../../src/components/data/FacetPanel/FacetPanel';
|
|
4
|
+
import { px } from '../../src/theme/components/shared';
|
|
5
|
+
import { fontWeights } from '../../src/theme/tokens/primitives';
|
|
6
|
+
import { styled } from '@mui/material/styles';
|
|
7
|
+
import { Example, Showcase } from '../frame';
|
|
8
|
+
|
|
9
|
+
const FACETS: Facet[] = [
|
|
10
|
+
{
|
|
11
|
+
field: 'level',
|
|
12
|
+
label: 'Niveau',
|
|
13
|
+
values: [
|
|
14
|
+
{ value: 'ERROR', count: 1204 },
|
|
15
|
+
{ value: 'WARN', count: 3310 },
|
|
16
|
+
{ value: 'INFO', count: 13906 },
|
|
17
|
+
],
|
|
18
|
+
estimated: false,
|
|
19
|
+
loading: false,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
field: 'env',
|
|
23
|
+
label: 'Environnement',
|
|
24
|
+
values: [
|
|
25
|
+
{ value: 'prod', count: 15012 },
|
|
26
|
+
{ value: 'staging', count: 3408 },
|
|
27
|
+
],
|
|
28
|
+
estimated: true,
|
|
29
|
+
loading: false,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
field: '@user.plan',
|
|
33
|
+
label: 'Plan (attribut)',
|
|
34
|
+
values: [],
|
|
35
|
+
estimated: false,
|
|
36
|
+
loading: true,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
field: 'release',
|
|
40
|
+
label: 'Version',
|
|
41
|
+
values: [],
|
|
42
|
+
estimated: false,
|
|
43
|
+
loading: false,
|
|
44
|
+
error: 'Lecture interrompue après 5 s : réduisez la période.',
|
|
45
|
+
onRetry: () => undefined,
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
const Expression = styled('code')(({ theme }) => ({
|
|
50
|
+
display: 'block',
|
|
51
|
+
fontFamily: theme.design.fonts.mono,
|
|
52
|
+
fontSize: px(theme.design.typeScale.small),
|
|
53
|
+
fontWeight: fontWeights.medium,
|
|
54
|
+
color: theme.design.tokens['text.secondary'],
|
|
55
|
+
overflowWrap: 'anywhere',
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
export const FacetPanelPage = () => {
|
|
59
|
+
const [expression, setExpression] = useState('level:ERROR -env:staging');
|
|
60
|
+
return (
|
|
61
|
+
<Showcase
|
|
62
|
+
family="Données"
|
|
63
|
+
name="FacetPanel"
|
|
64
|
+
description="Valeurs les plus fréquentes de chaque champ : une coche ajoute la condition à l'expression, « Exclure » ajoute son contraire. L'expression reste la seule source de vérité."
|
|
65
|
+
>
|
|
66
|
+
<Example
|
|
67
|
+
title="Quatre facettes : prête, estimée, en chargement, en échec"
|
|
68
|
+
maxWidth={360}
|
|
69
|
+
>
|
|
70
|
+
<FacetPanel
|
|
71
|
+
facets={FACETS}
|
|
72
|
+
expression={expression}
|
|
73
|
+
onExpressionChange={setExpression}
|
|
74
|
+
/>
|
|
75
|
+
</Example>
|
|
76
|
+
<Example title="Expression résultante" maxWidth={720}>
|
|
77
|
+
<Expression>{expression === '' ? '—' : expression}</Expression>
|
|
78
|
+
</Example>
|
|
79
|
+
</Showcase>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
1
2
|
import Button from '@mui/material/Button';
|
|
2
3
|
import Skeleton from '@mui/material/Skeleton';
|
|
3
4
|
import CreateNewFolderOutlined from '@mui/icons-material/CreateNewFolderOutlined';
|
|
@@ -10,6 +11,9 @@ import { Columns, Example, LastEvent, Showcase, useEventLog } from '../frame';
|
|
|
10
11
|
import { ACCOUNTS } from '../fixtures';
|
|
11
12
|
import { AccountCard } from '../parts';
|
|
12
13
|
|
|
14
|
+
// Seconds the rate-limit example counts down from, long enough to be read on a capture.
|
|
15
|
+
const RATE_LIMIT_SECONDS = 42;
|
|
16
|
+
|
|
13
17
|
// Height of an account card: 2 × 64 + 32.
|
|
14
18
|
const CARD_HEIGHT = space[10] * 2 + space[7];
|
|
15
19
|
|
|
@@ -23,6 +27,9 @@ const AccountSkeleton = () => (
|
|
|
23
27
|
|
|
24
28
|
export const PageStatePage = () => {
|
|
25
29
|
const { log } = useEventLog();
|
|
30
|
+
const [retryAt] = useState(
|
|
31
|
+
() => new Date(Date.now() + RATE_LIMIT_SECONDS * 1000),
|
|
32
|
+
);
|
|
26
33
|
const ready = (
|
|
27
34
|
<Columns>
|
|
28
35
|
{ACCOUNTS.map((account) => (
|
|
@@ -82,6 +89,16 @@ export const PageStatePage = () => {
|
|
|
82
89
|
{ready}
|
|
83
90
|
</PageState>
|
|
84
91
|
</Example>
|
|
92
|
+
<Example title="Trop de requêtes, avec compte à rebours">
|
|
93
|
+
<PageState
|
|
94
|
+
status={PageStatus.RateLimited}
|
|
95
|
+
skeleton={<AccountSkeleton />}
|
|
96
|
+
retryAt={retryAt}
|
|
97
|
+
error={{ onRetry: () => log('réessayer après la limite') }}
|
|
98
|
+
>
|
|
99
|
+
{ready}
|
|
100
|
+
</PageState>
|
|
101
|
+
</Example>
|
|
85
102
|
<Example title="Introuvable">
|
|
86
103
|
<PageState status={PageStatus.NotFound} skeleton={<AccountSkeleton />}>
|
|
87
104
|
{ready}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import Button from '@mui/material/Button';
|
|
3
|
+
import { PreviewDrawer } from '../../src/components/layers/PreviewDrawer/PreviewDrawer';
|
|
4
|
+
import { usePreviewStack } from '../../src/components/layers/PreviewDrawer/usePreviewStack';
|
|
5
|
+
import type { IPreviewRef } from '../../src/components/layers/PreviewDrawer/usePreviewStack';
|
|
6
|
+
import { KeyValueList } from '../../src/components/data/KeyValueList/KeyValueList';
|
|
7
|
+
import { Example, Row, Showcase } from '../frame';
|
|
8
|
+
|
|
9
|
+
const REQUEST: IPreviewRef = { type: 'request', id: '9f3c2a71' };
|
|
10
|
+
const SESSION: IPreviewRef = { type: 'session', id: 'b41d07ce' };
|
|
11
|
+
const LOG: IPreviewRef = { type: 'log', id: 'e7a90b14' };
|
|
12
|
+
const RELEASE: IPreviewRef = { type: 'release', id: '2.14.0' };
|
|
13
|
+
|
|
14
|
+
const TITLES: Record<string, string> = {
|
|
15
|
+
request: 'GET /api/invoices',
|
|
16
|
+
session: 'Session b41d07ce',
|
|
17
|
+
log: 'Log e7a90b14',
|
|
18
|
+
release: 'Version 2.14.0',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const titleOf = (ref: IPreviewRef) =>
|
|
22
|
+
TITLES[ref.type] ?? `${ref.type} ${ref.id}`;
|
|
23
|
+
|
|
24
|
+
const Content = ({ reference }: { reference: IPreviewRef }) => {
|
|
25
|
+
const { open } = usePreviewStack();
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<KeyValueList
|
|
29
|
+
items={[
|
|
30
|
+
{ id: 'id', label: 'Identifiant', value: reference.id, mono: true },
|
|
31
|
+
{
|
|
32
|
+
id: 'status',
|
|
33
|
+
label: 'Statut',
|
|
34
|
+
value: reference.type === 'request' ? '200' : 'terminée',
|
|
35
|
+
mono: true,
|
|
36
|
+
},
|
|
37
|
+
{ id: 'duration', label: 'Durée', value: '184 ms', mono: true },
|
|
38
|
+
{ id: 'env', label: 'Environnement', value: 'prod' },
|
|
39
|
+
]}
|
|
40
|
+
/>
|
|
41
|
+
{reference.type === 'request' && (
|
|
42
|
+
<Button variant="outlined" onClick={() => open(SESSION)}>
|
|
43
|
+
Ouvrir la session liée
|
|
44
|
+
</Button>
|
|
45
|
+
)}
|
|
46
|
+
{reference.type === 'session' && (
|
|
47
|
+
<Button variant="outlined" onClick={() => open(LOG)}>
|
|
48
|
+
Ouvrir le log lié
|
|
49
|
+
</Button>
|
|
50
|
+
)}
|
|
51
|
+
{reference.type === 'log' && (
|
|
52
|
+
<Button variant="outlined" onClick={() => open(RELEASE)}>
|
|
53
|
+
Ouvrir la version liée
|
|
54
|
+
</Button>
|
|
55
|
+
)}
|
|
56
|
+
</>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** Steps of the opening sequence: nothing yet, then each preview opened on the previous one. */
|
|
61
|
+
enum Arrival {
|
|
62
|
+
Idle,
|
|
63
|
+
RequestOpened,
|
|
64
|
+
SessionOpened,
|
|
65
|
+
LogOpened,
|
|
66
|
+
Done,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const PreviewDrawerPage = () => {
|
|
70
|
+
const { stack, open } = usePreviewStack();
|
|
71
|
+
const arrival = useRef(Arrival.Idle);
|
|
72
|
+
|
|
73
|
+
// Four previews stacked on arrival, so the page shows the trail without a click. Each one is
|
|
74
|
+
// opened once the stack of the previous render is in the URL.
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (arrival.current === Arrival.Idle && stack.length === 0) {
|
|
77
|
+
arrival.current = Arrival.RequestOpened;
|
|
78
|
+
open(REQUEST);
|
|
79
|
+
} else if (
|
|
80
|
+
arrival.current === Arrival.RequestOpened &&
|
|
81
|
+
stack.length === 1
|
|
82
|
+
) {
|
|
83
|
+
arrival.current = Arrival.SessionOpened;
|
|
84
|
+
open(SESSION);
|
|
85
|
+
} else if (
|
|
86
|
+
arrival.current === Arrival.SessionOpened &&
|
|
87
|
+
stack.length === 2
|
|
88
|
+
) {
|
|
89
|
+
arrival.current = Arrival.LogOpened;
|
|
90
|
+
open(LOG);
|
|
91
|
+
} else if (arrival.current === Arrival.LogOpened && stack.length === 3) {
|
|
92
|
+
arrival.current = Arrival.Done;
|
|
93
|
+
open(RELEASE);
|
|
94
|
+
}
|
|
95
|
+
}, [stack, open]);
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<Showcase
|
|
99
|
+
family="Couches"
|
|
100
|
+
name="PreviewDrawer"
|
|
101
|
+
description="Aperçu empilable porté par l'URL (?preview=type:id,type:id, cinq au plus) : fil de toute la pile (chaque niveau ramène à lui, replié en « … » et les deux derniers sur téléphone), Échap ferme le dernier, lien vers la pleine page."
|
|
102
|
+
>
|
|
103
|
+
<Example title="Ouvrir un aperçu">
|
|
104
|
+
<Row>
|
|
105
|
+
<Button variant="outlined" onClick={() => open(REQUEST)}>
|
|
106
|
+
Requête GET /api/invoices
|
|
107
|
+
</Button>
|
|
108
|
+
<Button variant="outlined" onClick={() => open(SESSION)}>
|
|
109
|
+
Session b41d07ce
|
|
110
|
+
</Button>
|
|
111
|
+
</Row>
|
|
112
|
+
</Example>
|
|
113
|
+
<PreviewDrawer
|
|
114
|
+
titleOf={titleOf}
|
|
115
|
+
fullPageHref={(ref) => `/${ref.type}/${ref.id}`}
|
|
116
|
+
render={(ref) => <Content reference={ref} />}
|
|
117
|
+
/>
|
|
118
|
+
</Showcase>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
@@ -35,7 +35,7 @@ export const ProductTourPage = () => {
|
|
|
35
35
|
<Showcase
|
|
36
36
|
family="Couches"
|
|
37
37
|
name="ProductTour"
|
|
38
|
-
description="Visite guidée d'un service : une bulle ancrée sur un élément data-tour, le reste de la page voilé autour d'une découpe. Échap
|
|
38
|
+
description="Visite guidée d'un service : une bulle ancrée sur un élément data-tour, le reste de la page voilé autour d'une découpe. « Étape n sur N » et une croix en en-tête, « Précédent » dès la deuxième étape ; Échap et la croix passent la visite, les flèches changent d'étape ; sous 768 px, une feuille du bas. Une cible cachée est ignorée."
|
|
39
39
|
>
|
|
40
40
|
<Example title="Première visite de Memories">
|
|
41
41
|
<Row>
|
|
@@ -46,6 +46,14 @@ export const ProductTourPage = () => {
|
|
|
46
46
|
>
|
|
47
47
|
Importer
|
|
48
48
|
</Button>
|
|
49
|
+
{/* A hidden twin of the first target: the tour points at the rendered one. */}
|
|
50
|
+
<Button
|
|
51
|
+
data-tour="catalog-create-event"
|
|
52
|
+
variant="contained"
|
|
53
|
+
sx={{ display: 'none' }}
|
|
54
|
+
>
|
|
55
|
+
Créer un événement
|
|
56
|
+
</Button>
|
|
49
57
|
<Button
|
|
50
58
|
data-tour="catalog-create-event"
|
|
51
59
|
variant="contained"
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
Section,
|
|
5
5
|
SectionTone,
|
|
6
6
|
} from '../../src/components/structure/Section/Section';
|
|
7
|
+
import { ToolIcon } from '../../src/components/meaning/ToolIcon/ToolIcon';
|
|
8
|
+
import { ToolKind } from '../../src/theme/tokens/domain';
|
|
7
9
|
import { KeyValueList } from '../../src/components/data/KeyValueList/KeyValueList';
|
|
8
10
|
import { Columns, Example, LastEvent, Showcase, useEventLog } from '../frame';
|
|
9
11
|
import { ACCOUNTS, LONG_NAV_LABEL, formatEuro } from '../fixtures';
|
|
@@ -38,6 +40,14 @@ export const SectionPage = () => {
|
|
|
38
40
|
</Columns>
|
|
39
41
|
</Section>
|
|
40
42
|
</Example>
|
|
43
|
+
<Example title="Avec le logo d'un service">
|
|
44
|
+
<Section
|
|
45
|
+
title="Mes clouds"
|
|
46
|
+
icon={<ToolIcon tool={ToolKind.Cloud} size={20} />}
|
|
47
|
+
>
|
|
48
|
+
<LastEvent />
|
|
49
|
+
</Section>
|
|
50
|
+
</Example>
|
|
41
51
|
<Example title="Sans action, contenu en liste clé-valeur">
|
|
42
52
|
<Section title="Disponible à dépenser">
|
|
43
53
|
<KeyValueList
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { StatTile } from '../../src/components/data/StatTile/StatTile';
|
|
2
|
+
import { ToolIcon } from '../../src/components/meaning/ToolIcon/ToolIcon';
|
|
3
|
+
import { ToolKind } from '../../src/theme/tokens/domain';
|
|
2
4
|
import { Columns, Example, Showcase } from '../frame';
|
|
3
5
|
import {
|
|
4
6
|
BANKING_TOTALS,
|
|
@@ -45,6 +47,17 @@ export const StatTilePage = () => (
|
|
|
45
47
|
<Example title="Transactions d'octobre (la dernière tuile est un lien)">
|
|
46
48
|
<BankingStatTiles />
|
|
47
49
|
</Example>
|
|
50
|
+
<Example title="Avec le logo d'un service">
|
|
51
|
+
<Columns>
|
|
52
|
+
<StatTile
|
|
53
|
+
label="Stockage Cloud"
|
|
54
|
+
icon={<ToolIcon tool={ToolKind.Cloud} size={16} />}
|
|
55
|
+
value="3,2"
|
|
56
|
+
unit="Go"
|
|
57
|
+
delta={{ value: 'sur 10 Go', tone: 'text.muted' }}
|
|
58
|
+
/>
|
|
59
|
+
</Columns>
|
|
60
|
+
</Example>
|
|
48
61
|
<Example title="Avec unité">
|
|
49
62
|
<Columns>
|
|
50
63
|
<StatTile
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ValueMenu } from '../../src/components/meaning/ValueMenu/ValueMenu';
|
|
2
|
+
import { Example, LastEvent, Row, Showcase, useEventLog } from '../frame';
|
|
3
|
+
|
|
4
|
+
export const ValueMenuPage = () => {
|
|
5
|
+
const { log } = useEventLog();
|
|
6
|
+
const handlers = (field: string, value: string) => ({
|
|
7
|
+
onFilter: () => log(`filtrer ${field}:${value}`),
|
|
8
|
+
onExclude: () => log(`exclure ${field}:${value}`),
|
|
9
|
+
onCopy: () => log(`copier ${value}`),
|
|
10
|
+
});
|
|
11
|
+
return (
|
|
12
|
+
<Showcase
|
|
13
|
+
family="Sens"
|
|
14
|
+
name="ValueMenu"
|
|
15
|
+
description="Rend une valeur cliquable : filtrer, exclure, copier, puis selon la page ajouter en colonne, voir l'évolution ou ouvrir l'objet désigné."
|
|
16
|
+
>
|
|
17
|
+
<Example title="Valeur d'un champ connu : trois actions" framed>
|
|
18
|
+
<ValueMenu field="level" value="ERROR" {...handlers('level', 'ERROR')}>
|
|
19
|
+
ERROR
|
|
20
|
+
</ValueMenu>
|
|
21
|
+
</Example>
|
|
22
|
+
<Example title="Valeur d'une liste : six actions" framed>
|
|
23
|
+
<Row>
|
|
24
|
+
<span>Requête</span>
|
|
25
|
+
<ValueMenu
|
|
26
|
+
field="requestId"
|
|
27
|
+
value="9f3c2a71"
|
|
28
|
+
{...handlers('requestId', '9f3c2a71')}
|
|
29
|
+
onAddColumn={() => log('ajouter requestId en colonne')}
|
|
30
|
+
onTrend={() => log('évolution de requestId')}
|
|
31
|
+
onOpen={() => log('ouvrir 9f3c2a71')}
|
|
32
|
+
>
|
|
33
|
+
9f3c2a71
|
|
34
|
+
</ValueMenu>
|
|
35
|
+
</Row>
|
|
36
|
+
</Example>
|
|
37
|
+
<LastEvent />
|
|
38
|
+
</Showcase>
|
|
39
|
+
);
|
|
40
|
+
};
|
package/catalog/pages/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ComponentType } from 'react';
|
|
|
2
2
|
import { AccountMenuPage } from './AccountMenu';
|
|
3
3
|
import { ActionBarPage } from './ActionBar';
|
|
4
4
|
import { AmbientBackgroundPage } from './AmbientBackground';
|
|
5
|
+
import { AnchoredPanelPage } from './AnchoredPanel';
|
|
5
6
|
import { AppShellPage } from './AppShell';
|
|
6
7
|
import { AvatarPage } from './Avatar';
|
|
7
8
|
import { BarChartPage } from './BarChart';
|
|
@@ -27,6 +28,8 @@ import { DateFieldPage } from './DateField';
|
|
|
27
28
|
import { DateRangeFieldPage } from './DateRangeField';
|
|
28
29
|
import { DrawerPage } from './Drawer';
|
|
29
30
|
import { DropZonePage } from './DropZone';
|
|
31
|
+
import { ExpressionSearchPage } from './ExpressionSearch';
|
|
32
|
+
import { FacetPanelPage } from './FacetPanel';
|
|
30
33
|
import { FilterBarPage } from './FilterBar';
|
|
31
34
|
import { FormFieldPage } from './FormField';
|
|
32
35
|
import { ImportButtonPage } from './ImportButton';
|
|
@@ -41,6 +44,7 @@ import { NotificationsMenuPage } from './NotificationsMenu';
|
|
|
41
44
|
import { NumberFieldPage } from './NumberField';
|
|
42
45
|
import { PageHeaderPage } from './PageHeader';
|
|
43
46
|
import { PageStatePage } from './PageState';
|
|
47
|
+
import { PreviewDrawerPage } from './PreviewDrawer';
|
|
44
48
|
import { ProductTourPage } from './ProductTour';
|
|
45
49
|
import { QRCodeModalPage } from './QRCodeModal';
|
|
46
50
|
import { RichSelectPage } from './RichSelect';
|
|
@@ -63,12 +67,14 @@ import { ToastPage } from './Toast';
|
|
|
63
67
|
import { ToolbarPage } from './Toolbar';
|
|
64
68
|
import { ToolIconPage } from './ToolIcon';
|
|
65
69
|
import { UsageMeterPage } from './UsageMeter';
|
|
70
|
+
import { ValueMenuPage } from './ValueMenu';
|
|
66
71
|
|
|
67
72
|
/** Every catalog page, by the `component` URL parameter (`catalog-entries.ts`). */
|
|
68
73
|
export const CATALOG_PAGES: Record<string, ComponentType> = {
|
|
69
74
|
AccountMenu: AccountMenuPage,
|
|
70
75
|
ActionBar: ActionBarPage,
|
|
71
76
|
AmbientBackground: AmbientBackgroundPage,
|
|
77
|
+
AnchoredPanel: AnchoredPanelPage,
|
|
72
78
|
AppShell: AppShellPage,
|
|
73
79
|
Avatar: AvatarPage,
|
|
74
80
|
BarChart: BarChartPage,
|
|
@@ -94,6 +100,8 @@ export const CATALOG_PAGES: Record<string, ComponentType> = {
|
|
|
94
100
|
DateRangeField: DateRangeFieldPage,
|
|
95
101
|
Drawer: DrawerPage,
|
|
96
102
|
DropZone: DropZonePage,
|
|
103
|
+
ExpressionSearch: ExpressionSearchPage,
|
|
104
|
+
FacetPanel: FacetPanelPage,
|
|
97
105
|
FilterBar: FilterBarPage,
|
|
98
106
|
FormField: FormFieldPage,
|
|
99
107
|
ImportButton: ImportButtonPage,
|
|
@@ -108,6 +116,7 @@ export const CATALOG_PAGES: Record<string, ComponentType> = {
|
|
|
108
116
|
NumberField: NumberFieldPage,
|
|
109
117
|
PageHeader: PageHeaderPage,
|
|
110
118
|
PageState: PageStatePage,
|
|
119
|
+
PreviewDrawer: PreviewDrawerPage,
|
|
111
120
|
ProductTour: ProductTourPage,
|
|
112
121
|
QRCodeModal: QRCodeModalPage,
|
|
113
122
|
RichSelect: RichSelectPage,
|
|
@@ -130,4 +139,5 @@ export const CATALOG_PAGES: Record<string, ComponentType> = {
|
|
|
130
139
|
Toolbar: ToolbarPage,
|
|
131
140
|
ToolIcon: ToolIconPage,
|
|
132
141
|
UsageMeter: UsageMeterPage,
|
|
142
|
+
ValueMenu: ValueMenuPage,
|
|
133
143
|
};
|
|
@@ -32,6 +32,13 @@ const REACH: Record<
|
|
|
32
32
|
.click();
|
|
33
33
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
34
34
|
},
|
|
35
|
+
// The value of a list (six actions): the menu lists every action with its label.
|
|
36
|
+
'menu-open': async (page) => {
|
|
37
|
+
await page.getByRole('button', { name: /Actions sur requestId/ }).click();
|
|
38
|
+
const menu = page.getByRole('menu').or(page.getByRole('dialog'));
|
|
39
|
+
await expect(menu).toBeVisible();
|
|
40
|
+
await expect(menu.getByRole('menuitem')).toHaveCount(6);
|
|
41
|
+
},
|
|
35
42
|
// The first top bar trigger of the page: the notifications bell or the account avatar.
|
|
36
43
|
'panel-open': async (page) => {
|
|
37
44
|
await page.locator('[aria-haspopup]').first().click();
|