diff --git a/.changeset/funky-actors-lie.md b/.changeset/funky-actors-lie.md new file mode 100644 index 000000000..1f437a4f0 --- /dev/null +++ b/.changeset/funky-actors-lie.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Align styling of PageLinkItem and PageDocumentItem diff --git a/.changeset/grumpy-dogs-cough.md b/.changeset/grumpy-dogs-cough.md new file mode 100644 index 000000000..8165924af --- /dev/null +++ b/.changeset/grumpy-dogs-cough.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Better conditional display of page actions diff --git a/.changeset/rare-ties-find.md b/.changeset/rare-ties-find.md new file mode 100644 index 000000000..3c7de54c2 --- /dev/null +++ b/.changeset/rare-ties-find.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Standardise toggle chevrons across the app diff --git a/packages/gitbook/e2e/internal.spec.ts b/packages/gitbook/e2e/internal.spec.ts index e3e1aa076..ad403bae4 100644 --- a/packages/gitbook/e2e/internal.spec.ts +++ b/packages/gitbook/e2e/internal.spec.ts @@ -273,10 +273,10 @@ const testCases: TestsCase[] = [ await expect(navigationLink).not.toBeVisible(); // Find and click the chevron element that is next to "Editor" in the TOC - // It is a span inside the link + // It is a button inside the link const editorChevron = page .getByRole('link', { name: 'Editor' }) - .locator('span'); + .locator('button'); await editorChevron.click(); // Verify "Navigation" link becomes visible after expansion @@ -295,10 +295,10 @@ const testCases: TestsCase[] = [ await expect(navigationLink).not.toBeVisible(); // Find and click the chevron element that is next to "Editor" in the TOC - // It is a span inside the link + // It is a button inside the link const editorChevron = page .getByRole('link', { name: 'Editor' }) - .locator('span'); + .locator('button'); await editorChevron.click(); // At this stage the link should still not be visible @@ -307,7 +307,7 @@ const testCases: TestsCase[] = [ // Then we click 'Content Structure' chevron to expand further const contentStructureChevron = page .getByRole('link', { name: 'Content Structure' }) - .locator('span'); + .locator('button'); await contentStructureChevron.click(); // Verify "Spaces" link becomes visible after expansion diff --git a/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlock.tsx b/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlock.tsx index b9d79be41..09219f191 100644 --- a/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlock.tsx +++ b/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlock.tsx @@ -6,7 +6,7 @@ import { useEffect, useId, useMemo, useRef, useState } from 'react'; import { useAdaptiveVisitor } from '@/components/Adaptive'; import { useInViewportListener } from '@/components/hooks/useInViewportListener'; import { useScrollListener } from '@/components/hooks/useScrollListener'; -import { Button } from '@/components/primitives'; +import { Button, ToggleChevron } from '@/components/primitives'; import { t, useLanguage } from '@/intl/client'; import { tcls } from '@/lib/tailwind'; import { useDebounceCallback } from 'usehooks-ts'; @@ -183,7 +183,7 @@ function CodeBlockExpandable(props: {
); diff --git a/packages/gitbook/src/components/Header/SpacesDropdown.tsx b/packages/gitbook/src/components/Header/SpacesDropdown.tsx index 4224ea26f..d4a628dbe 100644 --- a/packages/gitbook/src/components/Header/SpacesDropdown.tsx +++ b/packages/gitbook/src/components/Header/SpacesDropdown.tsx @@ -4,8 +4,8 @@ import { useMemo } from 'react'; import type { GitBookSiteContext } from '@/lib/context'; import { getSiteSpaceURL } from '@/lib/sites'; import { tcls } from '@/lib/tailwind'; -import { Button, type ButtonProps } from '../primitives'; -import { DropdownChevron, DropdownMenu } from '../primitives/DropdownMenu'; +import { Button, type ButtonProps, ToggleChevron } from '../primitives'; +import { DropdownMenu } from '../primitives/DropdownMenu'; import { SpacesDropdownMenuItems } from './SpacesDropdownMenuItem'; // Memoized regex for checking if a string starts with an emoji @@ -37,7 +37,7 @@ export function SpacesDropdown(props: { data-testid="space-dropdown-button" size="small" variant={variant} - trailing={} + trailing={} className={tcls('bg-tint-base', className)} > {siteSpace.title} diff --git a/packages/gitbook/src/components/PageActions/PageActionsDropdown.tsx b/packages/gitbook/src/components/PageActions/PageActionsDropdown.tsx index f2c66e641..2a041ea74 100644 --- a/packages/gitbook/src/components/PageActions/PageActionsDropdown.tsx +++ b/packages/gitbook/src/components/PageActions/PageActionsDropdown.tsx @@ -4,9 +4,9 @@ import { Button, ButtonGroup } from '@/components/primitives/Button'; import { DropdownMenu, DropdownMenuSeparator } from '@/components/primitives/DropdownMenu'; import { tString, useLanguage } from '@/intl/client'; import type { GitSyncState, SiteCustomizationSettings } from '@gitbook/api'; -import { Icon } from '@gitbook/icons'; import React, { useRef } from 'react'; import { useAI } from '../AI'; +import { ToggleChevron } from '../primitives'; import { ActionCopyMCPURL, ActionCopyMarkdown, @@ -51,18 +51,13 @@ export function PageActionsDropdown(props: PageActionsDropdownProps) { return defaultAction || dropdownActions.length > 0 ? ( {defaultAction} - {dropdownActions.length > 0 ? ( + {!defaultAction || dropdownActions.length > 1 ? ( - } + icon={} label={tString(language, defaultAction ? 'more' : 'actions')} iconOnly={!!defaultAction} size="xsmall" diff --git a/packages/gitbook/src/components/PageBody/PageHeader.tsx b/packages/gitbook/src/components/PageBody/PageHeader.tsx index a4cb9218c..81ee8e18b 100644 --- a/packages/gitbook/src/components/PageBody/PageHeader.tsx +++ b/packages/gitbook/src/components/PageBody/PageHeader.tsx @@ -28,6 +28,14 @@ export async function PageHeader(props: { const hasAncestors = ancestors.length > 0; + // Show page actions if *any* of the actions are enabled + const hasPageActions = [ + ...Object.values(context.customization.pageActions), + context.customization.pdf.enabled, + context.customization.git.showEditLink, + withRSSFeed, + ].some(Boolean); + return (
- {page.layout.tableOfContents ? ( - // Show page actions if *any* of the actions are enabled + {page.layout.tableOfContents && hasPageActions ? ( } + trailing={} /> } > @@ -123,7 +123,7 @@ function SearchScopeVariantControl(props: SearchScopeControlProps & { isExtended isExtended ? 'search_scope_variant_all' : 'search_scope_variant_current', spaceTitle ?? '' )} - trailing={} + trailing={} /> } > diff --git a/packages/gitbook/src/components/SiteSections/SiteSectionList.tsx b/packages/gitbook/src/components/SiteSections/SiteSectionList.tsx index 5254a7606..9dc149203 100644 --- a/packages/gitbook/src/components/SiteSections/SiteSectionList.tsx +++ b/packages/gitbook/src/components/SiteSections/SiteSectionList.tsx @@ -1,13 +1,13 @@ 'use client'; -import { Icon, type IconName } from '@gitbook/icons'; +import type { IconName } from '@gitbook/icons'; import { motion } from 'motion/react'; import React from 'react'; import { type ClassValue, tcls } from '@/lib/tailwind'; import { findSectionInGroup } from '@/lib/utils'; import { useToggleAnimation } from '../hooks'; -import { Link } from '../primitives'; +import { Link, ToggleChevron } from '../primitives'; import { ScrollContainer } from '../primitives/ScrollContainer'; import { SectionIcon } from './SectionIcon'; import type { @@ -199,21 +199,18 @@ export function SiteSectionGroupItem(props: { isActiveGroup && 'hover:bg-tint-hover' )} > - diff --git a/packages/gitbook/src/components/SiteSections/SiteSectionTabs.tsx b/packages/gitbook/src/components/SiteSections/SiteSectionTabs.tsx index 0e26edeac..3f98fe3cf 100644 --- a/packages/gitbook/src/components/SiteSections/SiteSectionTabs.tsx +++ b/packages/gitbook/src/components/SiteSections/SiteSectionTabs.tsx @@ -4,7 +4,7 @@ import type { IconName } from '@gitbook/icons'; import * as NavigationMenu from '@radix-ui/react-navigation-menu'; import React from 'react'; -import { Button, DropdownChevron, Link } from '@/components/primitives'; +import { Button, Link, ToggleChevron } from '@/components/primitives'; import { tcls } from '@/lib/tailwind'; import { findSectionInGroup } from '@/lib/utils'; import { useIsMobile } from '../hooks/useIsMobile'; @@ -188,7 +188,7 @@ const SectionTab = React.forwardRef(function SectionTab( {...rest} icon={icon ? : null} label={title} - trailing={isGroup ? : null} + trailing={isGroup ? : null} active={isActive} className={tcls( 'group/dropdown relative my-1.5 overflow-visible', diff --git a/packages/gitbook/src/components/TableOfContents/PageDocumentItem.tsx b/packages/gitbook/src/components/TableOfContents/PageDocumentItem.tsx index 13b398887..7bd05e409 100644 --- a/packages/gitbook/src/components/TableOfContents/PageDocumentItem.tsx +++ b/packages/gitbook/src/components/TableOfContents/PageDocumentItem.tsx @@ -38,14 +38,8 @@ export function PageDocumentItem(props: { page: ClientTOCPageDocument }) { ) : null } > - {page.emoji || page.icon ? ( - - - {page.title} - - ) : ( - page.title - )} + + {page.title} ); diff --git a/packages/gitbook/src/components/TableOfContents/PageLinkItem.tsx b/packages/gitbook/src/components/TableOfContents/PageLinkItem.tsx index bf14adf9d..04d14ef94 100644 --- a/packages/gitbook/src/components/TableOfContents/PageLinkItem.tsx +++ b/packages/gitbook/src/components/TableOfContents/PageLinkItem.tsx @@ -12,11 +12,13 @@ import { TOCPageIcon } from './TOCPageIcon'; export function PageLinkItem(props: { page: ClientTOCPageLink }) { const { page } = props; + const isExternal = page.target.kind === 'url'; + return (
  • {page.title} - path]:transition-opacity', - '[&>path]:opacity-[0.4]', - 'group-hover:[&>path]:opacity-11' - )} - /> + {isExternal ? ( + + ) : null}
  • ); diff --git a/packages/gitbook/src/components/TableOfContents/ToggleableLinkItem.tsx b/packages/gitbook/src/components/TableOfContents/ToggleableLinkItem.tsx index 4d222da04..ad132d6fd 100644 --- a/packages/gitbook/src/components/TableOfContents/ToggleableLinkItem.tsx +++ b/packages/gitbook/src/components/TableOfContents/ToggleableLinkItem.tsx @@ -1,13 +1,8 @@ 'use client'; - -import { Icon } from '@gitbook/icons'; import { AnimatePresence, motion } from 'motion/react'; import React, { useRef } from 'react'; - -import { tcls } from '@/lib/tailwind'; - import { useCurrentPagePath } from '../hooks'; -import { Link, type LinkInsightsProps, type LinkProps } from '../primitives'; +import { Button, Link, type LinkInsightsProps, type LinkProps, ToggleChevron } from '../primitives'; import { useScrollToActiveTOCItem } from './TOCScroller'; /** @@ -138,42 +133,28 @@ function Toggler(props: { isOpen: boolean; onToggle: () => void; }) { - const { isLinkActive, isOpen, onToggle } = props; + const { isOpen, onToggle } = props; return ( - + } onClick={(event) => { event.preventDefault(); event.stopPropagation(); onToggle(); }} - > - - + label={undefined} + size="xsmall" + iconOnly + variant="blank" + className="ml-auto text-current hover:bg-tint-base" + tabIndex={-1} // Prevent focus on the button since it's already inside a clickable link that performs the same toggle action. + /> ); } diff --git a/packages/gitbook/src/components/TableOfContents/styles.ts b/packages/gitbook/src/components/TableOfContents/styles.ts index 8f30c0f99..e50bc9e11 100644 --- a/packages/gitbook/src/components/TableOfContents/styles.ts +++ b/packages/gitbook/src/components/TableOfContents/styles.ts @@ -1,26 +1,6 @@ -export const PageLinkItemStyles = [ - 'flex', - 'justify-start', - 'items-center', - 'gap-3', - 'p-1.5', - 'pl-3', - 'text-sm', - 'transition-colors', - 'duration-100', - 'text-tint-strong/7', - 'rounded-md', - 'straight-corners:rounded-none', - 'circular-corners:rounded-xl', - 'before:content-none', - 'font-normal', - 'hover:bg-tint', - 'hover:text-tint-strong', -]; - export const ToggleableLinkItemStyles = [ 'group/toclink toclink relative transition-colors', - 'flex flex-row justify-between', + 'flex flex-row justify-start items-center gap-3', 'circular-corners:rounded-2xl rounded-md straight-corners:rounded-none p-1.5 pl-3', 'text-balance font-normal text-sm text-tint-strong/7 hover:bg-tint-hover hover:text-tint-strong contrast-more:text-tint-strong', 'contrast-more:hover:text-tint-strong contrast-more:hover:ring-1 contrast-more:hover:ring-tint-12', diff --git a/packages/gitbook/src/components/primitives/DropdownMenu.tsx b/packages/gitbook/src/components/primitives/DropdownMenu.tsx index 87160de36..130a1e138 100644 --- a/packages/gitbook/src/components/primitives/DropdownMenu.tsx +++ b/packages/gitbook/src/components/primitives/DropdownMenu.tsx @@ -10,6 +10,7 @@ import * as RadixDropdownMenu from '@radix-ui/react-dropdown-menu'; import { assert } from 'ts-essentials'; import { Link, type LinkInsightsProps } from '.'; +import { ToggleChevron } from './ToggleChevron'; export type DropdownButtonProps = Omit< Partial, E>>, @@ -99,26 +100,6 @@ export function DropdownMenu(props: { ); } -/** - * Animated chevron to display in the dropdown button. - */ -export function DropdownChevron() { - return ( - - ); -} - /** * Button with a chevron for use in dropdowns. */ @@ -131,7 +112,7 @@ export function DropdownButton(props: { return (
    {children} - +
    ); } diff --git a/packages/gitbook/src/components/primitives/StyleProvider.tsx b/packages/gitbook/src/components/primitives/StyleProvider.tsx index a1e1b1fe0..61909fd7d 100644 --- a/packages/gitbook/src/components/primitives/StyleProvider.tsx +++ b/packages/gitbook/src/components/primitives/StyleProvider.tsx @@ -3,7 +3,6 @@ import type { ClassValue } from '@/lib/tailwind'; import { RecordCardStyles } from '../DocumentView/Table/styles'; import { - PageLinkItemStyles, ToggleableLinkItemActiveStyles, ToggleableLinkItemStyles, } from '../TableOfContents/styles'; @@ -14,7 +13,6 @@ const styles = { CardStyles, ButtonStyles, RecordCardStyles, - PageLinkItemStyles, ToggleableLinkItemStyles, ToggleableLinkItemActiveStyles, }; diff --git a/packages/gitbook/src/components/primitives/ToggleChevron.tsx b/packages/gitbook/src/components/primitives/ToggleChevron.tsx new file mode 100644 index 000000000..0ef35f520 --- /dev/null +++ b/packages/gitbook/src/components/primitives/ToggleChevron.tsx @@ -0,0 +1,44 @@ +import { type ClassValue, tcls } from '@/lib/tailwind'; +import { Icon, type IconName } from '@gitbook/icons'; + +/** + * Animated chevron to display in dropdowns and other toggleable elements. + * Uses the `data-[state=open]` attribute to animate the chevron, or you can pass it an `open` prop to control the state. + */ +export function ToggleChevron(props: { + open?: boolean; + orientation?: 'down-to-up' | 'right-to-down'; // The direction of the chevron when open. + className?: ClassValue; +}) { + const { + open, + orientation = 'down-to-up', + className = 'opacity-6 group-hover/dropdown:opacity-11', + } = props; + + const classes = { + 'down-to-up': { + icon: 'chevron-down', + animation: 'rotate-180', + autoAnimation: 'group-data-[state=open]/dropdown:rotate-180 group-open:rotate-180', + }, + 'right-to-down': { + icon: 'chevron-right', + animation: 'rotate-90', + autoAnimation: 'group-data-[state=open]/dropdown:rotate-90 group-open:rotate-90', + }, + }; + + return ( + + ); +} diff --git a/packages/gitbook/src/components/primitives/index.ts b/packages/gitbook/src/components/primitives/index.ts index 74927a29b..6da921669 100644 --- a/packages/gitbook/src/components/primitives/index.ts +++ b/packages/gitbook/src/components/primitives/index.ts @@ -14,3 +14,4 @@ export * from './LoadingStateProvider'; export * from './HoverCard'; export * from './DropdownMenu'; export * from './Input'; +export * from './ToggleChevron';