Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c2efe1f
feat: update Header
jamiehenson Oct 13, 2025
c46dc3b
feat: add redesigned left navigation sidebar
jamiehenson Oct 20, 2025
7c9c2bb
feat: add redesigned right sidebar nav with stepped functionality
jamiehenson Nov 7, 2025
3b5bfe3
chore: add test page to showcase stepped right sidebar
jamiehenson Nov 7, 2025
799a4a4
chore: shift location of inkeep-search bar between desktop and mobile…
jamiehenson Nov 7, 2025
464f349
chore: update navigation component tests to respect new functionality
jamiehenson Nov 10, 2025
ed88a33
chore: render empty divs for null nav elements to preserve gap
jamiehenson Nov 10, 2025
4ede4c7
fix: post-review code tidyups for core nav components
jamiehenson Nov 12, 2025
ea6c2d7
fix: make leftsidebar bolding conditions dependent on open state and …
jamiehenson Nov 12, 2025
10e2078
chore: add radix-ui/react-select
jamiehenson Nov 11, 2025
95dc20e
chore: add ibm plex serif font as font-serif
jamiehenson Nov 11, 2025
d3333a6
chore: rewrite LanguageSelector using radix/react-select
jamiehenson Nov 11, 2025
f9c9f97
feat: add new PageHeader unit
jamiehenson Nov 11, 2025
b7913a8
fix: update breadcrumbs behaviour
jamiehenson Nov 11, 2025
dcf8ef2
fix: allow padding for container-bordering focus outlines
jamiehenson Nov 11, 2025
ed9a328
chore: add skeleton component as a placeholder for loading language s…
jamiehenson Nov 12, 2025
7c71bce
fix: unlock scrolling when radix select dropdown open
jamiehenson Nov 13, 2025
509e799
feat: add new redesigned footer, feedback functionality disabled
jamiehenson Nov 12, 2025
022b693
feat: add Admonitions MDX component
jamiehenson Nov 28, 2025
02994a5
feat: add Table MDX component and update MDXWrapper
jamiehenson Nov 28, 2025
1935e45
fix: adopt existing data-type admonition conventions and fix multi-pa…
jamiehenson Dec 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ INSIGHTS_ENABLED=false
INSIGHTS_DEBUG=true
POSTHOG_API_KEY=''
POSTHOG_API_HOST=''
POSTHOG_FEEDBACK_SURVEY_NAME=''
MIXPANEL_API_KEY=''
MIXPANEL_AUTO_CAPTURE=''

Expand Down
4 changes: 3 additions & 1 deletion gatsby-browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
reducerBlogPosts,
reducerSessionData,
} from '@ably/ui/core/scripts';
import { PostHogProvider } from 'posthog-js/react';
import posthog from 'posthog-js';

import { reducerApiKeyData } from './src/redux/api-key/api-key-reducer';
import UserContextWrapper from './src/contexts/user-context/wrap-with-provider';
Expand Down Expand Up @@ -66,7 +68,7 @@ const InsightsWrapper = ({ children }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return children;
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
};

export const wrapRootElement = ({ element }) => {
Expand Down
1 change: 1 addition & 0 deletions gatsby-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const siteMetadata = {
mixpanelAutoCapture: !!process.env.MIXPANEL_AUTO_CAPTURE,
posthogApiKey: process.env.POSTHOG_API_KEY,
posthogHost: process.env.POSTHOG_API_HOST || 'https://insights.ably.com',
posthogFeedbackSurveyName: process.env.POSTHOG_FEEDBACK_SURVEY_NAME || 'Docs Feedback',
conversationsUrl: process.env.CONVERSATIONS_API_URL,
},
};
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
"@codesandbox/sandpack-themes": "^2.0.21",
"@gfx/zopfli": "^1.0.15",
"@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",
"@types/cheerio": "^0.22.31",
Expand Down
2 changes: 1 addition & 1 deletion src/components/Article/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { FunctionComponent as FC } from 'react';
import { ArticleFooter } from './ArticleFooter';

const Article: FC<{ children: React.ReactNode }> = ({ children }) => (
<article className="flex-1 overflow-x-hidden relative z-10">
<article className="flex-1 overflow-x-hidden px-4 -mx-4 relative z-10">
{children}
<ArticleFooter />
</article>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Head.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const Head = ({
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
<link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Manrope:[email protected]&family=Source+Code+Pro:wght@600&display=swap"
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Manrope:[email protected]&family=Source+Code+Pro:wght@600&family=IBM+Plex+Serif:ital,wght@1,400;1,700&display=swap"
rel="stylesheet"
/>
</Helmet>
Expand Down
47 changes: 29 additions & 18 deletions src/components/Layout/Breadcrumbs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,50 +37,61 @@ describe('Breadcrumbs', () => {
jest.clearAllMocks();
});

it('renders relevant breadcrumb nodes', () => {
it('renders all breadcrumb nodes from activePage tree', () => {
render(<Breadcrumbs />);
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();
});

it('includes relevant links on the breadcrumb nodes', () => {
render(<Breadcrumbs />);
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(<Breadcrumbs />);

// 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(<Breadcrumbs />);

// 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(<Breadcrumbs />);

// 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');
});
});
46 changes: 25 additions & 21 deletions src/components/Layout/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<nav aria-label="breadcrumb" className="flex mt-8 items-center gap-1">
Expand All @@ -35,17 +38,18 @@ const Breadcrumbs: React.FC = () => {
<Icon
name="icon-gui-chevron-right-micro"
size="16px"
additionalCSS={cn('rotate-180 sm:rotate-0', { 'hidden sm:flex': breadcrumbNodes.length === 1 })}
color="text-neutral-900 dark:text-neutral-400"
additionalCSS={cn('rotate-180 sm:rotate-0', { 'hidden sm:flex': lastActiveNodeIndex === null })}
/>
{breadcrumbNodes.map((node, index) => (
{activePage.tree.map((node, index) => (
<React.Fragment key={hierarchicalKey(node.page.link, index, activePage.tree)}>
{index > 0 ? <Icon name="icon-gui-chevron-right-micro" size="16px" additionalCSS="hidden sm:flex" /> : null}
<Link
to={node.page.link}
className={cn(linkStyles, {
'text-gui-unavailable dark:text-gui-unavailable-dark pointer-events-none':
index === breadcrumbNodes.length - 1,
'hidden sm:flex': index !== breadcrumbNodes.length - 2,
index === activePage.tree.length - 1 || node.page.link === '#',
'hidden sm:flex': index !== lastActiveNodeIndex,
})}
>
{node.page.name}
Expand Down
Loading