feat(frontend): lib make Detaildrawer available (#11827)

* feat(frontend): lib make Detaildrawer available

Co-authored-by: Copilot <copilot@github.com>

* move StylishText to lib

Co-authored-by: Copilot <copilot@github.com>

* add changelog entry

Co-authored-by: Copilot <copilot@github.com>

* fix import path

---------

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Matthias Mair
2026-05-01 14:49:43 +10:00
committed by GitHub
co-authored by Copilot
parent 76da739c48
commit 6f56d44287
76 changed files with 121 additions and 93 deletions
@@ -0,0 +1,46 @@
import {
type MantineSize,
Text,
darken,
getThemeColor,
useMantineTheme
} from '@mantine/core';
import { useMemo } from 'react';
import type { JSX } from 'react';
// Hook that memoizes the gradient color based on the primary color of the theme
const useThematicGradient = () => {
const theme = useMantineTheme();
const primary = useMemo(() => {
return getThemeColor(theme.primaryColor, theme);
}, [theme]);
const secondary = useMemo(() => darken(primary, 0.25), [primary]);
return useMemo(() => {
return { primary, secondary };
}, [primary, secondary]);
};
// A stylish text component that uses the primary color of the theme
export function StylishText({
children,
size
}: Readonly<{
children: JSX.Element | string;
size?: MantineSize;
}>) {
const { primary, secondary } = useThematicGradient();
return (
<Text
fw={700}
size={size ?? 'xl'}
variant='gradient'
gradient={{ from: primary.toString(), to: secondary.toString() }}
>
{children}
</Text>
);
}
@@ -0,0 +1,6 @@
import { style } from '@vanilla-extract/css';
export const flex = style({
display: 'flex',
flex: 1
});
@@ -0,0 +1,108 @@
import { ActionIcon, Divider, Drawer, Group, Stack, Text } from '@mantine/core';
import { IconChevronLeft } from '@tabler/icons-react';
import { useCallback, useMemo } from 'react';
import { Link, Route, Routes, useNavigate, useParams } from 'react-router-dom';
import type { To } from 'react-router-dom';
import type { UiSizeType } from '../../types/Core';
import { useShallow } from 'zustand/react/shallow';
import { useLocalLibState } from '../../states/LocalLibState';
import { StylishText } from '../StylishText';
import * as classes from './DetailDrawer.css';
/**
* @param title - drawer title
* @param position - drawer position
* @param renderContent - function used to render the drawer content
* @param urlPrefix - set an additional url segment, useful when multiple drawers are rendered on one page (e.g. "user/")
*/
export interface DrawerProps {
title: string;
position?: 'right' | 'left';
renderContent: (id?: string) => React.ReactNode;
urlPrefix?: string;
size?: UiSizeType;
closeOnEscape?: boolean;
}
function DetailDrawerComponent({
title,
position = 'right',
size,
closeOnEscape = true,
renderContent
}: Readonly<DrawerProps>) {
const navigate = useNavigate();
const { id } = useParams();
const content = renderContent(id);
const opened = useMemo(() => !!id && !!content, [id, content]);
const [detailDrawerStack, addDetailDrawer] = useLocalLibState(
useShallow((state) => [state.detailDrawerStack, state.addDetailDrawer])
);
return (
<Drawer
opened={opened}
onClose={() => {
navigate('../');
addDetailDrawer(false);
}}
position={position}
closeOnEscape={closeOnEscape}
size={size}
classNames={{ root: classes.flex, body: classes.flex }}
scrollAreaComponent={Stack}
title={
<Group>
{detailDrawerStack > 0 && (
<ActionIcon
variant='outline'
onClick={() => {
navigate(-1);
addDetailDrawer(-1);
}}
>
<IconChevronLeft />
</ActionIcon>
)}
<StylishText size='xl'>{title}</StylishText>
</Group>
}
>
<Stack gap={'xs'} className={classes.flex}>
<Divider />
{content}
</Stack>
</Drawer>
);
}
export function DetailDrawer(props: Readonly<DrawerProps>) {
return (
<Routes>
<Route path=':id?/' element={<DetailDrawerComponent {...props} />} />
</Routes>
);
}
export function DetailDrawerLink({
to,
text
}: Readonly<{ to: To; text: string }>) {
const addDetailDrawer = useLocalLibState(
useShallow((state) => state.addDetailDrawer)
);
const onNavigate = useCallback(() => {
addDetailDrawer(1);
}, [addDetailDrawer]);
return (
<Link to={to} onClick={onNavigate}>
<Text>{text}</Text>
</Link>
);
}
+7
View File
@@ -132,6 +132,13 @@ export {
type TableStateExtraProps
} from './hooks/UseTable';
export {
type DrawerProps,
DetailDrawer,
DetailDrawerLink
} from './components/nav/DetailDrawer';
export { StylishText } from './components/StylishText';
// State management
export {
type StoredTableStateProps,
+23
View File
@@ -0,0 +1,23 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const useLocalLibState = create<LocalLibStateProps>()(
persist(
(set, get) => ({
detailDrawerStack: 0,
addDetailDrawer: (value) => {
set({
detailDrawerStack:
value === false ? 0 : get().detailDrawerStack + value
});
}
}),
{
name: 'session-settings-inventreedb_lib'
}
)
);
export interface LocalLibStateProps {
detailDrawerStack: number;
addDetailDrawer: (value: number | false) => void;
}