mirror of
https://github.com/suitenumerique/docs.git
synced 2026-08-17 21:25:43 +02:00
♻️(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:
+2
-1
@@ -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();
|
||||
});
|
||||
|
||||
+23
-13
@@ -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>
|
||||
|
||||
+9
-3
@@ -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);
|
||||
}}
|
||||
|
||||
+23
-79
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-10
@@ -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 };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user