2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-01 19:20:55 +00:00

feat(PUI): Make header tabs links to simpilfy new tab behaviour (#8779)

* add anchor element to tabs to enable opening in new tab

* simplify

* use unstyled button instead

* also enable linking on nav panels

* make sure metakey also works (reduces duplication)

* remove headers changes

* move check for modified key to lib

* render nav items as link

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
Matthias Mair
2025-06-25 17:07:32 +02:00
committed by GitHub
parent be99b645ad
commit 83b6653d78
6 changed files with 47 additions and 14 deletions

View File

@ -72,7 +72,7 @@ export const navigateToLink = (
const base = `/${getBaseUrl()}`; const base = `/${getBaseUrl()}`;
if (event?.ctrlKey || event?.shiftKey) { if (eventModified(event)) {
// Open the link in a new tab // Open the link in a new tab
let url = link; let url = link;
if (link.startsWith('/') && !link.startsWith(base)) { if (link.startsWith('/') && !link.startsWith(base)) {
@ -91,3 +91,14 @@ export const navigateToLink = (
navigate(url); navigate(url);
} }
}; };
/**
* Check if the event is modified (e.g. ctrl, shift, or meta key pressed)
* @param event - The event to check
* @returns true if the event was modified
*/
export const eventModified = (
event: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>
) => {
return event?.ctrlKey || event?.shiftKey || event?.metaKey;
};

View File

@ -4,6 +4,7 @@ import { useCallback, useMemo } from 'react';
import { ModelInformationDict } from '@lib/enums/ModelInformation'; import { ModelInformationDict } from '@lib/enums/ModelInformation';
import type { ModelType } from '@lib/enums/ModelType'; import type { ModelType } from '@lib/enums/ModelType';
import { eventModified } from '@lib/functions/Navigation';
import { generateUrl } from '../../functions/urls'; import { generateUrl } from '../../functions/urls';
import { useServerApiState } from '../../states/ApiState'; import { useServerApiState } from '../../states/ApiState';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
@ -66,7 +67,7 @@ export default function AdminButton(props: Readonly<AdminButtonProps>) {
`${server.server.django_admin}${modelDef.admin_url}${props.id}/` `${server.server.django_admin}${modelDef.admin_url}${props.id}/`
); );
if (event?.ctrlKey || event?.shiftKey) { if (eventModified(event)) {
// Open the link in a new tab // Open the link in a new tab
window.open(url, '_blank'); window.open(url, '_blank');
} else { } else {

View File

@ -5,7 +5,8 @@ import {
Indicator, Indicator,
Tabs, Tabs,
Text, Text,
Tooltip Tooltip,
UnstyledButton
} from '@mantine/core'; } from '@mantine/core';
import { useDisclosure, useDocumentVisibility } from '@mantine/hooks'; import { useDisclosure, useDocumentVisibility } from '@mantine/hooks';
import { IconBell, IconSearch } from '@tabler/icons-react'; import { IconBell, IconSearch } from '@tabler/icons-react';
@ -15,12 +16,14 @@ import { useMatch, useNavigate } from 'react-router-dom';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api'; import { apiUrl } from '@lib/functions/Api';
import { getBaseUrl } from '@lib/functions/Navigation';
import { navigateToLink } from '@lib/functions/Navigation'; import { navigateToLink } from '@lib/functions/Navigation';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import { api } from '../../App'; import { api } from '../../App';
import type { NavigationUIFeature } from '../../components/plugins/PluginUIFeatureTypes'; import type { NavigationUIFeature } from '../../components/plugins/PluginUIFeatureTypes';
import { getNavTabs } from '../../defaults/links'; import { getNavTabs } from '../../defaults/links';
import { generateUrl } from '../../functions/urls';
import { usePluginUIFeature } from '../../hooks/UsePluginUIFeature'; import { usePluginUIFeature } from '../../hooks/UsePluginUIFeature';
import * as classes from '../../main.css'; import * as classes from '../../main.css';
import { useServerApiState } from '../../states/ApiState'; import { useServerApiState } from '../../states/ApiState';
@ -215,7 +218,12 @@ function NavTabs() {
navigateToLink(`/${tab.name}`, navigate, event) navigateToLink(`/${tab.name}`, navigate, event)
} }
> >
{tab.title} <UnstyledButton
component={'a'}
href={generateUrl(`/${getBaseUrl()}/${tab.name}`)}
>
{tab.title}
</UnstyledButton>
</Tabs.Tab> </Tabs.Tab>
); );
}); });

View File

