♻️(frontend) refacto Version modal to fit with the design system

We refactored the version modal to fit
the design system. We removed some dead code and
fixed some state issues.
This commit is contained in:
Anthony LC
2026-03-23 10:58:50 +01:00
parent a4326366c2
commit d9d7b70b71
7 changed files with 73 additions and 109 deletions
+2 -1
View File
@@ -14,6 +14,7 @@ and this project adheres to
- ♿️(frontend) announce formatting shortcuts for screen readers #2070
- ✨(frontend) add markdown copy icon for Copy as Markdown option #2096
- ♻️(backend) skip saving in database a document when payload is empty #2062
- ♻️(frontend) refacto Version modal to fit with the design system #2091
### Fixed
@@ -22,7 +23,7 @@ and this project adheres to
- 💫(frontend) fix the help button to the bottom in tree #2073
- ♿️(frontend) fix aria-labels for table of contents #2065
- 🐛(backend) allow using search endpoint without refresh token enabled #2097
- 🐛(frontend) fix close panel when click on subdoc
- 🐛(frontend) fix close panel when click on subdoc #2094
- 🐛(frontend) fix leftpanel button in doc version #9238
## [v4.8.2] - 2026-03-19
@@ -164,11 +164,16 @@ test.describe('Doc Version', () => {
await expect(modal.getByText('World')).toBeHidden();
await page.getByRole('button', { name: 'Restore', exact: true }).click();
await expect(page.getByText('Your current document will')).toBeVisible();
await page.getByText('If a member is editing, his').click();
await expect(
page.getByText(
"The current document will be replaced, but you'll still find it in the version history.",
),
).toBeVisible();
await page.getByLabel('Restore', { exact: true }).click();
await page.waitForTimeout(500);
await expect(page.getByText('Hello')).toBeVisible();
await expect(page.getByText('World')).toBeHidden();
});
@@ -5,8 +5,8 @@ import {
VariantType,
useToastProvider,
} from '@gouvfr-lasuite/cunningham-react';
import { useRouter } from 'next/router';
import { useTranslation } from 'react-i18next';
import { createGlobalStyle } from 'styled-components';
import { Box, Text } from '@/components';
import {
@@ -21,15 +21,22 @@ import { KEY_LIST_DOC_VERSIONS } from '../api/useDocVersions';
import { Versions } from '../types';
import { revertUpdate } from '../utils';
interface ModalConfirmationVersionProps {
onClose: () => void;
docId: Doc['id'];
const ModalStyle = createGlobalStyle`
.c__modal__title {
margin-bottom: var(--c--globals--spacings--sm);
}
`;
interface ModalConfirmationVersionProps {
docId: Doc['id'];
onClose: () => void;
onSuccess: () => void;
versionId: Versions['version_id'];
}
export const ModalConfirmationVersion = ({
onClose,
onSuccess,
docId,
versionId,
}: ModalConfirmationVersionProps) => {
@@ -39,14 +46,13 @@ export const ModalConfirmationVersion = ({
});
const { t } = useTranslation();
const { toast } = useToastProvider();
const { push } = useRouter();
const { provider } = useProviderStore();
const { mutate: updateDoc } = useUpdateDoc({
listInvalidQueries: [KEY_LIST_DOC_VERSIONS],
onSuccess: () => {
const onDisplaySuccess = () => {
toast(t('Version restored successfully'), VariantType.SUCCESS);
void push(`/docs/${docId}`);
onSuccess();
};
if (!provider || !version?.content) {
@@ -64,6 +70,10 @@ export const ModalConfirmationVersion = ({
},
});
if (!version) {
return null;
}
return (
<Modal
isOpen
@@ -102,7 +112,7 @@ export const ModalConfirmationVersion = ({
</Button>
</>
}
size={ModalSize.SMALL}
size={ModalSize.MEDIUM}
title={
<Text
as="h1"
@@ -111,17 +121,17 @@ export const ModalConfirmationVersion = ({
$size="h6"
$align="flex-start"
>
{t('Warning')}
{t('Restoring an older version')}
</Text>
}
>
<ModalStyle />
<Box className="--docs--modal-confirmation-version">
<Box>
<Text $variation="secondary" as="p">
{t('Your current document will revert to this version.')}
</Text>
<Text $variation="secondary" as="p">
{t('If a member is editing, his works can be lost.')}
<Text $variation="secondary" as="p" $margin="none">
{t(
"The current document will be replaced, but you'll still find it in the version history.",
)}
</Text>
</Box>
</Box>
@@ -114,7 +114,8 @@ export const ModalSelectVersion = ({
$height="calc(100vh - 2em - 12px)"
$css={css`
overflow-y: hidden;
border-left: 1px solid var(--c--globals--colors--gray-200);
border-left: 1px solid
var(--c--contextuals--border--surface--primary);
`}
>
<Box
@@ -130,7 +131,8 @@ export const ModalSelectVersion = ({
$direction="row"
$align="center"
$css={css`
border-bottom: 1px solid var(--c--globals--colors--gray-200);
border-bottom: 1px solid
var(--c--contextuals--border--surface--primary);
`}
$padding="sm"
>
@@ -155,7 +157,8 @@ export const ModalSelectVersion = ({
<Box
$padding="xs"
$css={css`
border-top: 1px solid var(--c--globals--colors--gray-200);
border-top: 1px solid
var(--c--contextuals--border--surface--primary);
`}
>
<Button
@@ -174,6 +177,9 @@ export const ModalSelectVersion = ({
<ModalConfirmationVersion
onClose={() => {
restoreModal.close();
}}
onSuccess={() => {
restoreModal.close();
onClose();
setSelectedVersionId(undefined);
}}
@@ -1,94 +1,38 @@
import dynamic from 'next/dynamic';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, BoxButton, Text } from '@/components';
import { BoxButton, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Doc } from '@/docs/doc-management';
import { Versions } from '../types';
const ModalConfirmationVersion = dynamic(
() =>
import('./ModalConfirmationVersion').then((mod) => ({
default: mod.ModalConfirmationVersion,
})),
{ ssr: false },
);
interface VersionItemProps {
docId: Doc['id'];
text: string;
versionId?: Versions['version_id'];
isActive: boolean;
onSelect?: () => void;
}
export const VersionItem = ({
docId,
versionId,
text,
isActive,
onSelect,
}: VersionItemProps) => {
export const VersionItem = ({ text, isActive, onSelect }: VersionItemProps) => {
const { t } = useTranslation();
const { colorsTokens, spacingsTokens } = useCunninghamTheme();
const [isModalVersionOpen, setIsModalVersionOpen] = useState(false);
const { spacingsTokens } = useCunninghamTheme();
return (
<>
<BoxButton
aria-label={t('Restore version of {{date}}', { date: text })}
aria-pressed={isActive}
$width="100%"
$css={`
&:focus-visible {
background: var(--c--globals--colors--gray-100);
border-radius: var(--c--globals--spacings--st);
}
`}
className="version-item"
onClick={onSelect}
>
<Box
$width="100%"
as="span"
$background={isActive ? colorsTokens['gray-100'] : 'transparent'}
$radius={spacingsTokens['3xs']}
$css={`
cursor: pointer;
&:hover {
background: ${colorsTokens['gray-100']};
}
`}
$hasTransition
$minWidth="13rem"
className="--docs--version-item"
>
<Box
$padding={{ vertical: '0.7rem', horizontal: 'small' }}
$align="center"
$direction="row"
$justify="space-between"
$width="100%"
>
<Box $direction="row" $gap="0.5rem" $align="center">
<Text $weight="bold" $size="sm">
{text}
</Text>
</Box>
</Box>
</Box>
</BoxButton>
{isModalVersionOpen && versionId && (
<ModalConfirmationVersion
onClose={() => setIsModalVersionOpen(false)}
docId={docId}
versionId={versionId}
/>
)}
</>
<BoxButton
aria-label={t('Restore version of {{date}}', { date: text })}
aria-pressed={isActive}
$width="100%"
$css={`
background: ${isActive ? 'var(--c--contextuals--background--semantic--overlay--primary)' : 'transparent'};
&:focus-visible, &:hover {
background: var(--c--contextuals--background--semantic--overlay--primary);
}
`}
className="version-item --docs--version-item"
onClick={onSelect}
$radius={spacingsTokens['3xs']}
$padding={{ vertical: 'm', horizontal: 'xs' }}
$hasTransition
>
<Text $weight="bold" $size="sm" $textAlign="left">
{text}
</Text>
</BoxButton>
);
};
@@ -16,7 +16,6 @@ interface VersionListStateProps {
isLoading: boolean;
error: APIError<unknown> | null;
versions?: Versions[];
doc: Doc;
selectedVersionId?: Versions['version_id'];
onSelectVersion?: (versionId: Versions['version_id']) => void;
}
@@ -24,13 +23,11 @@ interface VersionListStateProps {
const VersionListState = ({
onSelectVersion,
selectedVersionId,
isLoading,
error,
versions,
doc,
}: VersionListStateProps) => {
const { formatDate } = useDate();
const { formatDateSpecial } = useDate();
if (isLoading) {
return (
@@ -41,19 +38,17 @@ const VersionListState = ({
}
return (
<Box $gap="10px" $padding="xs">
<Box $gap="xxs" $padding="xs">
{versions?.map((version) => {
const formattedDate = formatDate(
const formattedDate = formatDateSpecial(
version.last_modified,
DateTime.DATETIME_MED,
'dd MMMM · HH:mm',
);
const isSelected = version.version_id === selectedVersionId;
return (
<Box as="li" key={version.version_id} $css="list-style: none;">
<VersionItem
versionId={version.version_id}
text={formattedDate}
docId={doc.id}
isActive={isSelected}
onSelect={() => onSelectVersion?.(version.version_id)}
/>
@@ -142,7 +137,6 @@ export const VersionList = ({
isLoading={isLoading}
error={error}
versions={versions}
doc={doc}
selectedVersionId={selectedVersionId}
/>
</InfiniteScroll>
@@ -21,6 +21,10 @@ export const useDate = () => {
.toLocaleString(format);
};
const formatDateSpecial = (date: string, format: string): string => {
return DateTime.fromISO(date).setLocale(i18n.language).toFormat(format);
};
const relativeDate = (date: string): string => {
const dateToCompare = DateTime.fromISO(date);
@@ -45,5 +49,5 @@ export const useDate = () => {
),
);
return { formatDate, relativeDate, calculateDaysLeft };
return { formatDate, formatDateSpecial, relativeDate, calculateDaysLeft };
};