diff --git a/package.json b/package.json index cf3e1dcb9f..0d74044835 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@mdx-js/react": "^2.3.0", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-tooltip": "^1.2.8", "@react-hook/media-query": "^1.1.1", "@sentry/gatsby": "^9.19.0", diff --git a/src/components/Article/index.tsx b/src/components/Article/index.tsx index 1aa0df84bb..16c343e39e 100644 --- a/src/components/Article/index.tsx +++ b/src/components/Article/index.tsx @@ -2,7 +2,7 @@ import React, { FunctionComponent as FC } from 'react'; import { ArticleFooter } from './ArticleFooter'; const Article: FC<{ children: React.ReactNode }> = ({ children }) => ( -
+
{children}
diff --git a/src/components/Head.tsx b/src/components/Head.tsx index 6789294f81..1a5b7a91aa 100644 --- a/src/components/Head.tsx +++ b/src/components/Head.tsx @@ -27,7 +27,7 @@ export const Head = ({ diff --git a/src/components/Layout/Breadcrumbs.test.tsx b/src/components/Layout/Breadcrumbs.test.tsx index 6219fb7bf6..8427c4f781 100644 --- a/src/components/Layout/Breadcrumbs.test.tsx +++ b/src/components/Layout/Breadcrumbs.test.tsx @@ -37,11 +37,11 @@ describe('Breadcrumbs', () => { jest.clearAllMocks(); }); - it('renders relevant breadcrumb nodes', () => { + it('renders all breadcrumb nodes from activePage tree', () => { render(); expect(screen.getByText('Home')).toBeInTheDocument(); expect(screen.getByText('Section 1')).toBeInTheDocument(); - expect(screen.queryByText('Subsection 1')).not.toBeInTheDocument(); + expect(screen.getByText('Subsection 1')).toBeInTheDocument(); expect(screen.getByText('Current Page')).toBeInTheDocument(); }); @@ -49,38 +49,49 @@ describe('Breadcrumbs', () => { render(); expect(screen.getByText('Home')).toHaveAttribute('href', '/docs'); expect(screen.getByText('Section 1')).toHaveAttribute('href', '/section-1'); - expect(screen.queryByText('Subsection 1')).not.toBeInTheDocument(); + expect(screen.getByText('Subsection 1')).toHaveAttribute('href', '#'); expect(screen.getByText('Current Page')).toHaveAttribute('href', '/section-1/subsection-1/page-1'); }); - it('disables the link for the current page', () => { + it('disables the link for the current page and non-linked nodes', () => { render(); + + // Current page (last item) should be disabled expect(screen.getByText('Current Page')).toHaveClass('text-gui-unavailable'); expect(screen.getByText('Current Page')).toHaveClass('pointer-events-none'); + + // Non-linked nodes (link='#') should be disabled + expect(screen.getByText('Subsection 1')).toHaveClass('text-gui-unavailable'); + expect(screen.getByText('Subsection 1')).toHaveClass('pointer-events-none'); + + // Active links should not be disabled + expect(screen.getByText('Section 1')).not.toHaveClass('text-gui-unavailable'); + expect(screen.getByText('Section 1')).not.toHaveClass('pointer-events-none'); + }); + + it('shows only the last active node in mobile view', () => { + render(); + + // All items except index 0 should have 'hidden sm:flex' classes + expect(screen.getByText('Section 1')).not.toHaveClass('hidden'); + expect(screen.getByText('Subsection 1')).toHaveClass('hidden', 'sm:flex'); + expect(screen.getByText('Current Page')).toHaveClass('hidden', 'sm:flex'); }); - it('removes duplicate links from breadcrumb nodes', () => { + it('correctly identifies last active node when current page is non-linked', () => { mockUseLayoutContext.mockReturnValue({ activePage: { tree: [ { page: { name: 'Section 1', link: '/section-1' } }, - { page: { name: 'Duplicate Section', link: '/section-1' } }, - { page: { name: 'Current Page', link: '/section-1/page-1' } }, + { page: { name: 'Subsection 1', link: '/section-1/subsection-1' } }, + { page: { name: 'Current Page', link: '#' } }, ], }, }); render(); - - // Should only show one instance of the duplicate link - const section1Links = screen.getAllByText('Section 1'); - expect(section1Links).toHaveLength(1); - - // Should not render the duplicate with different text - expect(screen.queryByText('Duplicate Section')).not.toBeInTheDocument(); - - // Should still render other breadcrumb elements - expect(screen.getByText('Home')).toBeInTheDocument(); - expect(screen.getByText('Current Page')).toBeInTheDocument(); + expect(screen.getByText('Section 1')).toHaveClass('hidden', 'sm:flex'); + expect(screen.getByText('Subsection 1')).not.toHaveClass('hidden'); + expect(screen.getByText('Current Page')).toHaveClass('hidden', 'sm:flex'); }); }); diff --git a/src/components/Layout/Breadcrumbs.tsx b/src/components/Layout/Breadcrumbs.tsx index 4a7dd63cb8..bd5cd359a2 100644 --- a/src/components/Layout/Breadcrumbs.tsx +++ b/src/components/Layout/Breadcrumbs.tsx @@ -1,31 +1,34 @@ -import React, { useMemo } from 'react'; +import React from 'react'; import { useLayoutContext } from 'src/contexts/layout-context'; import Link from '../Link'; import Icon from '@ably/ui/core/Icon'; import cn from '@ably/ui/core/utils/cn'; -import { hierarchicalKey, PageTreeNode } from './utils/nav'; +import { hierarchicalKey } from './utils/nav'; + +const linkStyles = + 'ui-text-label4 font-semibold text-neutral-900 hover:text-neutral-1300 active:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-000 dark:active:text-neutral-500 focus-base transition-colors'; const Breadcrumbs: React.FC = () => { const { activePage } = useLayoutContext(); - const breadcrumbNodes = useMemo(() => { - const filteredNodes = activePage?.tree.filter((node) => node.page.link !== '#') ?? []; - const uniqueNodes = filteredNodes.reduce((acc: PageTreeNode[], current) => { - const isDuplicate = acc.some((item) => item.page.link === current.page.link); - if (!isDuplicate) { - acc.push(current); - } - return acc; - }, []); - return uniqueNodes; - }, [activePage?.tree]); - - if (breadcrumbNodes.length === 0) { + if (!activePage?.tree || activePage.tree.length === 0) { return null; } - const linkStyles = - 'ui-text-label4 font-semibold text-neutral-900 hover:text-neutral-1300 active:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-000 dark:active:text-neutral-500'; + const lastActiveNodeIndex = (() => { + const index = activePage.tree + .toReversed() + .slice(1) + .findIndex((node) => node.page.link !== '#'); + + if (index !== -1) { + return activePage.tree.length - index - 2; + } + + return null; + })(); + + console.log(lastActiveNodeIndex, activePage.tree); return (