mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-13 06:09:21 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52a61fd1e3 | |||
| 5abe43b451 | |||
| e82744d952 | |||
| 68bfc636b2 | |||
| c008cc7cc2 | |||
| a41fb53445 | |||
| 8f4085d392 | |||
| a4f81c28b0 |
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Update header styling of sections, variant selector, and button links
|
||||
|
||||
- Change position of variant selector depending on context (next to logo or in table of contents)
|
||||
- Update section tab styling and animation
|
||||
- Make header buttons smaller with a new `medium` button size
|
||||
@@ -50,16 +50,12 @@ export function CompactHeader(props: {
|
||||
'grow-0',
|
||||
'md:grow',
|
||||
'sm:max-w-xs',
|
||||
'lg:my-4',
|
||||
'lg:mt-2',
|
||||
'lg:mb-8',
|
||||
'lg:max-w-full',
|
||||
'justify-self-end',
|
||||
)}
|
||||
>
|
||||
{isMultiVariants ? (
|
||||
<div className={tcls('mb-2')}>
|
||||
<SpacesDropdown space={space} spaces={spaces} buttonKind="bordered" />
|
||||
</div>
|
||||
) : null}
|
||||
<React.Suspense fallback={null}>
|
||||
<SearchButton>
|
||||
<span className={tcls('flex-1')}>
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { CustomizationSettings, Site, SiteCustomizationSettings, Space } from '@gitbook/api';
|
||||
import {
|
||||
CustomizationSettings,
|
||||
Site,
|
||||
SiteCustomizationSettings,
|
||||
SiteSection,
|
||||
Space,
|
||||
} from '@gitbook/api';
|
||||
import { CustomizationHeaderPreset } from '@gitbook/api';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
@@ -13,6 +19,7 @@ import { HeaderLinks } from './HeaderLinks';
|
||||
import { HeaderLogo } from './HeaderLogo';
|
||||
import { SpacesDropdown } from './SpacesDropdown';
|
||||
import { SearchButton } from '../Search';
|
||||
import { SiteSectionTabs } from '../SiteSectionTabs';
|
||||
/**
|
||||
* Render the header for the space.
|
||||
*/
|
||||
@@ -20,14 +27,17 @@ export function Header(props: {
|
||||
space: Space;
|
||||
site: Site | null;
|
||||
spaces: Space[];
|
||||
sections: { list: SiteSection[]; section: SiteSection } | null;
|
||||
context: ContentRefContext;
|
||||
customization: CustomizationSettings | SiteCustomizationSettings;
|
||||
withTopHeader?: boolean;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
const { children, context, space, site, spaces, customization, withTopHeader } = props;
|
||||
const { children, context, space, site, spaces, sections, customization, withTopHeader } =
|
||||
props;
|
||||
const isCustomizationDefault =
|
||||
customization.header.preset === CustomizationHeaderPreset.Default;
|
||||
const hasSiteSections = sections && sections.list.length > 1;
|
||||
const isMultiVariants = site && spaces.length > 1;
|
||||
|
||||
return (
|
||||
@@ -70,7 +80,9 @@ export function Header(props: {
|
||||
>
|
||||
<HeaderLogo site={site} space={space} customization={customization} />
|
||||
<span>
|
||||
{isMultiVariants ? <SpacesDropdown space={space} spaces={spaces} /> : null}
|
||||
{!hasSiteSections && isMultiVariants ? (
|
||||
<SpacesDropdown space={space} spaces={spaces} />
|
||||
) : null}
|
||||
</span>
|
||||
<HeaderLinks>
|
||||
{customization.header.links.map((link, index) => {
|
||||
@@ -134,7 +146,17 @@ export function Header(props: {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
{sections ? (
|
||||
<div
|
||||
className={tcls(
|
||||
'w-full shadow-thintop dark:shadow-light/1 bg-light dark:bg-dark z-[9] mt-0.5',
|
||||
)}
|
||||
>
|
||||
<div className={tcls(CONTAINER_STYLE)}>
|
||||
<SiteSectionTabs sections={sections.list} section={sections.section} />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ export async function HeaderLink(props: {
|
||||
<Button
|
||||
href={target.href}
|
||||
variant={variant}
|
||||
size="medium"
|
||||
className={tcls(
|
||||
{
|
||||
'button-primary':
|
||||
|
||||
@@ -5,45 +5,37 @@ import { tcls } from '@/lib/tailwind';
|
||||
import { Dropdown, DropdownChevron, DropdownMenu } from './Dropdown';
|
||||
import { SpacesDropdownMenuItem } from './SpacesDropdownMenuItem';
|
||||
|
||||
export function SpacesDropdown(props: {
|
||||
space: Space;
|
||||
spaces: Space[];
|
||||
buttonKind?: 'default' | 'bordered';
|
||||
}) {
|
||||
const { space, spaces, buttonKind = 'default' } = props;
|
||||
export function SpacesDropdown(props: { space: Space; spaces: Space[] }) {
|
||||
const { space, spaces } = props;
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
className={buttonKind === 'bordered' ? tcls('w-full') : undefined}
|
||||
button={(buttonProps) => (
|
||||
<div
|
||||
{...buttonProps}
|
||||
data-testid="space-dropdown-button"
|
||||
className={tcls(
|
||||
'justify-self-start',
|
||||
'flex',
|
||||
'flex-row',
|
||||
'items-center',
|
||||
'rounded-2xl',
|
||||
'straight-corners:rounded-none',
|
||||
'bg-light-2',
|
||||
'border',
|
||||
'border-light-3',
|
||||
'text-dark-4',
|
||||
'text-sm',
|
||||
'px-3',
|
||||
'py-1.5',
|
||||
'text-header-link-500',
|
||||
buttonKind === 'bordered'
|
||||
? [
|
||||
'ring-1',
|
||||
'ring-inset',
|
||||
'ring-dark/2',
|
||||
'pointer-events-auto',
|
||||
'justify-between',
|
||||
'bg-light',
|
||||
'dark:bg-dark',
|
||||
'rounded-lg',
|
||||
'straight-corners:rounded-none',
|
||||
'lg:ring-0',
|
||||
'border',
|
||||
'border-dark/2',
|
||||
'dark:border-light/2',
|
||||
]
|
||||
: [],
|
||||
'py-1',
|
||||
'contrast-more:border-dark',
|
||||
'contrast-more:bg-light',
|
||||
'contrast-more:text-dark',
|
||||
'dark:bg-dark-3',
|
||||
'dark:border-dark-4',
|
||||
'dark:text-light-4',
|
||||
'contrast-more:dark:border-light',
|
||||
'contrast-more:dark:bg-dark',
|
||||
'contrast-more:dark:text-light',
|
||||
)}
|
||||
>
|
||||
{space.title}
|
||||
|
||||
@@ -69,9 +69,24 @@ export function SiteSectionTabs(props: { sections: SiteSection[]; section: SiteS
|
||||
>
|
||||
<div
|
||||
className={tcls(
|
||||
'relative flex gap-2 bg-transparent',
|
||||
'relative',
|
||||
'flex',
|
||||
'gap-2',
|
||||
'bg-transparent',
|
||||
/* add a pseudo element for active tab indicator */
|
||||
"after:block after:content-[''] after:origin-left after:absolute after:-bottom-px after:left-0 after:scale-x-[--tab-scale] after:transition-transform after:translate-x-[var(--tab-start)] after:h-0.5 after:w-[100px] after:bg-primary dark:after:bg-primary-500",
|
||||
'after:block',
|
||||
"after:content-['']",
|
||||
'after:origin-left',
|
||||
'after:absolute',
|
||||
'after:-bottom-px',
|
||||
'after:left-0',
|
||||
'after:scale-x-[--tab-scale]',
|
||||
'after:transition-transform',
|
||||
'after:translate-x-[var(--tab-start)]',
|
||||
'after:h-0.5',
|
||||
'after:w-[100px]',
|
||||
'after:bg-primary',
|
||||
'dark:after:bg-primary-400',
|
||||
)}
|
||||
role="tablist"
|
||||
>
|
||||
@@ -82,6 +97,7 @@ export function SiteSectionTabs(props: { sections: SiteSection[]; section: SiteS
|
||||
label={tab.label}
|
||||
href={tab.path}
|
||||
ref={currentIndex === index ? currentTabRef : null}
|
||||
onClick={() => setCurrentIndex(index)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -101,9 +117,9 @@ const Tab = React.forwardRef<
|
||||
return (
|
||||
<Link
|
||||
className={tcls(
|
||||
'px-3 py-3.5 rounded',
|
||||
active && 'text-primary dark:text-primary-500',
|
||||
!active && 'hover:text-primary dark:hover:text-primary-500 transition-colors',
|
||||
'px-3 py-1 my-2 rounded straight-corners:rounded-none transition-colors',
|
||||
active && 'text-primary dark:text-primary-400',
|
||||
!active && ' hover:bg-light-2 dark:hover:bg-dark-3',
|
||||
)}
|
||||
role="tab"
|
||||
href={href}
|
||||
|
||||
@@ -21,7 +21,7 @@ import { ContentTarget, SiteContentPointer } from '@/lib/api';
|
||||
import { ContentRefContext } from '@/lib/references';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { SiteSectionTabs } from '../SiteSectionTabs';
|
||||
import { SpacesDropdown } from '../Header/SpacesDropdown';
|
||||
|
||||
/**
|
||||
* Render the entire content of the space (header, table of contents, footer, and page content).
|
||||
@@ -61,6 +61,7 @@ export function SpaceLayout(props: {
|
||||
};
|
||||
|
||||
const withSections = Boolean(sections && sections.list.length > 0);
|
||||
const withVariants = Boolean(site && spaces.length > 1);
|
||||
const headerOffset = { sectionsHeader: withSections, topHeader: withTopHeader };
|
||||
|
||||
return (
|
||||
@@ -71,21 +72,10 @@ export function SpaceLayout(props: {
|
||||
space={space}
|
||||
site={site}
|
||||
spaces={spaces}
|
||||
sections={sections}
|
||||
context={contentRefContext}
|
||||
customization={customization}
|
||||
>
|
||||
{sections ? (
|
||||
<div
|
||||
className={tcls(
|
||||
'w-full shadow-thintop dark:shadow-light/1 bg-light dark:bg-dark z-[9] mt-0.5',
|
||||
)}
|
||||
>
|
||||
<div className={tcls(CONTAINER_STYLE)}>
|
||||
<SiteSectionTabs sections={sections.list} section={sections.section} />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</Header>
|
||||
/>
|
||||
<div className={tcls('scroll-nojump')}>
|
||||
<div
|
||||
className={tcls(
|
||||
@@ -116,6 +106,11 @@ export function SpaceLayout(props: {
|
||||
/>
|
||||
)
|
||||
}
|
||||
innerHeader={
|
||||
(sections || !withTopHeader) && withVariants ? (
|
||||
<SpacesDropdown space={space} spaces={spaces} />
|
||||
) : null
|
||||
}
|
||||
headerOffset={headerOffset}
|
||||
/>
|
||||
<div className={tcls('flex-1', 'flex', 'flex-col')}>{children}</div>
|
||||
|
||||
@@ -33,10 +33,12 @@ export function TableOfContents(props: {
|
||||
context: ContentRefContext;
|
||||
pages: Revision['pages'];
|
||||
ancestors: Array<RevisionPageDocument | RevisionPageGroup>;
|
||||
header?: React.ReactNode;
|
||||
header?: React.ReactNode; // Displayed outside the scrollable TOC as a sticky header
|
||||
headerOffset: { sectionsHeader: boolean; topHeader: boolean };
|
||||
innerHeader?: React.ReactNode; // Displayed inside the scrollable TOC, directly above the page list
|
||||
}) {
|
||||
const { space, customization, pages, ancestors, header, context, headerOffset } = props;
|
||||
const { innerHeader, space, customization, pages, ancestors, header, context, headerOffset } =
|
||||
props;
|
||||
|
||||
const withHeaderOffset = headerOffset.sectionsHeader || headerOffset.topHeader;
|
||||
const topOffset = getTopOffset(headerOffset);
|
||||
@@ -91,6 +93,7 @@ export function TableOfContents(props: {
|
||||
customization.trademark.enabled ? 'lg:pb-20' : 'lg:pb-4',
|
||||
)}
|
||||
>
|
||||
{innerHeader && <div className={tcls('ms-5', 'mb-4')}>{innerHeader}</div>}
|
||||
<PagesList
|
||||
rootPages={pages}
|
||||
pages={pages}
|
||||
|
||||
@@ -9,7 +9,7 @@ type ButtonProps = {
|
||||
onClick?: () => void;
|
||||
children: React.ReactNode;
|
||||
variant?: 'primary' | 'secondary';
|
||||
size?: 'default' | 'small';
|
||||
size?: 'default' | 'medium' | 'small';
|
||||
className?: ClassValue;
|
||||
};
|
||||
|
||||
@@ -43,12 +43,13 @@ export function Button({
|
||||
'dark:hover:bg-light/3',
|
||||
];
|
||||
|
||||
const sizeClasses =
|
||||
size === 'default'
|
||||
? // DEFAULT
|
||||
['text-base', 'px-4', 'py-2']
|
||||
: // SMALL
|
||||
['text-xs', 'px-3 py-2'];
|
||||
const sizes = {
|
||||
default: ['text-base', 'px-4', 'py-2'],
|
||||
medium: ['text-base', 'px-3', 'py-1'],
|
||||
small: ['text-xs', 'px-3 py-2'],
|
||||
};
|
||||
|
||||
const sizeClasses = sizes[size] || sizes.default;
|
||||
|
||||
const domClassName = tcls(
|
||||
'inline-block',
|
||||
@@ -60,6 +61,7 @@ export function Button({
|
||||
'grow-0',
|
||||
'shrink-0',
|
||||
'truncate',
|
||||
'transition-colors',
|
||||
variantClasses,
|
||||
sizeClasses,
|
||||
className,
|
||||
|
||||
Reference in New Issue
Block a user