mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 23:55:20 +00:00
Style PR (#84)
* fixes for table values * color tweaks rating * inc legibility on aside active * add tab separators * fix hint icon * fix header colors & remove custom balance * Better mobile burger colors * remove border from logofallback img * style divider dark mode * compact header + file + expandable hover + mt mobile nav * testing file types
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
@apply gutter-stable;
|
||||
}
|
||||
h1 {
|
||||
@apply tracking-[-0.025em] text-dark dark:text-light textwrap-balance;
|
||||
@apply tracking-[-0.025em] text-dark dark:text-light text-balance;
|
||||
}
|
||||
h2,
|
||||
h3,
|
||||
@@ -101,12 +101,6 @@
|
||||
.gutter-stable {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
.textwrap-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
.textwrap-pretty {
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.triangle {
|
||||
position: relative;
|
||||
|
||||
@@ -54,7 +54,7 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
|
||||
'right-4',
|
||||
'left-16',
|
||||
'max-w-md',
|
||||
'textwrap-balance',
|
||||
'text-balance',
|
||||
'sm:left-auto',
|
||||
'dark:ring-light/2',
|
||||
'dark:bg-dark',
|
||||
|
||||
@@ -7,5 +7,5 @@ import { BlockProps } from './Block';
|
||||
export function Divider(props: BlockProps<DocumentBlockDivider>) {
|
||||
const { style } = props;
|
||||
|
||||
return <hr className={tcls(style)} />;
|
||||
return <hr className={tcls(style, 'border-dark/2', 'dark:border-light/2')} />;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export function Expandable(props: BlockProps<DocumentBlockExpandable>) {
|
||||
<details
|
||||
className={tcls(
|
||||
style,
|
||||
|
||||
'group/expandable',
|
||||
'shadow-dark/1',
|
||||
'bg-gradient-to-t',
|
||||
'from-light-1',
|
||||
@@ -31,7 +31,6 @@ export function Expandable(props: BlockProps<DocumentBlockExpandable>) {
|
||||
'border',
|
||||
'border-b-0',
|
||||
'border-light-3/8',
|
||||
'transition-color',
|
||||
//all
|
||||
'[&]:mt-[0px]',
|
||||
//select first child
|
||||
@@ -63,8 +62,10 @@ export function Expandable(props: BlockProps<DocumentBlockExpandable>) {
|
||||
'list-none',
|
||||
'select-none',
|
||||
'transition-colors',
|
||||
'group-hover/expandable:text-dark-5/7',
|
||||
'group-open:text-dark-5/7',
|
||||
'dark:group-open:text-light-5',
|
||||
'dark:group-hover/expandable:text-light-5',
|
||||
'[&::-webkit-details-marker]:hidden',
|
||||
)}
|
||||
>
|
||||
@@ -74,8 +75,9 @@ export function Expandable(props: BlockProps<DocumentBlockExpandable>) {
|
||||
'w-4',
|
||||
'h-4',
|
||||
'mr-2',
|
||||
'mb-1',
|
||||
'transition-transform',
|
||||
'shrink-0',
|
||||
'transition-all',
|
||||
'group-open:rotate-90',
|
||||
)}
|
||||
/>
|
||||
@@ -86,7 +88,7 @@ export function Expandable(props: BlockProps<DocumentBlockExpandable>) {
|
||||
document={document}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
context={context}
|
||||
style={['px-10', 'pb-3', 'space-y-3']}
|
||||
style={['px-10', 'pb-5', 'space-y-3']}
|
||||
/>
|
||||
</details>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import IconDownload from '@geist-ui/icons/download';
|
||||
import IconFileText from '@geist-ui/icons/fileText';
|
||||
import IconImage from '@geist-ui/icons/image';
|
||||
import IconPaperClip from '@geist-ui/icons/paperclip';
|
||||
import { DocumentBlockFile } from '@gitbook/api';
|
||||
|
||||
import { getRevisionFile } from '@/lib/api';
|
||||
@@ -16,31 +19,107 @@ export async function File(props: BlockProps<DocumentBlockFile>) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const contentType = (() => {
|
||||
switch (file.contentType) {
|
||||
case 'application/pdf':
|
||||
case 'application/x-pdf':
|
||||
return 'pdf';
|
||||
case 'image/png':
|
||||
case 'image/jpeg':
|
||||
case 'image/gif':
|
||||
case 'image/webp':
|
||||
case 'image/tiff':
|
||||
case 'image/svg+xml':
|
||||
return 'image';
|
||||
case 'application/zip':
|
||||
case 'application/x-7z-compressed':
|
||||
case 'application/x-zip-compressed':
|
||||
case 'application/x-tar':
|
||||
case 'application/x-rar-compressed':
|
||||
case 'application/vnd.rar':
|
||||
return 'archive';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
const icon = (() => {
|
||||
switch (contentType) {
|
||||
case 'pdf':
|
||||
return <IconFileText />;
|
||||
case 'image':
|
||||
return <IconImage />;
|
||||
case 'archive':
|
||||
return <IconPaperClip />;
|
||||
default:
|
||||
return <IconDownload />;
|
||||
}
|
||||
})();
|
||||
|
||||
return (
|
||||
<a
|
||||
href={file.downloadURL}
|
||||
download={file.name}
|
||||
className={tcls(
|
||||
'group/file',
|
||||
'flex',
|
||||
'flex-row',
|
||||
'items-center',
|
||||
'rounded',
|
||||
'border',
|
||||
'border-b-0',
|
||||
'px-5',
|
||||
'py-2',
|
||||
'py-3',
|
||||
'border-dark/3',
|
||||
'hover:border-dark/5',
|
||||
|
||||
//all
|
||||
'[&]:mt-[0px]',
|
||||
//select first child
|
||||
'[&:first-child]:mt-5',
|
||||
'[&:first-child]:rounded-t-lg',
|
||||
//select first in group
|
||||
'[:not(&)_+&]:mt-5',
|
||||
'[:not(&)_+&]:rounded-t-lg',
|
||||
//select last in group
|
||||
'[&:not(:has(+_&))]:mb-5',
|
||||
'[&:not(:has(+_&))]:rounded-b-lg',
|
||||
'[&:not(:has(+_&))]:border-b',
|
||||
|
||||
'hover:text-primary-600',
|
||||
'dark:border-light/3',
|
||||
'dark:hover:border-light/4',
|
||||
'dark:hover:text-primary-300',
|
||||
style,
|
||||
)}
|
||||
>
|
||||
<div className={tcls('mr-5')}>
|
||||
<IconDownload className={tcls('w-6', 'h-6')} />
|
||||
<div
|
||||
className={tcls(
|
||||
'min-w-14',
|
||||
'mr-5',
|
||||
'pr-5',
|
||||
'flex',
|
||||
'flex-col',
|
||||
'items-center',
|
||||
'gap-1',
|
||||
'border-r',
|
||||
'border-dark/2',
|
||||
'dark:border-light/2',
|
||||
)}
|
||||
>
|
||||
<div className={tcls('*:w-5', '*:h-5', '*:stroke-primary')}>{icon}</div>
|
||||
<div
|
||||
className={tcls(
|
||||
'text-xs',
|
||||
'text-dark-5/8',
|
||||
'group-hover/file:text-dark',
|
||||
'dark:text-light-4/7',
|
||||
'dark:group-hover/file:text-light',
|
||||
)}
|
||||
>
|
||||
{getHumanFileSize(file.size)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className={tcls('text-base')}>{file.name}</div>
|
||||
<div className={tcls('text-xs')}>{getHumanFileSize(file.size)}</div>
|
||||
<div className={tcls('text-sm', 'opacity-9', 'dark:opacity-8')}>{contentType}</div>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
|
||||
@@ -41,22 +41,40 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
|
||||
);
|
||||
case 'rating':
|
||||
const rating = value as number;
|
||||
const max = definition.max;
|
||||
|
||||
return (
|
||||
<Tag className={tcls('text-primary')}>
|
||||
<Tag className={tcls('inline-grid')}>
|
||||
{value ? (
|
||||
<span
|
||||
role="meter"
|
||||
aria-label={definition.title ?? ''}
|
||||
aria-valuenow={rating}
|
||||
aria-valuemin={1}
|
||||
aria-valuemax={definition.max}
|
||||
className={tcls('inline-flex', 'gap-1')}
|
||||
>
|
||||
{Array.from({ length: rating }).map((_, i) => (
|
||||
<IconStar key={i} className={tcls('size-4')} />
|
||||
))}
|
||||
</span>
|
||||
<>
|
||||
<span className={tcls('inline-flex', 'grid-area-1-1', 'gap-0.5')}>
|
||||
{Array.from({ length: max }).map((_, i) => (
|
||||
<IconStar
|
||||
key={i}
|
||||
className={tcls(
|
||||
'size-[15px]',
|
||||
'stroke-primary-700/5',
|
||||
'dark:stroke-primary-300/5',
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
<span
|
||||
role="meter"
|
||||
aria-label={definition.title ?? ''}
|
||||
aria-valuenow={rating}
|
||||
aria-valuemin={1}
|
||||
aria-valuemax={definition.max}
|
||||
className={tcls('inline-flex', 'grid-area-1-1', 'gap-0.5')}
|
||||
>
|
||||
{Array.from({ length: rating }).map((_, i) => (
|
||||
<IconStar
|
||||
key={i}
|
||||
className={tcls('size-[15px]', 'stroke-primary')}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
</>
|
||||
) : null}
|
||||
</Tag>
|
||||
);
|
||||
@@ -106,7 +124,7 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
|
||||
case 'content-ref': {
|
||||
const resolved = value ? await context.resolveContentRef(value as ContentRef) : null;
|
||||
return (
|
||||
<Tag className={tcls('text-base')}>
|
||||
<Tag className={tcls('text-base', 'text-balance')}>
|
||||
{resolved ? <Link href={resolved.href}>{resolved.text}</Link> : null}
|
||||
</Tag>
|
||||
);
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function RecordRow(
|
||||
'dark:border-light/2',
|
||||
)}
|
||||
>
|
||||
<div className={tcls('pr-4 ', 'textwrap-balance')}>
|
||||
<div className={tcls('pr-4 ', 'text-balance')}>
|
||||
<RecordColumnValue key={column} {...props} column={column} />
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -151,7 +151,7 @@ export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
|
||||
className={tcls(
|
||||
tableTH,
|
||||
'align-baseline',
|
||||
'textwrap-balance',
|
||||
'text-balance',
|
||||
'border-b',
|
||||
'border-b-dark/4',
|
||||
'text-left',
|
||||
|
||||
@@ -31,7 +31,17 @@ export function DynamicTabs(props: {
|
||||
style,
|
||||
)}
|
||||
>
|
||||
<div role="tablist" className={tcls('flex', 'flex-row', 'p-4', 'space-x-1.5')}>
|
||||
<div
|
||||
role="tablist"
|
||||
className={tcls(
|
||||
'group/tabs',
|
||||
'inline-flex',
|
||||
'flex-row',
|
||||
'p-4',
|
||||
'self-start',
|
||||
'space-x-1.5',
|
||||
)}
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
@@ -51,14 +61,33 @@ export function DynamicTabs(props: {
|
||||
'transition-colors',
|
||||
'font-semibold',
|
||||
'text-dark-4',
|
||||
|
||||
'after:transition-colors',
|
||||
'after:group-hover/tabs:border-transparent',
|
||||
'after:border-r',
|
||||
|
||||
'after:border-dark/4',
|
||||
'after:left-4',
|
||||
'after:relative',
|
||||
|
||||
'last:after:border-transparent',
|
||||
'hover:bg-light-2',
|
||||
|
||||
'dark:text-light-5',
|
||||
'dark:hover:bg-dark-2',
|
||||
'dark:hover:text-light-4',
|
||||
'dark:after:border-light/2',
|
||||
|
||||
active === tab.id
|
||||
? [
|
||||
'active-tab',
|
||||
'text-dark-4',
|
||||
'bg-light-3',
|
||||
|
||||
'after:[&.active-tab]:border-transparent',
|
||||
'after:[:has(+_&.active-tab)]:border-transparent',
|
||||
'after:[:has(&_+)]:border-transparent',
|
||||
|
||||
'hover:bg-light-4',
|
||||
'dark:bg-dark-4',
|
||||
'dark:text-light',
|
||||
|
||||
@@ -23,6 +23,7 @@ export function CompactHeader(props: {
|
||||
<div
|
||||
className={tcls(
|
||||
'hidden',
|
||||
'pr-4',
|
||||
'lg:flex',
|
||||
'lg:flex-col',
|
||||
'flex-wrap',
|
||||
|
||||
@@ -93,16 +93,21 @@ export function Header(props: {
|
||||
style={
|
||||
!isCustomizationDefault && withTopHeader
|
||||
? [
|
||||
'bg-header-background-400/6',
|
||||
'text-header-background-200',
|
||||
'ring-light/2',
|
||||
'[&>span]:text-header-background-200',
|
||||
'[&_svg]:stroke-header-background-200',
|
||||
'bg-header-background-400/8',
|
||||
'shadow-sm',
|
||||
'text-header-link/8',
|
||||
'ring-header-background-200/5',
|
||||
'[&>span]:text-header-link/7',
|
||||
'[&_svg]:stroke-header-link',
|
||||
'dark:bg-header-link-600/3',
|
||||
'dark:ring-header-link-600/2',
|
||||
'dark:text-header-link/7',
|
||||
'dark:[&_svg]:stroke-header-link/7',
|
||||
]
|
||||
: null
|
||||
}
|
||||
>
|
||||
<span>{t(getSpaceLanguage(customization), 'search')}</span>
|
||||
<p>{t(getSpaceLanguage(customization), 'search')}</p>
|
||||
</SearchButton>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Collection, CustomizationSettings, Space } from '@gitbook/api';
|
||||
import { Collection, CustomizationHeaderPreset, CustomizationSettings, Space } from '@gitbook/api';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { HeaderMobileMenu } from '@/components/Header/HeaderMobileMenu';
|
||||
@@ -17,12 +17,19 @@ interface HeaderLogoProps {
|
||||
/**
|
||||
* Render the logo for a space using the customization settings.
|
||||
*/
|
||||
|
||||
export function HeaderLogo(props: HeaderLogoProps) {
|
||||
const { customization } = props;
|
||||
|
||||
const isCustomizationDefault =
|
||||
customization.header.preset === CustomizationHeaderPreset.Default;
|
||||
return (
|
||||
<div className={tcls('flex', 'flex-row', 'gap-3')}>
|
||||
<HeaderMobileMenu className={tcls('lg:hidden')} />
|
||||
<HeaderMobileMenu
|
||||
className={tcls(
|
||||
'lg:hidden',
|
||||
isCustomizationDefault ? ['text-dark', 'dark:text-light'] : 'text-header-link',
|
||||
)}
|
||||
/>
|
||||
<Link
|
||||
href={absoluteHref('')}
|
||||
className={tcls('group/headerlogo', 'flex', 'flex-row', 'items-center', 'shrink-0')}
|
||||
@@ -104,12 +111,12 @@ function LogoFallback(props: HeaderLogoProps) {
|
||||
}
|
||||
sizes={[]}
|
||||
fetchPriority="high"
|
||||
style={['w-8', 'h-8', 'rounded', 'overflow-hidden', 'border', 'border-dark/2']}
|
||||
style={['w-8', 'h-8']}
|
||||
/>
|
||||
|
||||
<h1
|
||||
className={tcls(
|
||||
'textwrap-balance',
|
||||
'text-balance',
|
||||
'flex',
|
||||
'leading-tight',
|
||||
'tracking-tight',
|
||||
|
||||
@@ -43,9 +43,10 @@ export function HeaderMobileMenu(props: Partial<React.ButtonHTMLAttributes<HTMLB
|
||||
>
|
||||
<IconMenu
|
||||
className={tcls(
|
||||
'w-8',
|
||||
'h-8',
|
||||
'w-7',
|
||||
'h-7',
|
||||
'rounded',
|
||||
'text-inherit',
|
||||
'hover:bg-dark/3',
|
||||
'p-[0.25rem]',
|
||||
'dark:hover:bg-light/2',
|
||||
|
||||
@@ -43,8 +43,7 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
|
||||
'py-1',
|
||||
'ps-3',
|
||||
'hover:text-primary',
|
||||
'transition-colors',
|
||||
'duration-200',
|
||||
'transition-all',
|
||||
'border-l',
|
||||
'border-transparent',
|
||||
section.depth > 1 ? ['ps-6', 'opacity-8'] : null,
|
||||
@@ -54,6 +53,7 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
|
||||
'border-primary',
|
||||
'dark:text-primary-400',
|
||||
'dark:border-primary-400',
|
||||
'opacity-[1]',
|
||||
]
|
||||
: '',
|
||||
)}
|
||||
|
||||
@@ -52,8 +52,10 @@ export function SearchButton(props: { children?: React.ReactNode; style?: ClassV
|
||||
'dark:bg-light/1',
|
||||
'dark:ring-light/1',
|
||||
'dark:text-light/7',
|
||||
'[&>p]:hidden',
|
||||
'[&>span]:hidden',
|
||||
'md:justify-start',
|
||||
'md:[&>p]:flex',
|
||||
'md:[&>span]:flex',
|
||||
'md:w-full',
|
||||
'md:px-3.5',
|
||||
|
||||
@@ -68,7 +68,7 @@ export function TableOfContents(props: {
|
||||
{header ? header : null}
|
||||
<div
|
||||
className={tcls(
|
||||
withHeaderOffset ? 'pt-4' : null,
|
||||
withHeaderOffset ? 'pt-4' : ['pt-4', 'lg:pt-0'],
|
||||
'hidden',
|
||||
'lg:flex',
|
||||
'flex-grow',
|
||||
|
||||
@@ -101,7 +101,7 @@ export function ToggleableLinkItem(props: {
|
||||
'text-sm',
|
||||
'transition-colors',
|
||||
'relative',
|
||||
'textwrap-balance',
|
||||
'text-balance',
|
||||
'before:border-l',
|
||||
'before:absolute',
|
||||
'before:left-[-1px]',
|
||||
|
||||
@@ -12,6 +12,7 @@ export function Link(props: LinkProps & { children: React.ReactNode }) {
|
||||
className={tcls(
|
||||
'underline',
|
||||
'underline-offset-2',
|
||||
'decoration-primary/6',
|
||||
'text-primary',
|
||||
'hover:text-primary-700',
|
||||
'transition-colors',
|
||||
|
||||
@@ -62,7 +62,7 @@ export const LoadingPane = (props: {
|
||||
'will-change-transform',
|
||||
|
||||
pulse
|
||||
? 'animate-[pulse_6s_cubic-bezier(.44,.12,.29,.94)_infinite]'
|
||||
? 'animate-[pulseAlt_6s_cubic-bezier(.44,.12,.29,.94)_infinite]'
|
||||
: 'animate-[rotateLoop_2s_linear_infinite]',
|
||||
pulse
|
||||
? `[background-image:radial-gradient(circle_closest-side,_var(--tw-gradient-stops)_0,_var(--tw-gradient-stops)_33%,_var(--tw-gradient-stops)_66%)]`
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ const config: Config = {
|
||||
'header-link': generateShades('header-link'),
|
||||
},
|
||||
keyframes: {
|
||||
pulse: {
|
||||
pulseAlt: {
|
||||
'0%': {
|
||||
transform: 'scale(0.01)',
|
||||
opacity: '0',
|
||||
|
||||
Reference in New Issue
Block a user