mirror of
https://github.com/suitenumerique/docs.git
synced 2026-08-17 21:25:43 +02:00
♻️(frontend) replace header by floating header
We replace the header by a floating header, to have more space for the content and in fine to have more harmony between the doc and the doc-grid, in mobile view or not. The layout have to be adapted, as well the legacy home page.
This commit is contained in:
@@ -11,6 +11,7 @@ and this project adheres to
|
||||
- ✨(y-provider) preserve callouts, PDFs, page breaks, interlinking
|
||||
links and commented text on HTML/markdown export #2296
|
||||
- ✨(frontend) add a user menu #2463
|
||||
- ✨(frontend) new header and responsive harmonization #2471
|
||||
- ✨(backend) add management command to reset a Document #1882
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
"icon": {
|
||||
"src": "/assets/icon-docs.svg",
|
||||
"style": {
|
||||
"width": "32px",
|
||||
"width": "40px",
|
||||
"height": "auto"
|
||||
},
|
||||
"alt": "",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(
|
||||
page.locator('header').first().locator('h1').getByText('Docs'),
|
||||
page.locator('nav').first().locator('h1').getByText('Docs'),
|
||||
).toBeVisible();
|
||||
await page.goto('unknown-page404');
|
||||
});
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 6.6 KiB |
@@ -1,4 +1,12 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.6305 28.8312C22.7983 28.5038 23.9166 27.9062 24.6505 26.8503C25.3749 25.8163 25.5789 24.5047 25.5789 23.2425V4.75099C25.5789 4.42358 25.5611 4.09557 25.5216 3.77148C26.1016 3.99961 26.5486 4.37658 26.8626 4.90239C27.2331 5.50024 27.4184 6.28757 27.4184 7.26435V26.0464C27.4184 27.3684 27.0942 28.3578 26.4458 29.0146C25.7974 29.6714 24.8207 29.9998 23.5155 29.9998H16.4209C16.5889 29.9704 16.7574 29.9401 16.9262 29.909C18.4067 29.6444 19.9713 29.2854 21.6185 28.8346L21.6305 28.8312Z" fill="#4747A3"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.58203 25.655V6.8477C4.58203 5.70251 4.88938 4.83519 5.50408 4.24575C6.1272 3.65631 6.95242 3.33212 7.97972 3.27318C9.49542 3.18055 10.9311 3.05425 12.2868 2.89425C13.6425 2.72584 14.9393 2.53217 16.1771 2.31324C17.4234 2.0943 18.6359 1.85011 19.8148 1.58065C21.0274 1.29435 21.9578 1.4375 22.6062 2.0101C23.2546 2.58269 23.5788 3.49632 23.5788 4.75099V23.2425C23.5788 24.3456 23.3893 25.1666 23.0104 25.7055C22.6315 26.2529 21.9915 26.6528 21.0905 26.9054C19.4906 27.3433 17.9833 27.6886 16.5687 27.9412C15.154 28.2022 13.7731 28.4001 12.4258 28.5348C11.0785 28.6696 9.69751 28.7748 8.28286 28.8506C7.11241 28.918 6.20299 28.6738 5.5546 28.118C4.90622 27.5707 4.58203 26.7497 4.58203 25.655ZM9.20865 10.2624C11.0635 10.1444 12.7632 9.96305 14.3075 9.71831C14.6822 9.65722 15.0564 9.5936 15.4291 9.52759C15.8192 9.45851 16.1013 9.11859 16.1013 8.72337C16.1013 8.21154 15.638 7.82609 15.135 7.91189C14.846 7.96118 14.5555 8.00909 14.2635 8.05562C12.7346 8.29923 11.0452 8.47998 9.19523 8.5977C8.91819 8.61558 8.69776 8.70188 8.55608 8.87391C8.42209 9.03661 8.35645 9.23229 8.35645 9.45535C8.35645 9.68212 8.43296 9.87951 8.58568 10.0418L8.58783 10.0439C8.75336 10.2095 8.96369 10.2811 9.20865 10.2624ZM9.20801 14.456C11.0631 14.338 12.763 14.1566 14.3075 13.9119C15.8588 13.6589 17.3936 13.3638 18.9112 13.0266C19.2191 12.9581 19.4498 12.8503 19.5652 12.683C19.6786 12.5221 19.7347 12.3376 19.7347 12.1332C19.7347 11.9026 19.6469 11.704 19.476 11.5426C19.2921 11.3689 19.0348 11.3284 18.7304 11.3911L18.7285 11.3915C17.2823 11.7194 15.794 12.0053 14.2635 12.2492C12.7346 12.4928 11.0452 12.6735 9.19523 12.7913C8.91819 12.8091 8.69776 12.8954 8.55608 13.0675C8.42276 13.2294 8.35645 13.4205 8.35645 13.6363C8.35645 13.8703 8.43209 14.0723 8.58558 14.2354L8.59 14.2396C8.75499 14.3949 8.96316 14.4655 9.20551 14.4562L9.20801 14.456ZM9.20847 18.6494C11.0634 18.5229 12.7631 18.3374 14.3075 18.0927C15.8589 17.8482 17.3934 17.5573 18.9112 17.22C19.2199 17.1514 19.4508 17.0391 19.566 16.8627C19.6783 16.7029 19.7347 16.5233 19.7347 16.3266C19.7347 16.0961 19.6469 15.8974 19.476 15.7361C19.2921 15.5623 19.0348 15.5218 18.7304 15.5845L18.729 15.5848C17.2827 15.9043 15.7942 16.1861 14.2635 16.43C12.7345 16.6736 11.045 16.8586 9.19495 16.9847C8.91804 17.0026 8.69771 17.0889 8.55608 17.2609C8.42276 17.4228 8.35645 17.6139 8.35645 17.8297C8.35645 18.0637 8.43209 18.2658 8.58558 18.4289L8.59 18.433C8.75499 18.5883 8.96316 18.6589 9.20551 18.6496L9.20847 18.6494ZM14.3075 22.257C12.7632 22.5018 11.0635 22.6831 9.20867 22.8012C8.9637 22.8198 8.75337 22.7482 8.58783 22.5826L8.58572 22.5805C8.433 22.4182 8.35645 22.2208 8.35645 21.9941C8.35645 21.771 8.42209 21.5753 8.55608 21.4126C8.69776 21.2406 8.91827 21.1543 9.19531 21.1364C11.0453 21.0187 12.7346 20.838 14.2635 20.5943C14.5555 20.5478 14.846 20.4999 15.135 20.4506C15.638 20.3648 16.1013 20.7503 16.1013 21.2621C16.1013 21.6573 15.8192 21.9972 15.4291 22.0663C15.0564 22.1323 14.6822 22.1959 14.3075 22.257Z" fill="#4747A3"/>
|
||||
<svg viewBox="4.5 0 27.5 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M21.6305 28.8312C22.7983 28.5038 23.9166 27.9062 24.6505 26.8503C25.3749 25.8163 25.5789 24.5047 25.5789 23.2425V4.75099C25.5789 4.42358 25.5611 4.09557 25.5216 3.77148C26.1016 3.99961 26.5486 4.37658 26.8626 4.90239C27.2331 5.50024 27.4184 6.28757 27.4184 7.26435V26.0464C27.4184 27.3684 27.0942 28.3578 26.4458 29.0146C25.7974 29.6714 24.8207 29.9998 23.5155 29.9998H16.4209C16.5889 29.9704 16.7574 29.9401 16.9262 29.909C18.4067 29.6444 19.9713 29.2854 21.6185 28.8346L21.6305 28.8312Z"
|
||||
fill="#4747A3"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M4.58203 25.655V6.8477C4.58203 5.70251 4.88938 4.83519 5.50408 4.24575C6.1272 3.65631 6.95242 3.33212 7.97972 3.27318C9.49542 3.18055 10.9311 3.05425 12.2868 2.89425C13.6425 2.72584 14.9393 2.53217 16.1771 2.31324C17.4234 2.0943 18.6359 1.85011 19.8148 1.58065C21.0274 1.29435 21.9578 1.4375 22.6062 2.0101C23.2546 2.58269 23.5788 3.49632 23.5788 4.75099V23.2425C23.5788 24.3456 23.3893 25.1666 23.0104 25.7055C22.6315 26.2529 21.9915 26.6528 21.0905 26.9054C19.4906 27.3433 17.9833 27.6886 16.5687 27.9412C15.154 28.2022 13.7731 28.4001 12.4258 28.5348C11.0785 28.6696 9.69751 28.7748 8.28286 28.8506C7.11241 28.918 6.20299 28.6738 5.5546 28.118C4.90622 27.5707 4.58203 26.7497 4.58203 25.655ZM9.20865 10.2624C11.0635 10.1444 12.7632 9.96305 14.3075 9.71831C14.6822 9.65722 15.0564 9.5936 15.4291 9.52759C15.8192 9.45851 16.1013 9.11859 16.1013 8.72337C16.1013 8.21154 15.638 7.82609 15.135 7.91189C14.846 7.96118 14.5555 8.00909 14.2635 8.05562C12.7346 8.29923 11.0452 8.47998 9.19523 8.5977C8.91819 8.61558 8.69776 8.70188 8.55608 8.87391C8.42209 9.03661 8.35645 9.23229 8.35645 9.45535C8.35645 9.68212 8.43296 9.87951 8.58568 10.0418L8.58783 10.0439C8.75336 10.2095 8.96369 10.2811 9.20865 10.2624ZM9.20801 14.456C11.0631 14.338 12.763 14.1566 14.3075 13.9119C15.8588 13.6589 17.3936 13.3638 18.9112 13.0266C19.2191 12.9581 19.4498 12.8503 19.5652 12.683C19.6786 12.5221 19.7347 12.3376 19.7347 12.1332C19.7347 11.9026 19.6469 11.704 19.476 11.5426C19.2921 11.3689 19.0348 11.3284 18.7304 11.3911L18.7285 11.3915C17.2823 11.7194 15.794 12.0053 14.2635 12.2492C12.7346 12.4928 11.0452 12.6735 9.19523 12.7913C8.91819 12.8091 8.69776 12.8954 8.55608 13.0675C8.42276 13.2294 8.35645 13.4205 8.35645 13.6363C8.35645 13.8703 8.43209 14.0723 8.58558 14.2354L8.59 14.2396C8.75499 14.3949 8.96316 14.4655 9.20551 14.4562L9.20801 14.456ZM9.20847 18.6494C11.0634 18.5229 12.7631 18.3374 14.3075 18.0927C15.8589 17.8482 17.3934 17.5573 18.9112 17.22C19.2199 17.1514 19.4508 17.0391 19.566 16.8627C19.6783 16.7029 19.7347 16.5233 19.7347 16.3266C19.7347 16.0961 19.6469 15.8974 19.476 15.7361C19.2921 15.5623 19.0348 15.5218 18.7304 15.5845L18.729 15.5848C17.2827 15.9043 15.7942 16.1861 14.2635 16.43C12.7345 16.6736 11.045 16.8586 9.19495 16.9847C8.91804 17.0026 8.69771 17.0889 8.55608 17.2609C8.42276 17.4228 8.35645 17.6139 8.35645 17.8297C8.35645 18.0637 8.43209 18.2658 8.58558 18.4289L8.59 18.433C8.75499 18.5883 8.96316 18.6589 9.20551 18.6496L9.20847 18.6494ZM14.3075 22.257C12.7632 22.5018 11.0635 22.6831 9.20867 22.8012C8.9637 22.8198 8.75337 22.7482 8.58783 22.5826L8.58572 22.5805C8.433 22.4182 8.35645 22.2208 8.35645 21.9941C8.35645 21.771 8.42209 21.5753 8.55608 21.4126C8.69776 21.2406 8.91827 21.1543 9.19531 21.1364C11.0453 21.0187 12.7346 20.838 14.2635 20.5943C14.5555 20.5478 14.846 20.4999 15.135 20.4506C15.638 20.3648 16.1013 20.7503 16.1013 21.2621C16.1013 21.6573 15.8192 21.9972 15.4291 22.0663C15.0564 22.1323 14.6822 22.1959 14.3075 22.257Z"
|
||||
fill="#4747A3"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
+5
-4
@@ -1,21 +1,21 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Text } from '@/components/';
|
||||
import { Text, TextType } from '@/components/';
|
||||
|
||||
type TitleSemanticsProps = {
|
||||
type TitleSemanticsProps = TextType & {
|
||||
headingLevel?: 'h1' | 'h2' | 'h3';
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const Title = ({
|
||||
headingLevel = 'h2',
|
||||
className,
|
||||
...props
|
||||
}: TitleSemanticsProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Text
|
||||
className={`--docs--title${className ? ` ${className}` : ''}`}
|
||||
className={`--docs--title${props.className ? ` ${props.className}` : ''}`}
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$margin="none"
|
||||
@@ -23,6 +23,7 @@ export const Title = ({
|
||||
$zIndex={1}
|
||||
$size="1.375rem"
|
||||
$color="var(--c--contextuals--content--logo1)"
|
||||
{...props}
|
||||
>
|
||||
{t('Docs')}
|
||||
</Text>
|
||||
@@ -1,25 +1,23 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Box, BoxType } from '../Box';
|
||||
|
||||
import { Box } from '../Box';
|
||||
|
||||
type SeparatedSectionProps = {
|
||||
type SeparatedSectionProps = BoxType & {
|
||||
showSeparator?: 'top' | 'bottom' | boolean;
|
||||
};
|
||||
|
||||
export const SeparatedSection = ({
|
||||
showSeparator = true,
|
||||
children,
|
||||
...boxProps
|
||||
}: PropsWithChildren<SeparatedSectionProps>) => {
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
$width="100%"
|
||||
$padding={{ vertical: 'sm', horizontal: '0' }}
|
||||
{...boxProps}
|
||||
$css={css`
|
||||
width: 100%;
|
||||
padding: ${spacingsTokens['sm']} 0;
|
||||
${showSeparator === 'top' || showSeparator === true
|
||||
? css`
|
||||
border-top: 1px solid
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { LinkHTMLAttributes } from 'react';
|
||||
import { APIError, errorCauses, fetchAPI } from '@/api';
|
||||
import type { Theme } from '@/cunningham/';
|
||||
import type { FooterType } from '@/features/footer';
|
||||
import type { HeaderType } from '@/features/header';
|
||||
import { HeaderType } from '@/features/left-panel/types';
|
||||
import type { PostHogConf } from '@/services/PosthogAnalytic';
|
||||
|
||||
type Imagetype = React.ComponentProps<typeof Image>;
|
||||
|
||||
+23
-5
@@ -4,8 +4,11 @@ import { css } from 'styled-components';
|
||||
import { Box, Text } from '@/components';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { DocsBlockNoteEditor } from '@/docs/doc-editor/types';
|
||||
import { getMainContentElement } from '@/layouts/utils';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
const SCROLL_MARGIN_TOP = 50;
|
||||
|
||||
const leftPaddingMap: { [key: number]: string } = {
|
||||
3: '1.5rem',
|
||||
2: '0.9rem',
|
||||
@@ -57,11 +60,26 @@ export const Heading = ({
|
||||
editor.setTextCursorPosition(headingId, 'end');
|
||||
|
||||
const blockEl = document.getElementById(headingId);
|
||||
blockEl?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
inline: 'start',
|
||||
block: 'start',
|
||||
});
|
||||
|
||||
// Try to scroll the main content container instead of the block itself
|
||||
// to avoid the block being hidden behind the header
|
||||
const container = getMainContentElement();
|
||||
|
||||
if (blockEl && container) {
|
||||
const top =
|
||||
blockEl.getBoundingClientRect().top -
|
||||
container.getBoundingClientRect().top +
|
||||
container.scrollTop -
|
||||
SCROLL_MARGIN_TOP;
|
||||
|
||||
container.scrollTo({ top, behavior: 'smooth' });
|
||||
} else {
|
||||
blockEl?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
inline: 'start',
|
||||
block: 'start',
|
||||
});
|
||||
}
|
||||
}}
|
||||
$radius="var(--c--globals--spacings--st)"
|
||||
$background={
|
||||
|
||||
@@ -79,12 +79,13 @@ export const DocsGrid = ({
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="--docs--doc-grid"
|
||||
$position="relative"
|
||||
$padding={{ horizontal: 'sm' }}
|
||||
$width="100%"
|
||||
$maxWidth="960px"
|
||||
$maxHeight={`calc(100vh - 52px - ${isDesktop ? '2rem' : '0rem'})`}
|
||||
$minHeight="0"
|
||||
$align="center"
|
||||
className="--docs--doc-grid"
|
||||
>
|
||||
<DocsGridLoader isLoading={isRefetching || loading || isImportPending} />
|
||||
<Card
|
||||
@@ -92,7 +93,7 @@ export const DocsGrid = ({
|
||||
$height="100%"
|
||||
$width="100%"
|
||||
$css={css`
|
||||
${!isDesktop ? 'border: none;' : ''}
|
||||
border: none;
|
||||
${isDragOver
|
||||
? `
|
||||
border: 2px dashed var(--c--contextuals--border--semantic--brand--primary);
|
||||
@@ -118,7 +119,6 @@ export const DocsGrid = ({
|
||||
{hasDocs && (
|
||||
<Box
|
||||
$gap="6px"
|
||||
$overflow="auto"
|
||||
$padding={{ vertical: 'sm', horizontal: isDesktop ? 'md' : 'xs' }}
|
||||
>
|
||||
<Box aria-label={t('Documents grid')}>
|
||||
@@ -193,7 +193,7 @@ const DocGridTitleBar = ({ target }: { target: DocDefaultFilter }) => {
|
||||
<Box
|
||||
$direction="row"
|
||||
$padding={{
|
||||
vertical: 'md',
|
||||
vertical: 'sm',
|
||||
horizontal: isDesktop ? 'md' : 'xs',
|
||||
}}
|
||||
$css={css`
|
||||
|
||||
@@ -4,10 +4,9 @@ import { useTranslation } from 'react-i18next';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { Box, StyledLink, Text } from '@/components/';
|
||||
import { Title } from '@/components/Title';
|
||||
import { useConfig } from '@/core/config';
|
||||
|
||||
import { Title } from '../header';
|
||||
|
||||
import IconLink from './assets/external-link.svg';
|
||||
import { ContentType } from './types';
|
||||
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import Image from 'next/image';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, SkipToContent, StyledLink } from '@/components/';
|
||||
import { useConfig } from '@/core/config';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { ButtonLogin } from '@/features/auth';
|
||||
import { LeftPanelToggleMobile } from '@/features/left-panel';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { HEADER_HEIGHT } from '../conf';
|
||||
|
||||
import { Title } from './Title';
|
||||
|
||||
export const Header = () => {
|
||||
const { t } = useTranslation();
|
||||
const { data: config } = useConfig();
|
||||
const { spacingsTokens } = useCunninghamTheme();
|
||||
const { isLargeScreen } = useResponsiveStore();
|
||||
|
||||
const icon = config?.theme_customization?.header?.icon;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SkipToContent />
|
||||
<Box
|
||||
as="header"
|
||||
className="--docs--header"
|
||||
role="banner"
|
||||
$css={css`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: ${HEADER_HEIGHT}px;
|
||||
padding: 0 ${spacingsTokens['base']};
|
||||
background-color: var(--c--contextuals--background--surface--primary);
|
||||
border-bottom: 1px solid
|
||||
var(--c--contextuals--border--surface--primary);
|
||||
`}
|
||||
>
|
||||
{!isLargeScreen && <LeftPanelToggleMobile />}
|
||||
<StyledLink
|
||||
href="/"
|
||||
data-testid="header-logo-link"
|
||||
aria-label={t('Back to homepage')}
|
||||
$css={css`
|
||||
outline: none;
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--c--globals--colors--brand-400) !important;
|
||||
border-radius: var(--c--globals--spacings--st);
|
||||
}
|
||||
`}
|
||||
>
|
||||
<Box
|
||||
$align="center"
|
||||
$gap={spacingsTokens['3xs']}
|
||||
$direction="row"
|
||||
$position="relative"
|
||||
$height="fit-content"
|
||||
$margin={{ top: 'auto' }}
|
||||
>
|
||||
{icon && (
|
||||
<Image
|
||||
data-testid="header-icon-docs"
|
||||
width={0}
|
||||
height={0}
|
||||
priority
|
||||
{...(({ withTitle: _, ...rest }) => rest)(icon)}
|
||||
/>
|
||||
)}
|
||||
<Title
|
||||
headingLevel="h1"
|
||||
className={icon?.withTitle ? undefined : 'sr-only'}
|
||||
/>
|
||||
</Box>
|
||||
</StyledLink>
|
||||
{isLargeScreen && (
|
||||
<Box
|
||||
className="--docs--header-block-right"
|
||||
$align="center"
|
||||
$gap={spacingsTokens['sm']}
|
||||
$direction="row"
|
||||
>
|
||||
<ButtonLogin />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,2 +1 @@
|
||||
export * from './Header';
|
||||
export * from './Title';
|
||||
export * from './HeaderFloatingBar';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const HEADER_HEIGHT = 52;
|
||||
@@ -1,3 +1 @@
|
||||
export * from './components/';
|
||||
export * from './conf';
|
||||
export * from './types';
|
||||
|
||||
@@ -2,10 +2,10 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import IconDocs from '@/assets/icons/icon-docs.svg';
|
||||
import { Box, Text } from '@/components';
|
||||
import { Title } from '@/components/Title';
|
||||
import { useConfig } from '@/core/config';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { ProConnectButton } from '@/features/auth';
|
||||
import { Title } from '@/features/header';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
export function HomeBottom() {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { css } from 'styled-components';
|
||||
|
||||
import { Box, Icon, Text } from '@/components';
|
||||
import { Footer } from '@/features/footer';
|
||||
import { LeftPanel } from '@/features/left-panel';
|
||||
import { MAIN_LAYOUT_ID } from '@/layouts/conf';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
@@ -50,11 +49,6 @@ export function HomeContent() {
|
||||
`}
|
||||
>
|
||||
<HomeHeader />
|
||||
{isSmallMobile && (
|
||||
<Box $css="& .--docs--left-panel-header{display: none;}">
|
||||
<LeftPanel />
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
$css={css`
|
||||
height: calc(100vh - ${getHeaderHeight(isSmallMobile)}px);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import Image from 'next/image';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { Title } from '@/components/Title';
|
||||
import { Waffle } from '@/components/Waffle';
|
||||
import { useConfig } from '@/core';
|
||||
import { useCunninghamTheme } from '@/cunningham';
|
||||
import { Title } from '@/features/header';
|
||||
import { LanguagePickerLegacy } from '@/features/language';
|
||||
import { LeftPanelToggleMobile } from '@/features/left-panel';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
export const HEADER_HEIGHT = 91;
|
||||
@@ -38,14 +37,9 @@ export const HomeHeader = () => {
|
||||
$align="center"
|
||||
$gap="2rem"
|
||||
$direction="row"
|
||||
$width={isSmallMobile ? '100%' : 'auto'}
|
||||
$width="auto"
|
||||
$justify="center"
|
||||
>
|
||||
{isSmallMobile && (
|
||||
<Box $position="absolute" $css="left: 1rem;">
|
||||
<LeftPanelToggleMobile />
|
||||
</Box>
|
||||
)}
|
||||
{!isSmallMobile && logo?.src && (
|
||||
<Image
|
||||
priority
|
||||
@@ -78,12 +72,14 @@ export const HomeHeader = () => {
|
||||
{icon?.withTitle && <Title />}
|
||||
</Box>
|
||||
</Box>
|
||||
{!isSmallMobile && (
|
||||
<Box $direction="row" $gap="1rem" $align="center">
|
||||
<LanguagePickerLegacy />
|
||||
<Waffle />
|
||||
</Box>
|
||||
)}
|
||||
<Box
|
||||
$direction="row"
|
||||
$gap={!isSmallMobile ? '1rem' : undefined}
|
||||
$align="center"
|
||||
>
|
||||
<LanguagePickerLegacy />
|
||||
<Waffle />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
getMatchingLocales,
|
||||
useSynchronizedLanguage,
|
||||
} from '@/features/language';
|
||||
import { useResponsiveStore } from '@/stores/useResponsiveStore';
|
||||
|
||||
/**
|
||||
* LanguagePickerLegacy component for selecting language.
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
* @returns JSX.Element
|
||||
*/
|
||||
export const LanguagePickerLegacy = () => {
|
||||
const { isSmallMobile } = useResponsiveStore();
|
||||
const { t, i18n } = useTranslation();
|
||||
const { data: conf } = useConfig();
|
||||
const { data: user } = useAuthQuery();
|
||||
@@ -87,7 +89,9 @@ export const LanguagePickerLegacy = () => {
|
||||
$gap="0.5rem"
|
||||
$align="center"
|
||||
>
|
||||
<Icon iconName="translate" $color="inherit" $size="xl" />
|
||||
{!isSmallMobile && (
|
||||
<Icon iconName="translate" $color="inherit" $size="xl" />
|
||||
)}
|
||||
<span lang={toLangTag(currentLanguageCode)}>
|
||||
{currentLanguageLabel}
|
||||
</span>
|
||||
|
||||
@@ -21,7 +21,7 @@ export const LeftPanelFooter = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<SeparatedSection showSeparator="top">
|
||||
<SeparatedSection showSeparator="top" $margin={{ top: 'auto' }}>
|
||||
<Box
|
||||
$padding={{ horizontal: 'sm' }}
|
||||
$justify="space-between"
|
||||
|
||||
@@ -1,57 +1,130 @@
|
||||
import { Button } from '@gouvfr-lasuite/cunningham-react';
|
||||
import { t } from 'i18next';
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/router';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import HomeSVG from '@/assets/icons/ui-kit/house-rounded.svg';
|
||||
import { Box, SeparatedSection } from '@/components';
|
||||
import {
|
||||
Box,
|
||||
ButtonCloseModal,
|
||||
SeparatedSection,
|
||||
StyledLink,
|
||||
} from '@/components';
|
||||
import { Title } from '@/components/Title';
|
||||
import { useConfig } from '@/core';
|
||||
import { NewDocButton } from '@/docs/doc-management/components/NewDocButton';
|
||||
import { DocSearchButtonModal } from '@/docs/doc-search/components/DocSearchButtonModal';
|
||||
import { useAuth } from '@/features/auth';
|
||||
import { NewDocButton } from '@/features/docs/doc-management/components/NewDocButton';
|
||||
import { DocSearchButtonModal } from '@/features/docs/doc-search/components/DocSearchButtonModal';
|
||||
import HomeSVG from '@/icons/house-rounded.svg';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { useLeftPanelStore } from '../stores';
|
||||
|
||||
export const LeftPanelHeader = ({ children }: PropsWithChildren) => {
|
||||
const router = useRouter();
|
||||
const { authenticated } = useAuth();
|
||||
|
||||
const { togglePanel, closePanel } = useLeftPanelStore();
|
||||
|
||||
const goToHome = () => {
|
||||
void router.push('/');
|
||||
togglePanel({ type: 'mobile' });
|
||||
};
|
||||
export const LeftPanelHeader = () => {
|
||||
const { data: config } = useConfig();
|
||||
const { isMobile } = useResponsiveStore();
|
||||
const { closePanel } = useLeftPanelStore();
|
||||
const icon = config?.theme_customization?.header?.icon;
|
||||
|
||||
return (
|
||||
<Box $width="100%" className="--docs--left-panel-header">
|
||||
<SeparatedSection>
|
||||
<Box
|
||||
$padding={{ horizontal: 'sm' }}
|
||||
$width="100%"
|
||||
$direction="row"
|
||||
$justify="space-between"
|
||||
$align="center"
|
||||
<Box
|
||||
$padding={{ horizontal: 'sm' }}
|
||||
$direction="row"
|
||||
$align="center"
|
||||
$gap="2xs"
|
||||
$minHeight="68px"
|
||||
>
|
||||
<StyledLink
|
||||
href="/"
|
||||
data-testid="header-logo-link"
|
||||
$css={css`
|
||||
outline: none;
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--c--globals--colors--brand-400) !important;
|
||||
border-radius: var(--c--globals--spacings--st);
|
||||
}
|
||||
`}
|
||||
>
|
||||
{authenticated && (
|
||||
<NewDocButton onClose={() => closePanel({ type: 'mobile' })} />
|
||||
)}
|
||||
<Box $direction="row" $gap="2px" $margin={{ left: 'auto' }}>
|
||||
{router.pathname !== '/' && (
|
||||
<Button
|
||||
data-testid="home-button"
|
||||
onClick={goToHome}
|
||||
aria-label={t('Back to homepage')}
|
||||
size="medium"
|
||||
color="brand"
|
||||
variant="tertiary"
|
||||
icon={<HomeSVG aria-hidden="true" width={24} height={24} />}
|
||||
<Box
|
||||
$align="center"
|
||||
$gap="var(--c--globals--spacings--4xs)"
|
||||
$direction="row"
|
||||
$position="relative"
|
||||
$height="fit-content"
|
||||
$margin={{ top: 'auto' }}
|
||||
>
|
||||
{icon && (
|
||||
<Image
|
||||
data-testid="header-icon-docs"
|
||||
width={0}
|
||||
height={0}
|
||||
priority
|
||||
{...(({ withTitle: _, ...rest }) => rest)(icon)}
|
||||
/>
|
||||
)}
|
||||
<DocSearchButtonModal />
|
||||
<Title
|
||||
headingLevel="h1"
|
||||
className={icon?.withTitle ? undefined : 'sr-only'}
|
||||
$size="1.8rem"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</SeparatedSection>
|
||||
{children}
|
||||
</StyledLink>
|
||||
{isMobile && (
|
||||
<Box $margin={{ left: 'auto' }}>
|
||||
<ButtonCloseModal
|
||||
onClick={closePanel}
|
||||
aria-label="Close left panel"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<LeftPanelHeaderActions />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export const LeftPanelHeaderActions = () => {
|
||||
const router = useRouter();
|
||||
const { authenticated } = useAuth();
|
||||
const { togglePanel, closePanel } = useLeftPanelStore();
|
||||
const { t } = useTranslation();
|
||||
const { isMobile } = useResponsiveStore();
|
||||
|
||||
const goToHome = () => {
|
||||
void router.push('/');
|
||||
|
||||
if (isMobile) {
|
||||
togglePanel();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SeparatedSection>
|
||||
<Box
|
||||
$padding={{ horizontal: 'sm' }}
|
||||
$width="100%"
|
||||
$direction="row"
|
||||
$justify="space-between"
|
||||
$align="center"
|
||||
>
|
||||
{authenticated && (
|
||||
<NewDocButton onClose={() => isMobile && closePanel()} />
|
||||
)}
|
||||
<Box $direction="row" $gap="2px" $margin={{ left: 'auto' }}>
|
||||
{router.pathname !== '/' && (
|
||||
<Button
|
||||
data-testid="home-button"
|
||||
onClick={goToHome}
|
||||
aria-label={t('Back to homepage')}
|
||||
size="medium"
|
||||
color="brand"
|
||||
variant="tertiary"
|
||||
icon={<HomeSVG aria-hidden="true" width={24} height={24} />}
|
||||
/>
|
||||
)}
|
||||
<DocSearchButtonModal />
|
||||
</Box>
|
||||
</Box>
|
||||
</SeparatedSection>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Box } from '@/components';
|
||||
import { CommentSideBar } from '@/features/docs/doc-comments/components/CommentSideBar';
|
||||
import { useDocStore, useProviderStore } from '@/features/docs/doc-management';
|
||||
import { TableContentSideBar } from '@/features/docs/doc-table-content/components/TableContentSideBar';
|
||||
import { HEADER_HEIGHT } from '@/features/header';
|
||||
import { useFocusStore, useResponsiveStore } from '@/stores';
|
||||
|
||||
import {
|
||||
@@ -77,7 +76,7 @@ export const RightPanel = () => {
|
||||
aria-label={panelLabel}
|
||||
inert={!isPanelOpen}
|
||||
$width="300px"
|
||||
$height={`calc(100dvh - ${HEADER_HEIGHT}px)`}
|
||||
$height="100dvh"
|
||||
$position={isMobile ? 'absolute' : 'sticky'}
|
||||
$zIndex={25}
|
||||
$hasTransition
|
||||
|
||||
@@ -34,21 +34,10 @@ export const useRouteChangeCompleteFocus = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const prefersReducedMotion = window.matchMedia(
|
||||
'(prefers-reduced-motion: reduce)',
|
||||
).matches;
|
||||
|
||||
if (isKeyboardNavigationRef.current) {
|
||||
focusMainContentStart({ preventScroll: true });
|
||||
isKeyboardNavigationRef.current = false;
|
||||
}
|
||||
if (router.pathname === '/docs/[id]') {
|
||||
return;
|
||||
}
|
||||
focusTarget.scrollIntoView({
|
||||
behavior: prefersReducedMotion ? 'auto' : 'smooth',
|
||||
block: 'start',
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -3,109 +3,63 @@ import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box, BoxProps } from '@/components';
|
||||
import { Header } from '@/features/header';
|
||||
import { HEADER_HEIGHT } from '@/features/header/conf';
|
||||
import { LeftPanel, ResizableLeftPanel } from '@/features/left-panel';
|
||||
import { RightPanel } from '@/features/right-panel/components/RightPanel';
|
||||
import { DocEditorSkeleton, Skeleton } from '@/features/skeletons';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { MAIN_LAYOUT_ID } from './conf';
|
||||
import { usePanelCoordination } from './usePanelCoordination';
|
||||
|
||||
type MainLayoutProps = {
|
||||
backgroundColor?: 'white' | 'grey';
|
||||
enableResizablePanel?: boolean;
|
||||
};
|
||||
|
||||
export function MainLayout({
|
||||
children,
|
||||
backgroundColor = 'white',
|
||||
enableResizablePanel = false,
|
||||
}: PropsWithChildren<MainLayoutProps>) {
|
||||
return (
|
||||
<Box className="--docs--main-layout">
|
||||
<Header />
|
||||
<Box
|
||||
$direction="row"
|
||||
$margin={{ top: `${HEADER_HEIGHT}px` }}
|
||||
$width="100%"
|
||||
$height={`calc(100dvh - ${HEADER_HEIGHT}px)`}
|
||||
>
|
||||
<MainLayoutContent
|
||||
backgroundColor={backgroundColor}
|
||||
enableResizablePanel={enableResizablePanel}
|
||||
>
|
||||
{children}
|
||||
</MainLayoutContent>
|
||||
</Box>
|
||||
<Box
|
||||
className="--docs--main-layout"
|
||||
$direction="row"
|
||||
$width="100%"
|
||||
$height="100dvh"
|
||||
>
|
||||
<MainLayoutContent enableResizablePanel={enableResizablePanel}>
|
||||
{children}
|
||||
</MainLayoutContent>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export interface MainLayoutContentProps {
|
||||
backgroundColor: 'white' | 'grey';
|
||||
enableResizablePanel: boolean;
|
||||
}
|
||||
|
||||
export function MainLayoutContent({
|
||||
children,
|
||||
backgroundColor,
|
||||
enableResizablePanel,
|
||||
}: PropsWithChildren<MainLayoutContentProps>) {
|
||||
const { isLargeScreen } = useResponsiveStore();
|
||||
|
||||
if (enableResizablePanel) {
|
||||
return (
|
||||
<MainResizableLayout backgroundColor={backgroundColor}>
|
||||
{children}
|
||||
</MainResizableLayout>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isLargeScreen) {
|
||||
return (
|
||||
<>
|
||||
<LeftPanel />
|
||||
<MainContent backgroundColor={backgroundColor}>{children}</MainContent>
|
||||
</>
|
||||
);
|
||||
return <MainResizableLayout>{children}</MainResizableLayout>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
$css={css`
|
||||
width: 300px;
|
||||
border-right: 1px solid
|
||||
var(--c--contextuals--border--surface--primary);
|
||||
`}
|
||||
>
|
||||
<LeftPanel />
|
||||
</Box>
|
||||
<MainContent backgroundColor={backgroundColor}>{children}</MainContent>
|
||||
<LeftPanel />
|
||||
<MainContent>{children}</MainContent>
|
||||
<RightPanel />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface MainResizableLayoutProps {
|
||||
backgroundColor: 'white' | 'grey';
|
||||
}
|
||||
|
||||
const MainResizableLayout = ({
|
||||
children,
|
||||
backgroundColor,
|
||||
}: PropsWithChildren<MainResizableLayoutProps>) => {
|
||||
const MainResizableLayout = ({ children }: PropsWithChildren) => {
|
||||
usePanelCoordination();
|
||||
|
||||
return (
|
||||
<ResizableLeftPanel leftPanel={<LeftPanel />}>
|
||||
<ResizableLeftPanel>
|
||||
<Box $direction="row" $width="100%" $position="relative">
|
||||
<MainContent
|
||||
backgroundColor={backgroundColor}
|
||||
$flex="auto"
|
||||
$padding="0"
|
||||
>
|
||||
<MainContent $flex="auto" $padding="0">
|
||||
{children}
|
||||
</MainContent>
|
||||
<RightPanel />
|
||||
@@ -114,19 +68,11 @@ const MainResizableLayout = ({
|
||||
);
|
||||
};
|
||||
|
||||
type MainContentProps = BoxProps & {
|
||||
backgroundColor: 'white' | 'grey';
|
||||
};
|
||||
|
||||
const MainContent = ({
|
||||
export const MainContent = ({
|
||||
children,
|
||||
backgroundColor,
|
||||
...props
|
||||
}: PropsWithChildren<MainContentProps>) => {
|
||||
const { isDesktop } = useResponsiveStore();
|
||||
|
||||
}: PropsWithChildren<BoxProps>) => {
|
||||
const { t } = useTranslation();
|
||||
const currentBackgroundColor = !isDesktop ? 'white' : backgroundColor;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -137,14 +83,9 @@ const MainContent = ({
|
||||
$align="center"
|
||||
$flex={1}
|
||||
$width="100%"
|
||||
$height={`calc(100dvh - ${HEADER_HEIGHT}px)`}
|
||||
$height="100dvh"
|
||||
$position="relative"
|
||||
$padding={isDesktop ? 'base' : '0'}
|
||||
$background={
|
||||
currentBackgroundColor === 'white'
|
||||
? 'var(--c--contextuals--background--surface--primary)'
|
||||
: 'var(--c--contextuals--background--surface--tertiary)'
|
||||
}
|
||||
$background="var(--c--contextuals--background--surface--primary)"
|
||||
$css={css`
|
||||
overflow-y: auto;
|
||||
overflow-x: clip;
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { css } from 'styled-components';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { Footer } from '@/features/footer';
|
||||
import { HEADER_HEIGHT, Header } from '@/features/header';
|
||||
import { HeaderFloatingBar } from '@/features/header/components/HeaderFloatingBar';
|
||||
import { LeftPanel } from '@/features/left-panel';
|
||||
import { useResponsiveStore } from '@/stores';
|
||||
|
||||
import { MAIN_LAYOUT_ID } from './conf';
|
||||
import { MainContent } from './MainLayout';
|
||||
|
||||
interface PageLayoutProps {
|
||||
withFooter?: boolean;
|
||||
@@ -20,36 +17,13 @@ export function PageLayout({
|
||||
withFooter = true,
|
||||
withLeftPanel = true,
|
||||
}: PropsWithChildren<PageLayoutProps>) {
|
||||
const { isLargeScreen } = useResponsiveStore();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box
|
||||
$minHeight={`calc(100vh - ${HEADER_HEIGHT}px)`}
|
||||
$margin={{ top: `${HEADER_HEIGHT}px` }}
|
||||
className="--docs--page-layout"
|
||||
>
|
||||
<Header />
|
||||
<Box
|
||||
as="main"
|
||||
role="main"
|
||||
id={MAIN_LAYOUT_ID}
|
||||
tabIndex={-1}
|
||||
$width="100%"
|
||||
$css={css`
|
||||
flex-grow: 1;
|
||||
&:focus {
|
||||
outline: 3px solid var(--c--globals--colors--primary-600);
|
||||
outline-offset: -3px;
|
||||
}
|
||||
&:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
`}
|
||||
aria-label={t('Main content')}
|
||||
>
|
||||
{withLeftPanel && !isLargeScreen && <LeftPanel />}
|
||||
<Box className="--docs--page-layout" $direction="row" $minHeight="100vh">
|
||||
{withLeftPanel && <LeftPanel />}
|
||||
<MainContent>
|
||||
<HeaderFloatingBar />
|
||||
{children}
|
||||
</Box>
|
||||
</MainContent>
|
||||
{withFooter && <Footer />}
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user