mirror of
https://github.com/suitenumerique/docs.git
synced 2026-04-25 17:15:01 +02:00
💄(frontend) update interlinking ux/ui
Update interlinking to fit the new design. The notable changes is that we cannot create a subdoc from the search dropdown.
This commit is contained in:
@@ -52,28 +52,6 @@ test.describe('Doc Create', () => {
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('it creates a sub doc from interlinking dropdown', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
const [title] = await createDoc(page, 'my-new-slash-doc', browserName, 1);
|
||||
|
||||
await verifyDocName(page, title);
|
||||
|
||||
await page.locator('.bn-block-outer').last().fill('/');
|
||||
await page.getByText('Link a doc').first().click();
|
||||
await page
|
||||
.locator('.quick-search-container')
|
||||
.getByText('New sub-doc')
|
||||
.click();
|
||||
|
||||
const input = page.getByRole('textbox', { name: 'Document title' });
|
||||
await expect(input).toHaveText('', { timeout: 10000 });
|
||||
await expect(
|
||||
page.locator('.c__tree-view--row-content').getByText('Untitled document'),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('it creates a doc with link "/doc/new/', async ({
|
||||
page,
|
||||
browserName,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Command } from 'cmdk';
|
||||
import { PropsWithChildren, ReactNode, useId, useRef, useState } from 'react';
|
||||
import { PropsWithChildren, ReactNode, useId, useRef } from 'react';
|
||||
|
||||
import { hasChildrens } from '@/utils/children';
|
||||
|
||||
@@ -24,6 +24,7 @@ export type QuickSearchData<T> = {
|
||||
};
|
||||
|
||||
export type QuickSearchProps = {
|
||||
isSelectByDefault?: boolean;
|
||||
onFilter?: (str: string) => void;
|
||||
inputValue?: string;
|
||||
inputContent?: ReactNode;
|
||||
@@ -36,6 +37,7 @@ export type QuickSearchProps = {
|
||||
};
|
||||
|
||||
export const QuickSearch = ({
|
||||
isSelectByDefault,
|
||||
onFilter,
|
||||
inputContent,
|
||||
inputValue,
|
||||
@@ -47,13 +49,6 @@ export const QuickSearch = ({
|
||||
}: PropsWithChildren<QuickSearchProps>) => {
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const listId = useId();
|
||||
/**
|
||||
* Hack to prevent cmdk from auto-selecting the first element on open
|
||||
*
|
||||
* TODO: Find a clean solution to prevent cmdk from auto-selecting
|
||||
* the first element on open
|
||||
*/
|
||||
const [selectedValue, _] = useState('__none__');
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -65,7 +60,7 @@ export const QuickSearch = ({
|
||||
ref={ref}
|
||||
tabIndex={-1}
|
||||
disablePointerSelection
|
||||
value={selectedValue}
|
||||
value={!isSelectByDefault ? '__none__' : undefined}
|
||||
>
|
||||
{showInput && (
|
||||
<QuickSearchInput
|
||||
|
||||
@@ -19,7 +19,13 @@ export const QuickSearchGroup = <T,>({
|
||||
}: Props<T>) => {
|
||||
return (
|
||||
<Box>
|
||||
<Text as="h2" $weight="700" $size="sm" $margin="none">
|
||||
<Text
|
||||
className="--docs--quick-search-group-title"
|
||||
as="h2"
|
||||
$weight="700"
|
||||
$size="sm"
|
||||
$margin="none"
|
||||
>
|
||||
{group.groupName}
|
||||
</Text>
|
||||
<Command.Group
|
||||
@@ -61,7 +67,11 @@ export const QuickSearchGroup = <T,>({
|
||||
);
|
||||
})}
|
||||
{group.emptyString && group.elements.length === 0 && (
|
||||
<Text $margin={{ left: '2xs', bottom: '3xs' }} $size="sm">
|
||||
<Text
|
||||
className="--docs--quick-search-group-empty"
|
||||
$margin={{ left: '2xs', bottom: '3xs' }}
|
||||
$size="sm"
|
||||
>
|
||||
{group.emptyString}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -9,8 +9,7 @@ import { useEffect } from 'react';
|
||||
import { css } from 'styled-components';
|
||||
import { validate as uuidValidate } from 'uuid';
|
||||
|
||||
import { BoxButton, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Box, BoxButton, Text } from '@/components';
|
||||
import SelectedPageIcon from '@/docs/doc-editor/assets/doc-selected.svg';
|
||||
import { getEmojiAndTitle, useDoc } from '@/docs/doc-management/';
|
||||
|
||||
@@ -120,8 +119,6 @@ export const LinkSelected = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [doc?.title, docId, isEditable]);
|
||||
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
|
||||
const { emoji, titleWithoutEmoji } = getEmojiAndTitle(title);
|
||||
const router = useRouter();
|
||||
const href = `/docs/${docId}/`;
|
||||
@@ -154,6 +151,7 @@ export const LinkSelected = ({
|
||||
onClick={handleClick}
|
||||
onAuxClick={handleAuxClick}
|
||||
draggable="false"
|
||||
$height="28px"
|
||||
$css={css`
|
||||
display: inline;
|
||||
padding: 0.1rem 0.4rem;
|
||||
@@ -179,18 +177,38 @@ export const LinkSelected = ({
|
||||
{emoji ? (
|
||||
<Text $size="16px">{emoji}</Text>
|
||||
) : (
|
||||
<SelectedPageIcon width={11.5} color={colorsTokens['brand-400']} />
|
||||
<SelectedPageIcon
|
||||
width={11.5}
|
||||
color="var(--c--contextuals--content--semantic--brand--tertiary)"
|
||||
/>
|
||||
)}
|
||||
<Text
|
||||
$weight="500"
|
||||
spellCheck="false"
|
||||
$size="16px"
|
||||
$display="inline"
|
||||
$position="relative"
|
||||
$css={css`
|
||||
margin-left: 2px;
|
||||
`}
|
||||
>
|
||||
{titleWithoutEmoji}
|
||||
<Box
|
||||
className="--docs-interlinking-underline"
|
||||
as="span"
|
||||
$height="1px"
|
||||
$width="100%"
|
||||
$background="var(--c--contextuals--border--semantic--neutral--tertiary)"
|
||||
$position="absolute"
|
||||
$hasTransition
|
||||
$radius="2px"
|
||||
$css={css`
|
||||
left: 0;
|
||||
bottom: 0px;
|
||||
`}
|
||||
/>
|
||||
<Box as="span" $zIndex="1" $position="relative">
|
||||
{titleWithoutEmoji}
|
||||
</Box>
|
||||
</Text>
|
||||
</BoxButton>
|
||||
);
|
||||
|
||||
@@ -15,30 +15,21 @@ import {
|
||||
Card,
|
||||
Icon,
|
||||
QuickSearch,
|
||||
QuickSearchGroup,
|
||||
QuickSearchItemContent,
|
||||
Text,
|
||||
} from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import {
|
||||
DocsBlockSchema,
|
||||
DocsInlineContentSchema,
|
||||
DocsStyleSchema,
|
||||
} from '@/docs/doc-editor';
|
||||
import FoundPageIcon from '@/docs/doc-editor/assets/doc-found.svg';
|
||||
import AddPageIcon from '@/docs/doc-editor/assets/doc-plus.svg';
|
||||
import {
|
||||
Doc,
|
||||
getEmojiAndTitle,
|
||||
useCreateChildDocTree,
|
||||
useDocStore,
|
||||
useTrans,
|
||||
} from '@/docs/doc-management';
|
||||
import { Doc, getEmojiAndTitle, useTrans } from '@/docs/doc-management';
|
||||
import { DocSearchContent, DocSearchTarget } from '@/docs/doc-search';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
const inputStyle = css`
|
||||
background-color: var(--c--globals--colors--gray-100);
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: var(--c--globals--colors--gray-700);
|
||||
@@ -76,15 +67,12 @@ export const SearchPage = ({
|
||||
trigger,
|
||||
updateInlineContent,
|
||||
}: SearchPageProps) => {
|
||||
const { colorsTokens } = useCunninghamTheme();
|
||||
const editor = useBlockNoteEditor<
|
||||
DocsBlockSchema,
|
||||
DocsInlineContentSchema,
|
||||
DocsStyleSchema
|
||||
>();
|
||||
const { t } = useTranslation();
|
||||
const { currentDoc } = useDocStore();
|
||||
const createChildDoc = useCreateChildDocTree(currentDoc?.id);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [search, setSearch] = useState('');
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
@@ -174,11 +162,11 @@ export const SearchPage = ({
|
||||
<Box
|
||||
as="span"
|
||||
className="inline-content"
|
||||
$background={colorsTokens['gray-100']}
|
||||
$color="var(--c--globals--colors--gray-700)"
|
||||
$background="var(--c--contextuals--background--semantic--overlay--primary)"
|
||||
$color="var(--c--contextuals--content--semantic--neutral--primary)"
|
||||
$direction="row"
|
||||
$radius="3px"
|
||||
$padding="1px"
|
||||
$padding="2px"
|
||||
$display="inline-flex"
|
||||
tabIndex={-1} // Ensure the span is focusable
|
||||
>
|
||||
@@ -196,6 +184,7 @@ export const SearchPage = ({
|
||||
aria-autocomplete="list"
|
||||
aria-controls={dropdownId}
|
||||
$padding={{ left: '3px' }}
|
||||
placeholder={t('mention a sub-doc...')}
|
||||
$css={inputStyle}
|
||||
ref={inputRef}
|
||||
$display="inline-flex"
|
||||
@@ -229,13 +218,22 @@ export const SearchPage = ({
|
||||
|
||||
& .quick-search-container [cmdk-root] {
|
||||
border-radius: inherit;
|
||||
background: transparent;
|
||||
}
|
||||
`}
|
||||
>
|
||||
<QuickSearch showInput={false}>
|
||||
<QuickSearch showInput={false} isSelectByDefault>
|
||||
<Card
|
||||
$css={css`
|
||||
box-shadow: 0 0 3px 0px var(--c--globals--colors--gray-200);
|
||||
box-shadow: 0 0 6px 0 rgba(0, 0, 145, 0.1);
|
||||
border: 1px solid
|
||||
var(--c--contextuals--border--surface--primary);
|
||||
background: var(
|
||||
--c--contextuals--background--surface--primary
|
||||
);
|
||||
.quick-search-container & [cmdk-group] {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
& > div {
|
||||
margin-top: var(--c--globals--spacings--0);
|
||||
& [cmdk-group-heading] {
|
||||
@@ -257,15 +255,27 @@ export const SearchPage = ({
|
||||
& .--docs--doc-search-item > div {
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
& .--docs--quick-search-group-title {
|
||||
font-size: 12px;
|
||||
margin: var(--c--globals--spacings--sm);
|
||||
margin-bottom: var(--c--globals--spacings--xxs);
|
||||
}
|
||||
|
||||
& .--docs--quick-search-group-empty {
|
||||
margin: var(--c--globals--spacings--sm);
|
||||
}
|
||||
}
|
||||
`}
|
||||
$margin={{ top: '0.5rem' }}
|
||||
$margin="sm"
|
||||
$padding="none"
|
||||
>
|
||||
<DocSearchContent
|
||||
groupName={t('Select a document')}
|
||||
groupName={t('Link a doc')}
|
||||
search={search}
|
||||
target={DocSearchTarget.CURRENT}
|
||||
parentPath={treeContext?.root?.path}
|
||||
isSearchNotMandatory
|
||||
onSelect={(doc) => {
|
||||
if (!isEditable) {
|
||||
return;
|
||||
@@ -343,52 +353,6 @@ export const SearchPage = ({
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<QuickSearchGroup
|
||||
group={{
|
||||
groupName: '',
|
||||
elements: [],
|
||||
endActions: [
|
||||
{
|
||||
onSelect: createChildDoc,
|
||||
content: (
|
||||
<Box
|
||||
$css={css`
|
||||
border-top: 1px solid
|
||||
var(--c--globals--colors--gray-200);
|
||||
`}
|
||||
$width="100%"
|
||||
>
|
||||
<Box
|
||||
$direction="row"
|
||||
$gap="0.4rem"
|
||||
$align="center"
|
||||
$padding={{
|
||||
vertical: '0.5rem',
|
||||
horizontal: '0.3rem',
|
||||
}}
|
||||
$css={css`
|
||||
&:hover {
|
||||
background-color: var(
|
||||
--c--globals--colors--gray-100
|
||||
);
|
||||
}
|
||||
`}
|
||||
>
|
||||
<AddPageIcon />
|
||||
<Text
|
||||
$size="sm"
|
||||
$color="var(--c--globals--colors--gray-1000)"
|
||||
contentEditable={false}
|
||||
>
|
||||
{t('New sub-doc')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
),
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</QuickSearch>
|
||||
</Box>
|
||||
|
||||
@@ -17,6 +17,7 @@ type DocSearchContentProps = {
|
||||
search: string;
|
||||
filterResults?: (doc: Doc) => boolean;
|
||||
isSearchNotMandatory?: boolean;
|
||||
onResults?: (results: Doc[]) => void;
|
||||
onSelect: (doc: Doc) => void;
|
||||
onLoadingChange?: (loading: boolean) => void;
|
||||
target?: DocSearchTarget;
|
||||
@@ -28,6 +29,7 @@ export const DocSearchContent = ({
|
||||
groupName,
|
||||
search,
|
||||
filterResults,
|
||||
onResults,
|
||||
onSelect,
|
||||
onLoadingChange,
|
||||
renderSearchElement,
|
||||
@@ -76,8 +78,10 @@ export const DocSearchContent = ({
|
||||
|
||||
const elements = search || isSearchNotMandatory ? docs : [];
|
||||
|
||||
onResults?.(elements);
|
||||
|
||||
setDocsData({
|
||||
groupName: docs.length > 0 ? groupName : '',
|
||||
groupName: groupName,
|
||||
groupKey: 'docs',
|
||||
elements,
|
||||
emptyString: t('No document found'),
|
||||
@@ -109,6 +113,7 @@ export const DocSearchContent = ({
|
||||
loading,
|
||||
hasNextPage,
|
||||
fetchNextPage,
|
||||
onResults,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -36,6 +36,7 @@ const DocSearchModalGlobal = ({
|
||||
}: DocSearchModalGlobalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [results, setResults] = useState<Doc[]>([]);
|
||||
const restoreFocus = useFocusStore((state) => state.restoreFocus);
|
||||
const router = useRouter();
|
||||
const [search, setSearch] = useState('');
|
||||
@@ -120,9 +121,10 @@ const DocSearchModalGlobal = ({
|
||||
)}
|
||||
{search && (
|
||||
<DocSearchContent
|
||||
groupName={t('Select a document')}
|
||||
groupName={results.length ? t('Select a document') : ''}
|
||||
search={search}
|
||||
onSelect={handleSelect}
|
||||
onResults={setResults}
|
||||
onLoadingChange={setLoading}
|
||||
target={
|
||||
filters.target === DocSearchTarget.CURRENT
|
||||
|
||||
Reference in New Issue
Block a user