mirror of
https://github.com/suitenumerique/docs
synced 2026-04-21 13:37:20 +00:00
🛂(frontend) fix cannot manage member on small screen
We can now manage document members on small screens (mobile and tablet). We improved the overall responsive design of the doc share modal.
This commit is contained in:
parent
30ed563be4
commit
caff7af118
10 changed files with 90 additions and 30 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -6,16 +6,17 @@ and this project adheres to
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- ♿️(frontend) structure correctly 5xx error alerts #2128
|
||||
- ♿️(frontend) make doc search result labels uniquely identifiable #2212
|
||||
|
||||
### Fixed
|
||||
|
||||
- 🚸(frontend) redirect on current url tab after 401 #2197
|
||||
- 🐛(frontend) abort check media status unmount #2194
|
||||
- ✨(backend) order pinned documents by last updated at #2028
|
||||
|
||||
### Changed
|
||||
|
||||
- ♿️(frontend) structure correctly 5xx error alerts #2128
|
||||
- ♿️(frontend) make doc search result labels uniquely identifiable #2212
|
||||
- 🛂(frontend) fix cannot manage member on small screen #2226
|
||||
|
||||
## [v4.8.6] - 2026-04-08
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { ReactNode } from 'react';
|
||||
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { Box } from '../Box';
|
||||
|
||||
|
|
@ -18,29 +17,29 @@ export const QuickSearchItemContent = ({
|
|||
}: QuickSearchItemContentProps) => {
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="--docs--quick-search-item-content"
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$padding={{ horizontal: '2xs', vertical: '4xs' }}
|
||||
$justify="space-between"
|
||||
$minHeight="34px"
|
||||
$width="100%"
|
||||
$gap="sm"
|
||||
>
|
||||
<Box
|
||||
className="--docs--quick-search-item-content-left"
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$gap={spacingsTokens['2xs']}
|
||||
$width="100%"
|
||||
>
|
||||
{left}
|
||||
</Box>
|
||||
|
||||
{isDesktop && right && (
|
||||
{right && (
|
||||
<Box
|
||||
className={!alwaysShowRight ? 'show-right-on-focus' : ''}
|
||||
className={`--docs--quick-search-item-content-right ${!alwaysShowRight ? 'show-right-on-focus' : ''}`}
|
||||
$direction="row"
|
||||
$align="center"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
} from '@gouvfr-lasuite/cunningham-react';
|
||||
import { MouseEventHandler, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { createGlobalStyle } from 'styled-components';
|
||||
import { createGlobalStyle, css } from 'styled-components';
|
||||
|
||||
import {
|
||||
Box,
|
||||
|
|
@ -20,6 +20,7 @@ import { QuickSearchData, QuickSearchGroup } from '@/components/quick-search';
|
|||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { AccessRequest, Doc, Role } from '@/docs/doc-management/';
|
||||
import { useAuth } from '@/features/auth';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import {
|
||||
useAcceptDocAccessRequest,
|
||||
|
|
@ -33,8 +34,12 @@ import { DocRoleDropdown } from './DocRoleDropdown';
|
|||
import { SearchUserRow } from './SearchUserRow';
|
||||
|
||||
const QuickSearchGroupAccessRequestStyle = createGlobalStyle`
|
||||
.--docs--share-access-request [cmdk-item][data-selected='true'] {
|
||||
background: inherit
|
||||
.quick-search-container .--docs--share-access-request [cmdk-item]:hover,
|
||||
.quick-search-container .--docs--share-access-request [cmdk-item][data-selected='true'] {
|
||||
background: inherit;
|
||||
}
|
||||
.--docs--doc-share-access-request-item:hover {
|
||||
background: var(--c--contextuals--background--semantic--contextual--primary);
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
@ -45,6 +50,7 @@ type Props = {
|
|||
|
||||
const DocShareAccessRequestItem = ({ doc, accessRequest }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { isSmallMobile } = useResponsiveStore();
|
||||
const { toast } = useToastProvider();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const { mutate: acceptDocAccessRequests } = useAcceptDocAccessRequest();
|
||||
|
|
@ -67,6 +73,15 @@ const DocShareAccessRequestItem = ({ doc, accessRequest }: Props) => {
|
|||
$width="100%"
|
||||
data-testid={`doc-share-access-request-row-${accessRequest.user.email}`}
|
||||
className="--docs--doc-share-access-request-item"
|
||||
$css={css`
|
||||
& .--docs--quick-search-item-content {
|
||||
flex-wrap: wrap;
|
||||
|
||||
.--docs--quick-search-item-content-right {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
`}
|
||||
>
|
||||
<SearchUserRow
|
||||
alwaysShowRight={true}
|
||||
|
|
@ -84,7 +99,7 @@ const DocShareAccessRequestItem = ({ doc, accessRequest }: Props) => {
|
|||
/>
|
||||
<Button
|
||||
color="brand"
|
||||
variant="tertiary"
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
acceptDocAccessRequests({
|
||||
docId: doc.id,
|
||||
|
|
@ -92,7 +107,7 @@ const DocShareAccessRequestItem = ({ doc, accessRequest }: Props) => {
|
|||
role,
|
||||
})
|
||||
}
|
||||
size="small"
|
||||
size={isSmallMobile ? 'nano' : 'small'}
|
||||
>
|
||||
{t('Approve')}
|
||||
</Button>
|
||||
|
|
@ -153,6 +168,7 @@ export const QuickSearchGroupAccessRequest = ({
|
|||
<Box
|
||||
aria-label={t('List request access card')}
|
||||
className="--docs--share-access-request"
|
||||
$padding={{ horizontal: 'base' }}
|
||||
>
|
||||
<QuickSearchGroupAccessRequestStyle />
|
||||
<QuickSearchGroup
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { Box, Card } from '@/components';
|
|||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Doc, Role } from '@/docs/doc-management';
|
||||
import { User } from '@/features/auth';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useCreateDocAccess, useCreateDocInvitation } from '../api';
|
||||
import { OptionType } from '../types';
|
||||
|
|
@ -38,7 +39,7 @@ export const DocShareAddMemberList = ({
|
|||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToastProvider();
|
||||
|
||||
const { isSmallMobile } = useResponsiveStore();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const [invitationRole, setInvitationRole] = useState<Role>(Role.EDITOR);
|
||||
|
|
@ -118,14 +119,15 @@ export const DocShareAddMemberList = ({
|
|||
<Card
|
||||
className="--docs--doc-share-add-member-list"
|
||||
data-testid="doc-share-add-member-list"
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$direction={isSmallMobile ? 'column' : 'row'}
|
||||
$align={isSmallMobile ? 'stretch' : 'center'}
|
||||
$padding={spacingsTokens.sm}
|
||||
$scope="surface"
|
||||
$theme="tertiary"
|
||||
$variation=""
|
||||
$border="1px solid var(--c--contextuals--border--surface--primary)"
|
||||
$margin={{ bottom: 'sm' }}
|
||||
$gap={spacingsTokens.xs}
|
||||
>
|
||||
<Box
|
||||
$direction="row"
|
||||
|
|
@ -142,7 +144,12 @@ export const DocShareAddMemberList = ({
|
|||
/>
|
||||
))}
|
||||
</Box>
|
||||
<Box $direction="row" $align="center" $gap={spacingsTokens.xs}>
|
||||
<Box
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$gap={spacingsTokens.xs}
|
||||
$margin={{ left: isSmallMobile ? 'auto' : '' }}
|
||||
>
|
||||
<DocRoleDropdown
|
||||
canUpdate={canShare}
|
||||
currentRole={invitationRole}
|
||||
|
|
@ -154,6 +161,7 @@ export const DocShareAddMemberList = ({
|
|||
disabled={isLoading}
|
||||
aria-label={inviteLabel}
|
||||
data-testid="doc-share-invite-button"
|
||||
size={isSmallMobile ? 'small' : 'medium'}
|
||||
>
|
||||
{t('Invite')}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, BoxButton, Icon, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
|
|
@ -31,7 +32,13 @@ export const DocShareAddMemberListItem = ({ user, onRemoveUser }: Props) => {
|
|||
$theme="neutral"
|
||||
$variation="secondary"
|
||||
>
|
||||
<Text $withThemeInherited $size="xs">
|
||||
<Text
|
||||
$withThemeInherited
|
||||
$size="xs"
|
||||
$css={css`
|
||||
line-break: anywhere;
|
||||
`}
|
||||
>
|
||||
{user.full_name || user.email}
|
||||
</Text>
|
||||
<BoxButton
|
||||
|
|
|
|||
|
|
@ -162,7 +162,10 @@ export const QuickSearchGroupInvitation = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<Box aria-label={t('List invitation card')}>
|
||||
<Box
|
||||
aria-label={t('List invitation card')}
|
||||
$padding={{ horizontal: 'base' }}
|
||||
>
|
||||
<QuickSearchGroup
|
||||
group={invitationsData}
|
||||
renderElement={(invitation) => (
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ export const DocShareMemberItem = ({
|
|||
const { t } = useTranslation();
|
||||
const { isLastOwner } = useWhoAmI(access);
|
||||
const { toast } = useToastProvider();
|
||||
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
|
||||
const message = isLastOwner
|
||||
|
|
@ -121,7 +120,10 @@ export const QuickSearchGroupMember = ({
|
|||
}, [membersQuery.data, t]);
|
||||
|
||||
return (
|
||||
<Box aria-label={t('List members card')} $padding={{ bottom: '3xs' }}>
|
||||
<Box
|
||||
aria-label={t('List members card')}
|
||||
$padding={{ horizontal: 'base', bottom: '3xs' }}
|
||||
>
|
||||
<QuickSearchGroup
|
||||
group={membersData}
|
||||
renderElement={(access) => (
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
|||
const API_USERS_SEARCH_QUERY_MIN_LENGTH =
|
||||
config?.API_USERS_SEARCH_QUERY_MIN_LENGTH || 5;
|
||||
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
const { isLargeScreen } = useResponsiveStore();
|
||||
|
||||
/**
|
||||
* The modal content height is calculated based on the viewport height.
|
||||
|
|
@ -75,7 +75,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
|||
* - 690px is the height of the content in desktop
|
||||
* This ensures that the modal content is always visible and does not overflow.
|
||||
*/
|
||||
const modalContentHeight = isDesktop
|
||||
const modalContentHeight = isLargeScreen
|
||||
? 'min(690px, calc(100dvh - 2em - 12px - 34px))'
|
||||
: `calc(100dvh - 34px)`;
|
||||
const [selectedUsers, setSelectedUsers] = useState<User[]>([]);
|
||||
|
|
@ -181,7 +181,7 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
|||
closeOnClickOutside
|
||||
data-testid="doc-share-modal"
|
||||
aria-label={t('Share the document')}
|
||||
size={isDesktop ? ModalSize.LARGE : ModalSize.FULL}
|
||||
size={isLargeScreen ? ModalSize.LARGE : ModalSize.FULL}
|
||||
aria-modal="true"
|
||||
onClose={onClose}
|
||||
title={
|
||||
|
|
@ -289,9 +289,11 @@ export const DocShareModal = ({ doc, onClose, isRootDoc = true }: Props) => {
|
|||
/>
|
||||
)}
|
||||
{showMemberSection && isRootDoc && (
|
||||
<Box $padding={{ horizontal: 'base', top: 'base' }}>
|
||||
<Box $padding={{ top: 'base' }}>
|
||||
<QuickSearchGroupAccessRequest doc={doc} />
|
||||
<HorizontalSeparator $margin={{ vertical: 'sm' }} />
|
||||
<QuickSearchGroupInvitation doc={doc} />
|
||||
<HorizontalSeparator $margin={{ vertical: 'sm' }} />
|
||||
<QuickSearchGroupMember doc={doc} />
|
||||
</Box>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, Text } from '@/components';
|
||||
import {
|
||||
QuickSearchItemContent,
|
||||
|
|
@ -38,11 +40,24 @@ export const SearchUserRow = ({
|
|||
background={isInvitation ? colorsTokens['gray-400'] : undefined}
|
||||
/>
|
||||
<Box $direction="column">
|
||||
<Text $size="sm" $weight="500">
|
||||
<Text
|
||||
$size="sm"
|
||||
$weight="500"
|
||||
$css={css`
|
||||
line-break: anywhere;
|
||||
`}
|
||||
>
|
||||
{hasFullName ? user.full_name : user.email}
|
||||
</Text>
|
||||
{hasFullName && (
|
||||
<Text $size="xs" $margin={{ top: '-2px' }} $variation="secondary">
|
||||
<Text
|
||||
$size="xs"
|
||||
$margin={{ top: '-2px' }}
|
||||
$variation="secondary"
|
||||
$css={css`
|
||||
line-break: anywhere;
|
||||
`}
|
||||
>
|
||||
{user.email}
|
||||
</Text>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,12 @@ export interface UseResponsiveStore {
|
|||
screenWidth: number;
|
||||
setScreenSize: (size: ScreenSize) => void;
|
||||
isDesktop: boolean;
|
||||
isLargeScreen: boolean;
|
||||
initializeResizeListener: () => () => void;
|
||||
}
|
||||
|
||||
const initialState = {
|
||||
isLargeScreen: false,
|
||||
isMobile: false,
|
||||
isSmallMobile: false,
|
||||
isTablet: false,
|
||||
|
|
@ -24,6 +26,7 @@ const initialState = {
|
|||
|
||||
export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
|
||||
isDesktop: initialState.isDesktop,
|
||||
isLargeScreen: initialState.isLargeScreen,
|
||||
isMobile: initialState.isMobile,
|
||||
isSmallMobile: initialState.isSmallMobile,
|
||||
isTablet: initialState.isTablet,
|
||||
|
|
@ -40,6 +43,7 @@ export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
|
|||
isMobile: true,
|
||||
isTablet: true,
|
||||
isSmallMobile: true,
|
||||
isLargeScreen: false,
|
||||
});
|
||||
} else if (width < 768) {
|
||||
set({
|
||||
|
|
@ -48,6 +52,7 @@ export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
|
|||
isTablet: true,
|
||||
isMobile: true,
|
||||
isSmallMobile: false,
|
||||
isLargeScreen: false,
|
||||
});
|
||||
} else if (width >= 768 && width < 1024) {
|
||||
set({
|
||||
|
|
@ -56,6 +61,7 @@ export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
|
|||
isTablet: true,
|
||||
isMobile: false,
|
||||
isSmallMobile: false,
|
||||
isLargeScreen: true,
|
||||
});
|
||||
} else {
|
||||
set({
|
||||
|
|
@ -64,6 +70,7 @@ export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
|
|||
isTablet: false,
|
||||
isMobile: false,
|
||||
isSmallMobile: false,
|
||||
isLargeScreen: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue