📱(frontend) reduce breakpoint mobile view

Most of the application was switching to mobile
view at the medium breakpoint.
Medium breakpoint let enough place to display
most of the application features and it is more
user friendly to switch to mobile view at the
small breakpoint.
We are switching to mobile view at the small breakpoint
to give more place to the application features
and to be more user friendly.
This commit is contained in:
Anthony LC
2026-05-27 17:03:27 +02:00
parent 68b9342401
commit 43a3620700
12 changed files with 61 additions and 49 deletions
@@ -84,16 +84,13 @@ test.describe('Left panel desktop', () => {
});
});
test.describe('Left panel mobile', () => {
test.use({ viewport: { width: 500, height: 1200 } });
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('checks all the desktop elements are hidden and all mobile elements are visible', async ({
test.describe('Left panel responsive', () => {
test('checks elements visibility on different screen sizes', async ({
page,
}) => {
await page.setViewportSize({ width: 500, height: 1200 });
await page.goto('/');
await expect(page.getByTestId('left-panel-desktop')).toBeHidden();
await expect(page.getByTestId('left-panel-mobile')).not.toBeInViewport();
@@ -120,12 +117,27 @@ test.describe('Left panel mobile', () => {
await expect(newDocButton).toBeInViewport();
await expect(languageButton).toBeInViewport();
await expect(logoutButton).toBeInViewport();
await header.getByLabel('Close the header menu').click();
// Tablet size - like in desktop, left panel should be visible
await page.setViewportSize({ width: 900, height: 1200 });
await page.goto('/');
await expect(page.getByRole('link', { name: 'All docs' })).toBeInViewport();
await expect(newDocButton).toBeInViewport();
await expect(languageButton).toBeInViewport();
await expect(logoutButton).toBeInViewport();
await expect(header.getByLabel('Open the header menu')).toBeHidden();
});
test('checks panel closes when clicking on a subdoc', async ({
page,
browserName,
}) => {
await page.setViewportSize({ width: 500, height: 1200 });
await page.goto('/');
const [docTitle] = await createDoc(
page,
'mobile-doc-test',
@@ -162,12 +174,4 @@ test.describe('Left panel mobile', () => {
await verifyDocName(page, docChild);
await expect(page.getByTestId('left-panel-mobile')).not.toBeInViewport();
});
test('checks resize handle is not present on mobile', async ({ page }) => {
await page.goto('/');
// Verify the resize handle is NOT present on mobile
const resizeHandle = page.locator('[data-panel-resize-handle-id]');
await expect(resizeHandle).toBeHidden();
});
});
@@ -110,7 +110,7 @@ const DocTitleEmojiPicker = ({ doc }: DocTitleProps) => {
};
const DocTitleInput = ({ doc }: DocTitleProps) => {
const { isDesktop } = useResponsiveStore();
const { isLargeScreen } = useResponsiveStore();
const { t } = useTranslation();
const { isTopRoot } = useDocUtils(doc);
const { untitledDocument } = useTrans();
@@ -227,9 +227,9 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
pointer-events: none;
font-style: italic;
}
font-size: ${isDesktop
? css`var(--c--globals--font--sizes--h2)`
: css`var(--c--globals--font--sizes--sm)`};
font-size: ${isLargeScreen
? 'var(--c--globals--font--sizes--h2)'
: 'var(--c--globals--font--sizes--sm)'};
font-weight: 700;
outline: none;
`}
@@ -16,7 +16,7 @@ import { useResponsiveStore } from '@/stores';
*/
export const FloatingBar = () => {
const { spacingsTokens } = useCunninghamTheme();
const { isDesktop } = useResponsiveStore();
const { isLargeScreen } = useResponsiveStore();
const FLOATING_STYLES = useMemo(() => {
const base = spacingsTokens['base'];
@@ -69,7 +69,7 @@ export const FloatingBar = () => {
$direction="row"
$justify="space-between"
>
{isDesktop ? <LeftPanelCollapseButton /> : <Box />}
{isLargeScreen ? <LeftPanelCollapseButton /> : <Box />}
<RightPanelCollapseButton />
</Box>
);
@@ -4,6 +4,7 @@ import Image from 'next/image';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { createGlobalStyle } from 'styled-components';
import { useDebouncedCallback } from 'use-debounce';
import { Box, ButtonCloseModal, Text } from '@/components';
@@ -20,6 +21,12 @@ import EmptySearchIcon from '../assets/illustration-docs-empty.png';
import { DocSearchContent } from './DocSearchContent';
const ModalStyle = createGlobalStyle`
.c__modal__scroller {
overflow: inherit ;
}
`;
type DocSearchModalGlobalProps = {
onClose: () => void;
isOpen: boolean;
@@ -43,7 +50,7 @@ const DocSearchModalGlobal = ({
const [filters, setFilters] = useState<DocSearchFiltersValues>(
defaultFilters ?? {},
);
const { isDesktop } = useResponsiveStore();
const { isLargeScreen } = useResponsiveStore();
const handleInputSearch = useDebouncedCallback(setSearch, 300);
const handleSelect = (doc: Doc) => {
@@ -60,10 +67,11 @@ const DocSearchModalGlobal = ({
<Modal
{...modalProps}
closeOnClickOutside
size={isDesktop ? ModalSize.LARGE : ModalSize.FULL}
size={isLargeScreen ? ModalSize.LARGE : ModalSize.FULL}
hideCloseButton
aria-describedby="doc-search-modal-title"
>
<ModalStyle />
<Box
aria-label={t('Search modal')}
$direction="column"
@@ -107,7 +115,7 @@ const DocSearchModalGlobal = ({
>
<Box
$padding={{ horizontal: '10px', vertical: 'base' }}
$height={isDesktop ? '500px' : 'calc(100vh - 68px - 1rem)'}
$height={isLargeScreen ? '500px' : 'calc(100vh - 68px - 1rem)'}
>
{search.length === 0 && (
<Box
@@ -97,7 +97,7 @@ export const DocsGrid = ({
$position="relative"
$width="100%"
$maxWidth="960px"
$maxHeight="calc(100vh - 52px - 2rem)"
$maxHeight={`calc(100vh - 52px - ${isDesktop ? '2rem' : '0rem'})`}
$align="center"
className="--docs--doc-grid"
>
@@ -19,7 +19,7 @@ export const Header = () => {
const { t } = useTranslation();
const { data: config } = useConfig();
const { spacingsTokens } = useCunninghamTheme();
const { isDesktop } = useResponsiveStore();
const { isLargeScreen } = useResponsiveStore();
const icon = config?.theme_customization?.header?.icon;
@@ -46,7 +46,7 @@ export const Header = () => {
var(--c--contextuals--border--surface--primary);
`}
>
{!isDesktop && <LeftPanelToggleMobile />}
{!isLargeScreen && <LeftPanelToggleMobile />}
<StyledLink
href="/"
data-testid="header-logo-link"
@@ -82,7 +82,7 @@ export const Header = () => {
/>
</Box>
</StyledLink>
{!isDesktop ? (
{!isLargeScreen ? (
<Box $direction="row" $gap={spacingsTokens['sm']}>
<Waffle />
</Box>
@@ -24,8 +24,8 @@ const MobileLeftPanelStyle = createGlobalStyle`
`;
export const LeftPanel = () => {
const { isDesktop } = useResponsiveStore();
if (isDesktop) {
const { isLargeScreen } = useResponsiveStore();
if (isLargeScreen) {
return <LeftPanelDesktop />;
}
@@ -14,7 +14,7 @@ type LeftPanelFavoriteItemProps = {
export const LeftPanelFavoriteItem = ({ doc }: LeftPanelFavoriteItemProps) => {
const { colorsTokens, spacingsTokens } = useCunninghamTheme();
const { isDesktop } = useResponsiveStore();
const { isLargeScreen } = useResponsiveStore();
return (
<Box
@@ -26,7 +26,7 @@ export const LeftPanelFavoriteItem = ({ doc }: LeftPanelFavoriteItemProps) => {
padding: ${spacingsTokens['2xs']};
border-radius: 4px;
.pinned-actions {
opacity: ${isDesktop ? 0 : 1};
opacity: ${isLargeScreen ? 0 : 1};
}
&:hover {
background-color: var(
@@ -51,7 +51,7 @@ export const ResizableLeftPanel = ({
maxPanelSizePx = 450,
}: ResizableLeftPanelProps) => {
const { t } = useTranslation();
const { isDesktop } = useResponsiveStore();
const { isLargeScreen } = useResponsiveStore();
const { isPanelOpen } = useLeftPanelStore();
const ref = useRef<ImperativePanelHandle>(null);
const savedWidthPxRef = useRef<number>(minPanelSizePx);
@@ -89,7 +89,7 @@ export const ResizableLeftPanel = ({
* to either expand/collapse
*/
useEffect(() => {
if (!ref.current || !isDesktop) {
if (!ref.current || !isLargeScreen) {
return;
}
if (isPanelOpen) {
@@ -97,11 +97,11 @@ export const ResizableLeftPanel = ({
} else {
ref.current.collapse();
}
}, [isPanelOpen, isDesktop]);
}, [isPanelOpen, isLargeScreen]);
// Keep pixel width constant on window resize
useEffect(() => {
if (!isDesktop) {
if (!isLargeScreen) {
return;
}
@@ -117,7 +117,7 @@ export const ResizableLeftPanel = ({
return () => {
window.removeEventListener('resize', handleResize);
};
}, [isDesktop]);
}, [isLargeScreen]);
/**
* Workaround: NVDA does not enter focus mode for role="separator"
@@ -158,15 +158,15 @@ export const ResizableLeftPanel = ({
}}
order={0}
defaultSize={
isDesktop
isLargeScreen
? Math.max(
minPanelSizePercent,
Math.min(panelSizePercent, maxPanelSizePercent),
)
: 0
}
minSize={isDesktop ? minPanelSizePercent : 0}
maxSize={isDesktop ? maxPanelSizePercent : 0}
minSize={isLargeScreen ? minPanelSizePercent : 0}
maxSize={isLargeScreen ? maxPanelSizePercent : 0}
onResize={handleResize}
>
{leftPanel}
@@ -193,7 +193,7 @@ export const ResizableLeftPanel = ({
cursor: 'col-resize',
}}
onDragging={setIsDragging}
disabled={!isDesktop}
disabled={!isLargeScreen}
/>
)}
<Panel order={1}>{children}</Panel>
@@ -52,7 +52,7 @@ export function MainLayoutContent({
backgroundColor,
enableResizablePanel,
}: PropsWithChildren<MainLayoutContentProps>) {
const { isDesktop } = useResponsiveStore();
const { isLargeScreen } = useResponsiveStore();
if (enableResizablePanel) {
return (
@@ -71,7 +71,7 @@ export function MainLayoutContent({
);
}
if (!isDesktop) {
if (!isLargeScreen) {
return (
<>
<LeftPanel />
@@ -18,7 +18,7 @@ export function PageLayout({
children,
withFooter = true,
}: PropsWithChildren<PageLayoutProps>) {
const { isDesktop } = useResponsiveStore();
const { isLargeScreen } = useResponsiveStore();
const { t } = useTranslation();
return (
<Box
@@ -45,7 +45,7 @@ export function PageLayout({
`}
aria-label={t('Main content')}
>
{!isDesktop && <LeftPanel />}
{!isLargeScreen && <LeftPanel />}
{children}
</Box>
{withFooter && <Footer />}
@@ -3,6 +3,7 @@ import { create } from 'zustand';
export type ScreenSize = 'small-mobile' | 'mobile' | 'tablet' | 'desktop';
export interface UseResponsiveStore {
isLargeScreen: boolean;
isMobile: boolean;
isTablet: boolean;
isSmallMobile: boolean;
@@ -10,7 +11,6 @@ export interface UseResponsiveStore {
screenWidth: number;
setScreenSize: (size: ScreenSize) => void;
isDesktop: boolean;
isLargeScreen: boolean;
initializeResizeListener: () => () => void;
}
@@ -57,20 +57,20 @@ export const useResponsiveStore = create<UseResponsiveStore>((set) => ({
} else if (width >= 768 && width < 1024) {
set({
isDesktop: false,
isLargeScreen: true,
screenSize: 'tablet',
isTablet: true,
isMobile: false,
isSmallMobile: false,
isLargeScreen: true,
});
} else {
set({
isDesktop: true,
isLargeScreen: true,
screenSize: 'desktop',
isTablet: false,
isMobile: false,
isSmallMobile: false,
isLargeScreen: true,
});
}