Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion examples/inkform-docs/app/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export default async function Page({ params }: { params: Promise<{ slug?: string
const headings = extractHeadings(page.content);
const { prev, next } = docNeighbours(config, ref.slug);
const sidebar = sidebarForDoc(config, tab, ref.slug, renderIcon);
const topBar = buildTopBar(withContentNavLinks(config), tab.tab);
const pathname = (slug ?? []).length === 0 ? '/' : `/${(slug ?? []).join('/')}`;
const topBar = buildTopBar(withContentNavLinks(config), tab.tab, pathname);

return (
<DocsShell
Expand Down
2 changes: 1 addition & 1 deletion examples/inkform-docs/app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default async function BlogPostPage({ params }: { params: Promise<{ slug:
if (!post) notFound();

const config = loadDocsConfig();
const topBar = config ? buildTopBar(withContentNavLinks(config), '') : null;
const topBar = config ? buildTopBar(withContentNavLinks(config), '', `/blog/${slug}`) : null;

return (
<DocsShell contentType="blog" logo={topBar?.logo} topNav={topBar?.topNav} topActions={topBar?.topActions} cta={topBar?.cta} hideSidebar hideToc>
Expand Down
2 changes: 1 addition & 1 deletion examples/inkform-docs/app/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const metadata = { title: 'Blog' };
export default function BlogIndexPage() {
const config = loadDocsConfig();
const posts = loadBlogPosts();
const topBar = config ? buildTopBar(withContentNavLinks(config), '') : null;
const topBar = config ? buildTopBar(withContentNavLinks(config), '', '/blog') : null;

return (
<DocsShell contentType="blog" logo={topBar?.logo} topNav={topBar?.topNav} topActions={topBar?.topActions} cta={topBar?.cta} hideSidebar hideToc>
Expand Down
2 changes: 1 addition & 1 deletion examples/inkform-docs/app/changelog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const metadata = { title: 'Changelog' };
export default function ChangelogPage() {
const config = loadDocsConfig();
const entries = loadChangelogEntries();
const topBar = config ? buildTopBar(withContentNavLinks(config), '') : null;
const topBar = config ? buildTopBar(withContentNavLinks(config), '', '/changelog') : null;

return (
<DocsShell contentType="changelog" logo={topBar?.logo} topNav={topBar?.topNav} topActions={topBar?.topActions} cta={topBar?.cta} hideSidebar hideToc>
Expand Down
5 changes: 3 additions & 2 deletions examples/inkform-docs/app/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ html.dark {
row-gap: 0.625rem;
align-items: center;
padding-top: 0.875rem;
padding-bottom: 0.75rem;
padding-bottom: 0;
}

.fw-shell-header-start {
Expand Down Expand Up @@ -199,7 +199,8 @@ html.dark {
}

.fw-topnav-tab {
padding: 0.4rem 0.0625rem 0.6rem;
padding: 0.5rem 0.0625rem 0.75rem;
margin-bottom: -1px;
border: none;
border-bottom: 2px solid transparent;
border-radius: 0;
Expand Down
101 changes: 14 additions & 87 deletions examples/inkform-docs/components/top-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import type { ReactNode } from 'react';
import Link from 'next/link';
import { AskAi } from '@inkform/framework/ask-ai';
import { SearchDialog } from '@inkform/framework/search-dialog';
import { SecondaryTopNav } from '@inkform/framework/secondary-top-nav';
import type { DocsConfig } from '@inkform/framework';
import { docTabs } from '@inkform/framework';
import { loadDocsConfig } from '@inkform/framework/content';
import { apiBasePath } from '@/lib/route';

// ── Brand logo ────────────────────────────────────────────────────────────────
Expand All @@ -27,60 +26,6 @@ function Logo({ config }: { config: DocsConfig }) {
);
}

// ── Tab nav ───────────────────────────────────────────────────────────────────
// Rendered into the `topNav` slot, but pushed onto its own full-width row
// below the logo/search/assistant row by Galley's own theme.css (see the
// "header" section there) — verified on palm.mintlify.site: the tabs sit on
// a second line, left-aligned under the logo, underlined when active (no
// pill background). `.fw-topnav-tab`/`--active` classes are shared with
// every other theme; only the underline treatment is Galley-specific CSS.

function TabNav({ config, activeTab }: { config: DocsConfig; activeTab: string }) {
const tabs = docTabs(config);
const apiBase = apiBasePath(config);

return (
<div className="fw-topnav-tabs">
{tabs.map((t) => {
const href = t.openapi && apiBase ? `/${apiBase}` : '/';
const isActive = t.tab === activeTab;
return (
<a
key={t.tab}
href={href}
className={`fw-topnav-tab${isActive ? ' fw-topnav-tab--active' : ''}`}
aria-current={isActive ? 'page' : undefined}
>
{t.tab}
</a>
);
})}
</div>
);
}

// ── Anchor links ──────────────────────────────────────────────────────────────

function AnchorLinks({ config }: { config: DocsConfig }) {
const anchors = config.anchors ?? [];
if (anchors.length === 0) return null;
return (
<div className="fw-topnav-anchors">
{anchors.map((a) => (
<a
key={a.href}
href={a.href}
className="fw-topnav-anchor"
target={a.href.startsWith('http') ? '_blank' : undefined}
rel={a.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{a.name}
</a>
))}
</div>
);
}

// ── Top bar props ─────────────────────────────────────────────────────────────

export interface TopBarParts {
Expand All @@ -93,43 +38,25 @@ export interface TopBarParts {

/**
* Build the three DocsShell header slot contents for Galley.
* Server component — client islands (SearchDialog, AskAi) are already
* marked 'use client' in the framework.
*
* Notes, verified directly on palm.mintlify.site:
* - No `<ThemeToggle>` here — Palm's header carries no theme control at
* all; the theme switcher lives at the bottom of the sidebar instead
* (see components/sidebar-footer.tsx), wired into page.tsx via
* CollapsibleSidebar's `footer` slot.
* - The search trigger is wrapped in `.fw-galley-search-center` so it reads
* as centered in the header instead of flush right (CSS in theme.css).
* - `config.cta` is simply never set in this theme's docs.json — Palm's
* header has no CTA button at all. The render-if-present branch below is
* left in place (confirmed `buildTopBar` already no-ops with no `cta`)
* so a fork of this theme can still opt back in without touching this
* file.
* Server component — client islands (SearchDialog, AskAi, ScrollableTopNav)
* are already marked 'use client' in the framework.
*/
export function buildTopBar(config: DocsConfig, activeTab: string): TopBarParts {
export function buildTopBar(
config: DocsConfig,
activeTab: string,
pathname = '/',
): TopBarParts {
const aiEnabled = process.env.NEXT_PUBLIC_DOCS_AI_ENABLED === 'true';

const logo = <Logo config={config} />;

const topNav = (
<>
<TabNav config={config} activeTab={activeTab} />
<AnchorLinks config={config} />
{(config.navbarLinks ?? []).map((l) => (
<a
key={l.href}
href={l.href}
className="fw-topnav-navlink"
target={l.href.startsWith('http') ? '_blank' : undefined}
rel={l.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{l.name}
</a>
))}
</>
<SecondaryTopNav
config={config}
activeTab={activeTab}
pathname={pathname}
apiBase={apiBasePath(config)}
/>
);

const topActions = (
Expand Down
3 changes: 2 additions & 1 deletion examples/markdown-docs/app/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export default async function Page({ params }: { params: Promise<{ slug?: string
const headings = extractHeadings(page.content);
const { prev, next } = docNeighbours(config, ref.slug);
const sidebar = sidebarForDoc(config, tab, ref.slug, renderIcon);
const topBar = buildTopBar(withContentNavLinks(config), tab.tab);
const pathname = (slug ?? []).length === 0 ? '/' : `/${(slug ?? []).join('/')}`;
const topBar = buildTopBar(withContentNavLinks(config), tab.tab, pathname);

return (
<DocsShell
Expand Down
2 changes: 1 addition & 1 deletion examples/markdown-docs/app/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default async function BlogPostPage({ params }: { params: Promise<{ slug:
if (!post) notFound();

const config = loadDocsConfig();
const topBar = config ? buildTopBar(withContentNavLinks(config), '') : null;
const topBar = config ? buildTopBar(withContentNavLinks(config), '', `/blog/${slug}`) : null;

return (
<DocsShell contentType="blog" logo={topBar?.logo} topNav={topBar?.topNav} topActions={topBar?.topActions} cta={topBar?.cta} hideSidebar hideToc>
Expand Down
2 changes: 1 addition & 1 deletion examples/markdown-docs/app/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const metadata = { title: 'Blog' };
export default function BlogIndexPage() {
const config = loadDocsConfig();
const posts = loadBlogPosts();
const topBar = config ? buildTopBar(withContentNavLinks(config), '') : null;
const topBar = config ? buildTopBar(withContentNavLinks(config), '', '/blog') : null;

return (
<DocsShell contentType="blog" logo={topBar?.logo} topNav={topBar?.topNav} topActions={topBar?.topActions} cta={topBar?.cta} hideSidebar hideToc>
Expand Down
2 changes: 1 addition & 1 deletion examples/markdown-docs/app/changelog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const metadata = { title: 'Changelog' };
export default function ChangelogPage() {
const config = loadDocsConfig();
const entries = loadChangelogEntries();
const topBar = config ? buildTopBar(withContentNavLinks(config), '') : null;
const topBar = config ? buildTopBar(withContentNavLinks(config), '', '/changelog') : null;

return (
<DocsShell contentType="changelog" logo={topBar?.logo} topNav={topBar?.topNav} topActions={topBar?.topActions} cta={topBar?.cta} hideSidebar hideToc>
Expand Down
5 changes: 3 additions & 2 deletions examples/markdown-docs/app/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ html.dark {
row-gap: 0.625rem;
align-items: center;
padding-top: 0.875rem;
padding-bottom: 0.75rem;
padding-bottom: 0;
}

.fw-shell-header-start {
Expand Down Expand Up @@ -192,7 +192,8 @@ html.dark {
}

.fw-topnav-tab {
padding: 0.4rem 0.0625rem 0.6rem;
padding: 0.5rem 0.0625rem 0.75rem;
margin-bottom: -1px;
border: none;
border-bottom: 2px solid transparent;
border-radius: 0;
Expand Down
107 changes: 12 additions & 95 deletions examples/markdown-docs/components/top-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ import type { ReactNode } from 'react';
import Link from 'next/link';
import { AskAi } from '@inkform/framework/ask-ai';
import { SearchDialog } from '@inkform/framework/search-dialog';
import { SecondaryTopNav } from '@inkform/framework/secondary-top-nav';
import type { DocsConfig } from '@inkform/framework';
import { docTabs } from '@inkform/framework';
import { loadDocsConfig } from '@inkform/framework/content';
import { apiBasePath } from '@/lib/route';

// ── Brand logo ────────────────────────────────────────────────────────────────

function Logo({ config }: { config: DocsConfig }) {
const logo =
typeof config.logo === 'string'
Expand All @@ -27,109 +24,29 @@ function Logo({ config }: { config: DocsConfig }) {
);
}

// ── Tab nav ───────────────────────────────────────────────────────────────────
// Rendered into the `topNav` slot, but pushed onto its own full-width row
// below the logo/search/assistant row by Mono's own theme.css (see the
// "header" section there) — verified on palm.mintlify.site: the tabs sit on
// a second line, left-aligned under the logo, underlined when active (no
// pill background). `.fw-topnav-tab`/`--active` classes are shared with
// every other theme; only the underline treatment is Mono-specific CSS.

function TabNav({ config, activeTab }: { config: DocsConfig; activeTab: string }) {
const tabs = docTabs(config);
const apiBase = apiBasePath(config);

return (
<div className="fw-topnav-tabs">
{tabs.map((t) => {
const href = t.openapi && apiBase ? `/${apiBase}` : '/';
const isActive = t.tab === activeTab;
return (
<a
key={t.tab}
href={href}
className={`fw-topnav-tab${isActive ? ' fw-topnav-tab--active' : ''}`}
aria-current={isActive ? 'page' : undefined}
>
{t.tab}
</a>
);
})}
</div>
);
}

// ── Anchor links ──────────────────────────────────────────────────────────────

function AnchorLinks({ config }: { config: DocsConfig }) {
const anchors = config.anchors ?? [];
if (anchors.length === 0) return null;
return (
<div className="fw-topnav-anchors">
{anchors.map((a) => (
<a
key={a.href}
href={a.href}
className="fw-topnav-anchor"
target={a.href.startsWith('http') ? '_blank' : undefined}
rel={a.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{a.name}
</a>
))}
</div>
);
}

// ── Top bar props ─────────────────────────────────────────────────────────────

export interface TopBarParts {
logo: ReactNode;
topNav: ReactNode;
topActions: ReactNode;
/** Separate from topActions so DocsShell can duplicate just this into the mobile drawer without also duplicating the (stateful) search/AI widgets. */
cta: ReactNode;
}

/**
* Build the three DocsShell header slot contents for Mono.
* Server component — client islands (SearchDialog, AskAi) are already
* marked 'use client' in the framework.
*
* Notes, verified directly on palm.mintlify.site:
* - No `<ThemeToggle>` here — Palm's header carries no theme control at
* all; the theme switcher lives at the bottom of the sidebar instead
* (see components/sidebar-footer.tsx), wired into page.tsx via
* CollapsibleSidebar's `footer` slot.
* - The search trigger is wrapped in `.fw-mono-search-center` so it reads
* as centered in the header instead of flush right (CSS in theme.css).
* - `config.cta` is simply never set in this theme's docs.json — Palm's
* header has no CTA button at all. The render-if-present branch below is
* left in place (confirmed `buildTopBar` already no-ops with no `cta`)
* so a fork of this theme can still opt back in without touching this
* file.
*/
export function buildTopBar(config: DocsConfig, activeTab: string): TopBarParts {
export function buildTopBar(
config: DocsConfig,
activeTab: string,
pathname = '/',
): TopBarParts {
const aiEnabled = process.env.NEXT_PUBLIC_DOCS_AI_ENABLED === 'true';

const logo = <Logo config={config} />;

const topNav = (
<>
<TabNav config={config} activeTab={activeTab} />
<AnchorLinks config={config} />
{(config.navbarLinks ?? []).map((l) => (
<a
key={l.href}
href={l.href}
className="fw-topnav-navlink"
target={l.href.startsWith('http') ? '_blank' : undefined}
rel={l.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{l.name}
</a>
))}
</>
<SecondaryTopNav
config={config}
activeTab={activeTab}
pathname={pathname}
apiBase={apiBasePath(config)}
/>
);

const topActions = (
Expand Down
3 changes: 2 additions & 1 deletion examples/pokeapi-docs/app/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export default async function Page({ params }: { params: Promise<{ slug?: string
const headings = extractHeadings(page.content);
const { prev, next } = docNeighbours(config, ref.slug);
const sidebar = sidebarForDoc(config, tab, ref.slug, renderIcon);
const topBar = buildTopBar(withContentNavLinks(config), tab.tab);
const pathname = (slug ?? []).length === 0 ? '/' : `/${(slug ?? []).join('/')}`;
const topBar = buildTopBar(withContentNavLinks(config), tab.tab, pathname);

return (
<DocsShell
Expand Down
6 changes: 5 additions & 1 deletion examples/pokeapi-docs/app/api-reference/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ export default async function ApiReferencePage({ params }: { params: Promise<{ s

const { config, tab, model, tree } = ref;
const basePath = `/${apiBasePath(config)}`;
const topBar = buildTopBar(withContentNavLinks(config), tab.tab);
const topBar = buildTopBar(
withContentNavLinks(config),
tab.tab,
slug?.length ? `${basePath}/${slug.join('/')}` : basePath,
);

let content: ReactNode;
let activeOperationId: string | undefined;
Expand Down
Loading
Loading