@abyss-project/commons-front-core 8.2.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) 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 +2 -1
  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 +3 -3
  94. package/dist/design-theme-CN_6fRX3.js +0 -9344
  95. /package/dist/{legacy/components/Inputs/CloudImport/cloud-accept.spec.d.ts → components/business/CloudImport/CloudImport.spec.d.ts} +0 -0
  96. /package/dist/{legacy/components/QuotaOffer/quota-offer-state.spec.d.ts → components/business/ImportFromCloudModal/ImportFromCloudModal.spec.d.ts} +0 -0
  97. /package/dist/{legacy/components/Inputs/CloudImport/cloud-accept.d.ts → utils/cloud-accept.utils.d.ts} +0 -0
  98. /package/dist/{legacy/components/QuotaOffer/quota-offer-state.d.ts → utils/quota-offer-state.utils.d.ts} +0 -0
@@ -0,0 +1,35 @@
1
+ import { CloudImport } from '../../src/components/business/CloudImport/CloudImport';
2
+ import { Example, LastEvent, Showcase, useEventLog } from '../frame';
3
+ import {
4
+ CATALOG_CLOUD_PICKER,
5
+ CATALOG_FAILED_CLOUD_PICKER,
6
+ } from '../cloud-picker-fixture';
7
+
8
+ export const CloudImportPage = () => {
9
+ const { log } = useEventLog();
10
+ return (
11
+ <Showcase
12
+ family="Métier"
13
+ name="CloudImport"
14
+ description="Le sélecteur AbyssCloud : espace (le mien, ceux des organisations, ceux partagés avec moi), arborescence, fichiers choisis, import. Les fichiers trop lourds ou d'un type refusé ne sont pas listés."
15
+ >
16
+ <Example title="Plusieurs fichiers, PDF et images" maxWidth={768}>
17
+ <CloudImport
18
+ cloudPicker={CATALOG_CLOUD_PICKER}
19
+ multiple
20
+ accept={['application/pdf', 'image/*']}
21
+ onImport={(_space, files) =>
22
+ log(`importé ${files.map((file) => file.name).join(', ')}`)
23
+ }
24
+ />
25
+ </Example>
26
+ <Example title="Le cloud n'a pas pu être préparé" maxWidth={768}>
27
+ <CloudImport
28
+ cloudPicker={CATALOG_FAILED_CLOUD_PICKER}
29
+ onImport={() => undefined}
30
+ />
31
+ </Example>
32
+ <LastEvent />
33
+ </Showcase>
34
+ );
35
+ };
@@ -0,0 +1,103 @@
1
+ import { useState } from 'react';
2
+ import {
3
+ ContextKind,
4
+ ContextSelector,
5
+ } from '../../src/components/inputs/ContextSelector/ContextSelector';
6
+ import type {
7
+ ContextSelectorData,
8
+ ContextValue,
9
+ } from '../../src/components/inputs/ContextSelector/ContextSelector';
10
+ import {
11
+ OrganizationAvatar,
12
+ ProjectAvatar,
13
+ UserAvatar,
14
+ } from '../../src/components/meaning/Avatar/Avatar';
15
+ import { LogoAbyssSmall } from '../../src/assets';
16
+ import { Example, LastEvent, Showcase, useEventLog } from '../frame';
17
+
18
+ const AVATAR = 22;
19
+ const picture = () => LogoAbyssSmall;
20
+
21
+ const DATA: ContextSelectorData = {
22
+ personal: {
23
+ label: 'Mehdi',
24
+ avatar: (
25
+ <UserAvatar
26
+ userId="u1"
27
+ alias="Mehdi"
28
+ hasThumbnail
29
+ size={AVATAR}
30
+ getProfilePictureUrl={picture}
31
+ />
32
+ ),
33
+ },
34
+ organizations: [
35
+ {
36
+ id: 'o1',
37
+ name: 'Abyss',
38
+ avatar: (
39
+ <OrganizationAvatar
40
+ orgId="o1"
41
+ name="Abyss"
42
+ hasThumbnail={false}
43
+ size={AVATAR}
44
+ getLogoUrl={picture}
45
+ />
46
+ ),
47
+ projects: [
48
+ {
49
+ id: 'p1',
50
+ name: 'Atlas',
51
+ avatar: (
52
+ <ProjectAvatar
53
+ projectId="p1"
54
+ title="Atlas"
55
+ hasThumbnail={false}
56
+ size={AVATAR}
57
+ getProjectPictureUrl={picture}
58
+ />
59
+ ),
60
+ },
61
+ { id: 'p2', name: 'Refonte du site vitrine' },
62
+ ],
63
+ },
64
+ { id: 'o2', name: 'Karbon Studio' },
65
+ ],
66
+ };
67
+
68
+ export const ContextSelectorPage = () => {
69
+ const { log } = useEventLog();
70
+ const [value, setValue] = useState<ContextValue>({
71
+ kind: ContextKind.Organization,
72
+ organizationId: 'o1',
73
+ });
74
+ return (
75
+ <Showcase
76
+ family="Saisie"
77
+ name="ContextSelector"
78
+ description="Choix de la portée d'une page : mon espace, puis un groupe par organisation (toute l'organisation, puis ses projets). La saisie filtre les entrées."
79
+ >
80
+ <Example title="Portée des ressources" maxWidth={480}>
81
+ <ContextSelector
82
+ data={DATA}
83
+ value={value}
84
+ onChange={(next) => {
85
+ setValue(next);
86
+ log(`portée ${next.kind}`);
87
+ }}
88
+ label="Espace"
89
+ />
90
+ </Example>
91
+ <Example title="Inactif" maxWidth={480}>
92
+ <ContextSelector
93
+ data={DATA}
94
+ value={{ kind: ContextKind.Personal }}
95
+ onChange={() => undefined}
96
+ label="Espace"
97
+ disabled
98
+ />
99
+ </Example>
100
+ <LastEvent />
101
+ </Showcase>
102
+ );
103
+ };
@@ -0,0 +1,29 @@
1
+ import { CopyField } from '../../src/components/inputs/CopyField/CopyField';
2
+ import { Example, LastEvent, Showcase, useEventLog } from '../frame';
3
+
4
+ export const CopyFieldPage = () => {
5
+ const { log } = useEventLog();
6
+ return (
7
+ <Showcase
8
+ family="Saisie"
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."
11
+ >
12
+ <Example title="Clé d'API, masquée" maxWidth={640}>
13
+ <CopyField
14
+ value="abyss_sk_live_5f2c9a7e1b3d4f60a8c2e9b7d1f3a5c7"
15
+ masked
16
+ onReveal={() => log('afficher la clé')}
17
+ copyLabel="Copier la clé"
18
+ />
19
+ </Example>
20
+ <Example title="Identifiant de connexion IA" maxWidth={640}>
21
+ <CopyField
22
+ value="https://mcp.abyss-project.fr/u/7f3a9c2e"
23
+ copyLabel="Copier l'adresse"
24
+ />
25
+ </Example>
26
+ <LastEvent />
27
+ </Showcase>
28
+ );
29
+ };
@@ -0,0 +1,37 @@
1
+ import { DropZone } from '../../src/components/inputs/DropZone/DropZone';
2
+ import { Example, LastEvent, Showcase, useEventLog } from '../frame';
3
+ import { CATALOG_CLOUD_PICKER } from '../cloud-picker-fixture';
4
+
5
+ const TEN_MIB = 10 * 1024 * 1024;
6
+
7
+ export const DropZonePage = () => {
8
+ const { log } = useEventLog();
9
+ const dropped = (files: File[]) =>
10
+ log(`déposé ${files.map((file) => file.name).join(', ')}`);
11
+ return (
12
+ <Showcase
13
+ family="Saisie"
14
+ name="DropZone"
15
+ description="Zone de dépôt de fichiers : bouton pour parcourir l'appareil, limite de taille, refus expliqués sous la zone, entrée AbyssCloud quand elle est branchée. Sous 768 px, seul le bouton reste."
16
+ >
17
+ <Example title="Une image, avec AbyssCloud" maxWidth={640}>
18
+ <DropZone
19
+ title="Signature"
20
+ onDrop={dropped}
21
+ multiple={false}
22
+ accept={{ 'image/png': ['.png'], 'image/jpeg': ['.jpg', '.jpeg'] }}
23
+ maxSize={TEN_MIB}
24
+ cloudImport={{
25
+ ...CATALOG_CLOUD_PICKER,
26
+ onImport: (_space, files) =>
27
+ log(`importé ${files.map((file) => file.name).join(', ')}`),
28
+ }}
29
+ />
30
+ </Example>
31
+ <Example title="Plusieurs fichiers, sans AbyssCloud" maxWidth={640}>
32
+ <DropZone onDrop={dropped} maxSize={TEN_MIB} />
33
+ </Example>
34
+ <LastEvent />
35
+ </Showcase>
36
+ );
37
+ };
@@ -0,0 +1,54 @@
1
+ import { useState } from 'react';
2
+ import MenuItem from '@mui/material/MenuItem';
3
+ import OutlinedInput from '@mui/material/OutlinedInput';
4
+ import Select from '@mui/material/Select';
5
+ import { FormField } from '../../src/components/inputs/FormField/FormField';
6
+ import { Columns, Example, Showcase } from '../frame';
7
+
8
+ export const FormFieldPage = () => {
9
+ const [company, setCompany] = useState('Abyss SAS');
10
+ const [vat, setVat] = useState('FR123');
11
+ const [country, setCountry] = useState('fr');
12
+ return (
13
+ <Showcase
14
+ family="Saisie"
15
+ name="FormField"
16
+ description="Libellé, aide et erreur autour d'un champ MUI : l'id et les aria-describedby sont reliés, l'erreur et l'obligation passent au champ."
17
+ >
18
+ <Example title="Aide et obligation, erreur, liste">
19
+ <Columns>
20
+ <FormField
21
+ label="Raison sociale"
22
+ help="Telle qu'elle figure sur vos factures."
23
+ required
24
+ >
25
+ <OutlinedInput
26
+ value={company}
27
+ onChange={(event) => setCompany(event.target.value)}
28
+ />
29
+ </FormField>
30
+ <FormField
31
+ label="Numéro de TVA"
32
+ help="FR suivi de 11 chiffres."
33
+ error="Ce numéro n'est pas valide."
34
+ >
35
+ <OutlinedInput
36
+ value={vat}
37
+ onChange={(event) => setVat(event.target.value)}
38
+ />
39
+ </FormField>
40
+ <FormField label="Pays de facturation">
41
+ <Select
42
+ value={country}
43
+ onChange={(event) => setCountry(event.target.value)}
44
+ >
45
+ <MenuItem value="fr">France</MenuItem>
46
+ <MenuItem value="be">Belgique</MenuItem>
47
+ <MenuItem value="ch">Suisse</MenuItem>
48
+ </Select>
49
+ </FormField>
50
+ </Columns>
51
+ </Example>
52
+ </Showcase>
53
+ );
54
+ };
@@ -0,0 +1,45 @@
1
+ import Button from '@mui/material/Button';
2
+ import FileUploadOutlinedIcon from '@mui/icons-material/FileUploadOutlined';
3
+ import { ImportButton } from '../../src/components/inputs/ImportButton/ImportButton';
4
+ import { Example, LastEvent, Showcase, useEventLog } from '../frame';
5
+ import { CATALOG_CLOUD_PICKER } from '../cloud-picker-fixture';
6
+
7
+ export const ImportButtonPage = () => {
8
+ const { log } = useEventLog();
9
+ return (
10
+ <Showcase
11
+ family="Saisie"
12
+ name="ImportButton"
13
+ description="Un déclencheur qui ouvre le menu des sources : l'appareil, un lien (adresse vérifiée avant l'import), AbyssCloud."
14
+ >
15
+ <Example title="Trois sources">
16
+ <ImportButton
17
+ isLoading={false}
18
+ handleFileUpload={async (event) =>
19
+ log(`fichier ${event.target.files?.[0]?.name ?? ''}`)
20
+ }
21
+ handleUploadFromUrl={async (url) => log(`lien ${url}`)}
22
+ accept={['image/*']}
23
+ cloudImport={{
24
+ ...CATALOG_CLOUD_PICKER,
25
+ onImport: (_space, files) =>
26
+ log(`importé ${files.map((file) => file.name).join(', ')}`),
27
+ }}
28
+ >
29
+ <Button
30
+ variant="outlined"
31
+ startIcon={<FileUploadOutlinedIcon aria-hidden />}
32
+ >
33
+ Importer une image
34
+ </Button>
35
+ </ImportButton>
36
+ </Example>
37
+ <Example title="L'appareil seulement, import en cours">
38
+ <ImportButton isLoading handleFileUpload={async () => undefined}>
39
+ <Button variant="outlined">Importer</Button>
40
+ </ImportButton>
41
+ </Example>
42
+ <LastEvent />
43
+ </Showcase>
44
+ );
45
+ };
@@ -0,0 +1,33 @@
1
+ import { useState } from 'react';
2
+ import Button from '@mui/material/Button';
3
+ import { ImportFromCloudModal } from '../../src/components/business/ImportFromCloudModal/ImportFromCloudModal';
4
+ import { Example, LastEvent, Showcase, useEventLog } from '../frame';
5
+ import { CATALOG_CLOUD_PICKER } from '../cloud-picker-fixture';
6
+
7
+ export const ImportFromCloudModalPage = () => {
8
+ const { log } = useEventLog();
9
+ const [open, setOpen] = useState(true);
10
+ return (
11
+ <Showcase
12
+ family="Métier"
13
+ name="ImportFromCloudModal"
14
+ description="Le sélecteur AbyssCloud dans une modale ; une feuille du bas sous 768 px. Importer remet les fichiers à l'hôte et ferme."
15
+ >
16
+ <Example title="Une image">
17
+ <Button variant="outlined" onClick={() => setOpen(true)}>
18
+ Importer depuis AbyssCloud
19
+ </Button>
20
+ </Example>
21
+ <LastEvent />
22
+ <ImportFromCloudModal
23
+ open={open}
24
+ onClose={() => setOpen(false)}
25
+ cloudPicker={CATALOG_CLOUD_PICKER}
26
+ accept={['image/*']}
27
+ onImport={(_space, files) =>
28
+ log(`importé ${files.map((file) => file.name).join(', ')}`)
29
+ }
30
+ />
31
+ </Showcase>
32
+ );
33
+ };
@@ -30,7 +30,7 @@ export const MenuPage = () => {
30
30
  <Showcase
31
31
  family="Couches"
32
32
  name="Menu"
33
- description="Menu d'actions : icône, raccourci, ton danger, entrées désactivées sautées par les flèches."
33
+ description="Menu d'actions : icône, description, raccourci, ton danger, entrées désactivées sautées par les flèches."
34
34
  >
35
35
  <Example title="Actions d'un fichier">
36
36
  <Button
@@ -51,6 +51,7 @@ export const MenuPage = () => {
51
51
  {
52
52
  id: 'share',
53
53
  label: 'Partager',
54
+ description: 'Lien ou invitation par courriel',
54
55
  icon: <IosShareOutlined />,
55
56
  shortcut: 'Ctrl+Maj+S',
56
57
  onSelect: select('partager'),
@@ -0,0 +1,46 @@
1
+ import { useState } from 'react';
2
+ import { NumberField } from '../../src/components/inputs/NumberField/NumberField';
3
+ import { Columns, Example, Showcase } from '../frame';
4
+
5
+ export const NumberFieldPage = () => {
6
+ const [ceiling, setCeiling] = useState<number | null>(1250.5);
7
+ const [seats, setSeats] = useState<number | null>(12);
8
+ const [empty, setEmpty] = useState<number | null>(null);
9
+ return (
10
+ <Showcase
11
+ family="Saisie"
12
+ name="NumberField"
13
+ description="Nombre saisi et affiché avec les séparateurs de la langue ; « , » ou « . » lus comme marque décimale, borné et arrondi en sortie du champ."
14
+ >
15
+ <Example title="Montant, quantité, champ vide et désactivé">
16
+ <Columns>
17
+ <NumberField
18
+ label="Plafond mensuel"
19
+ value={ceiling}
20
+ onChange={setCeiling}
21
+ decimals={2}
22
+ adornment="€"
23
+ />
24
+ <NumberField
25
+ label="Sièges"
26
+ value={seats}
27
+ onChange={setSeats}
28
+ min={1}
29
+ />
30
+ <NumberField
31
+ label="Stockage supplémentaire"
32
+ value={empty}
33
+ onChange={setEmpty}
34
+ adornment="Go"
35
+ />
36
+ <NumberField
37
+ label="Crédits offerts"
38
+ value={500}
39
+ onChange={() => undefined}
40
+ disabled
41
+ />
42
+ </Columns>
43
+ </Example>
44
+ </Showcase>
45
+ );
46
+ };
@@ -0,0 +1,26 @@
1
+ import { useState } from 'react';
2
+ import Button from '@mui/material/Button';
3
+ import { QRCodeModal } from '../../src/components/business/QRCodeModal/QRCodeModal';
4
+ import { Example, Showcase } from '../frame';
5
+
6
+ export const QRCodeModalPage = () => {
7
+ const [open, setOpen] = useState(true);
8
+ return (
9
+ <Showcase
10
+ family="Métier"
11
+ name="QRCodeModal"
12
+ description="Un lien en QR code, toujours sur une page claire (jeton paper) pour rester lisible par un appareil photo ; le lien à copier et « Partager »."
13
+ >
14
+ <Example title="Lien de parrainage">
15
+ <Button variant="outlined" onClick={() => setOpen(true)}>
16
+ Afficher le QR code
17
+ </Button>
18
+ </Example>
19
+ <QRCodeModal
20
+ open={open}
21
+ onClose={() => setOpen(false)}
22
+ url="https://abyss-project.fr/auth/register?sponsorship=7f3a9c2e"
23
+ />
24
+ </Showcase>
25
+ );
26
+ };
@@ -0,0 +1,67 @@
1
+ import { useState } from 'react';
2
+ import Button from '@mui/material/Button';
3
+ import MenuItem from '@mui/material/MenuItem';
4
+ import Select from '@mui/material/Select';
5
+ import Switch from '@mui/material/Switch';
6
+ import { SettingsRow } from '../../src/components/inputs/SettingsRow/SettingsRow';
7
+ import { Surface } from '../../src/components/structure/Surface/Surface';
8
+ import { Example, LastEvent, Showcase, useEventLog } from '../frame';
9
+
10
+ export const SettingsRowPage = () => {
11
+ const { log } = useEventLog();
12
+ const [renew, setRenew] = useState(true);
13
+ const [language, setLanguage] = useState('fr');
14
+ return (
15
+ <Showcase
16
+ family="Saisie"
17
+ name="SettingsRow"
18
+ description="Un réglage : libellé et description à gauche, contrôle à droite, lignes séparées d'un filet ; le contrôle passe sous le texte sous 768 px."
19
+ >
20
+ <Example title="Réglages de l'abonnement, dans une surface">
21
+ <Surface padding={4}>
22
+ <SettingsRow
23
+ label="Renouvellement automatique"
24
+ description="Votre abonnement est reconduit à chaque échéance, au tarif en vigueur."
25
+ htmlFor="catalog-auto-renew"
26
+ control={
27
+ <Switch
28
+ id="catalog-auto-renew"
29
+ checked={renew}
30
+ onChange={(event) => setRenew(event.target.checked)}
31
+ />
32
+ }
33
+ />
34
+ <SettingsRow
35
+ label="Langue des factures"
36
+ description="Les prochaines factures seront émises dans cette langue."
37
+ control={
38
+ <Select
39
+ size="small"
40
+ value={language}
41
+ inputProps={{ 'aria-label': 'Langue des factures' }}
42
+ onChange={(event) => setLanguage(event.target.value)}
43
+ >
44
+ <MenuItem value="fr">Français</MenuItem>
45
+ <MenuItem value="en">English</MenuItem>
46
+ </Select>
47
+ }
48
+ />
49
+ <SettingsRow
50
+ label="Résilier l'abonnement"
51
+ description="Vous gardez l'accès jusqu'à la fin de la période payée."
52
+ control={
53
+ <Button
54
+ variant="outlined"
55
+ color="error"
56
+ onClick={() => log('résilier')}
57
+ >
58
+ Résilier
59
+ </Button>
60
+ }
61
+ />
62
+ </Surface>
63
+ </Example>
64
+ <LastEvent />
65
+ </Showcase>
66
+ );
67
+ };
@@ -0,0 +1,72 @@
1
+ import { useState } from 'react';
2
+ import { Tabs, TabsVariant } from '../../src/components/structure/Tabs/Tabs';
3
+ import type { TabItem } from '../../src/components/structure/Tabs/Tabs';
4
+ import { Example, LastEvent, Showcase, useEventLog } from '../frame';
5
+
6
+ const ACCOUNT_TABS: TabItem[] = [
7
+ { value: 'profile', label: 'Profil', to: '/compte/profil' },
8
+ { value: 'security', label: 'Sécurité', to: '/compte/securite' },
9
+ {
10
+ value: 'notifications',
11
+ label: 'Notifications',
12
+ to: '/compte/notifications',
13
+ },
14
+ { value: 'family', label: 'Famille', to: '/compte/famille', badge: 2 },
15
+ { value: 'billing', label: 'Facturation', to: '/compte/facturation' },
16
+ { value: 'api-key', label: 'Clé d’API', to: '/compte/cle-api' },
17
+ { value: 'ai', label: 'Connexions IA', to: '/compte/connexions-ia' },
18
+ {
19
+ value: 'admin',
20
+ label: 'Administration',
21
+ to: '/compte/admin',
22
+ hidden: true,
23
+ },
24
+ ];
25
+
26
+ const BILLING_TABS: TabItem[] = [
27
+ { value: 'overview', label: 'Vue d’ensemble' },
28
+ { value: 'subscription', label: 'Abonnement' },
29
+ { value: 'storage', label: 'Stockage' },
30
+ { value: 'credits', label: 'Crédits' },
31
+ { value: 'payment', label: 'Moyens de paiement' },
32
+ { value: 'invoices', label: 'Factures', badge: 'Nouveau' },
33
+ ];
34
+
35
+ export const TabsPage = () => {
36
+ const { log } = useEventLog();
37
+ const [account, setAccount] = useState('billing');
38
+ const [billing, setBilling] = useState('overview');
39
+ return (
40
+ <Showcase
41
+ family="Structure"
42
+ name="Tabs"
43
+ description="Rangée d'onglets : liens de route ou boutons, deux variantes, un seul arrêt de tabulation, flèches, Début et Fin, défilement horizontal avec un fondu tant qu'il reste des onglets à droite."
44
+ >
45
+ <Example title="Navigation du compte (primaire, liens de route ; « Administration » est masqué)">
46
+ <Tabs
47
+ aria-label="Compte"
48
+ variant={TabsVariant.Primary}
49
+ items={ACCOUNT_TABS}
50
+ value={account}
51
+ onChange={(next) => {
52
+ setAccount(next);
53
+ log(`onglet ${next}`);
54
+ }}
55
+ />
56
+ </Example>
57
+ <Example title="Sous-navigation de la facturation (secondaire, boutons)">
58
+ <Tabs
59
+ aria-label="Facturation"
60
+ variant={TabsVariant.Secondary}
61
+ items={BILLING_TABS}
62
+ value={billing}
63
+ onChange={(next) => {
64
+ setBilling(next);
65
+ log(`sous-onglet ${next}`);
66
+ }}
67
+ />
68
+ </Example>
69
+ <LastEvent />
70
+ </Showcase>
71
+ );
72
+ };
@@ -0,0 +1,58 @@
1
+ import { UsageMeter } from '../../src/components/data/UsageMeter/UsageMeter';
2
+ import { Columns, Example, Showcase } from '../frame';
3
+ import { formatInteger } from '../fixtures';
4
+
5
+ const THRESHOLDS = { warning: 0.8, danger: 1 };
6
+
7
+ const gigabyteFormat = new Intl.NumberFormat('fr-FR', {
8
+ maximumFractionDigits: 1,
9
+ });
10
+
11
+ const gigabytes = (value: number): string =>
12
+ `${gigabyteFormat.format(value)} Go`;
13
+
14
+ export const UsageMeterPage = () => (
15
+ <Showcase
16
+ family="Données"
17
+ name="UsageMeter"
18
+ description="Consommation sur un plafond : jauge à l'accent, puis à l'avertissement et au danger aux seuils ; sans plafond, la valeur seule."
19
+ >
20
+ <Example title="Sous les seuils, au seuil d'avertissement, au plafond">
21
+ <Columns>
22
+ <UsageMeter
23
+ label="Stockage"
24
+ value={3.2}
25
+ max={10}
26
+ thresholds={THRESHOLDS}
27
+ formatValue={gigabytes}
28
+ />
29
+ <UsageMeter
30
+ label="Crédits IA"
31
+ value={8600}
32
+ max={10000}
33
+ thresholds={THRESHOLDS}
34
+ formatValue={formatInteger}
35
+ />
36
+ <UsageMeter
37
+ label="Membres"
38
+ value={5}
39
+ max={5}
40
+ thresholds={THRESHOLDS}
41
+ formatValue={formatInteger}
42
+ />
43
+ </Columns>
44
+ </Example>
45
+ <Example title="Au-delà du plafond, et sans plafond">
46
+ <Columns>
47
+ <UsageMeter
48
+ label="Stockage"
49
+ value={12.5}
50
+ max={10}
51
+ thresholds={THRESHOLDS}
52
+ formatValue={gigabytes}
53
+ />
54
+ <UsageMeter label="Fichiers" value={3210} formatValue={formatInteger} />
55
+ </Columns>
56
+ </Example>
57
+ </Showcase>
58
+ );