✨(frontend) update doc header ui
Modification of the header style to be consistent with the new UI : - We replace the option menu with the DropdownMenu component - We add a dowload button - We put an input in place of an editable div.
This commit is contained in:
committed by
Anthony LC
parent
1d85eee78f
commit
d5670640f5
@@ -89,6 +89,7 @@ and this project adheres to
|
|||||||
- ✨(frontend) add sentry #424
|
- ✨(frontend) add sentry #424
|
||||||
- ✨(frontend) add crisp chatbot #450
|
- ✨(frontend) add crisp chatbot #450
|
||||||
- 💄(frontend) update DocsGridOptions component #432
|
- 💄(frontend) update DocsGridOptions component #432
|
||||||
|
- 💄(frontend) update DocHeader ui #446
|
||||||
|
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
import { Text, TextType } from '@/components';
|
import { Text, TextType } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
|
|
||||||
@@ -40,23 +42,21 @@ export const IconBG = ({ iconName, ...textProps }: IconBGProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IconOptionsProps {
|
type IconOptionsProps = TextType & {
|
||||||
isOpen: boolean;
|
isHorizontal?: boolean;
|
||||||
'aria-label': string;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export const IconOptions = ({ isOpen, ...props }: IconOptionsProps) => {
|
export const IconOptions = ({ isHorizontal, ...props }: IconOptionsProps) => {
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
aria-label={props['aria-label']}
|
{...props}
|
||||||
$isMaterialIcon
|
$isMaterialIcon
|
||||||
$css={`
|
$css={css`
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
transform: rotate(${isOpen ? '90' : '0'}deg);
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
${props.$css}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
more_vert
|
{isHorizontal ? 'more_horiz' : 'more_vert'}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
|
|
||||||
|
import { Box } from '../Box';
|
||||||
|
|
||||||
|
export enum SeparatorVariant {
|
||||||
|
LIGHT = 'light',
|
||||||
|
DARK = 'dark',
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
variant?: SeparatorVariant;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const HorizontalSeparator = ({
|
||||||
|
variant = SeparatorVariant.LIGHT,
|
||||||
|
}: Props) => {
|
||||||
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
$height="1px"
|
||||||
|
$width="100%"
|
||||||
|
$margin={{ vertical: 'base' }}
|
||||||
|
$background={
|
||||||
|
variant === SeparatorVariant.DARK
|
||||||
|
? '#e5e5e533'
|
||||||
|
: colorsTokens()['greyscale-100']
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -352,8 +352,8 @@ input:-webkit-autofill:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c__button--nano {
|
.c__button--nano {
|
||||||
padding: 0 var(--c--theme--spacings--2xs) !important;
|
padding: 0 var(--c--theme--spacings--2xs);
|
||||||
gap: var(--c--theme--spacings--2xs) !important;
|
gap: var(--c--theme--spacings--2xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.c__button--medium {
|
.c__button--medium {
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import { Fragment } from 'react';
|
import { DateTime } from 'luxon';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { css } from 'styled-components';
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
import { Box, Card, StyledLink, Text } from '@/components';
|
import { Box, Icon, Text } from '@/components';
|
||||||
|
import { HorizontalSeparator } from '@/components/separators/HorizontalSeparator';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { Doc, currentDocRole, useTrans } from '@/features/docs/doc-management';
|
import {
|
||||||
import { useDate } from '@/hook';
|
Doc,
|
||||||
|
LinkReach,
|
||||||
|
currentDocRole,
|
||||||
|
useTrans,
|
||||||
|
} from '@/features/docs/doc-management';
|
||||||
import { useResponsiveStore } from '@/stores';
|
import { useResponsiveStore } from '@/stores';
|
||||||
|
|
||||||
import { DocTagPublic } from './DocTagPublic';
|
|
||||||
import { DocTitle } from './DocTitle';
|
import { DocTitle } from './DocTitle';
|
||||||
import { DocToolBox } from './DocToolBox';
|
import { DocToolBox } from './DocToolBox';
|
||||||
|
|
||||||
@@ -17,90 +21,78 @@ interface DocHeaderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DocHeader = ({ doc }: DocHeaderProps) => {
|
export const DocHeader = ({ doc }: DocHeaderProps) => {
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
const { colorsTokens, spacingsTokens } = useCunninghamTheme();
|
||||||
|
const { isDesktop } = useResponsiveStore();
|
||||||
|
const spacings = spacingsTokens();
|
||||||
|
const colors = colorsTokens();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { formatDate } = useDate();
|
const docIsPublic = doc.link_reach === LinkReach.PUBLIC;
|
||||||
|
|
||||||
const { transRole } = useTrans();
|
const { transRole } = useTrans();
|
||||||
const { isMobile, isSmallMobile } = useResponsiveStore();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card
|
<Box
|
||||||
$width="100%"
|
$width="100%"
|
||||||
$margin={isMobile ? 'tiny' : 'small'}
|
$padding={{ vertical: 'base' }}
|
||||||
|
$gap={spacings['base']}
|
||||||
aria-label={t('It is the card information about the document.')}
|
aria-label={t('It is the card information about the document.')}
|
||||||
>
|
>
|
||||||
<Box
|
{docIsPublic && (
|
||||||
$padding={isMobile ? 'tiny' : 'small'}
|
|
||||||
$direction="row"
|
|
||||||
$align="center"
|
|
||||||
>
|
|
||||||
<StyledLink href="/">
|
|
||||||
<Text
|
|
||||||
$isMaterialIcon
|
|
||||||
$theme="primary"
|
|
||||||
$variation="600"
|
|
||||||
$size="2rem"
|
|
||||||
$css={css`
|
|
||||||
&:hover {
|
|
||||||
background-color: ${colorsTokens()['primary-100']};
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
$hasTransition
|
|
||||||
$radius="5px"
|
|
||||||
$padding="tiny"
|
|
||||||
>
|
|
||||||
home
|
|
||||||
</Text>
|
|
||||||
</StyledLink>
|
|
||||||
<Box
|
<Box
|
||||||
$width="1px"
|
aria-label={t('Public document')}
|
||||||
$height="70%"
|
$color={colors['primary-600']}
|
||||||
$background={colorsTokens()['greyscale-100']}
|
$background={colors['primary-100']}
|
||||||
$margin={{ horizontal: 'tiny' }}
|
$radius={spacings['3xs']}
|
||||||
/>
|
$direction="row"
|
||||||
|
$padding="xs"
|
||||||
|
$flex={1}
|
||||||
|
$align="center"
|
||||||
|
$gap={spacings['3xs']}
|
||||||
|
$css={css`
|
||||||
|
border: 1px solid var(--c--theme--colors--primary-300, #e3e3fd);
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Icon data-testid="public-icon" iconName="public" />
|
||||||
|
<Text>{t('Public document')}</Text>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box $direction="row" $align="center" $width="100%">
|
||||||
<Box
|
<Box
|
||||||
$direction="row"
|
$direction="row"
|
||||||
$justify="space-between"
|
$justify="space-between"
|
||||||
$css="flex:1;"
|
$css="flex:1;"
|
||||||
$gap="0.5rem 1rem"
|
$gap="0.5rem 1rem"
|
||||||
$wrap="wrap"
|
|
||||||
$align="center"
|
$align="center"
|
||||||
>
|
>
|
||||||
<DocTitle doc={doc} />
|
<Box $gap={spacings['3xs']}>
|
||||||
|
<DocTitle doc={doc} />
|
||||||
|
<Box $direction="row">
|
||||||
|
{isDesktop && (
|
||||||
|
<>
|
||||||
|
<Text $variation="400" $size="s" $weight="bold">
|
||||||
|
{transRole(currentDocRole(doc.abilities))} ·
|
||||||
|
</Text>
|
||||||
|
<Text $variation="400" $size="s">
|
||||||
|
{t('Last update: {{update}}', {
|
||||||
|
update: DateTime.fromISO(doc.updated_at).toRelative(),
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!isDesktop && (
|
||||||
|
<Text $variation="400" $size="s">
|
||||||
|
{DateTime.fromISO(doc.updated_at).toRelative()}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
<DocToolBox doc={doc} />
|
<DocToolBox doc={doc} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<HorizontalSeparator />
|
||||||
$direction={isSmallMobile ? 'column' : 'row'}
|
</Box>
|
||||||
$align={isSmallMobile ? 'start' : 'center'}
|
|
||||||
$css="border-top:1px solid #eee"
|
|
||||||
$padding={{
|
|
||||||
horizontal: isMobile ? 'tiny' : 'big',
|
|
||||||
vertical: 'tiny',
|
|
||||||
}}
|
|
||||||
$gap="0.5rem 2rem"
|
|
||||||
$justify="space-between"
|
|
||||||
$wrap="wrap"
|
|
||||||
$position="relative"
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
$direction={isSmallMobile ? 'column' : 'row'}
|
|
||||||
$align={isSmallMobile ? 'start' : 'center'}
|
|
||||||
$gap="0.5rem 2rem"
|
|
||||||
$wrap="wrap"
|
|
||||||
>
|
|
||||||
<DocTagPublic doc={doc} />
|
|
||||||
<Text $size="s" $display="inline">
|
|
||||||
{t('Created at')} <strong>{formatDate(doc.created_at)}</strong>
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
<Text $size="s" $display="inline">
|
|
||||||
{t('Your role:')}{' '}
|
|
||||||
<strong>{transRole(currentDocRole(doc.abilities))}</strong>
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Card>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import {
|
|||||||
VariantType,
|
VariantType,
|
||||||
useToastProvider,
|
useToastProvider,
|
||||||
} from '@openfun/cunningham-react';
|
} from '@openfun/cunningham-react';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
import { Box, Text } from '@/components';
|
import { Box, Text } from '@/components';
|
||||||
import { useCunninghamTheme } from '@/cunningham';
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import { useHeadingStore } from '@/features/docs/doc-editor';
|
|
||||||
import {
|
import {
|
||||||
Doc,
|
Doc,
|
||||||
KEY_DOC,
|
KEY_DOC,
|
||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
useUpdateDoc,
|
useUpdateDoc,
|
||||||
} from '@/features/docs/doc-management';
|
} from '@/features/docs/doc-management';
|
||||||
import { useBroadcastStore, useResponsiveStore } from '@/stores';
|
import { useBroadcastStore, useResponsiveStore } from '@/stores';
|
||||||
import { isFirefox } from '@/utils/userAgent';
|
|
||||||
|
|
||||||
interface DocTitleProps {
|
interface DocTitleProps {
|
||||||
doc: Doc;
|
doc: Doc;
|
||||||
@@ -32,7 +31,7 @@ export const DocTitle = ({ doc }: DocTitleProps) => {
|
|||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
as="h2"
|
as="h2"
|
||||||
$margin={{ all: 'none', left: 'tiny' }}
|
$margin={{ all: 'none', left: 'none' }}
|
||||||
$size={isMobile ? 'h4' : 'h2'}
|
$size={isMobile ? 'h4' : 'h2'}
|
||||||
>
|
>
|
||||||
{doc.title}
|
{doc.title}
|
||||||
@@ -44,20 +43,18 @@ export const DocTitle = ({ doc }: DocTitleProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const DocTitleInput = ({ doc }: DocTitleProps) => {
|
const DocTitleInput = ({ doc }: DocTitleProps) => {
|
||||||
|
const { isDesktop } = useResponsiveStore();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { colorsTokens } = useCunninghamTheme();
|
const { colorsTokens } = useCunninghamTheme();
|
||||||
const [titleDisplay, setTitleDisplay] = useState(doc.title);
|
const [titleDisplay, setTitleDisplay] = useState(doc.title);
|
||||||
const { toast } = useToastProvider();
|
const { toast } = useToastProvider();
|
||||||
const { untitledDocument } = useTrans();
|
const { untitledDocument } = useTrans();
|
||||||
const isUntitled = titleDisplay === untitledDocument;
|
const isUntitled = titleDisplay === untitledDocument;
|
||||||
const { headings } = useHeadingStore();
|
|
||||||
const headingText = headings?.[0]?.contentText;
|
|
||||||
const debounceRef = useRef<NodeJS.Timeout>();
|
|
||||||
const { isMobile } = useResponsiveStore();
|
|
||||||
const { broadcast } = useBroadcastStore();
|
const { broadcast } = useBroadcastStore();
|
||||||
|
|
||||||
const { mutate: updateDoc } = useUpdateDoc({
|
const { mutate: updateDoc } = useUpdateDoc({
|
||||||
listInvalideQueries: [KEY_LIST_DOC],
|
listInvalideQueries: [KEY_DOC, KEY_LIST_DOC],
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
if (data.title !== untitledDocument) {
|
if (data.title !== untitledDocument) {
|
||||||
toast(t('Document title updated successfully'), VariantType.SUCCESS);
|
toast(t('Document title updated successfully'), VariantType.SUCCESS);
|
||||||
@@ -81,10 +78,7 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
|
|||||||
|
|
||||||
// If mutation we update
|
// If mutation we update
|
||||||
if (sanitizedTitle !== doc.title) {
|
if (sanitizedTitle !== doc.title) {
|
||||||
if (debounceRef.current) {
|
setTitleDisplay(sanitizedTitle);
|
||||||
clearTimeout(debounceRef.current);
|
|
||||||
debounceRef.current = undefined;
|
|
||||||
}
|
|
||||||
updateDoc({ id: doc.id, title: sanitizedTitle });
|
updateDoc({ id: doc.id, title: sanitizedTitle });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -98,74 +92,38 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnClick = () => {
|
|
||||||
if (isUntitled) {
|
|
||||||
setTitleDisplay('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTitleDisplay(doc.title);
|
|
||||||
}, [doc.title]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if ((!debounceRef.current && !isUntitled) || !headingText) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setTitleDisplay(headingText);
|
|
||||||
|
|
||||||
if (debounceRef.current) {
|
|
||||||
clearTimeout(debounceRef.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
debounceRef.current = setTimeout(() => {
|
|
||||||
handleTitleSubmit(headingText);
|
|
||||||
debounceRef.current = undefined;
|
|
||||||
}, 3000);
|
|
||||||
}, [isUntitled, handleTitleSubmit, headingText]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tooltip content={t('Rename')} placement="top">
|
<Tooltip content={t('Rename')} placement="top">
|
||||||
<Box
|
<Box
|
||||||
as="h2"
|
as="span"
|
||||||
$radius="4px"
|
role="textbox"
|
||||||
$padding={{ horizontal: 'tiny', vertical: '4px' }}
|
contentEditable
|
||||||
$margin="none"
|
defaultValue={isUntitled ? undefined : titleDisplay}
|
||||||
$minWidth="200px"
|
|
||||||
contentEditable={isFirefox() ? 'true' : 'plaintext-only'}
|
|
||||||
onClick={handleOnClick}
|
|
||||||
onBlurCapture={(e) =>
|
|
||||||
handleTitleSubmit(e.currentTarget.textContent || '')
|
|
||||||
}
|
|
||||||
onKeyDownCapture={handleKeyDown}
|
onKeyDownCapture={handleKeyDown}
|
||||||
suppressContentEditableWarning={true}
|
suppressContentEditableWarning={true}
|
||||||
$color={
|
aria-label={t('doc title input')}
|
||||||
isUntitled
|
onBlurCapture={(event) =>
|
||||||
? colorsTokens()['greyscale-200']
|
handleTitleSubmit(event.target.textContent || '')
|
||||||
: colorsTokens()['greyscale-text']
|
|
||||||
}
|
}
|
||||||
$css={`
|
$color={colorsTokens()['greyscale-text']}
|
||||||
${isUntitled && 'font-style: italic;'}
|
$margin={{ left: '-2px', right: '10px' }}
|
||||||
cursor: text;
|
$css={css`
|
||||||
font-size: ${isMobile ? '1.2rem' : '1.5rem'};
|
&[contenteditable='true']:empty:not(:focus):before {
|
||||||
transition: box-shadow 0.5s, border-color 0.5s;
|
content: '${untitledDocument}';
|
||||||
border: 1px dashed transparent;
|
color: grey;
|
||||||
|
pointer-events: none;
|
||||||
&:hover {
|
font-style: italic;
|
||||||
border-color: rgba(0, 123, 255, 0.25);
|
|
||||||
border-style: dashed;
|
|
||||||
}
|
}
|
||||||
|
font-size: ${isDesktop
|
||||||
|
? css`var(--c--theme--font--sizes--h2)`
|
||||||
|
: css`var(--c--theme--font--sizes--sm)`};
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
&:focus {
|
outline: none;
|
||||||
outline: none;
|
|
||||||
border-color: transparent;
|
|
||||||
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
|
||||||
}
|
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{titleDisplay}
|
{isUntitled ? '' : titleDisplay}
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -4,11 +4,19 @@ import {
|
|||||||
useToastProvider,
|
useToastProvider,
|
||||||
} from '@openfun/cunningham-react';
|
} from '@openfun/cunningham-react';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import React, { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
import { Box, DropButton, IconOptions } from '@/components';
|
import {
|
||||||
|
Box,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuOption,
|
||||||
|
Icon,
|
||||||
|
IconOptions,
|
||||||
|
} from '@/components';
|
||||||
import { useAuthStore } from '@/core';
|
import { useAuthStore } from '@/core';
|
||||||
|
import { useCunninghamTheme } from '@/cunningham';
|
||||||
import {
|
import {
|
||||||
useEditorStore,
|
useEditorStore,
|
||||||
usePanelEditorStore,
|
usePanelEditorStore,
|
||||||
@@ -32,10 +40,15 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
|||||||
query: { versionId },
|
query: { versionId },
|
||||||
} = useRouter();
|
} = useRouter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||||
|
|
||||||
|
const spacings = spacingsTokens();
|
||||||
|
const colors = colorsTokens();
|
||||||
|
|
||||||
const [isModalShareOpen, setIsModalShareOpen] = useState(false);
|
const [isModalShareOpen, setIsModalShareOpen] = useState(false);
|
||||||
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
|
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
|
||||||
const [isModalPDFOpen, setIsModalPDFOpen] = useState(false);
|
const [isModalPDFOpen, setIsModalPDFOpen] = useState(false);
|
||||||
const [isDropOpen, setIsDropOpen] = useState(false);
|
|
||||||
const { setIsPanelOpen, setIsPanelTableContentOpen } = usePanelEditorStore();
|
const { setIsPanelOpen, setIsPanelTableContentOpen } = usePanelEditorStore();
|
||||||
const [isModalVersionOpen, setIsModalVersionOpen] = useState(false);
|
const [isModalVersionOpen, setIsModalVersionOpen] = useState(false);
|
||||||
const { isSmallMobile } = useResponsiveStore();
|
const { isSmallMobile } = useResponsiveStore();
|
||||||
@@ -43,6 +56,66 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
|||||||
const { editor } = useEditorStore();
|
const { editor } = useEditorStore();
|
||||||
const { toast } = useToastProvider();
|
const { toast } = useToastProvider();
|
||||||
|
|
||||||
|
const options: DropdownMenuOption[] = [
|
||||||
|
...(isSmallMobile
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: t('Share'),
|
||||||
|
icon: 'upload',
|
||||||
|
callback: () => {
|
||||||
|
setIsModalShareOpen(true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Export'),
|
||||||
|
icon: 'download',
|
||||||
|
callback: () => {
|
||||||
|
setIsModalPDFOpen(true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{
|
||||||
|
label: t('Version history'),
|
||||||
|
icon: 'history',
|
||||||
|
disabled: !doc.abilities.versions_list,
|
||||||
|
callback: () => {
|
||||||
|
setIsPanelOpen(true);
|
||||||
|
setIsPanelTableContentOpen(false);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Table of contents'),
|
||||||
|
icon: 'summarize',
|
||||||
|
callback: () => {
|
||||||
|
setIsPanelOpen(true);
|
||||||
|
setIsPanelTableContentOpen(true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Copy as {{format}}', { format: 'Markdown' }),
|
||||||
|
icon: 'content_copy',
|
||||||
|
callback: () => {
|
||||||
|
void copyCurrentEditorToClipboard('markdown');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Copy as {{format}}', { format: 'HTML' }),
|
||||||
|
icon: 'content_copy',
|
||||||
|
callback: () => {
|
||||||
|
void copyCurrentEditorToClipboard('html');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Delete document'),
|
||||||
|
icon: 'delete',
|
||||||
|
disabled: !doc.abilities.destroy,
|
||||||
|
callback: () => {
|
||||||
|
setIsModalRemoveOpen(true);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const copyCurrentEditorToClipboard = async (
|
const copyCurrentEditorToClipboard = async (
|
||||||
asFormat: 'html' | 'markdown',
|
asFormat: 'html' | 'markdown',
|
||||||
) => {
|
) => {
|
||||||
@@ -87,9 +160,10 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Box $direction="row" $margin={{ left: 'auto' }} $gap="1rem">
|
<Box $direction="row" $margin={{ left: 'auto' }} $gap={spacings['2xs']}>
|
||||||
{authenticated && (
|
{authenticated && !isSmallMobile && (
|
||||||
<Button
|
<Button
|
||||||
|
color="primary-text"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsModalShareOpen(true);
|
setIsModalShareOpen(true);
|
||||||
}}
|
}}
|
||||||
@@ -98,91 +172,34 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
|||||||
{t('Share')}
|
{t('Share')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<DropButton
|
{!isSmallMobile && (
|
||||||
button={
|
<Button
|
||||||
<IconOptions
|
color="primary-text"
|
||||||
isOpen={isDropOpen}
|
icon={
|
||||||
aria-label={t('Open the document options')}
|
<Icon iconName="download" $theme="primary" $variation="800" />
|
||||||
/>
|
}
|
||||||
}
|
onClick={() => {
|
||||||
onOpenChange={(isOpen) => setIsDropOpen(isOpen)}
|
setIsModalPDFOpen(true);
|
||||||
isOpen={isDropOpen}
|
}}
|
||||||
>
|
size={isSmallMobile ? 'small' : 'medium'}
|
||||||
<Box>
|
/>
|
||||||
{doc.abilities.versions_list && (
|
)}
|
||||||
<Button
|
<DropdownMenu options={options}>
|
||||||
onClick={() => {
|
<IconOptions
|
||||||
setIsPanelOpen(true);
|
isHorizontal
|
||||||
setIsPanelTableContentOpen(false);
|
$theme="primary"
|
||||||
setIsDropOpen(false);
|
$radius={spacings['3xs']}
|
||||||
}}
|
$css={
|
||||||
color="primary-text"
|
isSmallMobile
|
||||||
icon={<span className="material-icons">history</span>}
|
? css`
|
||||||
size="small"
|
padding: 10px;
|
||||||
>
|
border: 1px solid ${colors['greyscale-300']};
|
||||||
{t('Version history')}
|
`
|
||||||
</Button>
|
: ''
|
||||||
)}
|
}
|
||||||
<Button
|
aria-label={t('Open the document options')}
|
||||||
onClick={() => {
|
/>
|
||||||
setIsPanelOpen(true);
|
</DropdownMenu>
|
||||||
setIsPanelTableContentOpen(true);
|
|
||||||
setIsDropOpen(false);
|
|
||||||
}}
|
|
||||||
color="primary-text"
|
|
||||||
icon={<span className="material-icons">summarize</span>}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{t('Table of contents')}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setIsModalPDFOpen(true);
|
|
||||||
setIsDropOpen(false);
|
|
||||||
}}
|
|
||||||
color="primary-text"
|
|
||||||
icon={<span className="material-icons">file_download</span>}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{t('Export')}
|
|
||||||
</Button>
|
|
||||||
{doc.abilities.destroy && (
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setIsModalRemoveOpen(true);
|
|
||||||
setIsDropOpen(false);
|
|
||||||
}}
|
|
||||||
color="primary-text"
|
|
||||||
icon={<span className="material-icons">delete</span>}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{t('Delete document')}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setIsDropOpen(false);
|
|
||||||
void copyCurrentEditorToClipboard('markdown');
|
|
||||||
}}
|
|
||||||
color="primary-text"
|
|
||||||
icon={<span className="material-icons">content_copy</span>}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{t('Copy as {{format}}', { format: 'Markdown' })}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setIsDropOpen(false);
|
|
||||||
void copyCurrentEditorToClipboard('html');
|
|
||||||
}}
|
|
||||||
color="primary-text"
|
|
||||||
icon={<span className="material-icons">content_copy</span>}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{t('Copy as {{format}}', { format: 'HTML' })}
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</DropButton>
|
|
||||||
</Box>
|
</Box>
|
||||||
{isModalShareOpen && (
|
{isModalShareOpen && (
|
||||||
<ModalShare onClose={() => setIsModalShareOpen(false)} doc={doc} />
|
<ModalShare onClose={() => setIsModalShareOpen(false)} doc={doc} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button } from '@openfun/cunningham-react';
|
import { Button } from '@openfun/cunningham-react';
|
||||||
import React, { PropsWithChildren, useState } from 'react';
|
import { PropsWithChildren, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { Box, DropButton, IconOptions, StyledLink, Text } from '@/components';
|
import { Box, DropButton, IconOptions, StyledLink, Text } from '@/components';
|
||||||
@@ -70,10 +70,7 @@ export const VersionItem = ({
|
|||||||
{isActive && versionId && (
|
{isActive && versionId && (
|
||||||
<DropButton
|
<DropButton
|
||||||
button={
|
button={
|
||||||
<IconOptions
|
<IconOptions aria-label={t('Open the version options')} />
|
||||||
isOpen={isDropOpen}
|
|
||||||
aria-label={t('Open the version options')}
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
onOpenChange={(isOpen) => setIsDropOpen(isOpen)}
|
onOpenChange={(isOpen) => setIsDropOpen(isOpen)}
|
||||||
isOpen={isDropOpen}
|
isOpen={isDropOpen}
|
||||||
|
|||||||
Reference in New Issue
Block a user