Styling PR 1 (#16)

* add maxwidth limit for very large screens

* testing animation on hints

* page aside style on darkmode

* fix icon TOC

* Make scrollbars stable when overflowing

* fix scrollbar on search

* search compact header

* search style

* test without bg toc

* Commit

* setup before mobile TOC

* test if style{} clashes

* fix TOC hover

* add prevent default on search shortcut

* add deps

* Add header mobile menu setup

* work on fallback logo

* style search modal position

* style hamburger button

* padding

* add rudimental toggle

* comment

* gap value responsive

* remove slate

* fix z index

* undo test

* Install and format

---------

Co-authored-by: Samy Pessé <samypesse@gmail.com>
This commit is contained in:
Sebastian Graz
2023-11-29 21:18:47 +01:00
committed by GitHub
parent 0cdb5fdf61
commit bc6244bb58
21 changed files with 326 additions and 67 deletions
BIN
View File
Binary file not shown.
+4 -3
View File
@@ -14,8 +14,8 @@
},
"dependencies": {
"@geist-ui/icons": "^1.0.2",
"@gitbook/api": "^0.15.0",
"@radix-ui/react-popover": "^1.0.7",
"@gitbook/api": "^0.17.0",
"@readme/openapi-parser": "^2.5.0",
"@upstash/redis": "^1.25.1",
"ajv": "^8.12.0",
@@ -23,14 +23,15 @@
"bun-types": "^1.0.7",
"jsontoxml": "^1.0.1",
"katex": "^0.16.9",
"next": "^14.0.4-canary.25",
"next": "^14.0.3",
"react": "^18",
"react-dom": "^18",
"react-hotkeys-hook": "^4.4.1",
"recoil": "^0.7.7",
"server-only": "^0.0.1",
"shiki": "^0.14.5",
"tailwind-merge": "^1.14.0"
"tailwind-merge": "^1.14.0",
"tailwind-shades": "^1.1.2"
},
"devDependencies": {
"@cloudflare/next-on-pages": "^1.7.3",
+53
View File
@@ -31,10 +31,63 @@
p {
@apply text-dark/9 dark:text-light/8;
}
/* Light mode */
::-webkit-scrollbar {
@apply bg-transparent;
width: 8px;
}
::-webkit-scrollbar-thumb {
@apply bg-dark/4;
border-radius: 8px;
}
::-webkit-scrollbar-thumb:hover {
@apply bg-dark/5;
}
/* Dark mode */
.dark ::-webkit-scrollbar {
@apply bg-light/1;
}
.dark ::-webkit-scrollbar-thumb {
@apply bg-light/3;
}
.dark ::-webkit-scrollbar-thumb:hover {
@apply bg-light/4;
}
/* Inverted theme light mode */
.inverted-theme-scroll::-webkit-scrollbar {
@apply bg-transparent;
}
.inverted-theme-scroll::-webkit-scrollbar-thumb {
@apply bg-dark/5;
}
.inverted-theme-scroll::-webkit-scrollbar-thumb:hover {
@apply bg-dark/6;
}
}
@layer utilities {
.linear-mask-gradient {
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 96px, rgba(0, 0, 0, 0));
}
.grid-area-1-1 {
grid-area: 1/1;
}
.gutter-stable {
scrollbar-gutter: stable;
}
.textwrap-balance {
text-wrap: balance;
}
.textwrap-pretty {
text-wrap: pretty;
}
}
+2 -2
View File
@@ -28,8 +28,8 @@ export async function File(props: BlockProps<DocumentBlockFile>) {
'hover:border-slate-300',
'px-5',
'py-2',
'text-slate-500',
'hover:text-slate-700',
/* 'text-slate-500', */
/* 'hover:text-slate-700', */
style,
)}
>
+88 -11
View File
@@ -3,6 +3,7 @@ import IconAlertTriangle from '@geist-ui/icons/alertTriangle';
import IconCheckInCircle from '@geist-ui/icons/checkInCircle';
import IconInfo from '@geist-ui/icons/info';
import { DocumentBlockHint } from '@gitbook/api';
import React from 'react';
import { ClassValue, tcls } from '@/lib/tailwind';
@@ -14,6 +15,7 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
const { block, style, ancestorBlocks, ...contextProps } = props;
const hintStyle = HINT_STYLES[block.data.style];
const firstLine = getBlockTextStyle(block.nodes[0]);
const RenderIcon = HINT_STYLES[block.data.style].icon;
return (
<div
@@ -24,7 +26,11 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
'py-4',
'transition-colors',
'rounded',
'border-l-4',
'border-b-[1px]',
'dark:border-t-[1px]',
'dark:border-b-0',
/* 'shadow-1xs', */
hintStyle.style,
style,
)}
@@ -40,7 +46,9 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
hintStyle.iconStyle,
)}
>
<hintStyle.icon className={tcls('w-5', 'h-5')} />
{/* <hintStyle.icon className={tcls('w-5', 'h-5')} /> */}
<RenderIcon />
</div>
<Blocks
{...contextProps}
@@ -52,31 +60,100 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
);
}
const SuccessIcon = () => (
<div
className={tcls(
'flex',
'flex-col',
'w-[17px]',
'h-[17px]',
'bg-white',
'rounded-full',
'justify-center',
'items-center',
'dark:bg-light/3',
)}
>
<div
className={tcls(
'grid',
'justify-items-center',
'items-center',
'w-[5px]',
'h-[5px]',
'top-[-0.5px]',
'relative',
)}
>
<div
className={tcls(
'animate-[pingAlt_8s_ease_infinite_forwards]',
'bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-dark/2 from-0% via-dark/2 via-55% to-dark to-100%',
'grid-area-1-1',
'w-[5px]',
'h-[5px]',
'rounded',
'dark:from-light/2 dark:via-light/2 dark:to-light/8',
)}
></div>
<div
className={tcls(
'animate-[pulse_8s_ease_infinite]',
'grid-area-1-1',
'w-[3px]',
'h-[3px]',
'bg-dark/7',
'rounded',
'dark:bg-light/8',
)}
></div>
</div>
<div
className={tcls(
'animate-[pulse_8s_ease_infinite]',
'w-[3px]',
'h-[7px]',
'bg-dark/7',
'rounded',
'dark:bg-light/8',
)}
></div>
</div>
);
const WarningIcon = () => SuccessIcon();
const DangerIcon = () => SuccessIcon();
const InfoIcon = () => SuccessIcon();
const HINT_STYLES: {
[style in DocumentBlockHint['data']['style']]: {
icon: React.ComponentType<{ className?: string }>;
icon: React.ComponentType;
iconStyle: ClassValue;
style: ClassValue;
};
} = {
info: {
icon: IconInfo,
icon: SuccessIcon,
iconStyle: ['text-current'],
style: ['border-dark/2', 'bg-dark/2', 'dark:bg-light/1', 'dark:border-light/3'],
style: ['border-dark/1', 'bg-dark/2', 'dark:bg-light/1', 'dark:border-light/1'],
},
warning: {
icon: IconAlertCircle,
icon: WarningIcon,
iconStyle: ['text-yellow, dark:text-yellow/1, dark:text-yellow/8'],
style: ['border-yellow/4', 'bg-yellow', 'dark:bg-yellow/1'],
style: ['border-yellow/6', 'bg-yellow/6', 'dark:bg-yellow/1', 'dark:border-yellow/2'],
},
danger: {
icon: IconAlertTriangle,
icon: DangerIcon,
iconStyle: ['text-pomegranate'],
style: ['border-pomegranate/4', 'bg-pomegranate/2', 'dark:bg-pomegranate/1'],
style: [
'border-pomegranate/2',
'bg-pomegranate/2',
'dark:bg-pomegranate/1',
'dark:border-pomegranate/3',
],
},
success: {
icon: IconCheckInCircle,
icon: InfoIcon,
iconStyle: ['text-teal'],
style: ['border-teal/4', 'bg-teal/2', 'dark:bg-teal/2'],
style: ['border-teal/2', 'bg-teal/2', 'dark:bg-teal/2'],
},
};
+1 -1
View File
@@ -21,7 +21,7 @@ export function Images(props: BlockProps<DocumentBlockImages>) {
block.data.align === 'right' && 'justify-end',
block.data.align === 'left' && 'justify-start',
style,
block.data.fullWidth ? 'max-w-full' : null,
block.data.fullWidth ? 'max-w-screen-2xl' : null,
'justify-center',
)}
>
+16 -4
View File
@@ -1,6 +1,7 @@
import { Collection, CustomizationSettings, Space } from '@gitbook/api';
import React from 'react';
import { t } from '@/lib/intl';
import { tcls } from '@/lib/tailwind';
import { HeaderLogo } from './HeaderLogo';
@@ -18,8 +19,17 @@ export function CompactHeader(props: {
const { space, collection, customization } = props;
return (
<div className={tcls('flex', 'flex-row', 'items-center')}>
<div className={tcls('flex-1')}>
<div
className={tcls(
'flex',
'lg:flex-col',
'flex-wrap',
'gap-x-5',
'gap-y-3',
'justify-between',
)}
>
<div className={tcls('flex-grow-0')}>
<HeaderLogo
collection={collection}
space={space}
@@ -30,9 +40,11 @@ export function CompactHeader(props: {
]}
/>
</div>
<div className={tcls(['ms-2'])}>
<div className={tcls('flex-shrink-0', 'sm:grow', 'sm:max-w-xs', 'lg:max-w-full')}>
<React.Suspense fallback={null}>
<SearchButton />
<SearchButton style={[]}>
<span>{t({ space }, 'search')}</span>
</SearchButton>
</React.Suspense>
</div>
</div>
+2 -2
View File
@@ -91,8 +91,8 @@ export function Header(props: {
</div>
<div className={tcls('flex', 'basis-56', 'grow-0', 'shrink-0')}>
<Suspense fallback={null}>
<SearchButton style={['bg-header-background-300']}>
{t({ space }, 'search')}
<SearchButton>
<span>{t({ space }, 'search')}</span>
</SearchButton>
</Suspense>
</div>
+34 -16
View File
@@ -1,6 +1,7 @@
import { Collection, CustomizationSettings, Space } from '@gitbook/api';
import Link from 'next/link';
import { HeaderMobileMenu } from '@/components/Header/HeaderMobileMenu';
import { Image } from '@/components/utils';
import { absoluteHref } from '@/lib/links';
import { ClassValue, tcls } from '@/lib/tailwind';
@@ -20,21 +21,24 @@ export function HeaderLogo(props: HeaderLogoProps) {
const { customization } = props;
return (
<Link
href={absoluteHref('')}
className={tcls('group/headerlogo', 'flex', 'flex-row', 'items-center')}
>
{customization.header.logo ? (
<Image
alt="Logo"
src={customization.header.logo}
fetchPriority="high"
style={['max-w-50', 'h-8']}
/>
) : (
<LogoFallback {...props} />
)}
</Link>
<div className={tcls('flex', 'flex-row', 'gap-3')}>
<HeaderMobileMenu className={tcls('lg:hidden')} />
<Link
href={absoluteHref('')}
className={tcls('group/headerlogo', 'flex', 'flex-row', 'items-center')}
>
{customization.header.logo ? (
<Image
alt="Logo"
src={customization.header.logo}
fetchPriority="high"
style={['max-w-50', 'h-8']}
/>
) : (
<LogoFallback {...props} />
)}
</Link>
</div>
);
}
@@ -56,7 +60,21 @@ function LogoFallback(props: HeaderLogoProps) {
style={['w-8', 'h-8']}
/>
<h1 className={tcls('text-lg', 'text-slate-800', 'font-semibold', 'ms-3', textStyle)}>
<h1
className={tcls(
'textwrap-balance',
'flex',
'leading-tight',
'tracking-tight',
'max-w-[13ch]',
'md:max-w-none',
'md:text-lg/tight',
'text-slate-800',
'font-semibold',
'ms-3',
textStyle,
)}
>
{collection ? collection.title : space.title}
</h1>
</>
@@ -0,0 +1,33 @@
'use client';
import { IconMenu } from '@/components/icons/IconMenu';
import { tcls } from '@/lib/tailwind';
export const HeaderMobileMenu = (props: Partial<React.ButtonHTMLAttributes<HTMLButtonElement>>) => {
const toggleNavigation = () => {
if (document.body.classList.contains('navigation-visible')) {
document.body.classList.remove('navigation-visible');
} else {
document.body.classList.add('navigation-visible');
}
};
return (
<button {...props} onClick={toggleNavigation}>
<IconMenu
className={tcls(
'w-8',
'h-8',
'bg-dark/2',
'rounded',
'border',
'hover:bg-dark/3',
'border-dark/1',
'p-[0.25rem]',
'dark:bg-light/1',
'dark:border-light/1',
'dark:hover:bg-light/2',
)}
/>
</button>
);
};
@@ -25,11 +25,10 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
'flex',
'flex-row',
'text-sm',
section.depth > 1
? ['font-light', 'ps-3', 'py-1', 'text-light/5']
: ['py-2', 'text-light/7'],
'hover:text-primary',
section.depth > 1 ? ['ps-3', 'py-1', 'opacity-8'] : ['py-2'],
activeId === section.id ? 'text-primary' : 'hover:text-light/9',
activeId === section.id ? 'text-primary' : '',
)}
>
{section.depth > 1 ? (
+17 -9
View File
@@ -26,21 +26,29 @@ export function SearchButton(props: { children?: React.ReactNode; style?: ClassV
'flex',
'flex-1',
'flex-row',
'justify-center',
'items-center',
'px-4',
'py-1',
'rounded',
'dark:bg-vanta/4',
'hover:bg-light',
'px-2',
'min-h-[2.5rem]',
'w-[2.5rem]',
'rounded-lg',
'bg-dark/2',
'hover:bg-dark/3',
'border',
'border-dark/2',
'shadow-lg',
'dark:border-light/3',
'border-dark/1',
'dark:bg-light/1',
'dark:border-light/1',
'dark:hover:bg-light/2',
'[&>span]:hidden',
'sm:justify-between',
'sm:[&>span]:flex',
'sm:w-full',
'sm:px-4',
style,
)}
>
<IconSearch className={tcls('w-4', 'h-4', children ? 'me-2' : null)} />
{children}
<IconSearch className={tcls('w-4', 'h-4')} />
</button>
);
}
+5 -2
View File
@@ -20,7 +20,8 @@ export function SearchModal(props: SearchModalProps) {
useHotkeys(
'ctrl+k, command+k',
() => {
(e) => {
e.preventDefault(); //might be inadvisable as it interferes with expected browser behavior.
setQuery('');
},
[],
@@ -51,7 +52,9 @@ export function SearchModal(props: SearchModalProps) {
'dark:bg-dark/9',
'opacity-100',
'z-30',
'pt-24',
'px-4',
'pt-4',
'md:pt-[min(12vw,_10rem)]',
)}
onClick={onClose}
>
+1 -1
View File
@@ -100,7 +100,7 @@ export const SearchResults = React.forwardRef(function SearchResults(
}
return (
<div className={tcls('max-h-[60vh]', 'overflow-auto', 'px')}>
<div className={tcls('max-h-[60vh]', 'overflow-auto', 'px', 'inverted-theme-scroll')}>
{results.length === 0 ? (
<div className={tcls('text-sm', 'text-dark', 'p-6', 'text-center')}>
{noResultsMessage}
+2 -1
View File
@@ -76,7 +76,8 @@ export function SpaceContent(props: {
<div
className={tcls(
'flex',
'flex-row',
'flex-col',
'lg:flex-row',
CONTAINER_PADDING,
asFullWidth ? null : [CONTAINER_MAX_WIDTH_NORMAL, 'mx-auto'],
)}
@@ -35,9 +35,9 @@ export function PageDocumentItem(props: {
? [
'font-semibold',
'text-primary',
'bg-primary-500/3',
'hover:bg-primary/3',
'dark:text-primary-400',
'dark:hover:bg-primary-500/5',
'dark:hover:bg-primary-500/3',
]
: [
'font-normal',
+1 -1
View File
@@ -18,7 +18,7 @@ export function PagesList(props: {
const { rootPages, pages, activePage, ancestors, context, style } = props;
return (
<ul className={tcls('flex', 'flex-col', style)}>
<ul className={tcls('flex', 'flex-1', 'flex-col', style)}>
{pages.map((page) => {
if (page.type === 'group') {
return (
@@ -34,24 +34,34 @@ export function TableOfContents(
return (
<aside
className={tcls(
'hidden',
'lg:flex',
'flex',
'flex-col',
'basis-72',
'basis-full',
'bg-light',
'lg:basis-72',
'border-dark/2',
'navigation-visible:border-b',
'grow-0',
'shrink-0',
'sticky',
'z-[1]',
'dark:bg-dark',
'dark:border-light/1',
withHeaderOffset ? SIDE_COLUMN_WITH_HEADER : SIDE_COLUMN_WITHOUT_HEADER,
)}
>
{header ? <div className={tcls('pt-6', 'pb-3', 'pr-4')}>{header}</div> : null}
{header ? <div className={tcls('py-3')}>{header}</div> : null}
<div
className={tcls(
'hidden',
'lg:flex',
'flex-1',
'flex-grow',
'overflow-y-auto',
'gutter-stable',
'navigation-visible:flex', // can be auto height animated as such https://stackoverflow.com/a/76944290
header ? 'pt-3' : 'pt-6',
'pb-14',
'pr-4',
'pb-6',
)}
>
<PagesList
@@ -62,7 +72,8 @@ export function TableOfContents(
context={context}
/>
</div>
<Trademark space={space} />
{/* TODO: integrate trademark into mobile menu */}
{/* <Trademark space={space} /> */}
</aside>
);
}
@@ -30,6 +30,7 @@ export function ToggleableLinkItem(
{children}
<Chevron
className={tcls(
'flex-shrink-0',
'w-5',
'h-5',
'p-0.5',
+19
View File
@@ -0,0 +1,19 @@
import { SVGProps } from 'react';
export function IconMenu(props: Partial<SVGProps<SVGSVGElement>>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
preserveAspectRatio="xMidYMid meet"
{...props}
>
<path d="M3 12h18M3 6h18M3 18h18"></path>
</svg>
);
}
+24 -1
View File
@@ -1,4 +1,5 @@
import type { Config } from 'tailwindcss';
import plugin from 'tailwindcss/plugin';
export const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
export const opacities = [4, 8, 12, 16, 24, 40, 64, 72, 88, 96];
@@ -46,9 +47,31 @@ const config: Config = {
'header-background': generateShades('header-background'),
'header-link': generateShades('header-link'),
},
keyframes: {
pingAlt: {
'0%': {
transform: 'scale(0.5)',
opacity: '0',
},
'20%': {
opacity: '1',
},
'30%, 100%': {
transform: 'scale(2.5)',
opacity: '0',
},
},
},
boxShadow: {
'1xs': '0px 1.54px 1px rgba(0, 0, 0, 0.09), 0px 3.267px 2.754px rgb(0, 0, 0, 0.05), 0px 6.278px 6.63px rgb(0, 0, 0, 0.05), 0px 14px 22px rgb(0, 0, 0, 0.04)',
},
},
opacity: opacity(),
},
plugins: [],
plugins: [
plugin(function ({ addVariant }) {
addVariant('navigation-visible', 'body.navigation-visible &');
}),
],
};
export default config;