mirror of
https://github.com/suitenumerique/docs.git
synced 2026-05-08 16:12:26 +02:00
wip
This commit is contained in:
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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) {
|
||||
|
||||
@@ -24,7 +24,7 @@ export const HorizontalSeparator = ({
|
||||
$background={
|
||||
variant === SeparatorVariant.DARK
|
||||
? '#e5e5e533'
|
||||
: colorsTokens()['greyscale-100']
|
||||
: colorsTokens()['greyscale-200']
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,6 +15,7 @@ export const DocShareModalInviteUserRow = ({ user }: Props) => {
|
||||
user={user}
|
||||
right={
|
||||
<Box
|
||||
className="right-hover"
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$css={css`
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user