diff --git a/package.json b/package.json index 461e846a6..6a2bd917a 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@mui/lab": "6.0.0-beta.22", "@mui/material": "6.3.1", "@sentry/react": "8.48.0", + "@tanstack/react-query": "5.63.0", "@tanstack/react-router": "1.95.1", "@tanstack/router-devtools": "1.95.1", "@tanstack/zod-adapter": "1.95.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4c432939..3f930799d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,7 +25,7 @@ importers: version: 5.1.1 '@graasp/chatbox': specifier: 3.3.1 - version: 3.3.1(htr2mqcqqbxuceudmn3hlrygky) + version: 3.3.1(hx4w7cipqfuoiuips3zqjs6csi) '@graasp/query-client': specifier: 5.7.0 version: 5.7.0(@graasp/sdk@5.5.0(date-fns@4.1.0)(uuid@11.0.4))(@graasp/translations@1.43.0(i18next@24.2.1(typescript@5.7.3)))(react@18.3.1) @@ -53,6 +53,9 @@ importers: '@sentry/react': specifier: 8.48.0 version: 8.48.0(react@18.3.1) + '@tanstack/react-query': + specifier: 5.63.0 + version: 5.63.0(react@18.3.1) '@tanstack/react-router': specifier: 1.95.1 version: 1.95.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -2168,6 +2171,9 @@ packages: '@tanstack/query-core@5.61.5': resolution: {integrity: sha512-iG5vqurEOEbv+paP6kW3zPENa99kSIrd1THISJMaTwVlJ+N5yjVDNOUwp9McK2DWqWCXM3v13ubBbAyhxT78UQ==} + '@tanstack/query-core@5.62.16': + resolution: {integrity: sha512-9Sgft7Qavcd+sN0V25xVyo0nfmcZXBuODy3FVG7BMWTg1HMLm8wwG5tNlLlmSic1u7l1v786oavn+STiFaPH2g==} + '@tanstack/query-devtools@5.61.4': resolution: {integrity: sha512-21Tw+u8E3IJJj4A/Bct4H0uBaDTEu7zBrR79FeSyY+mS2gx5/m316oDtJiKkILc819VSTYt+sFzODoJNcpPqZQ==} @@ -2182,6 +2188,11 @@ packages: peerDependencies: react: ^18 || ^19 + '@tanstack/react-query@5.63.0': + resolution: {integrity: sha512-QWizLzSiog8xqIRYmuJRok9VELlXVBAwtINgVCgW1SNvamQwWDO5R0XFSkjoBEj53x9Of1KAthLRBUC5xmtVLQ==} + peerDependencies: + react: ^18 || ^19 + '@tanstack/react-router@1.95.1': resolution: {integrity: sha512-P5x4yNhcdkYsCEoYeGZP8Q9Jlxf0WXJa4G/xvbmM905seZc9FqJqvCSRvX3dWTPOXRABhl4g+8DHqfft0c/AvQ==} engines: {node: '>=12'} @@ -7642,7 +7653,7 @@ snapshots: '@googlemaps/js-api-loader@1.16.8': {} - '@graasp/chatbox@3.3.1(htr2mqcqqbxuceudmn3hlrygky)': + '@graasp/chatbox@3.3.1(hx4w7cipqfuoiuips3zqjs6csi)': dependencies: '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) @@ -7654,7 +7665,7 @@ snapshots: '@mui/icons-material': 6.3.1(@mui/material@6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) '@mui/lab': 6.0.0-beta.22(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@mui/material@6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/material': 6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@tanstack/react-query': 5.61.5(react@18.3.1) + '@tanstack/react-query': 5.63.0(react@18.3.1) date-fns: 4.1.0 i18next: 24.2.1(typescript@5.7.3) lodash.groupby: 4.6.0 @@ -8314,6 +8325,8 @@ snapshots: '@tanstack/query-core@5.61.5': {} + '@tanstack/query-core@5.62.16': {} + '@tanstack/query-devtools@5.61.4': {} '@tanstack/react-query-devtools@5.61.5(@tanstack/react-query@5.61.5(react@18.3.1))(react@18.3.1)': @@ -8327,6 +8340,11 @@ snapshots: '@tanstack/query-core': 5.61.5 react: 18.3.1 + '@tanstack/react-query@5.63.0(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.62.16 + react: 18.3.1 + '@tanstack/react-router@1.95.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/history': 1.95.0 diff --git a/src/components/ui/MenuItemLink.tsx b/src/components/ui/MenuItemLink.tsx new file mode 100644 index 000000000..b8a681e66 --- /dev/null +++ b/src/components/ui/MenuItemLink.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +import { MenuItem, MenuItemProps } from '@mui/material'; + +import { LinkComponent, createLink } from '@tanstack/react-router'; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +interface MUIMenuItemProps extends Omit { + // Add any additional props you want to pass to the typography +} + +const MUIMenuItemComponent = React.forwardRef< + HTMLAnchorElement, + MUIMenuItemProps +>((props, ref) => { + return ; +}); + +const CreatedLinkComponent = createLink(MUIMenuItemComponent); + +export const MenuItemLink: LinkComponent = ( + props, +) => { + return ; +}; diff --git a/src/components/ui/Navigator/CurrentItemNavigation.tsx b/src/components/ui/Navigator/CurrentItemNavigation.tsx index 833cd3688..0e599dc03 100644 --- a/src/components/ui/Navigator/CurrentItemNavigation.tsx +++ b/src/components/ui/Navigator/CurrentItemNavigation.tsx @@ -1,11 +1,8 @@ -import { Typography } from '@mui/material'; - import { DiscriminatedItem, ItemType } from '@graasp/sdk'; +import { TypographyLink } from '../TypographyLink.js'; import ItemMenu, { ItemMenuProps } from './ItemMenu.js'; import CenterAlignWrapper from './common/CenterAlignWrapper.js'; -import NavigationLink from './common/NavigationLink.js'; -import { ITEM_NAME_MAX_LENGTH } from './common/constants.js'; export type CurrentItemProps = { item: DiscriminatedItem; @@ -14,35 +11,32 @@ export type CurrentItemProps = { buildMenuId?: (id: string) => string; buildMenuItemId?: (id: string) => string; useChildren: ItemMenuProps['useChildren']; - buildToItemPath: (id: string) => LinkProps['to']; showArrow: boolean; }; -const CurrentItemNavigation = ({ + +export function CurrentItemNavigation({ item, buildBreadcrumbsItemLinkId, - buildToItemPath, useChildren, buildIconId, buildMenuId, buildMenuItemId, showArrow, -}: CurrentItemProps): JSX.Element | null => { +}: Readonly): JSX.Element | null { return ( - - - {truncate(item.name, { length: ITEM_NAME_MAX_LENGTH })} - - + {item.name} + {(item.type === ItemType.FOLDER || showArrow) && ( ); -}; - -export default CurrentItemNavigation; +} diff --git a/src/components/ui/Navigator/ExtraItemsMenu.tsx b/src/components/ui/Navigator/ExtraItemsMenu.tsx index c789d0aad..e140dbb25 100644 --- a/src/components/ui/Navigator/ExtraItemsMenu.tsx +++ b/src/components/ui/Navigator/ExtraItemsMenu.tsx @@ -1,35 +1,23 @@ import { useState } from 'react'; -import { Link } from 'react-router-dom'; -import { - IconButton, - IconButtonProps, - Menu, - MenuItem, - Typography, -} from '@mui/material'; +import { IconButton, IconButtonProps, Menu, Typography } from '@mui/material'; import { ChevronRightIcon } from 'lucide-react'; -import { MenuItemType } from './Navigator.js'; +import { MenuItemLink } from '../MenuItemLink'; +import { MenuItemType } from './Navigator'; export type ExtraItemsMenuProps = { icon?: JSX.Element; menuItems: MenuItemType[]; - buildIconId?: (id: string) => string; - buildMenuId?: (itemId: string) => string; - name: string; }; const Separator = ; -const ExtraItemsMenu = ({ +export function ExtraItemsMenu({ icon = Separator, menuItems, - buildIconId, - buildMenuId, - name, -}: ExtraItemsMenuProps): JSX.Element => { +}: Readonly): JSX.Element { const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); const handleClick: IconButtonProps['onClick'] = (event) => { @@ -45,7 +33,6 @@ const ExtraItemsMenu = ({ {icon} @@ -53,7 +40,6 @@ const ExtraItemsMenu = ({ {menuItems?.map(({ name, path }) => ( - + {name} - + ))} ); -}; - -export default ExtraItemsMenu; +} diff --git a/src/components/ui/Navigator/ExtraItemsNavigation.tsx b/src/components/ui/Navigator/ExtraItemsNavigation.tsx index 93437e992..404a9741a 100644 --- a/src/components/ui/Navigator/ExtraItemsNavigation.tsx +++ b/src/components/ui/Navigator/ExtraItemsNavigation.tsx @@ -1,12 +1,8 @@ -import { Box, Typography } from '@mui/material'; +import { Box, Stack } from '@mui/material'; -import truncate from 'lodash.truncate'; - -import ExtraItemsMenu from './ExtraItemsMenu.js'; -import { MenuItemType } from './Navigator.js'; -import CenterAlignWrapper from './common/CenterAlignWrapper.js'; -import NavigationLink from './common/NavigationLink.js'; -import { ITEM_NAME_MAX_LENGTH } from './common/constants.js'; +import { TypographyLink } from '../TypographyLink'; +import { ExtraItemsMenu } from './ExtraItemsMenu'; +import { MenuItemType } from './Navigator'; export interface ExtraItem { name: string; @@ -15,26 +11,20 @@ export interface ExtraItem { menuItems?: MenuItemType[]; } -const ExtraItemsNavigation = ({ +export function ExtraItemsNavigation({ extraItems, -}: { +}: Readonly<{ extraItems: ExtraItem[]; -}): JSX.Element[] | null => { +}>): JSX.Element[] { return extraItems.map(({ icon, name, path, menuItems }) => ( - + {icon} - - - {truncate(name, { length: ITEM_NAME_MAX_LENGTH })} - - + {name} {menuItems && menuItems.length > 0 && ( - + )} - + )); -}; - -export default ExtraItemsNavigation; +} diff --git a/src/components/ui/Navigator/ItemMenu.tsx b/src/components/ui/Navigator/ItemMenu.tsx index f6a1405f5..8dfc04322 100644 --- a/src/components/ui/Navigator/ItemMenu.tsx +++ b/src/components/ui/Navigator/ItemMenu.tsx @@ -1,42 +1,35 @@ -import type { UseQueryResult } from '@tanstack/react-query'; -import { ChevronRightIcon } from 'lucide-react'; - -import { - IconButton, - IconButtonProps, - Menu, - MenuItem, - Typography, -} from '@mui/material'; - import { useState } from 'react'; -import { Link, LinkProps } from 'react-router-dom'; + +import { IconButton, IconButtonProps, Menu, Typography } from '@mui/material'; import { DiscriminatedItem } from '@graasp/sdk'; -export const Separator = ; +import { type UseQueryResult } from '@tanstack/react-query'; +import { ChevronRightIcon } from 'lucide-react'; + +import { MenuItemLink } from '../MenuItemLink'; + +export const Separator = ; export type ItemMenuProps = { buildIconId?: (id: string) => string; buildMenuId?: (itemId: string) => string; buildMenuItemId?: (itemId: string) => string; - buildToItemPath: (itemId: string) => LinkProps['to']; icon?: JSX.Element; itemId: string; - useChildren: (...args: unknown[]) => UseQueryResult; + useChildren: (itemId: string) => UseQueryResult; renderArrow?: boolean; }; -const ItemMenu = ({ +export function ItemMenu({ buildIconId, buildMenuId, buildMenuItemId, - buildToItemPath, icon = Separator, itemId, useChildren, renderArrow, -}: ItemMenuProps): JSX.Element | null => { +}: Readonly): JSX.Element | null { const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); @@ -63,7 +56,7 @@ const ItemMenu = ({ onClick={handleClick} id={buildIconId?.(itemId)} aria-controls={open ? buildMenuId?.(itemId) : undefined} - aria-haspopup='true' + aria-haspopup="true" aria-expanded={open ? true : undefined} > {icon} @@ -84,18 +77,18 @@ const ItemMenu = ({ }} > {items?.map(({ name, id }) => ( - {name} - + ))} ); -}; +} export default ItemMenu; diff --git a/src/components/ui/Navigator/Navigator.tsx b/src/components/ui/Navigator/Navigator.tsx index 568542a37..75566d8df 100644 --- a/src/components/ui/Navigator/Navigator.tsx +++ b/src/components/ui/Navigator/Navigator.tsx @@ -1,85 +1,79 @@ -import { Breadcrumbs } from '@mui/material'; +import { Breadcrumbs, SxProps, styled } from '@mui/material'; -export function Navigator({ parents }: {}) { - return ; -} +import { DiscriminatedItem } from '@graasp/sdk'; -// const StyledBreadcrumbs = styled(Breadcrumbs)(() => ({ -// '.MuiBreadcrumbs-separator': { -// margin: 0, -// }, -// })); +import { CurrentItemNavigation } from './CurrentItemNavigation'; +import { ExtraItem, ExtraItemsNavigation } from './ExtraItemsNavigation'; +import { ItemMenuProps } from './ItemMenu'; +import { ParentsNavigation } from './ParentsNavigation'; -// export type NavigationProps = { -// backgroundColor?: string; -// buildBreadcrumbsItemLinkId?: (id: string) => string; -// buildIconId?: (id: string) => string; -// buildMenuItemId?: (id: string) => string; -// buildToItemPath: (id: string) => string; -// buildMenuId?: (id: string) => string; -// id?: string; -// item?: DiscriminatedItem; -// parents?: DiscriminatedItem[]; -// renderRoot?: (item?: DiscriminatedItem) => JSX.Element | null; -// sx?: SxProps; -// useChildren: ItemMenuProps['useChildren']; -// maxItems?: number; -// extraItems?: ExtraItem[]; -// }; -// export interface MenuItemType { -// name: string; -// path: string; -// } +const StyledBreadcrumbs = styled(Breadcrumbs)(() => ({ + '.MuiBreadcrumbs-separator': { + margin: 0, + }, +})); -// const Navigation = ({ -// backgroundColor, -// buildBreadcrumbsItemLinkId, -// buildIconId, -// buildMenuItemId, -// buildToItemPath, -// id, -// item, -// parents, -// renderRoot, -// sx, -// useChildren, -// buildMenuId, -// maxItems = 4, -// extraItems, -// }: NavigationProps): JSX.Element | null => { -// return ( -// -// {renderRoot?.(item)} -// {item?.id && parents && ( -// -// )} -// {item?.id && item && ( -// -// )} -// {extraItems && } -// -// ); -// }; +export type NavigationProps = { + backgroundColor?: string; + buildBreadcrumbsItemLinkId?: (id: string) => string; + buildIconId?: (id: string) => string; + buildMenuItemId?: (id: string) => string; + buildMenuId?: (id: string) => string; + id?: string; + item?: DiscriminatedItem; + parents?: DiscriminatedItem[]; + sx?: SxProps; + useChildren: ItemMenuProps['useChildren']; + maxItems?: number; + extraItems?: ExtraItem[]; +}; +export interface MenuItemType { + name: string; + path: string; +} -// export default Navigation; +export function AnalyticsNavigator({ + backgroundColor, + buildBreadcrumbsItemLinkId, + buildIconId, + buildMenuItemId, + id, + item, + parents, + sx, + useChildren, + buildMenuId, + maxItems = 4, + extraItems, +}: Readonly): JSX.Element | null { + return ( + + {item?.id && parents && ( + + )} + {item?.id && item && ( + + )} + {extraItems && } + + ); +} diff --git a/src/components/ui/Navigator/ParentsNavigation.tsx b/src/components/ui/Navigator/ParentsNavigation.tsx index 1c21ed6ab..7d05dd1a5 100644 --- a/src/components/ui/Navigator/ParentsNavigation.tsx +++ b/src/components/ui/Navigator/ParentsNavigation.tsx @@ -1,48 +1,40 @@ -import truncate from 'lodash.truncate'; - -import { Stack, Typography } from '@mui/material'; +import { Stack } from '@mui/material'; import { DiscriminatedItem } from '@graasp/sdk'; +import { TypographyLink } from '../TypographyLink.js'; import ItemMenu, { ItemMenuProps } from './ItemMenu.js'; -import NavigationLink from './common/NavigationLink.js'; -import { ITEM_NAME_MAX_LENGTH } from './common/constants.js'; export type ParentsProps = { parents: DiscriminatedItem[]; buildBreadcrumbsItemLinkId?: (id: string) => string; useChildren: ItemMenuProps['useChildren']; - buildToItemPath: (id: string) => string; }; -const ParentsNavigation = ({ + +export function ParentsNavigation({ parents, useChildren, buildBreadcrumbsItemLinkId, - buildToItemPath, -}: ParentsProps): JSX.Element => ( - - {parents.map(({ name, id }) => ( - - ): JSX.Element { + return ( + + {parents.map(({ name, id }) => ( + - - {truncate(name, { length: ITEM_NAME_MAX_LENGTH })} - - - - - ))} - -); -export default ParentsNavigation; + + {name} + + + + ))} + + ); +} diff --git a/src/components/ui/Navigator/common/NavigationLink.tsx b/src/components/ui/Navigator/common/NavigationLink.tsx deleted file mode 100644 index f81402711..000000000 --- a/src/components/ui/Navigator/common/NavigationLink.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { styled } from '@mui/material'; - -import { ReactNode } from 'react'; -import { Link, LinkProps } from 'react-router-dom'; - -const NavigationLink: (props: { - id?: string; - children: ReactNode; - to: LinkProps['to']; -}) => ReactNode = styled(Link)(({ theme }) => ({ - textDecoration: 'none', - color: theme.palette.text.primary, -})); -export default NavigationLink; diff --git a/src/modules/analytics/Navigator.tsx b/src/modules/analytics/Navigator.tsx index b0bdd81aa..a31afbee8 100644 --- a/src/modules/analytics/Navigator.tsx +++ b/src/modules/analytics/Navigator.tsx @@ -1,11 +1,4 @@ -import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; -import HomeOutlinedIcon from '@mui/icons-material/HomeOutlined'; -import { IconButton } from '@mui/material'; - -import { Navigation } from '@graasp/ui'; - -import { useNavigate } from '@tanstack/react-router'; - +import { AnalyticsNavigator } from '@/components/ui/Navigator/Navigator'; import { hooks } from '@/config/queryClient'; import { @@ -16,16 +9,13 @@ import { } from './config/selectors'; import { NAVIGATOR_BACKGROUND_COLOR } from './constants'; -const { useItem, useParents, useCurrentMember, useChildren } = hooks; +const { useItem, useParents, useChildren } = hooks; export function Navigator({ itemId, }: Readonly<{ itemId: string }>): JSX.Element | null { - const { data: currentMember } = useCurrentMember(); const { data: item, isLoading: isItemLoading } = useItem(itemId); - const navigate = useNavigate(); - const { data: parents, isLoading: areParentsLoading } = useParents({ id: itemId, }); @@ -34,30 +24,12 @@ export function Navigator({ return null; } - const renderRoot = () => { - // no root access if signed out - if (!currentMember) { - return null; - } - - return ( - <> - navigate({ to: '/analytics' })}> - - - - - ); - }; - return ( - `/analytics/items/${id}`} parents={parents} - renderRoot={renderRoot} backgroundColor={NAVIGATOR_BACKGROUND_COLOR} buildBreadcrumbsItemLinkId={buildBreadcrumbsItemLink} buildMenuItemId={buildMenuItemId} diff --git a/src/routes/analytics/items/$itemId.lazy.tsx b/src/routes/analytics/items/$itemId.lazy.tsx index 2ee7f75b7..9c3d26f47 100644 --- a/src/routes/analytics/items/$itemId.lazy.tsx +++ b/src/routes/analytics/items/$itemId.lazy.tsx @@ -1,12 +1,13 @@ import { useContext } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box, Grid2, Skeleton, Stack, Typography } from '@mui/material'; +import { Box, Grid2, Skeleton, Stack } from '@mui/material'; import { Outlet, createLazyFileRoute } from '@tanstack/react-router'; import { NS } from '@/config/constants'; +import { Navigator } from '~analytics/Navigator'; import { PageWrapper } from '~analytics/PageWrapper'; import ActionsLegend from '~analytics/charts-layout/ActionsLegend'; import StyledAlert from '~analytics/common/StyledAlert'; @@ -26,13 +27,8 @@ function RouteComponent() { return ( - { - // temp disable as we need to adapt it to the current router - - here we should have the navigator - - /* */ - } + +