diff --git a/packages/gitbook/src/components/Header/Dropdown.tsx b/packages/gitbook/src/components/Header/Dropdown.tsx index 597522a49..598cf064f 100644 --- a/packages/gitbook/src/components/Header/Dropdown.tsx +++ b/packages/gitbook/src/components/Header/Dropdown.tsx @@ -82,11 +82,12 @@ export function Dropdown(props: { /** * Animated chevron to display in the dropdown button. */ -export function DropdownChevron(props: {}) { +export function DropdownChevron() { return ( & { + to: CustomizationHeaderLink['to'] | null; +}; + import { Dropdown, DropdownButtonProps, @@ -21,93 +26,40 @@ import { Button, Link } from '../primitives'; export async function HeaderLink(props: { context: ContentRefContext; - link: CustomizationHeaderLink; + link: CustomizationHeaderItem; customization: CustomizationSettings | SiteCustomizationSettings; }) { const { context, link, customization } = props; - const target = await resolveContentRef(link.to, context); - - if (!target) { - return null; - } - + const target = link.to ? await resolveContentRef(link.to, context) : null; const headerPreset = customization.header.preset; - - const renderLink = (linkProps: DropdownButtonProps) => { - const linkStyle = link.style ?? 'link'; - - switch (linkStyle) { - case 'button-secondary': - case 'button-primary': { - const variant = (() => { - switch (linkStyle) { - case 'button-secondary': - return 'secondary'; - case 'button-primary': - return 'primary'; - default: - assertNever(linkStyle); - } - })(); - return ( - - ); - } - case 'link': { - return ( - - {link.title} - {link.links && link.links.length > 0 ? : null} - - ); - } - default: - assertNever(linkStyle); - } - }; + const linkStyle = link.style ?? 'link'; if (link.links && link.links.length > 0) { return ( - + { + if (!target) { + return ( + + ); + } + return ( + + ); + }} + > {link.links.map((subLink, index) => ( @@ -117,7 +69,128 @@ export async function HeaderLink(props: { ); } - return renderLink({}); + if (!target) { + return null; + } + + return ( + + ); +} + +export type HeaderLinkNavItemProps = { + linkStyle: NonNullable; + headerPreset: CustomizationHeaderPreset; + title: string; + href: string; + isDropdown: boolean; +} & DropdownButtonProps; + +function HeaderLinkNavItem(props: HeaderLinkNavItemProps) { + switch (props.linkStyle) { + case 'button-secondary': + case 'button-primary': + return ; + case 'link': + return ; + default: + assertNever(props.linkStyle); + } +} + +function HeaderItemButton( + props: Omit & { + linkStyle: 'button-secondary' | 'button-primary'; + }, +) { + const { linkStyle, headerPreset, title, href, ...rest } = props; + const variant = (() => { + switch (linkStyle) { + case 'button-secondary': + return 'secondary'; + case 'button-primary': + return 'primary'; + default: + assertNever(linkStyle); + } + })(); + return ( + + ); +} + +function getHeaderLinkClassName(props: { headerPreset: CustomizationHeaderPreset }) { + return tcls( + 'overflow-hidden', + 'text-sm lg:text-base', + 'flex flex-row items-center', + 'whitespace-nowrap', + 'hover:text-header-link-400 dark:hover:text-light', + 'truncate', + + props.headerPreset === CustomizationHeaderPreset.Default + ? ['text-dark/8', 'dark:text-light/8'] + : ['text-header-link-500 hover:text-header-link-400'], + ); +} + +function HeaderItemLink(props: HeaderLinkNavItemProps) { + const { headerPreset, title, isDropdown, href, ...rest } = props; + return ( + + {title} + {isDropdown ? : null} + + ); +} + +function HeaderItemDropdown( + props: { + headerPreset: CustomizationHeaderPreset; + title: string; + } & DropdownButtonProps, +) { + const { headerPreset, title, ...rest } = props; + return ( + + {title} + + + ); } async function SubHeaderLink(props: { diff --git a/packages/gitbook/src/components/Header/HeaderLinkMore.tsx b/packages/gitbook/src/components/Header/HeaderLinkMore.tsx index 9eb98a313..b7010b7de 100644 --- a/packages/gitbook/src/components/Header/HeaderLinkMore.tsx +++ b/packages/gitbook/src/components/Header/HeaderLinkMore.tsx @@ -13,12 +13,17 @@ import { tcls } from '@/lib/tailwind'; import { Dropdown, DropdownMenu, DropdownMenuItem } from './Dropdown'; import styles from './headerLinks.module.css'; +// @TODO replace by api.CustomizationHeaderItem when available +type CustomizationHeaderItem = Omit & { + to: CustomizationHeaderLink['to'] | null; +}; + /** * Dropdown menu for header links hidden at small screen size. */ export function HeaderLinkMore(props: { label: React.ReactNode; - links: CustomizationHeaderLink[]; + links: CustomizationHeaderItem[]; context: ContentRefContext; customization: CustomizationSettings | SiteCustomizationSettings; }) { @@ -55,10 +60,10 @@ export function HeaderLinkMore(props: { ); } -async function MoreMenuLink(props: { context: ContentRefContext; link: CustomizationHeaderLink }) { +async function MoreMenuLink(props: { context: ContentRefContext; link: CustomizationHeaderItem }) { const { context, link } = props; - const target = await resolveContentRef(link.to, context); + const target = link.to ? await resolveContentRef(link.to, context) : null; if (!target) { return null; diff --git a/packages/gitbook/src/components/primitives/Button.tsx b/packages/gitbook/src/components/primitives/Button.tsx index dc60bf840..af95cf86c 100644 --- a/packages/gitbook/src/components/primitives/Button.tsx +++ b/packages/gitbook/src/components/primitives/Button.tsx @@ -1,17 +1,17 @@ 'use client'; +import type { ComponentPropsWithoutRef, HTMLAttributes } from 'react'; + import { tcls, ClassValue } from '@/lib/tailwind'; import { Link } from './Link'; type ButtonProps = { href?: string; - onClick?: () => void; - children: React.ReactNode; variant?: 'primary' | 'secondary'; size?: 'default' | 'medium' | 'small'; className?: ClassValue; -}; +} & HTMLAttributes; export function Button({ href, @@ -20,6 +20,7 @@ export function Button({ variant = 'primary', size = 'default', className, + ...rest }: ButtonProps) { const variantClasses = variant === 'primary' @@ -69,14 +70,14 @@ export function Button({ if (href) { return ( - + {children} ); } return ( - );