refactor: editor responsive navigation

This commit is contained in:
Carlos Valente
2025-06-22 12:43:11 +02:00
committed by Carlos Valente
parent cff73a65a5
commit b991627cb7
10 changed files with 131 additions and 66 deletions
@@ -9,10 +9,11 @@ import useViewEditor from './useViewEditor';
interface ViewNavigationMenuProps {
isLockable?: boolean;
supressSettings?: boolean;
}
export default memo(ViewNavigationMenu);
function ViewNavigationMenu({ isLockable }: ViewNavigationMenuProps) {
function ViewNavigationMenu({ isLockable, supressSettings }: ViewNavigationMenuProps) {
const { isOpen: isMenuOpen, onOpen: onMenuOpen, onClose: onMenuClose } = useDisclosure();
const { showEditFormDrawer, isViewLocked } = useViewEditor({ isLockable });
@@ -30,7 +31,7 @@ function ViewNavigationMenu({ isLockable }: ViewNavigationMenuProps) {
[
'mod + ,',
() => {
if (isViewLocked) return;
if (isViewLocked || supressSettings) return;
showEditFormDrawer();
},
{ preventDefault: true },
@@ -43,7 +44,10 @@ function ViewNavigationMenu({ isLockable }: ViewNavigationMenuProps) {
return (
<>
<FloatingNavigation toggleMenu={toggleMenu} toggleSettings={showEditFormDrawer} />
<FloatingNavigation
toggleMenu={toggleMenu}
toggleSettings={supressSettings ? undefined : () => showEditFormDrawer()}
/>
<NavigationMenu isOpen={isMenuOpen} onClose={onMenuClose} />
</>
);
@@ -1,14 +1,14 @@
import { IoLockClosedOutline } from 'react-icons/io5';
import { useNavigate } from 'react-router-dom';
import { useViewportSize } from '@mantine/hooks';
import { useIsSmallDevice } from '../../../hooks/useIsSmallDevice';
import NavigationMenuItem from '../navigation-menu-item/NavigationMenuItem';
export default function EditorNavigation() {
const { width } = useViewportSize();
const navigate = useNavigate();
const isSmallDevide = useIsSmallDevice();
if (width > 1440) {
if (!isSmallDevide) {
return (
<NavigationMenuItem active={location.pathname === '/editor'} onClick={() => navigate('/editor')}>
<IoLockClosedOutline />
@@ -9,7 +9,7 @@ import style from './FloatingNavigation.module.scss';
interface FloatingNavigationProps {
toggleMenu: () => void;
toggleSettings: () => void;
toggleSettings?: () => void;
}
export default function FloatingNavigation({ toggleMenu, toggleSettings }: FloatingNavigationProps) {
@@ -29,15 +29,17 @@ export default function FloatingNavigation({ toggleMenu, toggleSettings }: Float
>
<IoApps />
</IconButton>
<IconButton
variant='subtle-white'
size='xlarge'
onClick={toggleSettings}
aria-label='toggle settings'
data-testid='navigation__toggle-settings'
>
<IoSettingsOutline />
</IconButton>
{toggleSettings && (
<IconButton
variant='subtle-white'
size='xlarge'
onClick={toggleSettings}
aria-label='toggle settings'
data-testid='navigation__toggle-settings'
>
<IoSettingsOutline />
</IconButton>
)}
</div>
);
}
@@ -0,0 +1,9 @@
import { useMemo } from 'react';
import { useOs, useViewportSize } from '@mantine/hooks';
export function useIsMobile(): boolean {
const { width } = useViewportSize();
const os = useOs();
return useMemo(() => (os === 'ios' || os === 'android') && width < 800, [width, os]);
}
@@ -0,0 +1,9 @@
import { useMemo } from 'react';
import { useOs, useViewportSize } from '@mantine/hooks';
export function useIsSmallDevice(): boolean {
const { width } = useViewportSize();
const os = useOs();
return useMemo(() => (os === 'ios' || os === 'android') && width < 1300, [width, os]);
}
@@ -0,0 +1,8 @@
import { useMemo } from 'react';
import { useViewportSize } from '@mantine/hooks';
export function useIsSmallScreen(): boolean {
const { width } = useViewportSize();
return useMemo(() => width < 1300, [width]);
}
@@ -1,6 +1,8 @@
import { memo } from 'react';
import ErrorBoundary from '../../../common/components/error-boundary/ErrorBoundary';
import ViewNavigationMenu from '../../../common/components/navigation-menu/ViewNavigationMenu';
import ProtectRoute from '../../../common/components/protect-route/ProtectRoute';
import { handleLinks } from '../../../common/utils/linkUtils';
import { cx } from '../../../common/utils/styleUtils';
import { Corner } from '../../editors/editor-utils/EditorUtils';
@@ -9,20 +11,23 @@ import MessageControl from './MessageControl';
import style from '../../editors/Editor.module.scss';
const MessageControlExport = () => {
export default memo(MessageControlExport);
function MessageControlExport() {
const isExtracted = window.location.pathname.includes('/messagecontrol');
const classes = cx([style.content, style.contentColumnLayout]);
return (
<div className={style.messages} data-testid='panel-messages-control'>
{!isExtracted && <Corner onClick={(event) => handleLinks('messagecontrol', event)} />}
<div className={classes}>
<ErrorBoundary>
<MessageControl />
</ErrorBoundary>
</div>
</div>
);
};
<ProtectRoute permission='editor'>
<div className={style.messages} data-testid='panel-messages-control'>
{!isExtracted && <Corner onClick={(event) => handleLinks('messagecontrol', event)} />}
{isExtracted && <ViewNavigationMenu supressSettings />}
export default memo(MessageControlExport);
<div className={classes}>
<ErrorBoundary>
<MessageControl />
</ErrorBoundary>
</div>
</div>
</ProtectRoute>
);
}
@@ -1,6 +1,8 @@
import { memo } from 'react';
import ErrorBoundary from '../../../common/components/error-boundary/ErrorBoundary';
import ViewNavigationMenu from '../../../common/components/navigation-menu/ViewNavigationMenu';
import ProtectRoute from '../../../common/components/protect-route/ProtectRoute';
import { handleLinks } from '../../../common/utils/linkUtils';
import { Corner } from '../../editors/editor-utils/EditorUtils';
@@ -8,18 +10,22 @@ import PlaybackControl from './PlaybackControl';
import style from '../../editors/Editor.module.scss';
const TimerControlExport = () => {
const isExtracted = window.location.pathname.includes('/timercontrol');
return (
<div className={style.playback} data-testid='panel-timer-control'>
{!isExtracted && <Corner onClick={(event) => handleLinks('timercontrol', event)} />}
<div className={style.content}>
<ErrorBoundary>
<PlaybackControl />
</ErrorBoundary>
</div>
</div>
);
};
export default memo(TimerControlExport);
function TimerControlExport() {
const isExtracted = window.location.pathname.includes('/timercontrol');
return (
<ProtectRoute permission='editor'>
<div className={style.playback} data-testid='panel-timer-control'>
{!isExtracted && <Corner onClick={(event) => handleLinks('timercontrol', event)} />}
{isExtracted && <ViewNavigationMenu supressSettings />}
<div className={style.content}>
<ErrorBoundary>
<PlaybackControl />
</ErrorBoundary>
</div>
</div>
</ProtectRoute>
);
}
@@ -2,6 +2,9 @@ import { memo } from 'react';
import { ContextMenu } from '../../common/components/context-menu/ContextMenu';
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
import ViewNavigationMenu from '../../common/components/navigation-menu/ViewNavigationMenu';
import ProtectRoute from '../../common/components/protect-route/ProtectRoute';
import { useIsSmallDevice } from '../../common/hooks/useIsSmallDevice';
import { useAppMode } from '../../common/stores/appModeStore';
import { handleLinks } from '../../common/utils/linkUtils';
import { cx } from '../../common/utils/styleUtils';
@@ -18,30 +21,50 @@ export default memo(RundownExport);
function RundownExport() {
const isExtracted = window.location.pathname.includes('/rundown');
const appMode = useAppMode((state) => state.mode);
const hideSideBar = isExtracted && appMode === 'run';
const isSmallDevice = useIsSmallDevice();
const classes = cx([style.rundownExport, isExtracted && style.extracted]);
return (
<div className={classes} data-testid='panel-rundown'>
<FinderPlacement />
<div className={style.rundown}>
<div className={style.list}>
<ErrorBoundary>
{!isExtracted && <Corner onClick={(event) => handleLinks('rundown', event)} />}
<ContextMenu>
<RundownWrapper />
</ContextMenu>
</ErrorBoundary>
</div>
{!hideSideBar && (
<div className={style.side}>
if (isSmallDevice && isExtracted) {
return (
<ProtectRoute permission='editor'>
<div className={cx([style.rundownExport, style.extracted])} data-testid='panel-rundown'>
<FinderPlacement />
<ViewNavigationMenu supressSettings />
<div className={style.content}>
<ErrorBoundary>
<RundownEventEditor />
<ContextMenu>
<RundownWrapper />
</ContextMenu>
</ErrorBoundary>
</div>
)}
</div>
</ProtectRoute>
);
}
const hideSideBar = isExtracted && appMode === 'run';
return (
<ProtectRoute permission='editor'>
<div className={cx([style.rundownExport, isExtracted && style.extracted])} data-testid='panel-rundown'>
<FinderPlacement />
<div className={style.rundown}>
<div className={style.list}>
<ErrorBoundary>
<Corner onClick={(event) => handleLinks('rundown', event)} />
<ContextMenu>
<RundownWrapper />
</ContextMenu>
</ErrorBoundary>
</div>
{!hideSideBar && (
<div className={style.side}>
<ErrorBoundary>
<RundownEventEditor />
</ErrorBoundary>
</div>
)}
</div>
</div>
</div>
</ProtectRoute>
);
}
+3 -4
View File
@@ -1,5 +1,4 @@
import { useViewportSize } from '@mantine/hooks';
import { useIsMobile } from '../../common/hooks/useIsMobile';
import { cx } from '../../common/utils/styleUtils';
import { formatTime } from '../../common/utils/time';
import SuperscriptTime from '../../features/viewers/common/superscript-time/SuperscriptTime';
@@ -18,10 +17,10 @@ interface StudioClockProps {
}
export default function StudioClock({ onAir, clock, hideCards }: StudioClockProps) {
const { width } = useViewportSize();
const isMobile = useIsMobile();
// if we are on mobile and have to show the cards
if (width < 800 && !hideCards) {
if (isMobile && !hideCards) {
return <StudioClockMobile clock={clock} onAir={onAir} />;
}