💄(frontend) refactor document sharing and grid components
Improvements: - Added disabled state for dropdown menus in share settings - Updated document grid layout and responsiveness - Simplified sharing and access count logic - Improved tooltips and visibility of shared documents - Created a new responsive doc grid hook
This commit is contained in:
@@ -20,12 +20,14 @@ export type DropdownMenuProps = {
|
||||
showArrow?: boolean;
|
||||
label?: string;
|
||||
arrowCss?: BoxProps['$css'];
|
||||
disabled?: boolean;
|
||||
topMessage?: string;
|
||||
};
|
||||
|
||||
export const DropdownMenu = ({
|
||||
options,
|
||||
children,
|
||||
disabled = false,
|
||||
showArrow = false,
|
||||
arrowCss,
|
||||
label,
|
||||
@@ -40,6 +42,10 @@ export const DropdownMenu = ({
|
||||
setIsOpen(isOpen);
|
||||
};
|
||||
|
||||
if (disabled) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropButton
|
||||
isOpen={isOpen}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import Link from 'next/link';
|
||||
import styled from 'styled-components';
|
||||
import styled, { RuleSet } from 'styled-components';
|
||||
|
||||
export interface LinkProps {
|
||||
$css?: string;
|
||||
$css?: string | RuleSet<object>;
|
||||
}
|
||||
|
||||
export const StyledLink = styled(Link)<LinkProps>`
|
||||
@@ -12,5 +12,5 @@ export const StyledLink = styled(Link)<LinkProps>`
|
||||
color: #ffffff;
|
||||
}
|
||||
display: flex;
|
||||
${({ $css }) => $css && `${$css};`}
|
||||
${({ $css }) => $css && (typeof $css === 'string' ? `${$css};` : $css)}
|
||||
`;
|
||||
|
||||
@@ -42,7 +42,7 @@ export const DocHeader = ({ doc }: DocHeaderProps) => {
|
||||
<Box
|
||||
aria-label={t('Public document')}
|
||||
$color={colors['primary-800']}
|
||||
$background={colors['primary-100']}
|
||||
$background={colors['primary-050']}
|
||||
$radius={spacings['3xs']}
|
||||
$direction="row"
|
||||
$padding="xs"
|
||||
|
||||
@@ -18,11 +18,7 @@ import {
|
||||
} from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { useEditorStore } from '@/features/docs/doc-editor/';
|
||||
import {
|
||||
Doc,
|
||||
ModalRemoveDoc,
|
||||
useCopyDocLink,
|
||||
} from '@/features/docs/doc-management';
|
||||
import { Doc, ModalRemoveDoc } from '@/features/docs/doc-management';
|
||||
import { DocShareModal } from '@/features/docs/doc-share';
|
||||
import {
|
||||
KEY_LIST_DOC_VERSIONS,
|
||||
@@ -41,8 +37,6 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
const hasAccesses = doc.nb_accesses > 1;
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const copyDocLink = useCopyDocLink(doc.id);
|
||||
|
||||
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||
|
||||
const spacings = spacingsTokens();
|
||||
@@ -55,24 +49,15 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
|
||||
const { isSmallMobile, isDesktop } = useResponsiveStore();
|
||||
const { editor } = useEditorStore();
|
||||
|
||||
const { toast } = useToastProvider();
|
||||
const canViewAccesses = doc.abilities.accesses_view;
|
||||
|
||||
const options: DropdownMenuOption[] = [
|
||||
...(isSmallMobile
|
||||
? [
|
||||
{
|
||||
label: canViewAccesses ? t('Share') : t('Copy link'),
|
||||
icon: canViewAccesses ? 'group' : 'link',
|
||||
|
||||
callback: () => {
|
||||
if (canViewAccesses) {
|
||||
modalShare.open();
|
||||
return;
|
||||
}
|
||||
copyDocLink();
|
||||
},
|
||||
label: t('Share'),
|
||||
icon: 'group',
|
||||
callback: modalShare.open,
|
||||
},
|
||||
{
|
||||
label: t('Export'),
|
||||
@@ -165,7 +150,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
$margin={{ left: 'auto' }}
|
||||
$gap={spacings['2xs']}
|
||||
>
|
||||
{canViewAccesses && !isSmallMobile && (
|
||||
{!isSmallMobile && (
|
||||
<>
|
||||
{!hasAccesses && (
|
||||
<Button
|
||||
@@ -199,23 +184,13 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
|
||||
}}
|
||||
size={isSmallMobile ? 'small' : 'medium'}
|
||||
>
|
||||
{doc.nb_accesses - 1}
|
||||
{doc.nb_accesses}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!canViewAccesses && !isSmallMobile && (
|
||||
<Button
|
||||
color="tertiary-text"
|
||||
onClick={() => {
|
||||
copyDocLink();
|
||||
}}
|
||||
size={isSmallMobile ? 'small' : 'medium'}
|
||||
>
|
||||
{t('Copy link')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{!isSmallMobile && (
|
||||
<Button
|
||||
color="tertiary-text"
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { createGlobalStyle, css } from 'styled-components';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { Box, LoadMoreText } from '@/components';
|
||||
import { Box, HorizontalSeparator, LoadMoreText, Text } from '@/components';
|
||||
import {
|
||||
QuickSearch,
|
||||
QuickSearchData,
|
||||
@@ -56,7 +56,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
const [userQuery, setUserQuery] = useState('');
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
const [listHeight, setListHeight] = useState<string>('0px');
|
||||
const [listHeight, setListHeight] = useState<string>('400px');
|
||||
const canShare = doc.abilities.accesses_manage;
|
||||
const canViewAccesses = doc.abilities.accesses_view;
|
||||
const showMemberSection = inputValue === '' && selectedUsers.length === 0;
|
||||
@@ -95,7 +95,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
count === 1
|
||||
? t('Document owner')
|
||||
: t('Share with {{count}} users', {
|
||||
count: count - 1,
|
||||
count: count,
|
||||
}),
|
||||
elements: members,
|
||||
endActions: membersQuery.hasNextPage
|
||||
@@ -169,10 +169,6 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
};
|
||||
|
||||
const handleRef = (node: HTMLDivElement) => {
|
||||
if (!canViewAccesses) {
|
||||
setListHeight('0px');
|
||||
return;
|
||||
}
|
||||
const inputHeight = canShare ? 70 : 0;
|
||||
const marginTop = 11;
|
||||
const footerHeight = node?.clientHeight ?? 0;
|
||||
@@ -198,6 +194,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
aria-label={t('Share modal')}
|
||||
$height={canViewAccesses ? modalContentHeight : 'auto'}
|
||||
$overflow="hidden"
|
||||
className="noPadding"
|
||||
$justify="space-between"
|
||||
>
|
||||
<Box
|
||||
@@ -209,7 +206,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
}
|
||||
`}
|
||||
>
|
||||
<div ref={selectedUsersRef}>
|
||||
<Box ref={selectedUsersRef}>
|
||||
{canShare && selectedUsers.length > 0 && (
|
||||
<Box
|
||||
$padding={{ horizontal: 'base' }}
|
||||
@@ -227,59 +224,76 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
{!canViewAccesses && <HorizontalSeparator />}
|
||||
</Box>
|
||||
|
||||
<Box data-testid="doc-share-quick-search">
|
||||
<QuickSearch
|
||||
onFilter={(str) => {
|
||||
setInputValue(str);
|
||||
onFilter(str);
|
||||
}}
|
||||
inputValue={inputValue}
|
||||
showInput={canShare}
|
||||
loading={searchUsersQuery.isLoading}
|
||||
placeholder={t('Type a name or email')}
|
||||
>
|
||||
{canViewAccesses && (
|
||||
<>
|
||||
{!showMemberSection && inputValue !== '' && (
|
||||
<QuickSearchGroup
|
||||
group={searchUserData}
|
||||
onSelect={onSelect}
|
||||
renderElement={(user) => (
|
||||
<DocShareModalInviteUserRow user={user} />
|
||||
)}
|
||||
/>
|
||||
{!canViewAccesses && (
|
||||
<Box $height={listHeight} $align="center" $justify="center">
|
||||
<Text
|
||||
$maxWidth="320px"
|
||||
$textAlign="center"
|
||||
$variation="600"
|
||||
$size="sm"
|
||||
>
|
||||
{t(
|
||||
'You do not have permission to view users sharing this document or modify link settings.',
|
||||
)}
|
||||
{showMemberSection && (
|
||||
<>
|
||||
{invitationsData.elements.length > 0 && (
|
||||
<Box aria-label={t('List invitation card')}>
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
{canViewAccesses && (
|
||||
<QuickSearch
|
||||
onFilter={(str) => {
|
||||
setInputValue(str);
|
||||
onFilter(str);
|
||||
}}
|
||||
inputValue={inputValue}
|
||||
showInput={canShare}
|
||||
loading={searchUsersQuery.isLoading}
|
||||
placeholder={t('Type a name or email')}
|
||||
>
|
||||
{canViewAccesses && (
|
||||
<>
|
||||
{!showMemberSection && inputValue !== '' && (
|
||||
<QuickSearchGroup
|
||||
group={searchUserData}
|
||||
onSelect={onSelect}
|
||||
renderElement={(user) => (
|
||||
<DocShareModalInviteUserRow user={user} />
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{showMemberSection && (
|
||||
<>
|
||||
{invitationsData.elements.length > 0 && (
|
||||
<Box aria-label={t('List invitation card')}>
|
||||
<QuickSearchGroup
|
||||
group={invitationsData}
|
||||
renderElement={(invitation) => (
|
||||
<DocShareInvitationItem
|
||||
doc={doc}
|
||||
invitation={invitation}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box aria-label={t('List members card')}>
|
||||
<QuickSearchGroup
|
||||
group={invitationsData}
|
||||
renderElement={(invitation) => (
|
||||
<DocShareInvitationItem
|
||||
doc={doc}
|
||||
invitation={invitation}
|
||||
/>
|
||||
group={membersData}
|
||||
renderElement={(access) => (
|
||||
<DocShareMemberItem doc={doc} access={access} />
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box aria-label={t('List members card')}>
|
||||
<QuickSearchGroup
|
||||
group={membersData}
|
||||
renderElement={(access) => (
|
||||
<DocShareMemberItem doc={doc} access={access} />
|
||||
)}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</QuickSearch>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</QuickSearch>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -13,8 +13,6 @@ type Props = {
|
||||
};
|
||||
|
||||
export const DocShareModalFooter = ({ doc, onClose }: Props) => {
|
||||
const canShare = doc.abilities.accesses_manage;
|
||||
|
||||
const copyDocLink = useCopyDocLink(doc.id);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
@@ -24,12 +22,10 @@ export const DocShareModalFooter = ({ doc, onClose }: Props) => {
|
||||
`}
|
||||
>
|
||||
<HorizontalSeparator $withPadding={true} />
|
||||
{canShare && (
|
||||
<>
|
||||
<DocVisibility doc={doc} />
|
||||
<HorizontalSeparator />
|
||||
</>
|
||||
)}
|
||||
|
||||
<DocVisibility doc={doc} />
|
||||
<HorizontalSeparator />
|
||||
|
||||
<Box
|
||||
$direction="row"
|
||||
$justify="space-between"
|
||||
|
||||
@@ -26,10 +26,10 @@ export const DocShareModalInviteUserRow = ({ user }: Props) => {
|
||||
color: var(--c--theme--colors--greyscale-400);
|
||||
`}
|
||||
>
|
||||
<Text $theme="primary" $variation="600">
|
||||
<Text $theme="primary" $variation="800">
|
||||
{t('Add')}
|
||||
</Text>
|
||||
<Icon $theme="primary" $variation="600" iconName="add" />
|
||||
<Icon $theme="primary" $variation="800" iconName="add" />
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -34,6 +34,7 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
|
||||
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
|
||||
const spacing = spacingsTokens();
|
||||
const colors = colorsTokens();
|
||||
const canManage = doc.abilities.accesses_manage;
|
||||
const [linkReach, setLinkReach] = useState<LinkReach>(doc.link_reach);
|
||||
const [docLinkRole, setDocLinkRole] = useState<LinkRole>(doc.link_role);
|
||||
const { linkModeTranslations, linkReachChoices, linkReachTranslations } =
|
||||
@@ -106,29 +107,35 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
|
||||
$direction="row"
|
||||
$align={isDesktop ? 'center' : undefined}
|
||||
$padding={{ horizontal: '2xs' }}
|
||||
$gap={spacing['3xs']}
|
||||
$gap={canManage ? spacing['3xs'] : spacing['base']}
|
||||
>
|
||||
<DropdownMenu
|
||||
label={t('Visibility')}
|
||||
arrowCss={css`
|
||||
color: ${colors['primary-800']} !important;
|
||||
`}
|
||||
disabled={!doc.abilities.accesses_manage}
|
||||
showArrow={true}
|
||||
options={linkReachOptions}
|
||||
>
|
||||
<Box $direction="row" $align="center" $gap={spacing['3xs']}>
|
||||
<Icon
|
||||
$theme="primary"
|
||||
$variation="800"
|
||||
$theme={canManage ? 'primary' : 'greyscale'}
|
||||
$variation={canManage ? '800' : '600'}
|
||||
iconName={linkReachChoices[linkReach].icon}
|
||||
/>
|
||||
<Text $theme="primary" $variation="800">
|
||||
<Text
|
||||
$theme={canManage ? 'primary' : 'greyscale'}
|
||||
$variation={canManage ? '800' : '600'}
|
||||
$weight="500"
|
||||
$size="md"
|
||||
>
|
||||
{linkReachChoices[linkReach].label}
|
||||
</Text>
|
||||
</Box>
|
||||
</DropdownMenu>
|
||||
{isDesktop && (
|
||||
<Text $size="xs" $variation="600">
|
||||
<Text $size="xs" $variation="600" $weight="400">
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
@@ -137,6 +144,7 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
|
||||
<Box $direction="row" $align="center" $gap={spacing['3xs']}>
|
||||
{linkReach !== LinkReach.RESTRICTED && (
|
||||
<DropdownMenu
|
||||
disabled={!canManage}
|
||||
showArrow={true}
|
||||
options={linkMode}
|
||||
label={t('Visibility mode')}
|
||||
|
||||
@@ -26,7 +26,7 @@ export const useTranslatedShareSettings = () => {
|
||||
},
|
||||
[LinkReach.AUTHENTICATED]: {
|
||||
label: linkReachTranslations[LinkReach.AUTHENTICATED],
|
||||
icon: 'corporate_fare',
|
||||
icon: 'vpn_lock',
|
||||
value: LinkReach.AUTHENTICATED,
|
||||
descriptionReadOnly: t(
|
||||
'Anyone with the link can view the document if they are logged in',
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from '@/features/docs/doc-management';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useResponsiveDocGrid } from '../hooks/useResponsiveDocGrid';
|
||||
|
||||
import { DocsGridItem } from './DocsGridItem';
|
||||
import { DocsGridLoader } from './DocsGridLoader';
|
||||
|
||||
@@ -22,6 +24,7 @@ export const DocsGrid = ({
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const { flexLeft, flexRight } = useResponsiveDocGrid();
|
||||
|
||||
const {
|
||||
data,
|
||||
@@ -101,23 +104,21 @@ export const DocsGrid = ({
|
||||
<Box
|
||||
$direction="row"
|
||||
$padding={{ horizontal: 'xs' }}
|
||||
$gap="20px"
|
||||
$gap="10px"
|
||||
data-testid="docs-grid-header"
|
||||
>
|
||||
<Box $flex={6} $padding="3xs">
|
||||
<Box $flex={flexLeft} $padding="3xs">
|
||||
<Text $size="xs" $variation="600" $weight="500">
|
||||
{t('Name')}
|
||||
</Text>
|
||||
</Box>
|
||||
{isDesktop && (
|
||||
<Box $flex={2} $padding="3xs">
|
||||
<Box $flex={flexRight} $padding={{ vertical: '3xs' }}>
|
||||
<Text $size="xs" $weight="500" $variation="600">
|
||||
{t('Updated at')}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box $flex={1.15} $align="flex-end" $padding="3xs" />
|
||||
</Box>
|
||||
|
||||
{/* Body */}
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
Doc,
|
||||
KEY_LIST_DOC,
|
||||
ModalRemoveDoc,
|
||||
useCopyDocLink,
|
||||
useCreateFavoriteDoc,
|
||||
useDeleteFavoriteDoc,
|
||||
} from '@/features/docs/doc-management';
|
||||
@@ -22,10 +21,6 @@ export const DocsGridActions = ({
|
||||
}: DocsGridActionsProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const copyDocLink = useCopyDocLink(doc.id);
|
||||
|
||||
const canViewAccesses = doc.abilities.accesses_view;
|
||||
|
||||
const deleteModal = useModal();
|
||||
|
||||
const removeFavoriteDoc = useDeleteFavoriteDoc({
|
||||
@@ -49,14 +44,10 @@ export const DocsGridActions = ({
|
||||
testId: `docs-grid-actions-${doc.is_favorite ? 'unpin' : 'pin'}-${doc.id}`,
|
||||
},
|
||||
{
|
||||
label: canViewAccesses ? t('Share') : t('Copy link'),
|
||||
icon: canViewAccesses ? 'group' : 'link',
|
||||
label: t('Share'),
|
||||
icon: 'group',
|
||||
callback: () => {
|
||||
if (canViewAccesses) {
|
||||
openShareModal?.();
|
||||
return;
|
||||
}
|
||||
copyDocLink();
|
||||
openShareModal?.();
|
||||
},
|
||||
|
||||
testId: `docs-grid-actions-share-${doc.id}`,
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
import { useModal } from '@openfun/cunningham-react';
|
||||
import { Tooltip, useModal } from '@openfun/cunningham-react';
|
||||
import { DateTime } from 'luxon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, StyledLink, Text } from '@/components';
|
||||
import { Doc } from '@/features/docs/doc-management';
|
||||
import { Box, Icon, StyledLink, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc, LinkReach } from '@/features/docs/doc-management';
|
||||
import { DocShareModal } from '@/features/docs/doc-share';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useResponsiveDocGrid } from '../hooks/useResponsiveDocGrid';
|
||||
|
||||
import { DocsGridActions } from './DocsGridActions';
|
||||
import { DocsGridItemSharedButton } from './DocsGridItemSharedButton';
|
||||
import { SimpleDocItem } from './SimpleDocItem';
|
||||
|
||||
type DocsGridItemProps = {
|
||||
doc: Doc;
|
||||
};
|
||||
export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
|
||||
const { flexLeft, flexRight } = useResponsiveDocGrid();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const spacings = spacingsTokens();
|
||||
const shareModal = useModal();
|
||||
const isPublic = doc.link_reach === LinkReach.PUBLIC;
|
||||
const isAuthenticated = doc.link_reach === LinkReach.AUTHENTICATED;
|
||||
|
||||
const showAccesses = isPublic || isAuthenticated;
|
||||
|
||||
const handleShareClick = () => {
|
||||
shareModal.open();
|
||||
@@ -29,8 +39,8 @@ export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
|
||||
$direction="row"
|
||||
$width="100%"
|
||||
$align="center"
|
||||
$gap="20px"
|
||||
role="row"
|
||||
$gap="20px"
|
||||
$padding={{ vertical: '2xs', horizontal: isDesktop ? 'base' : 'xs' }}
|
||||
$css={css`
|
||||
cursor: pointer;
|
||||
@@ -41,39 +51,71 @@ export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
|
||||
`}
|
||||
>
|
||||
<StyledLink
|
||||
$css="flex: 8; align-items: center;"
|
||||
$css={css`
|
||||
flex: ${flexLeft};
|
||||
align-items: center;
|
||||
`}
|
||||
href={`/docs/${doc.id}`}
|
||||
>
|
||||
<Box
|
||||
data-testid={`docs-grid-name-${doc.id}`}
|
||||
$flex={6}
|
||||
$padding={{ right: 'base' }}
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$gap={spacings.xs}
|
||||
$flex={flexLeft}
|
||||
$padding={{ right: 'md' }}
|
||||
>
|
||||
<SimpleDocItem isPinned={doc.is_favorite} doc={doc} />
|
||||
{showAccesses && isDesktop && (
|
||||
<>
|
||||
<Tooltip
|
||||
content={
|
||||
<Text $textAlign="center" $variation="000">
|
||||
{isPublic
|
||||
? t('Accessible to anyone')
|
||||
: t('Accessible to authenticated users')}
|
||||
</Text>
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
<div>
|
||||
<Icon
|
||||
$theme="greyscale"
|
||||
$variation="600"
|
||||
$size="14px"
|
||||
iconName={isPublic ? 'public' : 'vpn_lock'}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
{isDesktop && (
|
||||
<Box $flex={2}>
|
||||
<Text $variation="600" $size="xs">
|
||||
{DateTime.fromISO(doc.updated_at).toRelative()}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</StyledLink>
|
||||
|
||||
<Box
|
||||
$flex={1.15}
|
||||
$flex={flexRight}
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$justify="flex-end"
|
||||
$justify={isDesktop ? 'space-between' : 'flex-end'}
|
||||
$gap="32px"
|
||||
>
|
||||
{isDesktop && (
|
||||
<DocsGridItemSharedButton
|
||||
doc={doc}
|
||||
handleClick={handleShareClick}
|
||||
/>
|
||||
<StyledLink href={`/docs/${doc.id}`}>
|
||||
<Text $variation="600" $size="xs">
|
||||
{DateTime.fromISO(doc.updated_at).toRelative()}
|
||||
</Text>
|
||||
</StyledLink>
|
||||
)}
|
||||
|
||||
<DocsGridActions doc={doc} openShareModal={handleShareClick} />
|
||||
<Box $direction="row" $align="center" $gap={spacings.lg}>
|
||||
{isDesktop && (
|
||||
<DocsGridItemSharedButton
|
||||
doc={doc}
|
||||
handleClick={handleShareClick}
|
||||
/>
|
||||
)}
|
||||
<DocsGridActions doc={doc} openShareModal={handleShareClick} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
{shareModal.isOpen && (
|
||||
|
||||
@@ -1,66 +1,45 @@
|
||||
import { Button } from '@openfun/cunningham-react';
|
||||
import { useMemo } from 'react';
|
||||
import { Button, Tooltip } from '@openfun/cunningham-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Box, Icon } from '@/components';
|
||||
import { Box, Icon, Text } from '@/components';
|
||||
|
||||
import { Doc, LinkReach } from '../../doc-management';
|
||||
import { Doc } from '../../doc-management';
|
||||
|
||||
type Props = {
|
||||
doc: Doc;
|
||||
handleClick: () => void;
|
||||
};
|
||||
export const DocsGridItemSharedButton = ({ doc, handleClick }: Props) => {
|
||||
const isPublic = doc.link_reach === LinkReach.PUBLIC;
|
||||
const isAuthenticated = doc.link_reach === LinkReach.AUTHENTICATED;
|
||||
const isRestricted = doc.link_reach === LinkReach.RESTRICTED;
|
||||
const sharedCount = doc.nb_accesses - 1;
|
||||
const isShared = sharedCount > 0;
|
||||
const { t } = useTranslation();
|
||||
const sharedCount = doc.nb_accesses;
|
||||
const isShared = sharedCount - 1 > 0;
|
||||
|
||||
const icon = useMemo(() => {
|
||||
if (isPublic) {
|
||||
return 'public';
|
||||
}
|
||||
if (isAuthenticated) {
|
||||
return 'corporate_fare';
|
||||
}
|
||||
if (isRestricted) {
|
||||
return 'group';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [isPublic, isAuthenticated, isRestricted]);
|
||||
|
||||
if (!icon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!doc.abilities.accesses_view) {
|
||||
return (
|
||||
<Box $align="center" $width="100%">
|
||||
<Icon $variation="800" $theme="primary" iconName={icon} />
|
||||
</Box>
|
||||
);
|
||||
if (!isShared) {
|
||||
return <Box $minWidth="50px"> </Box>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleClick();
|
||||
}}
|
||||
fullWidth
|
||||
color={isRestricted ? 'tertiary' : 'primary'}
|
||||
size="nano"
|
||||
icon={
|
||||
<Icon
|
||||
$variation={isRestricted ? '800' : '000'}
|
||||
$theme={isRestricted ? 'primary' : 'greyscale'}
|
||||
iconName={icon}
|
||||
/>
|
||||
<Tooltip
|
||||
content={
|
||||
<Text $textAlign="center" $variation="000">
|
||||
{t('Shared with {{count}} users', { count: sharedCount })}
|
||||
</Text>
|
||||
}
|
||||
placement="top"
|
||||
>
|
||||
{isShared ? sharedCount : undefined}
|
||||
</Button>
|
||||
<Button
|
||||
style={{ minWidth: '50px', justifyContent: 'center' }}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleClick();
|
||||
}}
|
||||
color="tertiary"
|
||||
size="nano"
|
||||
icon={<Icon $variation="800" $theme="primary" iconName="group" />}
|
||||
>
|
||||
{sharedCount}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, Icon, Text } from '@/components';
|
||||
import { Box, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc, LinkReach } from '@/features/docs/doc-management';
|
||||
import { Doc } from '@/features/docs/doc-management';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import PinnedDocumentIcon from '../assets/pinned-document.svg';
|
||||
@@ -34,11 +34,6 @@ export const SimpleDocItem = ({
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const spacings = spacingsTokens();
|
||||
|
||||
const isPublic = doc?.link_reach === LinkReach.PUBLIC;
|
||||
const isShared = !isPublic && doc.nb_accesses > 1;
|
||||
const accessCount = doc.nb_accesses - 1;
|
||||
const isSharedOrPublic = isShared || isPublic;
|
||||
|
||||
return (
|
||||
<Box $direction="row" $gap={spacings.sm}>
|
||||
<Box
|
||||
@@ -69,22 +64,6 @@ export const SimpleDocItem = ({
|
||||
$gap={spacings['3xs']}
|
||||
$margin={{ top: '-2px' }}
|
||||
>
|
||||
{isPublic && (
|
||||
<Icon iconName="public" $size="16px" $variation="600" />
|
||||
)}
|
||||
{isShared && (
|
||||
<Icon iconName="group" $size="16px" $variation="600" />
|
||||
)}
|
||||
{isSharedOrPublic && accessCount > 0 && (
|
||||
<Text $size="12px" $weight="bold" $variation="600">
|
||||
{accessCount}
|
||||
</Text>
|
||||
)}
|
||||
{isSharedOrPublic && (
|
||||
<Text $size="12px" $variation="600">
|
||||
·
|
||||
</Text>
|
||||
)}
|
||||
<Text $variation="600" $size="xs">
|
||||
{DateTime.fromISO(doc.updated_at).toRelative()}
|
||||
</Text>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
export const useResponsiveDocGrid = () => {
|
||||
const { isDesktop, screenWidth } = useResponsiveStore();
|
||||
|
||||
const flexLeft = useMemo(() => {
|
||||
if (!isDesktop) {
|
||||
return 1;
|
||||
} else if (screenWidth <= 1100) {
|
||||
return 6;
|
||||
} else if (screenWidth < 1200) {
|
||||
return 8;
|
||||
}
|
||||
return 8;
|
||||
}, [isDesktop, screenWidth]);
|
||||
|
||||
const flexRight = useMemo(() => {
|
||||
if (!isDesktop) {
|
||||
return undefined;
|
||||
} else if (screenWidth <= 1200) {
|
||||
return 5;
|
||||
}
|
||||
return 4;
|
||||
}, [isDesktop, screenWidth]);
|
||||
|
||||
return { flexLeft, flexRight };
|
||||
};
|
||||
@@ -41,3 +41,19 @@ main ::-webkit-scrollbar-thumb:hover,
|
||||
cursor: pointer;
|
||||
outline: inherit;
|
||||
}
|
||||
|
||||
.c__modal__scroller:has(.noPadding) {
|
||||
padding: 0 !important;
|
||||
|
||||
.c__modal__close .c__button {
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
|
||||
.c__modal__title {
|
||||
font-size: var(--c--theme--font--sizes--xs);
|
||||
padding: var(--c--theme--spacings--base);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user