📱(frontend) docs mobile friendly

We adapt the docs component to be
mobile friendly.
This commit is contained in:
Anthony LC
2024-10-08 17:03:42 +02:00
committed by Anthony LC
parent 8dd7671d1f
commit c682bce6f6
26 changed files with 669 additions and 323 deletions

View File

@@ -113,13 +113,14 @@ export const goToGridDoc = async (
const header = page.locator('header').first();
await header.locator('h2').getByText('Docs').click();
const datagrid = page
.getByLabel('Datagrid of the documents page 1')
.getByRole('table');
const datagrid = page.getByLabel('Datagrid of the documents page 1');
const datagridTable = datagrid.getByRole('table');
await expect(datagrid.getByLabel('Loading data')).toBeHidden();
await expect(datagrid.getByLabel('Loading data')).toBeHidden({
timeout: 10000,
});
const rows = datagrid.getByRole('row');
const rows = datagridTable.getByRole('row');
const row = title
? rows.filter({
hasText: title,
@@ -132,7 +133,7 @@ export const goToGridDoc = async (
expect(docTitle).toBeDefined();
await docTitleCell.click();
await row.getByRole('link').first().click();
return docTitle as string;
};

View File

@@ -18,12 +18,13 @@ test.describe('Doc Create', () => {
const header = page.locator('header').first();
await header.locator('h2').getByText('Docs').click();
const datagrid = page
.getByLabel('Datagrid of the documents page 1')
.getByRole('table');
const datagrid = page.getByLabel('Datagrid of the documents page 1');
const datagridTable = datagrid.getByRole('table');
await expect(datagrid.getByLabel('Loading data')).toBeHidden();
await expect(datagrid.getByText(docTitle)).toBeVisible({
await expect(datagrid.getByLabel('Loading data')).toBeHidden({
timeout: 10000,
});
await expect(datagridTable.getByText(docTitle)).toBeVisible({
timeout: 5000,
});
});

View File

@@ -117,7 +117,9 @@ test.describe('Documents Grid', () => {
.getByRole('cell')
.nth(cellNumber);
await expect(datagrid.getByLabel('Loading data')).toBeHidden();
await expect(datagrid.getByLabel('Loading data')).toBeHidden({
timeout: 10000,
});
// Initial state
await expect(docNameRow1).toHaveText(/.*/);
@@ -134,7 +136,9 @@ test.describe('Documents Grid', () => {
const responseOrderingAsc = await responsePromiseOrderingAsc;
expect(responseOrderingAsc.ok()).toBeTruthy();
await expect(datagrid.getByLabel('Loading data')).toBeHidden();
await expect(datagrid.getByLabel('Loading data')).toBeHidden({
timeout: 10000,
});
await expect(docNameRow1).toHaveText(/.*/);
await expect(docNameRow2).toHaveText(/.*/);
@@ -155,7 +159,9 @@ test.describe('Documents Grid', () => {
const responseOrderingDesc = await responsePromiseOrderingDesc;
expect(responseOrderingDesc.ok()).toBeTruthy();
await expect(datagrid.getByLabel('Loading data')).toBeHidden();
await expect(datagrid.getByLabel('Loading data')).toBeHidden({
timeout: 10000,
});
await expect(docNameRow1).toHaveText(/.*/);
await expect(docNameRow2).toHaveText(/.*/);
@@ -244,3 +250,87 @@ test.describe('Documents Grid', () => {
await expect(datagrid.getByText(docName!)).toBeHidden();
});
});
test.describe('Documents Grid mobile', () => {
test.use({ viewport: { width: 500, height: 1200 } });
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('it checks the grid when mobile', async ({ page }) => {
await page.route('**/documents/**', async (route) => {
const request = route.request();
if (request.method().includes('GET') && request.url().includes('page=')) {
await route.fulfill({
json: {
count: 1,
next: null,
previous: null,
results: [
{
id: 'b7fd9d9b-0642-4b4f-8617-ce50f69519ed',
title: 'My mocked document',
accesses: [
{
id: '8c1e047a-24e7-4a80-942b-8e9c7ab43e1f',
user: {
id: '7380f42f-02eb-4ad5-b8f0-037a0e66066d',
email: 'test@test.test',
full_name: 'John Doe',
short_name: 'John',
},
team: '',
role: 'owner',
abilities: {
destroy: false,
update: false,
partial_update: false,
retrieve: true,
set_role_to: [],
},
},
],
abilities: {
attachment_upload: true,
destroy: true,
link_configuration: true,
manage_accesses: true,
partial_update: true,
retrieve: true,
update: true,
versions_destroy: true,
versions_list: true,
versions_retrieve: true,
},
link_role: 'reader',
link_reach: 'public',
created_at: '2024-10-07T13:02:41.085298Z',
updated_at: '2024-10-07T13:30:21.829690Z',
},
],
},
});
} else {
await route.continue();
}
});
await page.goto('/');
const datagrid = page.getByLabel('Datagrid of the documents page 1');
const tableDatagrid = datagrid.getByRole('table');
await expect(datagrid.getByLabel('Loading data')).toBeHidden({
timeout: 10000,
});
const rows = tableDatagrid.getByRole('row');
const row = rows.filter({
hasText: 'My mocked document',
});
await expect(row.getByRole('cell').nth(0)).toHaveText('My mocked document');
await expect(row.getByRole('cell').nth(1)).toHaveText('Public');
});
});

View File

@@ -385,3 +385,35 @@ test.describe('Doc Header', () => {
).toBeHidden();
});
});
test.describe('Documents Header mobile', () => {
test.use({ viewport: { width: 500, height: 1200 } });
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('it checks the close button on Share modal', async ({ page }) => {
await mockedDocument(page, {
abilities: {
destroy: true, // Means owner
link_configuration: true,
versions_destroy: true,
versions_list: true,
versions_retrieve: true,
manage_accesses: true,
update: true,
partial_update: true,
retrieve: true,
},
});
await goToGridDoc(page);
await page.getByRole('button', { name: 'Share' }).click();
await expect(page.getByLabel('Share modal')).toBeVisible();
await page.getByRole('button', { name: 'close' }).click();
await expect(page.getByLabel('Share modal')).toBeHidden();
});
});

View File

@@ -8,6 +8,8 @@ test.beforeEach(async ({ page }) => {
test.describe('Doc Table Content', () => {
test('it checks the doc table content', async ({ page, browserName }) => {
test.setTimeout(60000);
const [randomDoc] = await createDoc(
page,
'doc-table-content',
@@ -37,7 +39,7 @@ test.describe('Doc Table Content', () => {
await page.locator('.bn-block-outer').last().click();
// Create space to fill the viewport
for (let i = 0; i < 5; i++) {
for (let i = 0; i < 10; i++) {
await page.keyboard.press('Enter');
}
@@ -48,7 +50,7 @@ test.describe('Doc Table Content', () => {
await page.locator('.bn-block-outer').last().click();
// Create space to fill the viewport
for (let i = 0; i < 5; i++) {
for (let i = 0; i < 10; i++) {
await page.keyboard.press('Enter');
}
@@ -61,11 +63,11 @@ test.describe('Doc Table Content', () => {
const another = panel.getByText('Another World');
await expect(hello).toBeVisible();
await expect(hello).toHaveCSS('font-size', /19/);
await expect(hello).toHaveCSS('font-size', /17/);
await expect(hello).toHaveAttribute('aria-selected', 'true');
await expect(superW).toBeVisible();
await expect(superW).toHaveCSS('font-size', /16/);
await expect(superW).toHaveCSS('font-size', /14/);
await expect(superW).toHaveAttribute('aria-selected', 'false');
await expect(another).toBeVisible();

View File

@@ -23,7 +23,9 @@ test.describe('Doc Visibility', () => {
.getByLabel('Datagrid of the documents page 1')
.getByRole('table');
await expect(datagrid.getByLabel('Loading data')).toBeHidden();
await expect(datagrid.getByLabel('Loading data')).toBeHidden({
timeout: 10000,
});
await expect(datagrid.getByText(docTitle)).toBeVisible();

View File

@@ -507,6 +507,23 @@ input:-webkit-autofill:focus {
overflow-y: auto;
}
@media screen and (width <= 420px) {
.c__modal__scroller {
padding: 0.7rem;
}
.c__modal__title h2 {
font-size: 1rem;
}
}
@media (width <= 576px) {
.c__modal__footer--sided {
gap: 0.5rem;
flex-direction: column-reverse;
}
}
/**
* Toast
*/

View File

@@ -18,10 +18,14 @@ import { randomColor } from '../utils';
import { BlockNoteToolbar } from './BlockNoteToolbar';
const cssEditor = `
const cssEditor = (readonly: boolean) => `
&, & > .bn-container, & .ProseMirror {
height:100%
};
& .bn-editor {
padding-right: 30px;
${readonly && `padding-left: 30px;`}
};
& .collaboration-cursor__caret.ProseMirror-widget{
word-wrap: initial;
}
@@ -30,6 +34,35 @@ const cssEditor = `
padding: 2px;
border-radius: 4px;
}
@media screen and (width <= 560px) {
& .bn-editor {
padding-left: 40px;
padding-right: 10px;
${readonly && `padding-left: 10px;`}
};
.bn-side-menu[data-block-type=heading][data-level="1"] {
height: 46px;
}
.bn-side-menu[data-block-type=heading][data-level="2"] {
height: 40px;
}
.bn-side-menu[data-block-type=heading][data-level="3"] {
height: 40px;
}
& .bn-editor h1 {
font-size: 1.6rem;
}
& .bn-editor h2 {
font-size: 1.35rem;
}
& .bn-editor h3 {
font-size: 1.2rem;
}
.bn-block-content[data-is-empty-and-focused][data-content-type="paragraph"]
.bn-inline-content:has(> .ProseMirror-trailingBreak:only-child)::before {
font-size: 14px;
}
}
`;
interface BlockNoteEditorProps {
@@ -70,8 +103,9 @@ export const BlockNoteContent = ({
const isVersion = doc.id !== storeId;
const { userData } = useAuthStore();
const { setStore, docsStore } = useDocStore();
const canSave = doc.abilities.partial_update && !isVersion;
useSaveDoc(doc.id, provider.document, canSave);
const readOnly = !doc.abilities.partial_update || isVersion;
useSaveDoc(doc.id, provider.document, !readOnly);
const storedEditor = docsStore?.[storeId]?.editor;
const {
mutateAsync: createDocAttachment,
@@ -130,7 +164,7 @@ export const BlockNoteContent = ({
}, [editor, resetHeadings, setHeadings]);
return (
<Box $css={cssEditor}>
<Box $css={cssEditor(readOnly)}>
{isErrorAttachment && (
<Box $margin={{ bottom: 'big' }}>
<TextErrors
@@ -144,7 +178,7 @@ export const BlockNoteContent = ({
<BlockNoteView
editor={editor}
formattingToolbar={false}
editable={doc.abilities.partial_update && !isVersion}
editable={!readOnly}
theme="light"
>
<BlockNoteToolbar />

View File

@@ -9,6 +9,7 @@ import { useCunninghamTheme } from '@/cunningham';
import { DocHeader } from '@/features/docs/doc-header';
import { Doc } from '@/features/docs/doc-management';
import { Versions, useDocVersion } from '@/features/docs/doc-versioning/';
import { useResponsiveStore } from '@/stores';
import { useHeadingStore } from '../stores';
@@ -25,6 +26,7 @@ export const DocEditor = ({ doc }: DocEditorProps) => {
} = useRouter();
const { t } = useTranslation();
const { headings } = useHeadingStore();
const { isMobile } = useResponsiveStore();
const isVersion = versionId && typeof versionId === 'string';
@@ -51,11 +53,12 @@ export const DocEditor = ({ doc }: DocEditorProps) => {
$background={colorsTokens()['primary-bg']}
$height="100%"
$direction="row"
$margin={{ all: 'small', top: 'none' }}
$margin={{ all: isMobile ? 'tiny' : 'small', top: 'none' }}
$css="overflow-x: clip;"
$position="relative"
>
<Card
$padding="big"
$padding={isMobile ? 'small' : 'big'}
$css="flex:1;"
$overflow="auto"
$position="relative"
@@ -65,7 +68,7 @@ export const DocEditor = ({ doc }: DocEditorProps) => {
) : (
<BlockNoteEditor doc={doc} />
)}
<IconOpenPanelEditor headings={headings} />
{!isMobile && <IconOpenPanelEditor headings={headings} />}
</Card>
<PanelEditor doc={doc} headings={headings} />
</Box>

View File

@@ -6,6 +6,7 @@ import { useCunninghamTheme } from '@/cunningham';
import { Doc } from '@/features/docs/doc-management';
import { TableContent } from '@/features/docs/doc-table-content';
import { VersionList } from '@/features/docs/doc-versioning';
import { useResponsiveStore } from '@/stores';
import { usePanelEditorStore } from '../stores';
import { HeadingBlock } from '../types';
@@ -21,7 +22,7 @@ export const PanelEditor = ({
}: PropsWithChildren<PanelProps>) => {
const { t } = useTranslation();
const { colorsTokens } = useCunninghamTheme();
const { isMobile } = useResponsiveStore();
const { isPanelTableContentOpen, setIsPanelTableContentOpen, isPanelOpen } =
usePanelEditorStore();
@@ -29,12 +30,12 @@ export const PanelEditor = ({
<Card
$width="100%"
$maxWidth="20rem"
$position="sticky"
$maxHeight="99vh"
$position={isMobile ? 'absolute' : 'sticky'}
$height="100%"
$hasTransition="slow"
$css={`
top: 0vh;
right: 0;
transform: translateX(0%);
flex: 1;
margin-left: 1rem;
@@ -60,8 +61,9 @@ export const PanelEditor = ({
top: 0;
opacity: ${isPanelOpen ? '1' : '0'};
`}
$maxHeight="100%"
$maxHeight="99vh"
>
{isMobile && <IconOpenPanelEditor headings={headings} />}
<Box
$direction="row"
$justify="space-between"
@@ -69,6 +71,7 @@ export const PanelEditor = ({
$position="relative"
$background={colorsTokens()['primary-400']}
$margin={{ bottom: 'tiny' }}
$radius="4px 4px 0 0"
>
<Box
$background="white"
@@ -78,7 +81,15 @@ export const PanelEditor = ({
$hasTransition="slow"
$css={`
border-top: 2px solid ${colorsTokens()['primary-600']};
${isPanelTableContentOpen ? 'transform: translateX(0);' : 'transform: translateX(100%);'}
border-radius: 0 4px 0 0;
${
isPanelTableContentOpen
? `
transform: translateX(0);
border-radius: 4px 0 0 0;
`
: `transform: translateX(100%);`
}
`}
/>
<BoxButton
@@ -134,6 +145,7 @@ export const IconOpenPanelEditor = ({ headings }: IconOpenPanelEditorProps) => {
const { setIsPanelOpen, isPanelOpen, setIsPanelTableContentOpen } =
usePanelEditorStore();
const [hasBeenOpen, setHasBeenOpen] = useState(isPanelOpen);
const { isMobile } = useResponsiveStore();
const setClosePanel = () => {
setHasBeenOpen(true);
@@ -142,12 +154,18 @@ export const IconOpenPanelEditor = ({ headings }: IconOpenPanelEditorProps) => {
// Open the panel if there are more than 1 heading
useEffect(() => {
if (headings?.length && headings.length > 1 && !hasBeenOpen) {
if (headings?.length && headings.length > 1 && !hasBeenOpen && !isMobile) {
setIsPanelTableContentOpen(true);
setIsPanelOpen(true);
setHasBeenOpen(true);
}
}, [headings, setIsPanelTableContentOpen, setIsPanelOpen, hasBeenOpen]);
}, [
headings,
setIsPanelTableContentOpen,
setIsPanelOpen,
hasBeenOpen,
isMobile,
]);
// If open from the doc header we set the state as well
useEffect(() => {
@@ -169,7 +187,7 @@ export const IconOpenPanelEditor = ({ headings }: IconOpenPanelEditorProps) => {
aria-label={isPanelOpen ? t('Close the panel') : t('Open the panel')}
$background="transparent"
$size="h2"
$zIndex={1}
$zIndex={10}
$hasTransition="slow"
$css={`
cursor: pointer;

View File

@@ -1,5 +1,4 @@
import { Button } from '@openfun/cunningham-react';
import React, { Fragment, useState } from 'react';
import React, { Fragment } from 'react';
import { useTranslation } from 'react-i18next';
import { Box, Card, StyledLink, Text } from '@/components';
@@ -10,8 +9,9 @@ import {
currentDocRole,
useTrans,
} from '@/features/docs/doc-management';
import { ModalVersion, Versions } from '@/features/docs/doc-versioning';
import { Versions } from '@/features/docs/doc-versioning';
import { useDate } from '@/hook';
import { useResponsiveStore } from '@/stores';
import { DocTagPublic } from './DocTagPublic';
import { DocTitle } from './DocTitle';
@@ -27,15 +27,19 @@ export const DocHeader = ({ doc, versionId }: DocHeaderProps) => {
const { t } = useTranslation();
const { formatDate } = useDate();
const { transRole } = useTrans();
const [isModalVersionOpen, setIsModalVersionOpen] = useState(false);
const { isMobile, isSmallMobile } = useResponsiveStore();
return (
<>
<Card
$margin="small"
$margin={isMobile ? 'tiny' : 'small'}
aria-label={t('It is the card information about the document.')}
>
<Box $padding="small" $direction="row" $align="center">
<Box
$padding={isMobile ? 'tiny' : 'small'}
$direction="row"
$align="center"
>
<StyledLink href="/">
<Text
$isMaterialIcon
@@ -53,33 +57,39 @@ export const DocHeader = ({ doc, versionId }: DocHeaderProps) => {
$width="1px"
$height="70%"
$background={colorsTokens()['greyscale-100']}
$margin={{ horizontal: 'small' }}
$margin={{ horizontal: 'tiny' }}
/>
<Box $gap="1rem" $direction="row" $align="center">
<Box
$direction="row"
$justify="space-between"
$css="flex:1;"
$gap="0.5rem 1rem"
$wrap="wrap"
$align="center"
>
<DocTitle doc={doc} />
{versionId && (
<Button
onClick={() => {
setIsModalVersionOpen(true);
}}
size="small"
>
{t('Restore this version')}
</Button>
)}
<DocToolBox doc={doc} versionId={versionId} />
</Box>
<DocToolBox doc={doc} />
</Box>
<Box
$direction="row"
$align="center"
$direction={isSmallMobile ? 'column' : 'row'}
$align={isSmallMobile ? 'start' : 'center'}
$css="border-top:1px solid #eee"
$padding={{ horizontal: 'big', vertical: 'tiny' }}
$padding={{
horizontal: isMobile ? 'tiny' : 'big',
vertical: 'tiny',
}}
$gap="0.5rem 2rem"
$justify="space-between"
$wrap="wrap"
$position="relative"
>
<Box $direction="row" $align="center" $gap="0.5rem 2rem" $wrap="wrap">
<Box
$direction={isSmallMobile ? 'column' : 'row'}
$align={isSmallMobile ? 'start' : 'center'}
$gap="0.5rem 2rem"
$wrap="wrap"
>
<DocTagPublic doc={doc} />
<Text $size="s" $display="inline">
{t('Created at')} <strong>{formatDate(doc.created_at)}</strong>
@@ -106,13 +116,6 @@ export const DocHeader = ({ doc, versionId }: DocHeaderProps) => {
</Text>
</Box>
</Card>
{isModalVersionOpen && versionId && (
<ModalVersion
onClose={() => setIsModalVersionOpen(false)}
docId={doc.id}
versionId={versionId}
/>
)}
</>
);
};

View File

@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
import { Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Doc, LinkReach } from '@/features/docs/doc-management';
import { useResponsiveStore } from '@/stores';
interface DocTagPublicProps {
doc: Doc;
@@ -11,6 +12,7 @@ interface DocTagPublicProps {
export const DocTagPublic = ({ doc }: DocTagPublicProps) => {
const { colorsTokens } = useCunninghamTheme();
const { t } = useTranslation();
const { isSmallMobile } = useResponsiveStore();
if (doc?.link_reach !== LinkReach.PUBLIC) {
return null;
@@ -24,6 +26,8 @@ export const DocTagPublic = ({ doc }: DocTagPublicProps) => {
$padding="xtiny"
$radius="3px"
$size="s"
$position={isSmallMobile ? 'absolute' : 'initial'}
$css={isSmallMobile ? 'right: 10px;' : ''}
>
{t('Public')}
</Text>

View File

@@ -19,6 +19,7 @@ import {
useTrans,
useUpdateDoc,
} from '@/features/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import { isFirefox } from '@/utils/userAgent';
const DocTitleStyle = createGlobalStyle`
@@ -32,9 +33,15 @@ interface DocTitleProps {
}
export const DocTitle = ({ doc }: DocTitleProps) => {
const { isMobile } = useResponsiveStore();
if (!doc.abilities.partial_update) {
return (
<Text as="h2" $align="center" $margin={{ all: 'none', left: 'tiny' }}>
<Text
as="h2"
$margin={{ all: 'none', left: 'tiny' }}
$size={isMobile ? 'h4' : 'h2'}
>
{doc.title}
</Text>
);
@@ -53,6 +60,7 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
const { headings } = useHeadingStore();
const headingText = headings?.[0]?.contentText;
const debounceRef = useRef<NodeJS.Timeout>();
const { isMobile } = useResponsiveStore();
const { mutate: updateDoc } = useUpdateDoc({
listInvalideQueries: [KEY_DOC, KEY_LIST_DOC],
@@ -124,7 +132,6 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
as="h2"
$radius="4px"
$padding={{ horizontal: 'tiny', vertical: '4px' }}
$align="center"
$margin="none"
contentEditable={isFirefox() ? 'true' : 'plaintext-only'}
onClick={handleOnClick}
@@ -141,7 +148,7 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
$css={`
${isUntitled && 'font-style: italic;'}
cursor: text;
font-size: 1.5rem;
font-size: ${isMobile ? '1.2rem' : '1.5rem'};
transition: box-shadow 0.5s, border-color 0.5s;
border: 1px dashed transparent;

View File

@@ -9,98 +9,121 @@ import {
ModalRemoveDoc,
ModalShare,
} from '@/features/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import { ModalVersion, Versions } from '../../doc-versioning';
import { ModalPDF } from './ModalExport';
interface DocToolBoxProps {
doc: Doc;
versionId?: Versions['version_id'];
}
export const DocToolBox = ({ doc }: DocToolBoxProps) => {
export const DocToolBox = ({ doc, versionId }: DocToolBoxProps) => {
const { t } = useTranslation();
const [isModalShareOpen, setIsModalShareOpen] = useState(false);
const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false);
const [isModalPDFOpen, setIsModalPDFOpen] = useState(false);
const [isDropOpen, setIsDropOpen] = useState(false);
const { setIsPanelOpen, setIsPanelTableContentOpen } = usePanelEditorStore();
const [isModalVersionOpen, setIsModalVersionOpen] = useState(false);
const { isSmallMobile } = useResponsiveStore();
return (
<Box
$margin={{ left: 'auto' }}
$direction="row"
$align="center"
$gap="1rem"
$gap="0.5rem 1.5rem"
$wrap={isSmallMobile ? 'wrap' : 'nowrap'}
>
<Button
onClick={() => {
setIsModalShareOpen(true);
}}
>
{t('Share')}
</Button>
<DropButton
button={
<IconOptions
isOpen={isDropOpen}
aria-label={t('Open the document options')}
/>
}
onOpenChange={(isOpen) => setIsDropOpen(isOpen)}
isOpen={isDropOpen}
>
<Box>
{doc.abilities.versions_list && (
{versionId && (
<Box $margin={{ left: 'auto' }}>
<Button
onClick={() => {
setIsModalVersionOpen(true);
}}
color="secondary"
size={isSmallMobile ? 'small' : 'medium'}
>
{t('Restore this version')}
</Button>
</Box>
)}
<Box $direction="row" $margin={{ left: 'auto' }} $gap="1rem">
<Button
onClick={() => {
setIsModalShareOpen(true);
}}
size={isSmallMobile ? 'small' : 'medium'}
>
{t('Share')}
</Button>
<DropButton
button={
<IconOptions
isOpen={isDropOpen}
aria-label={t('Open the document options')}
/>
}
onOpenChange={(isOpen) => setIsDropOpen(isOpen)}
isOpen={isDropOpen}
>
<Box>
{doc.abilities.versions_list && (
<Button
onClick={() => {
setIsPanelOpen(true);
setIsPanelTableContentOpen(false);
setIsDropOpen(false);
}}
color="primary-text"
icon={<span className="material-icons">history</span>}
size="small"
>
<Text $theme="primary">{t('Version history')}</Text>
</Button>
)}
<Button
onClick={() => {
setIsPanelOpen(true);
setIsPanelTableContentOpen(false);
setIsPanelTableContentOpen(true);
setIsDropOpen(false);
}}
color="primary-text"
icon={<span className="material-icons">history</span>}
icon={<span className="material-icons">summarize</span>}
size="small"
>
<Text $theme="primary">{t('Version history')}</Text>
<Text $theme="primary">{t('Table of contents')}</Text>
</Button>
)}
<Button
onClick={() => {
setIsPanelOpen(true);
setIsPanelTableContentOpen(true);
setIsDropOpen(false);
}}
color="primary-text"
icon={<span className="material-icons">summarize</span>}
size="small"
>
<Text $theme="primary">{t('Table of contents')}</Text>
</Button>
<Button
onClick={() => {
setIsModalPDFOpen(true);
setIsDropOpen(false);
}}
color="primary-text"
icon={<span className="material-icons">file_download</span>}
size="small"
>
<Text $theme="primary">{t('Export')}</Text>
</Button>
{doc.abilities.destroy && (
<Button
onClick={() => {
setIsModalRemoveOpen(true);
setIsModalPDFOpen(true);
setIsDropOpen(false);
}}
color="primary-text"
icon={<span className="material-icons">delete</span>}
icon={<span className="material-icons">file_download</span>}
size="small"
>
<Text $theme="primary">{t('Delete document')}</Text>
<Text $theme="primary">{t('Export')}</Text>
</Button>
)}
</Box>
</DropButton>
{doc.abilities.destroy && (
<Button
onClick={() => {
setIsModalRemoveOpen(true);
setIsDropOpen(false);
}}
color="primary-text"
icon={<span className="material-icons">delete</span>}
size="small"
>
<Text $theme="primary">{t('Delete document')}</Text>
</Button>
)}
</Box>
</DropButton>
</Box>
{isModalShareOpen && (
<ModalShare onClose={() => setIsModalShareOpen(false)} doc={doc} />
)}
@@ -110,6 +133,13 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => {
{isModalRemoveOpen && (
<ModalRemoveDoc onClose={() => setIsModalRemoveOpen(false)} doc={doc} />
)}
{isModalVersionOpen && versionId && (
<ModalVersion
onClose={() => setIsModalVersionOpen(false)}
docId={doc.id}
versionId={versionId}
/>
)}
</Box>
);
};

View File

@@ -43,48 +43,57 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
$direction="row"
$align="center"
$justify="space-between"
$gap="1rem"
>
<Box $direction="row" $gap="1rem" $align="center">
<IconBG iconName="public" $margin="none" />
<Switch
label={t(docPublic ? 'Doc public' : 'Doc private')}
defaultChecked={docPublic}
onChange={() => {
api.mutate({
id: doc.id,
link_reach: docPublic ? LinkReach.RESTRICTED : LinkReach.PUBLIC,
link_role: 'reader',
});
setDocPublic(!docPublic);
}}
disabled={!doc.abilities.link_configuration}
text={
docPublic
? t('Anyone on the internet with the link can view')
: t('Only for people with access')
}
/>
</Box>
<Button
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="primary"
icon={<span className="material-icons">copy</span>}
<IconBG iconName="public" $margin="none" />
<Box
$width="100%"
$wrap="wrap"
$gap="1rem"
$justify="space-between"
$direction="row"
>
{t('Copy link')}
</Button>
<Box $direction="row" $gap="1rem" $align="center">
<Switch
label={t(docPublic ? 'Doc public' : 'Doc private')}
defaultChecked={docPublic}
onChange={() => {
api.mutate({
id: doc.id,
link_reach: docPublic ? LinkReach.RESTRICTED : LinkReach.PUBLIC,
link_role: 'reader',
});
setDocPublic(!docPublic);
}}
disabled={!doc.abilities.link_configuration}
text={
docPublic
? t('Anyone on the internet with the link can view')
: t('Only for people with access')
}
/>
</Box>
<Button
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="primary"
icon={<span className="material-icons">copy</span>}
>
{t('Copy link')}
</Button>
</Box>
</Card>
);
};

View File

@@ -5,6 +5,7 @@ import { Box, Card, SideModal, Text } from '@/components';
import { InvitationList } from '@/features/docs/members/invitation-list';
import { AddMembers } from '@/features/docs/members/members-add';
import { MemberList } from '@/features/docs/members/members-list';
import { useResponsiveStore } from '@/stores';
import { Doc } from '../types';
import { currentDocRole } from '../utils';
@@ -20,6 +21,15 @@ const ModalShareStyle = createGlobalStyle`
padding: 0;
margin: 0;
}
.c__modal__close{
margin-right: 1rem;
button{
border-bottom: 1px solid #E0E0E0;
border-left: 1px solid #E0E0E0;
}
}
}
`;
@@ -29,18 +39,21 @@ interface ModalShareProps {
}
export const ModalShare = ({ onClose, doc }: ModalShareProps) => {
const { isMobile, isSmallMobile } = useResponsiveStore();
const width = isSmallMobile ? '100vw' : isMobile ? '90vw' : '70vw';
return (
<>
<ModalShareStyle />
<SideModal
isOpen
closeOnClickOutside
hideCloseButton
hideCloseButton={!isSmallMobile}
onClose={onClose}
width="70vw"
width={width}
$css="min-width: 320px;max-width: 777px;"
>
<Box aria-label={t('Share modal')}>
<Box aria-label={t('Share modal')} $margin={{ bottom: 'small' }}>
<Box $shrink="0">
<Card
$direction="row"

View File

@@ -3,10 +3,11 @@ import { useState } from 'react';
import { BoxButton, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { useResponsiveStore } from '@/stores';
const sizeMap: { [key: number]: string } = {
1: '1.2rem',
2: '1rem',
1: '1.1rem',
2: '0.9rem',
3: '0.8rem',
};
@@ -32,6 +33,7 @@ export const Heading = ({
}: HeadingProps) => {
const [isHover, setIsHover] = useState(isHighlight);
const { colorsTokens } = useCunninghamTheme();
const { isMobile } = useResponsiveStore();
return (
<BoxButton
@@ -39,7 +41,11 @@ export const Heading = ({
onMouseOver={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
onClick={() => {
editor.focus();
// With mobile the focus open the keyboard and the scroll is not working
if (!isMobile) {
editor.focus();
}
editor.setTextCursorPosition(headingId, 'end');
document.querySelector(`[data-id="${headingId}"]`)?.scrollIntoView({
behavior: 'smooth',

View File

@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import { Box, BoxButton, Text } from '@/components';
import { HeadingBlock, useDocStore } from '@/features/docs/doc-editor';
import { Doc } from '@/features/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import { Heading } from './Heading';
@@ -14,6 +15,7 @@ interface TableContentProps {
export const TableContent = ({ doc, headings }: TableContentProps) => {
const { docsStore } = useDocStore();
const { isMobile } = useResponsiveStore();
const { t } = useTranslation();
const editor = docsStore?.[doc.id]?.editor;
const [headingIdHighlight, setHeadingIdHighlight] = useState<string>();
@@ -66,17 +68,20 @@ export const TableContent = ({ doc, headings }: TableContentProps) => {
return (
<Box $padding={{ all: 'small', right: 'none' }} $maxHeight="95%">
<Box $overflow="auto">
{headings?.map((heading) => (
<Heading
editor={editor}
headingId={heading.id}
level={heading.props.level}
text={heading.contentText}
key={heading.id}
isHighlight={headingIdHighlight === heading.id}
/>
))}
<Box $overflow="auto" $padding={{ left: '2px' }}>
{headings?.map(
(heading) =>
heading.contentText && (
<Heading
editor={editor}
headingId={heading.id}
level={heading.props.level}
text={heading.contentText}
key={heading.id}
isHighlight={headingIdHighlight === heading.id}
/>
),
)}
</Box>
<Box
$height="1px"
@@ -87,7 +92,11 @@ export const TableContent = ({ doc, headings }: TableContentProps) => {
/>
<BoxButton
onClick={() => {
editor.focus();
// With mobile the focus open the keyboard and the scroll is not working
if (!isMobile) {
editor.focus();
}
document.querySelector(`.bn-editor`)?.scrollIntoView({
behavior: 'smooth',
block: 'start',
@@ -101,7 +110,11 @@ export const TableContent = ({ doc, headings }: TableContentProps) => {
</BoxButton>
<BoxButton
onClick={() => {
editor.focus();
// With mobile the focus open the keyboard and the scroll is not working
if (!isMobile) {
editor.focus();
}
document
.querySelector(
`.bn-editor > .bn-block-group > .bn-block-outer:last-child`,

View File

@@ -1,4 +1,9 @@
import { DataGrid, SortModel, usePagination } from '@openfun/cunningham-react';
import {
Column,
DataGrid,
SortModel,
usePagination,
} from '@openfun/cunningham-react';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { createGlobalStyle } from 'styled-components';
@@ -15,6 +20,7 @@ import {
useTrans,
} from '@/features/docs/doc-management';
import { useDate } from '@/hook/';
import { useResponsiveStore } from '@/stores';
import { PAGE_SIZE } from '../conf';
@@ -62,6 +68,7 @@ export const DocsGrid = () => {
]);
const { page, pageSize, setPagesCount } = pagination;
const [docs, setDocs] = useState<Doc[]>([]);
const { isMobile } = useResponsiveStore();
const ordering = sortModel.length ? formatSortModel(sortModel[0]) : undefined;
@@ -82,19 +89,117 @@ export const DocsGrid = () => {
setPagesCount(data?.count ? Math.ceil(data.count / pageSize) : 0);
}, [data?.count, pageSize, setPagesCount]);
const columns: Column<Doc>[] = [
{
headerName: '',
id: 'visibility',
size: 95,
renderCell: ({ row }) => {
return (
row.link_reach === LinkReach.PUBLIC && (
<StyledLink href={`/docs/${row.id}`}>
<Text
$weight="bold"
$background={colorsTokens()['primary-600']}
$color="white"
$padding="xtiny"
$radius="3px"
>
{t('Public')}
</Text>
</StyledLink>
)
);
},
},
{
headerName: t('Document name'),
field: 'title',
renderCell: ({ row }) => {
return (
<StyledLink href={`/docs/${row.id}`}>
<Text $weight="bold" $theme="primary">
{row.title}
</Text>
</StyledLink>
);
},
},
{
headerName: t('Created at'),
field: 'created_at',
renderCell: ({ row }) => {
return (
<StyledLink href={`/docs/${row.id}`}>
<Text $weight="bold">{formatDate(row.created_at)}</Text>
</StyledLink>
);
},
},
{
headerName: t('Updated at'),
field: 'updated_at',
renderCell: ({ row }) => {
return (
<StyledLink href={`/docs/${row.id}`}>
<Text $weight="bold">{formatDate(row.updated_at)}</Text>
</StyledLink>
);
},
},
{
headerName: t('Your role'),
id: 'your_role',
renderCell: ({ row }) => {
return (
<StyledLink href={`/docs/${row.id}`}>
<Text $weight="bold">
{transRole(currentDocRole(row.abilities))}
</Text>
</StyledLink>
);
},
},
{
headerName: t('Members'),
id: 'users_number',
renderCell: ({ row }) => {
return (
<StyledLink href={`/docs/${row.id}`}>
<Text $weight="bold">{row.accesses.length}</Text>
</StyledLink>
);
},
},
{
id: 'column-actions',
renderCell: ({ row }) => {
return <DocsGridActions doc={row} />;
},
},
];
// Inverse columns for mobile to have the most important information first
if (isMobile) {
const tmpCol = columns[0];
columns[0] = columns[1];
columns[1] = tmpCol;
}
return (
<Card
$padding={{ bottom: 'small', horizontal: 'big' }}
$margin={{ all: 'big', top: 'none' }}
$margin={{ all: isMobile ? 'small' : 'big', top: 'none' }}
$overflow="auto"
aria-label={t(`Datagrid of the documents page {{page}}`, { page })}
$height="100%"
>
<DocsGridStyle />
<Text
$weight="bold"
as="h2"
$theme="primary"
$margin={{ bottom: 'none' }}
$margin={{ bottom: 'small' }}
>
{t('Documents')}
</Text>
@@ -102,95 +207,7 @@ export const DocsGrid = () => {
{error && <TextErrors causes={error.cause} />}
<DataGrid
columns={[
{
headerName: '',
id: 'visibility',
size: 95,
renderCell: ({ row }) => {
return (
<StyledLink href={`/docs/${row.id}`}>
{row.link_reach === LinkReach.PUBLIC && (
<Text
$weight="bold"
$background={colorsTokens()['primary-600']}
$color="white"
$padding="xtiny"
$radius="3px"
>
{t('Public')}
</Text>
)}
</StyledLink>
);
},
},
{
headerName: t('Document name'),
field: 'title',
renderCell: ({ row }) => {
return (
<StyledLink href={`/docs/${row.id}`}>
<Text $weight="bold" $theme="primary">
{row.title}
</Text>
</StyledLink>
);
},
},
{
headerName: t('Created at'),
field: 'created_at',
renderCell: ({ row }) => {
return (
<StyledLink href={`/docs/${row.id}`}>
<Text $weight="bold">{formatDate(row.created_at)}</Text>
</StyledLink>
);
},
},
{
headerName: t('Updated at'),
field: 'updated_at',
renderCell: ({ row }) => {
return (
<StyledLink href={`/docs/${row.id}`}>
<Text $weight="bold">{formatDate(row.updated_at)}</Text>
</StyledLink>
);
},
},
{
headerName: t('Your role'),
id: 'your_role',
renderCell: ({ row }) => {
return (
<StyledLink href={`/docs/${row.id}`}>
<Text $weight="bold">
{transRole(currentDocRole(row.abilities))}
</Text>
</StyledLink>
);
},
},
{
headerName: t('Members'),
id: 'users_number',
renderCell: ({ row }) => {
return (
<StyledLink href={`/docs/${row.id}`}>
<Text $weight="bold">{row.accesses.length}</Text>
</StyledLink>
);
},
},
{
id: 'column-actions',
renderCell: ({ row }) => {
return <DocsGridActions doc={row} />;
},
},
]}
columns={columns}
rows={docs}
isLoading={isLoading}
pagination={pagination}

View File

@@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
import { Box } from '@/components';
import { useCreateDoc, useTrans } from '@/features/docs/doc-management/';
import { useResponsiveStore } from '@/stores';
import { DocsGrid } from './DocsGrid';
@@ -12,6 +13,7 @@ export const DocsGridContainer = () => {
const { t } = useTranslation();
const { untitledDocument } = useTrans();
const router = useRouter();
const { isMobile } = useResponsiveStore();
const { mutate: createDoc } = useCreateDoc({
onSuccess: (doc) => {
@@ -25,7 +27,11 @@ export const DocsGridContainer = () => {
return (
<Box $overflow="auto">
<Box $align="flex-end" $justify="center" $margin="big">
<Box
$align="flex-end"
$justify="center"
$margin={isMobile ? 'small' : 'big'}
>
<Button onClick={handleCreateDoc}>{t('Create a new document')}</Button>
</Box>
<DocsGrid />

View File

@@ -11,6 +11,7 @@ import { Box, IconBG, Text, TextErrors } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Doc, Role } from '@/features/docs/doc-management';
import { ChooseRole } from '@/features/docs/members/members-add/';
import { useResponsiveStore } from '@/stores';
import { useDeleteDocInvitation, useUpdateDocInvitation } from '../api';
import { Invitation } from '../types';
@@ -31,6 +32,7 @@ export const InvitationItem = ({
const canDelete = invitation.abilities.destroy;
const canUpdate = invitation.abilities.partial_update;
const { t } = useTranslation();
const { isSmallMobile, screenWidth } = useResponsiveStore();
const [localRole, setLocalRole] = useState(role);
const { colorsTokens } = useCunninghamTheme();
const { toast } = useToastProvider();
@@ -62,8 +64,7 @@ export const InvitationItem = ({
return (
<Box $width="100%" $gap="0.7rem">
<Box $direction="row" $gap="1rem">
<IconBG iconName="account_circle" $size="2rem" />
<Box $direction="row" $gap="1rem" $wrap="wrap">
<Box
$align="center"
$direction="row"
@@ -71,8 +72,10 @@ export const InvitationItem = ({
$justify="space-between"
$width="100%"
$wrap="wrap"
$css={`flex: ${isSmallMobile ? '100%' : '70%'};`}
>
<Box>
<IconBG iconName="account_circle" $size="2rem" />
<Box $css="flex:1;">
<Text
$size="t"
$background={colorsTokens()['info-600']}
@@ -85,8 +88,15 @@ export const InvitationItem = ({
</Text>
<Text $justify="center">{invitation.email}</Text>
</Box>
<Box $direction="row" $gap="1rem" $align="center">
<Box $minWidth="13rem">
<Box
$direction="row"
$gap="1rem"
$align="center"
$justify="space-between"
$css="flex:1;"
$wrap={screenWidth < 400 ? 'wrap' : 'nowrap'}
>
<Box $minWidth="13rem" $css={isSmallMobile ? 'flex:1;' : ''}>
<ChooseRole
label={t('Role')}
defaultRole={localRole}
@@ -103,25 +113,27 @@ export const InvitationItem = ({
/>
</Box>
{doc.abilities.manage_accesses && (
<Button
color="tertiary-text"
icon={
<Text
$isMaterialIcon
$theme={!canDelete ? 'greyscale' : 'primary'}
$variation={!canDelete ? '500' : 'text'}
>
delete
</Text>
}
disabled={!canDelete}
onClick={() =>
removeDocInvitation({
docId: doc.id,
invitationId: invitation.id,
})
}
/>
<Box $margin={isSmallMobile ? 'auto' : ''}>
<Button
color="tertiary-text"
icon={
<Text
$isMaterialIcon
$theme={!canDelete ? 'greyscale' : 'primary'}
$variation={!canDelete ? '500' : 'text'}
>
delete
</Text>
}
disabled={!canDelete}
onClick={() =>
removeDocInvitation({
docId: doc.id,
invitationId: invitation.id,
})
}
/>
</Box>
)}
</Box>
</Box>

View File

@@ -6,6 +6,7 @@ import { APIError } from '@/api';
import { Box, Card, InfiniteScroll, TextErrors } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Doc, currentDocRole } from '@/features/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import { useDocInvitationsInfinite } from '../api';
import { Invitation } from '../types';
@@ -26,6 +27,7 @@ const InvitationListState = ({
doc,
}: InvitationListStateProps) => {
const { colorsTokens } = useCunninghamTheme();
const { isSmallMobile } = useResponsiveStore();
if (error) {
return <TextErrors causes={error.cause} />;
@@ -49,7 +51,7 @@ const InvitationListState = ({
key={`${invitation.id}-${index}`}
$background={!(index % 2) ? 'white' : colorsTokens()['greyscale-000']}
$direction="row"
$padding="small"
$padding={isSmallMobile ? 'tiny' : 'small'}
$align="center"
$gap="1rem"
$radius="4px"

View File

@@ -11,6 +11,7 @@ import { Box, Card, IconBG } from '@/components';
import { Doc, Role } from '@/features/docs/doc-management';
import { useCreateDocInvitation } from '@/features/docs/members/invitation-list/';
import { useLanguage } from '@/i18n/hooks/useLanguage';
import { useResponsiveStore } from '@/stores';
import { useCreateDocAccess } from '../api';
import {
@@ -37,6 +38,7 @@ interface ModalAddMembersProps {
export const AddMembers = ({ currentRole, doc }: ModalAddMembersProps) => {
const { contentLanguage } = useLanguage();
const { t } = useTranslation();
const { isSmallMobile } = useResponsiveStore();
const [selectedUsers, setSelectedUsers] = useState<OptionsSelect>([]);
const [selectedRole, setSelectedRole] = useState<Role>();
const { toast } = useToastProvider();
@@ -145,7 +147,12 @@ export const AddMembers = ({ currentRole, doc }: ModalAddMembersProps) => {
$wrap="wrap"
>
<IconBG iconName="group_add" />
<Box $gap="0.7rem" $direction="row" $wrap="wrap" $css="flex: 70%;">
<Box
$gap="0.7rem"
$direction="row"
$wrap={isSmallMobile ? 'wrap' : 'nowrap'}
$css="flex: 70%;"
>
<Box $gap="0.7rem" $direction="row" $wrap="wrap" $css="flex: 80%;">
<Box $css="flex: auto;" $width="15rem">
<SearchUsers

View File

@@ -12,6 +12,7 @@ import { useTranslation } from 'react-i18next';
import { Box, IconBG, Text, TextErrors } from '@/components';
import { Access, Doc, Role } from '@/features/docs/doc-management';
import { ChooseRole } from '@/features/docs/members/members-add/';
import { useResponsiveStore } from '@/stores';
import { useDeleteDocAccess, useUpdateDocAccess } from '../api';
import { useWhoAmI } from '../hooks/useWhoAmI';
@@ -31,6 +32,7 @@ export const MemberItem = ({
}: MemberItemProps) => {
const { isMyself, isLastOwner, isOtherOwner } = useWhoAmI(access);
const { t } = useTranslation();
const { isSmallMobile, screenWidth } = useResponsiveStore();
const [localRole, setLocalRole] = useState(role);
const { toast } = useToastProvider();
const router = useRouter();
@@ -71,8 +73,7 @@ export const MemberItem = ({
return (
<Box $width="100%">
<Box $direction="row" $gap="1rem">
<IconBG iconName="account_circle" $size="2rem" />
<Box $direction="row" $gap="1rem" $wrap="wrap">
<Box
$align="center"
$direction="row"
@@ -80,10 +81,21 @@ export const MemberItem = ({
$justify="space-between"
$width="100%"
$wrap="wrap"
$css={`flex: ${isSmallMobile ? '100%' : '70%'};`}
>
<Text $justify="center">{access.user.email}</Text>
<Box $direction="row" $gap="1rem" $align="center">
<Box $minWidth="13rem">
<IconBG iconName="account_circle" $size="2rem" />
<Text $justify="center" $css="flex:1;">
{access.user.email}
</Text>
<Box
$direction="row"
$gap="1rem"
$align="center"
$justify="space-between"
$css="flex:1;"
$wrap={screenWidth < 400 ? 'wrap' : 'nowrap'}
>
<Box $minWidth="13rem" $css={isSmallMobile ? 'flex:1;' : ''}>
<ChooseRole
label={t('Role')}
defaultRole={localRole}
@@ -100,22 +112,24 @@ export const MemberItem = ({
/>
</Box>
{doc.abilities.manage_accesses && (
<Button
color="tertiary-text"
icon={
<Text
$isMaterialIcon
$theme={isNotAllowed ? 'greyscale' : 'primary'}
$variation={isNotAllowed ? '500' : 'text'}
>
delete
</Text>
}
disabled={isNotAllowed}
onClick={() =>
removeDocAccess({ docId: doc.id, accessId: access.id })
}
/>
<Box $margin={isSmallMobile ? 'auto' : ''}>
<Button
color="tertiary-text"
icon={
<Text
$isMaterialIcon
$theme={isNotAllowed ? 'greyscale' : 'primary'}
$variation={isNotAllowed ? '500' : 'text'}
>
delete
</Text>
}
disabled={isNotAllowed}
onClick={() =>
removeDocAccess({ docId: doc.id, accessId: access.id })
}
/>
</Box>
)}
</Box>
</Box>

View File

@@ -6,6 +6,7 @@ import { APIError } from '@/api';
import { Box, Card, InfiniteScroll, TextErrors } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Access, Doc, currentDocRole } from '@/features/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import { useDocAccessesInfinite } from '../api';
@@ -25,6 +26,7 @@ const MemberListState = ({
doc,
}: MemberListStateProps) => {
const { colorsTokens } = useCunninghamTheme();
const { isSmallMobile } = useResponsiveStore();
if (error) {
return <TextErrors causes={error.cause} />;
@@ -48,7 +50,7 @@ const MemberListState = ({
key={`${access.id}-${index}`}
$background={!(index % 2) ? 'white' : colorsTokens()['greyscale-000']}
$direction="row"
$padding="small"
$padding={isSmallMobile ? 'tiny' : 'small'}
$align="center"
$gap="1rem"
$radius="4px"