@ -24,8 +24,11 @@ import { useNavigate } from 'react-router-dom';
import type { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import type { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import type { ModelType } from '@lib/enums/ModelType'; import type { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api'; import { apiUrl } from '@lib/functions/Api';
import { getDetailUrl } from '@lib/functions/Navigation'; import {
import { navigateToLink } from '@lib/functions/Navigation'; eventModified,
getDetailUrl,
navigateToLink
} from '@lib/functions/Navigation';
import { useApi } from '../../contexts/ApiContext'; import { useApi } from '../../contexts/ApiContext';
import { ApiIcon } from '../items/ApiIcon'; import { ApiIcon } from '../items/ApiIcon';
import { StylishText } from '../items/StylishText'; import { StylishText } from '../items/StylishText';
@ -73,7 +76,7 @@ export default function NavigationTree({
const follow = useCallback( const follow = useCallback(
(node: TreeNodeData, event?: any) => { (node: TreeNodeData, event?: any) => {
const url = getDetailUrl(modelType, node.value); const url = getDetailUrl(modelType, node.value);
if (event?.shiftKey || event?.ctrlKey) { if (eventModified(event)) {
navigateToLink(url, navigate, event); navigateToLink(url, navigate, event);
} else { } else {
onClose(); onClose();

View File

@ -38,7 +38,7 @@ import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles'; import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api'; import { apiUrl } from '@lib/functions/Api';
import { cancelEvent } from '@lib/functions/Events'; import { cancelEvent } from '@lib/functions/Events';
import { navigateToLink } from '@lib/functions/Navigation'; import { eventModified, navigateToLink } from '@lib/functions/Navigation';
import { showNotification } from '@mantine/notifications'; import { showNotification } from '@mantine/notifications';
import { api } from '../../App'; import { api } from '../../App';
import { useUserSettingsState } from '../../states/SettingsState'; import { useUserSettingsState } from '../../states/SettingsState';
@ -96,7 +96,7 @@ function QueryResultGroup({
const url = `${overviewUrl}?search=${searchText}`; const url = `${overviewUrl}?search=${searchText}`;
// Close drawer if opening in the same tab // Close drawer if opening in the same tab
if (!(event?.ctrlKey || event?.shiftKey)) { if (!eventModified(event)) {
onClose(); onClose();
} }
@ -456,7 +456,7 @@ export function SearchDrawer({
return; return;
} }
if (event?.ctrlKey || event?.shiftKey) { if (eventModified(event)) {
// Keep the drawer open in this condition // Keep the drawer open in this condition
} else { } else {
closeDrawer(); closeDrawer();

View File

@ -8,7 +8,8 @@ import {
Stack, Stack,
Tabs, Tabs,
Text, Text,
Tooltip Tooltip,
UnstyledButton
} from '@mantine/core'; } from '@mantine/core';
import { import {
IconLayoutSidebarLeftCollapse, IconLayoutSidebarLeftCollapse,
@ -32,10 +33,12 @@ import {
import type { ModelType } from '@lib/enums/ModelType'; import type { ModelType } from '@lib/enums/ModelType';
import { cancelEvent } from '@lib/functions/Events'; import { cancelEvent } from '@lib/functions/Events';
import { eventModified, getBaseUrl } from '@lib/functions/Navigation';
import { navigateToLink } from '@lib/functions/Navigation'; import { navigateToLink } from '@lib/functions/Navigation';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import { identifierString } from '../../functions/conversion'; import { identifierString } from '../../functions/conversion';
import { generateUrl } from '../../functions/urls';
import { usePluginPanels } from '../../hooks/UsePluginPanels'; import { usePluginPanels } from '../../hooks/UsePluginPanels';
import { useLocalState } from '../../states/LocalState'; import { useLocalState } from '../../states/LocalState';
import { vars } from '../../theme'; import { vars } from '../../theme';
@ -170,9 +173,9 @@ function BasePanelGroup({
// Callback when the active panel changes // Callback when the active panel changes
const handlePanelChange = useCallback( const handlePanelChange = useCallback(
(targetPanel: string, event?: any) => { (targetPanel: string, event?: any) => {
if (event && (event?.ctrlKey || event?.shiftKey)) { cancelEvent(event);
if (event && eventModified(event)) {
const url = `${location.pathname}/../${targetPanel}`; const url = `${location.pathname}/../${targetPanel}`;
cancelEvent(event);
navigateToLink(url, navigate, event); navigateToLink(url, navigate, event);
} else { } else {
navigate(`../${targetPanel}`); navigate(`../${targetPanel}`);
@ -252,7 +255,14 @@ function BasePanelGroup({
handlePanelChange(panel.name, event) handlePanelChange(panel.name, event)
} }
> >
{expanded && panel.label} <UnstyledButton
component={'a'}
href={generateUrl(
`/${getBaseUrl()}${location.pathname}/${panel.name}`
)}
>
{expanded && panel.label}
</UnstyledButton>
</Tabs.Tab> </Tabs.Tab>
</Tooltip> </Tooltip>
) )