✨(frontend) enhance UI components and improve document management
- Updated DropdownMenu to include index-based styling for better visual consistency. - Refactored QuickSearchStyle to remove unnecessary transitions for smoother performance. - Adjusted modal styles in cunningham-style.css for improved layout. - Changed BlockNoteEditor to update block type from 'heading' to 'paragraph' for better content structure. - Enhanced DocHeader and DocToolBox components with updated color themes for improved visibility. - Modified ModalRemoveDoc to change size and clean up unnecessary props for better usability. - Improved Heading and TableContent components to handle empty states more gracefully. - Updated DocsGrid to conditionally render content based on document availability, enhancing user experience. - Refined LeftPanel components for better layout and visual hierarchy, including adjustments to padding and separators.
This commit is contained in:
committed by
Anthony LC
parent
684b77cbe6
commit
098df5c0b5
Binary file not shown.
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -77,7 +77,7 @@ export const DropdownMenu = ({
|
||||
{topMessage}
|
||||
</Text>
|
||||
)}
|
||||
{options.map((option) => {
|
||||
{options.map((option, index) => {
|
||||
if (option.show !== undefined && !option.show) {
|
||||
return;
|
||||
}
|
||||
@@ -104,6 +104,16 @@ export const DropdownMenu = ({
|
||||
$gap={spacings['base']}
|
||||
$css={css`
|
||||
border: none;
|
||||
${index === 0 &&
|
||||
css`
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
`}
|
||||
${index === options.length - 1 &&
|
||||
css`
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
`}
|
||||
font-size: var(--c--theme--font--sizes--sm);
|
||||
color: var(--c--theme--colors--greyscale-1000);
|
||||
font-weight: 500;
|
||||
|
||||
@@ -71,8 +71,6 @@ export const QuickSearchStyle = createGlobalStyle`
|
||||
flex:1;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
transition: 100ms ease;
|
||||
transition-property: height;
|
||||
}
|
||||
|
||||
[cmdk-vercel-shortcuts] {
|
||||
|
||||
@@ -545,8 +545,8 @@ input:-webkit-autofill:focus {
|
||||
color: var(--c--theme--colors--greyscale-600);
|
||||
}
|
||||
|
||||
.c__modal__footer {
|
||||
margin-top: -1rem;
|
||||
.c__modal__close .c__button {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.c__modal--full .c__modal__content {
|
||||
|
||||
@@ -10,7 +10,7 @@ import * as Y from 'yjs';
|
||||
|
||||
import { Box, TextErrors } from '@/components';
|
||||
import { useAuthStore } from '@/core/auth';
|
||||
import { Doc, Role, currentDocRole } from '@/features/docs/doc-management';
|
||||
import { Doc } from '@/features/docs/doc-management';
|
||||
|
||||
import { useUploadFile } from '../hook';
|
||||
import { useHeadings } from '../hook/useHeadings';
|
||||
@@ -167,23 +167,6 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
|
||||
);
|
||||
useHeadings(editor);
|
||||
|
||||
/**
|
||||
* With the collaboration it gets complicated to create the initial block
|
||||
* better to let Blocknote manage, then we update the block with the content.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (doc.content || currentDocRole(doc.abilities) !== Role.OWNER) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
editor.updateBlock(editor.document[0], {
|
||||
type: 'heading',
|
||||
content: '',
|
||||
});
|
||||
}, 100);
|
||||
}, [editor, doc.content, doc.abilities]);
|
||||
|
||||
useEffect(() => {
|
||||
setEditor(editor);
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
|
||||
{docIsPublic && (
|
||||
<Box
|
||||
aria-label={t('Public document')}
|
||||
$color={colors['primary-600']}
|
||||
$color={colors['primary-800']}
|
||||
$background={colors['primary-100']}
|
||||
$radius={spacings['3xs']}
|
||||
$direction="row"
|
||||
@@ -54,8 +54,15 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
|
||||
border: 1px solid var(--c--theme--colors--primary-300, #e3e3fd);
|
||||
`}
|
||||
>
|
||||
<Icon data-testid="public-icon" iconName="public" />
|
||||
<Text>{t('Public document')}</Text>
|
||||
<Icon
|
||||
$theme="primary"
|
||||
$variation="800"
|
||||
data-testid="public-icon"
|
||||
iconName="public"
|
||||
/>
|
||||
<Text $theme="primary" $variation="800">
|
||||
{t('Public document')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
<Box $direction="row" $align="center" $width="100%">
|
||||
|
||||
@@ -212,6 +212,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
$theme="primary"
|
||||
$padding={{ all: 'xs' }}
|
||||
$css={css`
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
background-color: ${colors['greyscale-100']};
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ export const ModalRemoveDoc = ({ onClose, doc }: ModalRemoveDocProps) => {
|
||||
<Modal
|
||||
isOpen
|
||||
closeOnClickOutside
|
||||
hideCloseButton
|
||||
onClose={() => onClose()}
|
||||
rightActions={
|
||||
<>
|
||||
@@ -72,17 +71,14 @@ export const ModalRemoveDoc = ({ onClose, doc }: ModalRemoveDocProps) => {
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
size={ModalSize.SMALL}
|
||||
size={ModalSize.MEDIUM}
|
||||
title={
|
||||
<Text $size="h6" as="h6" $margin={{ all: '0' }} $align="flex-start">
|
||||
{t('Delete a doc')}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
$margin={{ bottom: 'xl' }}
|
||||
aria-label={t('Content modal to delete document')}
|
||||
>
|
||||
<Box aria-label={t('Content modal to delete document')}>
|
||||
{!isError && (
|
||||
<Text $size="sm" $variation="600">
|
||||
{t('Are you sure you want to delete the document "{{title}}"?', {
|
||||
|
||||
@@ -65,7 +65,7 @@ export const Heading = ({
|
||||
$width="100%"
|
||||
$padding={{ vertical: 'xtiny', left: leftPaddingMap[level] }}
|
||||
$variation={isActive ? '1000' : '700'}
|
||||
$weight={isActive ? 'bold' : 'normal'}
|
||||
$weight={isHighlight ? 'bold' : 'normal'}
|
||||
$css="overflow-wrap: break-word;"
|
||||
$hasTransition
|
||||
aria-selected={isHighlight}
|
||||
|
||||
@@ -78,7 +78,12 @@ export const TableContent = () => {
|
||||
setIsHover(false);
|
||||
};
|
||||
|
||||
if (!editor) {
|
||||
if (
|
||||
!editor ||
|
||||
!headings ||
|
||||
headings.length === 0 ||
|
||||
(headings.length === 1 && !headings[0].contentText)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Loader } from '@openfun/cunningham-react';
|
||||
import { Button } from '@openfun/cunningham-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { InView } from 'react-intersection-observer';
|
||||
import { css } from 'styled-components';
|
||||
@@ -36,7 +36,7 @@ export const DocsGrid = ({
|
||||
}),
|
||||
});
|
||||
const loading = isFetching || isLoading;
|
||||
|
||||
const hasDocs = data?.pages.some((page) => page.results.length > 0);
|
||||
const loadMore = (inView: boolean) => {
|
||||
if (!inView || loading) {
|
||||
return;
|
||||
@@ -63,7 +63,7 @@ export const DocsGrid = ({
|
||||
overflow-y: auto;
|
||||
`}
|
||||
>
|
||||
<DocsGridLoader isLoading={isRefetching} />
|
||||
<DocsGridLoader isLoading={isRefetching || loading} />
|
||||
<Card
|
||||
data-testid="docs-grid"
|
||||
$height="100%"
|
||||
@@ -87,48 +87,46 @@ export const DocsGrid = ({
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
<Box $gap="6px">
|
||||
<Box
|
||||
$direction="row"
|
||||
$padding={{ horizontal: 'xs' }}
|
||||
$gap="20px"
|
||||
data-testid="docs-grid-header"
|
||||
>
|
||||
<Box $flex={6} $padding="3xs">
|
||||
<Text $size="xs" $variation="600" $weight="500">
|
||||
{t('Name')}
|
||||
</Text>
|
||||
</Box>
|
||||
{isDesktop && (
|
||||
<Box $flex={2} $padding="3xs">
|
||||
<Text $size="xs" $weight="500" $variation="600">
|
||||
{t('Updated at')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box $flex={1.15} $align="flex-end" $padding="3xs" />
|
||||
</Box>
|
||||
|
||||
{/* Body */}
|
||||
{data?.pages.map((currentPage) => {
|
||||
return currentPage.results.map((doc) => (
|
||||
<DocsGridItem doc={doc} key={doc.id} />
|
||||
));
|
||||
})}
|
||||
</Box>
|
||||
|
||||
{loading && (
|
||||
<Box
|
||||
data-testid="docs-grid-loader"
|
||||
$padding="md"
|
||||
$align="center"
|
||||
$justify="center"
|
||||
$width="100%"
|
||||
>
|
||||
<Loader />
|
||||
{!hasDocs && (
|
||||
<Box $padding={{ vertical: 'sm' }} $align="center" $justify="center">
|
||||
<Text $size="sm" $variation="600" $weight="700">
|
||||
{t('No documents found')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{hasDocs && (
|
||||
<Box $gap="6px">
|
||||
<Box
|
||||
$direction="row"
|
||||
$padding={{ horizontal: 'xs' }}
|
||||
$gap="20px"
|
||||
data-testid="docs-grid-header"
|
||||
>
|
||||
<Box $flex={6} $padding="3xs">
|
||||
<Text $size="xs" $variation="600" $weight="500">
|
||||
{t('Name')}
|
||||
</Text>
|
||||
</Box>
|
||||
{isDesktop && (
|
||||
<Box $flex={2} $padding="3xs">
|
||||
<Text $size="xs" $weight="500" $variation="600">
|
||||
{t('Updated at')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box $flex={1.15} $align="flex-end" $padding="3xs" />
|
||||
</Box>
|
||||
|
||||
{/* Body */}
|
||||
{data?.pages.map((currentPage) => {
|
||||
return currentPage.results.map((doc) => (
|
||||
<DocsGridItem doc={doc} key={doc.id} />
|
||||
));
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{hasNextPage && !loading && (
|
||||
<InView
|
||||
data-testid="infinite-scroll-trigger"
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './DocsGrid';
|
||||
export * from './DocsGridActions';
|
||||
export * from './SimpleDocItem';
|
||||
export * from './DocsGridLoader';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './components';
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './doc-editor';
|
||||
export * from './doc-management';
|
||||
export * from './docs-grid';
|
||||
|
||||
@@ -79,7 +79,6 @@ export const LeftPanelTargetFilters = () => {
|
||||
font-weight: ${isActive ? 700 : undefined};
|
||||
&:hover {
|
||||
background-color: ${colors['greyscale-100']};
|
||||
font-weight: 700;
|
||||
}
|
||||
`}
|
||||
>
|
||||
|
||||
@@ -22,7 +22,7 @@ export const LeftPanelContent = () => {
|
||||
flex: 0 0 auto;
|
||||
`}
|
||||
>
|
||||
<SeparatedSection>
|
||||
<SeparatedSection showSeparator={false}>
|
||||
<LeftPanelTargetFilters />
|
||||
</SeparatedSection>
|
||||
</Box>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, SeparatedSection } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { useDocStore } from '@/features/docs';
|
||||
import { SimpleDocItem } from '@/features/docs/docs-grid/components/SimpleDocItem';
|
||||
|
||||
export const LeftPanelDocContent = () => {
|
||||
const { currentDoc } = useDocStore();
|
||||
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const spacing = spacingsTokens();
|
||||
if (!currentDoc) {
|
||||
return null;
|
||||
}
|
||||
@@ -17,7 +21,15 @@ export const LeftPanelDocContent = () => {
|
||||
>
|
||||
<SeparatedSection showSeparator={false}>
|
||||
<Box $padding={{ horizontal: 'sm' }}>
|
||||
<SimpleDocItem doc={currentDoc} showAccesses={true} />
|
||||
<Box
|
||||
$css={css`
|
||||
padding: ${spacing['2xs']};
|
||||
border-radius: 4px;
|
||||
background-color: var(--c--theme--colors--greyscale-100);
|
||||
`}
|
||||
>
|
||||
<SimpleDocItem doc={currentDoc} showAccesses={true} />
|
||||
</Box>
|
||||
</Box>
|
||||
</SeparatedSection>
|
||||
</Box>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useModal } from '@openfun/cunningham-react';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, StyledLink } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc, DocsGridActions, SimpleDocItem } from '@/features/docs';
|
||||
import { DocShareModal } from '@/features/docs/doc-share/component/DocShareModal';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
type LeftPanelFavoriteItemProps = {
|
||||
doc: Doc;
|
||||
};
|
||||
|
||||
export const LeftPanelFavoriteItem = ({ doc }: LeftPanelFavoriteItemProps) => {
|
||||
const shareModal = useModal();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const spacing = spacingsTokens();
|
||||
return (
|
||||
<Box
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$justify="space-between"
|
||||
$css={css`
|
||||
padding: ${spacing['2xs']};
|
||||
border-radius: 4px;
|
||||
.pinned-actions {
|
||||
opacity: ${isDesktop ? 0 : 1};
|
||||
}
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
|
||||
background-color: var(--c--theme--colors--greyscale-100);
|
||||
.pinned-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`}
|
||||
key={doc.id}
|
||||
>
|
||||
<StyledLink href={`/docs/${doc.id}`}>
|
||||
<SimpleDocItem showAccesses doc={doc} />
|
||||
</StyledLink>
|
||||
<div className="pinned-actions">
|
||||
<DocsGridActions doc={doc} openShareModal={shareModal.open} />
|
||||
</div>
|
||||
{shareModal.isOpen && (
|
||||
<DocShareModal doc={doc} onClose={shareModal.close} />
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,16 +1,11 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import {
|
||||
Box,
|
||||
InfiniteScroll,
|
||||
SeparatedSection,
|
||||
StyledLink,
|
||||
Text,
|
||||
} from '@/components';
|
||||
import { Box, InfiniteScroll, Text } from '@/components';
|
||||
import { HorizontalSeparator } from '@/components/separators/HorizontalSeparator';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { useInfiniteDocs } from '@/features/docs';
|
||||
import { SimpleDocItem } from '@/features/docs/docs-grid/components/SimpleDocItem';
|
||||
|
||||
import { LeftPanelFavoriteItem } from './LeftPanelFavoriteItem';
|
||||
|
||||
export const LeftPanelFavorites = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -23,17 +18,18 @@ export const LeftPanelFavorites = () => {
|
||||
is_favorite: true,
|
||||
});
|
||||
|
||||
const invitations = docs.data?.pages.flatMap((page) => page.results) || [];
|
||||
const favoriteDocs = docs.data?.pages.flatMap((page) => page.results) || [];
|
||||
|
||||
if (invitations.length === 0) {
|
||||
if (favoriteDocs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SeparatedSection showSeparator={true}>
|
||||
<Box>
|
||||
<HorizontalSeparator $withPadding={false} />
|
||||
<Box
|
||||
$justify="center"
|
||||
$padding={{ horizontal: 'sm' }}
|
||||
$padding={{ horizontal: 'sm', top: 'sm' }}
|
||||
$gap={spacing['2xs']}
|
||||
$height="100%"
|
||||
data-testid="left-panel-favorites"
|
||||
@@ -51,25 +47,11 @@ export const LeftPanelFavorites = () => {
|
||||
isLoading={docs.isFetchingNextPage}
|
||||
next={() => void docs.fetchNextPage()}
|
||||
>
|
||||
{invitations.map((doc) => (
|
||||
<Box
|
||||
$css={css`
|
||||
padding: ${spacing['2xs']};
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--c--theme--colors--greyscale-100);
|
||||
}
|
||||
`}
|
||||
key={doc.id}
|
||||
>
|
||||
<StyledLink href={`/docs/${doc.id}`}>
|
||||
<SimpleDocItem showAccesses doc={doc} />
|
||||
</StyledLink>
|
||||
</Box>
|
||||
{favoriteDocs.map((doc) => (
|
||||
<LeftPanelFavoriteItem key={doc.id} doc={doc} />
|
||||
))}
|
||||
</InfiniteScroll>
|
||||
</Box>
|
||||
</SeparatedSection>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user