This commit is contained in:
Nathan Panchout
2024-11-27 15:51:41 +01:00
parent c928a08fc5
commit dc80cd3a02
9 changed files with 147 additions and 82 deletions

View File

@@ -44,7 +44,7 @@ export const QuickSearchInput = ({
$gap={spacing['2xs']}
$padding={{ horizontal: 'base' }}
>
{!loading && <Icon iconName="search" $variation="400" />}
{!loading && <Icon iconName="search" $variation="600" />}
{loading && (
<div>
<Loader size="small" />

View File

@@ -0,0 +1,44 @@
import { ReactNode } from 'react';
import { useCunninghamTheme } from '@/cunningham';
import { Box } from '../Box';
export type QuickSearchItemContentProps = {
alwaysShowRight?: boolean;
left: ReactNode;
right?: ReactNode;
};
export const QuickSearchItemContent = ({
alwaysShowRight = false,
left,
right,
}: QuickSearchItemContentProps) => {
const { spacingsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
return (
<Box
$direction="row"
$align="center"
$padding={{ horizontal: '2xs', vertical: '3xs' }}
$justify="space-between"
$width="100%"
>
<Box $direction="row" $align="center" $gap={spacings['2xs']}>
{left}
</Box>
{right && (
<Box
className={!alwaysShowRight ? 'show-right-on-focus' : ''}
$direction="row"
$align="center"
>
{right}
</Box>
)}
</Box>
);
};

View File

@@ -24,12 +24,12 @@ export const QuickSearchStyle = createGlobalStyle`
background: white;
outline: none;
color: var(--c--theme--colors--greyscale-700);
color: var(--c--theme--colors--greyscale-1000);
border-radius: 0;
&::placeholder {
color: var(--c--theme--colors--greyscale-300);
color: var(--c--theme--colors--greyscale-500);
}
}
@@ -66,8 +66,16 @@ export const QuickSearchStyle = createGlobalStyle`
transition: all 150ms ease;
transition-property: none;
.show-right-on-focus {
display: none;
}
&:hover,
&[data-selected='true'] {
background: var(--c--theme--colors--greyscale-100);
.show-right-on-focus {
display: inherit;
}
}
&[data-disabled='true'] {
@@ -126,34 +134,19 @@ export const QuickSearchStyle = createGlobalStyle`
[cmdk-group-heading] {
user-select: none;
font-size: var(--c--theme--font--sizes--sm);
color: var(--c--theme--colors--greyscale-500);
color: var(--c--theme--colors--greyscale-700);
font-weight: bold;
display: flex;
align-items: center;
margin: var(--c--theme--spacings--200W) 0;
margin-bottom: var(--c--theme--spacings--base);
}
[cmdk-empty] {
}
}
.inputContainer {
display: flex;
padding: 0 10px;
gap: 8px;
align-items: center;
.inputIcon {
color: var(--c--theme--colors--greyscale-400);
}
}
.loading {
display: flex;
justify-content: center;
align-items: center;
}
.c__modal__scroller:has(.quick-search-container),
.c__modal__scroller:has(.noPadding) {

View File

@@ -24,7 +24,7 @@ export const HorizontalSeparator = ({
$background={
variant === SeparatorVariant.DARK
? '#e5e5e533'
: colorsTokens()['greyscale-100']
: colorsTokens()['greyscale-200']
}
/>
);

View File

@@ -23,7 +23,7 @@ export const DocShareAddMemberListItem = ({ user, onRemoveUser }: Props) => {
$justify="center"
$align="center"
$gap={spacing.xs}
$background={color['greyscale-200']}
$background={color['greyscale-250']}
$padding={{ horizontal: spacing['2xs'], vertical: spacing['3xs'] }}
$css={css`
color: ${color['greyscale-1000']};
@@ -34,8 +34,8 @@ export const DocShareAddMemberListItem = ({ user, onRemoveUser }: Props) => {
<Button
color="primary-text"
size="nano"
onClick={() => onRemoveUser(user)}
icon={<Icon $variation="400" $size="sm" iconName="close" />}
onClick={() => onRemoveUser?.(user)}
icon={<Icon $variation="500" $size="sm" iconName="close" />}
/>
</Box>
);

View File

@@ -13,6 +13,7 @@ import {
} from '@/features/docs/members/members-list';
import { useWhoAmI } from '@/features/docs/members/members-list/hooks/useWhoAmI';
import { SearchUserRow } from '@/features/users/components/SearchUserRow';
import { useResponsiveStore } from '@/stores';
import { Access, Doc, Role } from '../../types';
import { DocRoleDropdown } from '../DocRoleDropdown';
@@ -25,6 +26,7 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
const { t } = useTranslation();
const { isLastOwner, isOtherOwner } = useWhoAmI(access);
const { toast } = useToastProvider();
const { isDesktop } = useResponsiveStore();
const isNotAllowed =
isOtherOwner || isLastOwner || !doc.abilities.accesses_manage;
@@ -64,9 +66,10 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
disabled: isNotAllowed,
},
];
return (
<SearchUserRow
showRightOnHover={false}
alwaysShowRight={true}
user={access.user}
right={
<Box $direction="row" $align="center">
@@ -76,9 +79,11 @@ export const DocShareMemberItem = ({ doc, access }: Props) => {
canUpdate={!isNotAllowed}
/>
<DropdownMenu options={moreActions}>
<IconOptions $variation="600" />
</DropdownMenu>
{isDesktop && (
<DropdownMenu options={moreActions}>
<IconOptions $variation="600" />
</DropdownMenu>
)}
</Box>
}
/>

View File

@@ -1,4 +1,10 @@
import { Modal, ModalSize } from '@openfun/cunningham-react';
import {
Button,
Modal,
ModalSize,
VariantType,
useToastProvider,
} from '@openfun/cunningham-react';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDebouncedCallback } from 'use-debounce';
@@ -10,14 +16,18 @@ import {
QuickSearchData,
} from '@/components/quick-search/QuickSearch';
import { QuickSearchGroup } from '@/components/quick-search/QuickSearchGroup';
import { HorizontalSeparator } from '@/components/separators/HorizontalSeparator';
import { User } from '@/core';
import { Access, Doc } from '@/features/docs';
import { useDocInvitationsInfinite } from '@/features/docs/members/invitation-list';
import { Invitation } from '@/features/docs/members/invitation-list/types';
import { KEY_LIST_USER, useUsers } from '@/features/docs/members/members-add';
import { useDocAccessesInfinite } from '@/features/docs/members/members-list';
import { useResponsiveStore } from '@/stores';
import { isValidEmail } from '@/utils';
import { DocVisibility } from '../DocVisibility';
import { DocShareAddMemberList } from './DocShareAddMemberList';
import { DocShareInvitationItem } from './DocShareInvitationItem';
import { DocShareMemberItem } from './DocShareMemberItem';
@@ -29,6 +39,9 @@ type Props = {
};
export const DocShareModal = ({ doc, onClose }: Props) => {
const { t } = useTranslation();
const { toast } = useToastProvider();
const { isDesktop } = useResponsiveStore();
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
const [userQuery, setUserQuery] = useState('');
const [inputValue, setInputValue] = useState('');
@@ -61,8 +74,12 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
const members =
membersQuery.data?.pages.flatMap((page) => page.results) || [];
const count = membersQuery.data?.pages[0]?.count ?? 1;
return {
groupName: t('Members'),
groupName: t('Share with {{count}} users', {
count: count,
}),
elements: members,
endActions: membersQuery.hasNextPage
? [
@@ -80,7 +97,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
invitationQuery.data?.pages.flatMap((page) => page.results) || [];
return {
groupName: t('Invitations'),
groupName: t('Pending invitations'),
elements: invitations,
endActions: invitationQuery.hasNextPage
? [
@@ -104,7 +121,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
};
return {
groupName: t('Users'),
groupName: t('Share with {{count}} users', { count: users.length }),
elements: users,
endActions:
isEmail && users.length === 0
@@ -137,7 +154,7 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
return (
<Modal
isOpen
size={ModalSize.LARGE}
size={isDesktop ? ModalSize.LARGE : ModalSize.FULL}
onClose={onClose}
title={
<Box $padding="base" $align="flex-start">
@@ -200,6 +217,35 @@ export const DocShareModal = ({ doc, onClose }: Props) => {
</>
)}
</QuickSearch>
<HorizontalSeparator />
<DocVisibility doc={doc} />
<HorizontalSeparator />
<Box $direction="row" $justify="space-between" $padding="base">
<Button
fullWidth={false}
onClick={() => {
navigator.clipboard
.writeText(window.location.href)
.then(() => {
toast(t('Link Copied !'), VariantType.SUCCESS, {
duration: 3000,
});
})
.catch(() => {
toast(t('Failed to copy link'), VariantType.ERROR, {
duration: 3000,
});
});
}}
color="tertiary"
icon={<span className="material-icons">add_link</span>}
>
{t('Copy link')}
</Button>
<Button onClick={onClose} color="primary">
{t('Ok')}
</Button>
</Box>
</Modal>
);
};

View File

@@ -15,6 +15,7 @@ export const DocShareModalInviteUserRow = ({ user }: Props) => {
user={user}
right={
<Box
className="right-hover"
$direction="row"
$align="center"
$css={css`

View File

@@ -1,7 +1,8 @@
import { ReactNode } from 'react';
import { css } from 'styled-components';
import { Box, Text } from '@/components';
import {
QuickSearchItemContent,
QuickSearchItemContentProps,
} from '@/components/quick-search/QuickSearchItemContent';
import { User } from '@/core';
import { useCunninghamTheme } from '@/cunningham';
@@ -9,63 +10,38 @@ import { UserAvatar } from './UserAvatar';
type Props = {
user: User;
showRightOnHover?: boolean;
right?: ReactNode;
alwaysShowRight?: boolean;
right?: QuickSearchItemContentProps['right'];
};
export const SearchUserRow = ({
user,
right,
showRightOnHover = true,
alwaysShowRight = false,
}: Props) => {
const hasFullName = user.full_name != null && user.full_name !== '';
const { spacingsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
const hasFullName = user.full_name != null && user.full_name !== '';
return (
<Box
$direction="row"
$align="center"
$padding={{ horizontal: '2xs', vertical: '3xs' }}
$justify="space-between"
$width="100%"
$css={css`
.right-user-row {
color: red !important;
display: ${showRightOnHover ? 'none' : 'flex'};
}
[data-cmdk-selected='true'] {
background-color: red !important;
}
&:hover {
.right-user-row {
color: green !important;
display: flex;
}
}
`}
>
<Box $direction="row" $align="center" $gap={spacings['2xs']}>
<UserAvatar user={user} />
<Box $direction="column">
<Text $size="sm" $variation="1000">
{hasFullName ? user.full_name : user.email}
</Text>
{hasFullName && (
<Text $size="xs" $variation="500">
{user.email}
<QuickSearchItemContent
right={right}
alwaysShowRight={alwaysShowRight}
left={
<Box $direction="row" $align="center" $gap={spacings['2xs']}>
<UserAvatar user={user} />
<Box $direction="column">
<Text $size="sm" $weight="500" $variation="1000">
{hasFullName ? user.full_name : user.email}
</Text>
)}
{hasFullName && (
<Text $size="xs" $variation="600">
{user.email}
</Text>
)}
</Box>
</Box>
</Box>
{right && (
<Box className="right-user-row" $direction="row" $align="center">
{right}
</Box>
)}
</Box>
}
/>
);
};