Compare commits

...

8 Commits

Author SHA1 Message Date
Zeno Kapitein 52a61fd1e3 Fix variant dropdown for sites without variants 2024-10-22 17:12:35 +02:00
Zeno Kapitein 5abe43b451 Better variable, add comments 2024-10-22 16:49:11 +02:00
Zeno Kapitein e82744d952 Formatting 2024-10-22 14:29:27 +02:00
Zeno Kapitein 68bfc636b2 Add changeset 2024-10-22 14:29:00 +02:00
Zeno Kapitein c008cc7cc2 Add support for sidebar-only layout 2024-10-22 14:16:08 +02:00
Zeno Kapitein a41fb53445 Make header buttons smaller 2024-10-22 14:00:37 +02:00
Zeno Kapitein 8f4085d392 Update tab styling and animation 2024-10-22 13:59:37 +02:00
Zeno Kapitein a4f81c28b0 Change position of variant selector for sites with sections 2024-10-22 12:58:49 +02:00
9 changed files with 101 additions and 65 deletions
+9
View File
@@ -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,