✨(frontend) enhance DocShareModal and footer components
- Refactored DocShareModal to improve user experience with dynamic list height and responsive design adjustments. - Introduced new styling for modal elements using createGlobalStyle for better visual consistency. - Updated footer button text from 'Ok' to 'OK' for improved clarity. - Enhanced user selection handling and search functionality within the modal for better document sharing experience.
This commit is contained in:
committed by
Anthony LC
parent
098df5c0b5
commit
f02dcae52a
@@ -1,7 +1,7 @@
|
|||||||
import { Modal, ModalSize } from '@openfun/cunningham-react';
|
import { Modal, ModalSize } from '@openfun/cunningham-react';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { css } from 'styled-components';
|
import { createGlobalStyle, css } from 'styled-components';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
import { Box } from '@/components';
|
import { Box } from '@/components';
|
||||||
@@ -29,20 +29,33 @@ import { DocShareMemberItem } from './DocShareMemberItem';
|
|||||||
import { DocShareModalFooter } from './DocShareModalFooter';
|
import { DocShareModalFooter } from './DocShareModalFooter';
|
||||||
import { DocShareModalInviteUserRow } from './DocShareModalInviteUserByEmail';
|
import { DocShareModalInviteUserRow } from './DocShareModalInviteUserByEmail';
|
||||||
|
|
||||||
|
const ShareModalStyle = createGlobalStyle`
|
||||||
|
|
||||||
|
.c__modal__title {
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
doc: Doc;
|
doc: Doc;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
export const DocShareModal = ({ doc, onClose }: Props) => {
|
export const DocShareModal = ({ doc, onClose }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const selectedUsersRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { isDesktop } = useResponsiveStore();
|
const { isDesktop } = useResponsiveStore();
|
||||||
|
|
||||||
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
|
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
|
||||||
const [userQuery, setUserQuery] = useState('');
|
const [userQuery, setUserQuery] = useState('');
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
|
||||||
|
const [listHeight, setListHeight] = useState<string>('400px');
|
||||||
const canShare = doc.abilities.accesses_manage;
|
const canShare = doc.abilities.accesses_manage;
|
||||||
const showMemberSection = inputValue === '' && selectedUsers.length === 0;
|
const showMemberSection = inputValue === '' && selectedUsers.length === 0;
|
||||||
|
const showFooter = selectedUsers.length === 0 && !inputValue;
|
||||||
|
|
||||||
const onSelect = (user: User) => {
|
const onSelect = (user: User) => {
|
||||||
setSelectedUsers((prev) => [...prev, user]);
|
setSelectedUsers((prev) => [...prev, user]);
|
||||||
@@ -73,9 +86,12 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
|||||||
const count = membersQuery.data?.pages[0]?.count ?? 1;
|
const count = membersQuery.data?.pages[0]?.count ?? 1;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
groupName: t('Share with {{count}} users', {
|
groupName:
|
||||||
count: count,
|
count === 1
|
||||||
}),
|
? t('Document owner')
|
||||||
|
: t('Share with {{count}} users', {
|
||||||
|
count: count,
|
||||||
|
}),
|
||||||
elements: members,
|
elements: members,
|
||||||
endActions: membersQuery.hasNextPage
|
endActions: membersQuery.hasNextPage
|
||||||
? [
|
? [
|
||||||
@@ -147,102 +163,120 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const handleRef = (node: HTMLDivElement) => {
|
||||||
<Modal
|
const contentHeight = isDesktop ? '690px' : '100dvh - 34px'; // 690px is the height of the content in desktop ad 34px is the height of the modal title in mobile
|
||||||
isOpen
|
const inputHeight = canShare ? 70 : 0;
|
||||||
closeOnClickOutside
|
const marginTop = 11;
|
||||||
data-testid="doc-share-modal"
|
const footerHeight = node?.clientHeight ?? 0;
|
||||||
aria-label={t('Share modal')}
|
const selectedUsersHeight = selectedUsersRef.current?.clientHeight ?? 0;
|
||||||
size={isDesktop ? ModalSize.LARGE : ModalSize.FULL}
|
|
||||||
onClose={onClose}
|
|
||||||
title={<Box $align="flex-start">{t('Share the document')}</Box>}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
aria-label={t('Share modal')}
|
|
||||||
$direction="column"
|
|
||||||
$justify="space-between"
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
$flex={1}
|
|
||||||
className="toto"
|
|
||||||
$css={css`
|
|
||||||
overflow-y: auto;
|
|
||||||
[cmdk-list] {
|
|
||||||
overflow-y: auto;
|
|
||||||
height: ${isDesktop
|
|
||||||
? '400px'
|
|
||||||
: 'calc(100vh - 49px - 68px - 229px)'};
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
{canShare && selectedUsers.length > 0 && (
|
|
||||||
<Box $padding={{ horizontal: 'base' }} $margin={{ top: '11px' }}>
|
|
||||||
<DocShareAddMemberList
|
|
||||||
doc={doc}
|
|
||||||
selectedUsers={selectedUsers}
|
|
||||||
onRemoveUser={onRemoveUser}
|
|
||||||
afterInvite={() => {
|
|
||||||
setUserQuery('');
|
|
||||||
setInputValue('');
|
|
||||||
setSelectedUsers([]);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Box data-testid="doc-share-quick-search">
|
const height = `calc(${contentHeight} - ${footerHeight}px - ${selectedUsersHeight}px - ${inputHeight}px - ${marginTop}px)`;
|
||||||
<QuickSearch
|
|
||||||
onFilter={(str) => {
|
setListHeight(height);
|
||||||
setInputValue(str);
|
};
|
||||||
onFilter(str);
|
|
||||||
}}
|
return (
|
||||||
inputValue={inputValue}
|
<>
|
||||||
showInput={canShare}
|
<Modal
|
||||||
loading={searchUsersQuery.isLoading}
|
isOpen
|
||||||
placeholder={t('Type a name or email')}
|
closeOnClickOutside
|
||||||
>
|
data-testid="doc-share-modal"
|
||||||
{!showMemberSection && inputValue !== '' && (
|
aria-label={t('Share modal')}
|
||||||
<QuickSearchGroup
|
size={isDesktop ? ModalSize.LARGE : ModalSize.FULL}
|
||||||
group={searchUserData}
|
onClose={onClose}
|
||||||
onSelect={onSelect}
|
title={<Box $align="flex-start">{t('Share the document')}</Box>}
|
||||||
renderElement={(user) => (
|
>
|
||||||
<DocShareModalInviteUserRow user={user} />
|
<ShareModalStyle />
|
||||||
)}
|
<Box
|
||||||
/>
|
aria-label={t('Share modal')}
|
||||||
|
$height={isDesktop ? '690px' : `calc(100dvh - 34px)`}
|
||||||
|
$overflow="hidden"
|
||||||
|
$justify="space-between"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
$flex={1}
|
||||||
|
$css={css`
|
||||||
|
[cmdk-list] {
|
||||||
|
overflow-y: auto;
|
||||||
|
height: ${listHeight};
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div ref={selectedUsersRef}>
|
||||||
|
{canShare && selectedUsers.length > 0 && (
|
||||||
|
<Box
|
||||||
|
$padding={{ horizontal: 'base' }}
|
||||||
|
$margin={{ top: '11px' }}
|
||||||
|
>
|
||||||
|
<DocShareAddMemberList
|
||||||
|
doc={doc}
|
||||||
|
selectedUsers={selectedUsers}
|
||||||
|
onRemoveUser={onRemoveUser}
|
||||||
|
afterInvite={() => {
|
||||||
|
setUserQuery('');
|
||||||
|
setInputValue('');
|
||||||
|
setSelectedUsers([]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
{showMemberSection && (
|
</div>
|
||||||
<>
|
|
||||||
{invitationsData.elements.length > 0 && (
|
<Box data-testid="doc-share-quick-search">
|
||||||
<Box aria-label={t('List invitation card')}>
|
<QuickSearch
|
||||||
|
onFilter={(str) => {
|
||||||
|
setInputValue(str);
|
||||||
|
onFilter(str);
|
||||||
|
}}
|
||||||
|
inputValue={inputValue}
|
||||||
|
showInput={canShare}
|
||||||
|
loading={searchUsersQuery.isLoading}
|
||||||
|
placeholder={t('Type a name or email')}
|
||||||
|
>
|
||||||
|
{!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
|
<QuickSearchGroup
|
||||||
group={invitationsData}
|
group={membersData}
|
||||||
renderElement={(invitation) => (
|
renderElement={(access) => (
|
||||||
<DocShareInvitationItem
|
<DocShareMemberItem doc={doc} access={access} />
|
||||||
doc={doc}
|
|
||||||
invitation={invitation}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
</>
|
||||||
|
)}
|
||||||
|
</QuickSearch>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box aria-label={t('List members card')}>
|
<Box ref={handleRef}>
|
||||||
<QuickSearchGroup
|
{showFooter && <DocShareModalFooter doc={doc} onClose={onClose} />}
|
||||||
group={membersData}
|
|
||||||
renderElement={(access) => (
|
|
||||||
<DocShareMemberItem doc={doc} access={access} />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</QuickSearch>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{selectedUsers.length === 0 && !inputValue && (
|
</Modal>
|
||||||
<DocShareModalFooter doc={doc} onClose={onClose} />
|
</>
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Modal>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const DocShareModalFooter = ({ doc, onClose }: Props) => {
|
|||||||
{t('Copy link')}
|
{t('Copy link')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={onClose} color="primary">
|
<Button onClick={onClose} color="primary">
|
||||||
{t('Ok')}
|
{t('OK')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user