mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-24 19:32:07 +00:00
Styling PR (#56)
* v1 loading state * refactor primitives * fix order * fix toast styling * v1 rating * transition on page spinner * visual fixes
This commit is contained in:
@@ -1,15 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import IconX from '@geist-ui/icons/x';
|
||||
import * as React from 'react';
|
||||
|
||||
import { Button } from '@/components/primitives';
|
||||
import { useLanguage } from '@/intl/client';
|
||||
import { t } from '@/intl/translate';
|
||||
import { isCookiesTrackingDisabled, setCookiesTracking } from '@/lib/analytics';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
/**
|
||||
* Toast to accept or reject the use of cookies.
|
||||
*/
|
||||
|
||||
export function CookiesToast(props: { privacyPolicy?: string }) {
|
||||
const { privacyPolicy = 'https://policies.gitbook.com/privacy/cookies' } = props;
|
||||
const [show, setShow] = React.useState(false);
|
||||
@@ -36,17 +38,18 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
|
||||
'fixed',
|
||||
'z-10',
|
||||
'bg-white',
|
||||
'dark:bg-slate-800',
|
||||
'rounded',
|
||||
'border-slate-400',
|
||||
'dark:border-slate-700',
|
||||
'shadow-md',
|
||||
'ring-1',
|
||||
'ring-dark/2',
|
||||
'shadow-1xs',
|
||||
'p-4',
|
||||
'bottom-4',
|
||||
'right-4',
|
||||
'left-4',
|
||||
'left-16',
|
||||
'max-w-sm',
|
||||
'sm:left-auto',
|
||||
'dark:ring-light/2',
|
||||
'dark:bg-dark',
|
||||
)}
|
||||
>
|
||||
<p className={tcls('text-sm')}>
|
||||
@@ -55,56 +58,54 @@ export function CookiesToast(props: { privacyPolicy?: string }) {
|
||||
'cookies_prompt',
|
||||
<a
|
||||
href={privacyPolicy}
|
||||
className={tcls('text-primary-500', 'hover:text-primary-700', 'underline')}
|
||||
className={tcls(
|
||||
'text-primary-500',
|
||||
'hover:text-primary-600',
|
||||
'hover:underline',
|
||||
)}
|
||||
>
|
||||
{t(language, 'cookies_prompt_privacy')}
|
||||
</a>,
|
||||
)}
|
||||
</p>
|
||||
<div className={tcls('mt-3', 'flex', 'flex-row', 'gap-4')}>
|
||||
<ToastButton
|
||||
<button
|
||||
onClick={() => setShow(false)}
|
||||
className={tcls(
|
||||
'absolute',
|
||||
'top-2',
|
||||
'right-2',
|
||||
'w-5',
|
||||
'h-5',
|
||||
'flex',
|
||||
'justify-center',
|
||||
'items-center',
|
||||
'rounded-sm',
|
||||
'hover:bg-dark/2',
|
||||
'dark:hover:bg-light/1',
|
||||
)}
|
||||
>
|
||||
<IconX className={tcls('w-4')} />
|
||||
</button>
|
||||
<div className={tcls('mt-3', 'flex', 'flex-row', 'gap-2')}>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
onUpdateState(true);
|
||||
}}
|
||||
>
|
||||
{t(language, 'cookies_accept')}
|
||||
</ToastButton>
|
||||
<ToastButton
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
onUpdateState(false);
|
||||
}}
|
||||
>
|
||||
{t(language, 'cookies_reject')}
|
||||
</ToastButton>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ToastButton(props: { onClick: () => void; children: React.ReactNode }) {
|
||||
const { onClick, children } = props;
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={tcls(
|
||||
'bg-white',
|
||||
'dark:bg-slate-800',
|
||||
'text-xs',
|
||||
'text-slate-800',
|
||||
'dark:text-slate-200',
|
||||
'rounded',
|
||||
'border',
|
||||
'border-slate-300',
|
||||
'dark:border-slate-700',
|
||||
'px-2',
|
||||
'py-1',
|
||||
'shadow-md',
|
||||
'hover:bg-slate-100',
|
||||
'dark:hover:bg-slate-700',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DocumentBlockListItem } from '@gitbook/api';
|
||||
|
||||
import { Checkbox } from '@/components/utils';
|
||||
import { Checkbox } from '@/components/primitives';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { BlockProps } from './Block';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DocumentBlockTable, DocumentTableViewCards } from '@gitbook/api';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Checkbox } from '@/components/utils';
|
||||
import { Checkbox } from '@/components/primitives';
|
||||
import { getNodeFragmentByName } from '@/lib/document';
|
||||
import { resolveContentRef } from '@/lib/references';
|
||||
import { ClassValue, tcls } from '@/lib/tailwind';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import { Loading } from '@/components/primitives';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { useIsLoadingPage } from '../state';
|
||||
import { Loading } from '../utils/Loading';
|
||||
|
||||
/**
|
||||
* When navigating between pages, display an overlay on top of the page.
|
||||
@@ -12,31 +12,31 @@ export function PageLoading(props: {}) {
|
||||
const loading = useIsLoadingPage();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={tcls(
|
||||
'absolute',
|
||||
'flex',
|
||||
'items-start',
|
||||
'justify-center',
|
||||
'inset-0',
|
||||
'bg-white',
|
||||
loading ? 'opacity-6' : ['opacity-0', 'pointer-events-none'],
|
||||
'transition-opacity',
|
||||
)}
|
||||
>
|
||||
<div className={tcls('absolute', 'grid', 'inset-0', 'pointer-events-none')}>
|
||||
<div
|
||||
className={tcls(
|
||||
'mt-11',
|
||||
'w-12',
|
||||
'h-12',
|
||||
'rounded-full',
|
||||
'bg-white',
|
||||
'grid-area-1-1',
|
||||
'bg-light',
|
||||
'grid',
|
||||
loading ? 'opacity-8' : ['opacity-0'],
|
||||
'transition-opacity',
|
||||
'dark:bg-dark',
|
||||
)}
|
||||
></div>
|
||||
<div
|
||||
className={tcls(
|
||||
'grid-area-1-1',
|
||||
'flex',
|
||||
'sticky',
|
||||
'top-0',
|
||||
'h-[100vh]',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'transition-opacity',
|
||||
loading ? 'opacity-[1]' : ['opacity-0'],
|
||||
)}
|
||||
>
|
||||
<Loading className={tcls('w-8', 'text-primary')} />
|
||||
<Loading className={tcls('w-6', 'text-primary')} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -26,24 +26,38 @@ export function PageFeedbackForm(props: { spaceId: string; pageId: string }) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className={tcls('text-sm')}>{t(languages, 'was_this_helpful')}</p>
|
||||
<p className={tcls('text-sm', 'text-dark/6', 'dark:text-light/5')}>
|
||||
{t(languages, 'was_this_helpful')}
|
||||
</p>
|
||||
|
||||
{submitted ? (
|
||||
<p>{t(languages, 'was_this_helpful_thank_you')}</p>
|
||||
) : (
|
||||
<div className={tcls('flex', 'flex-row', 'gap-2')}>
|
||||
<div
|
||||
className={tcls(
|
||||
'inline-flex',
|
||||
'mt-2',
|
||||
'flex-row',
|
||||
'rounded-full',
|
||||
'ring-1',
|
||||
'ring-inset',
|
||||
'ring-dark/2',
|
||||
'dark:ring-light/1',
|
||||
'[&>*:not(:last-child,_:first-child)]:rounded-none',
|
||||
)}
|
||||
>
|
||||
<RatingButton
|
||||
emoji="☹️"
|
||||
rating={0}
|
||||
label={tString(languages, 'was_this_helpful_negative')}
|
||||
onClick={() => onSubmit(PageFeedbackRating.Bad)}
|
||||
/>
|
||||
<RatingButton
|
||||
emoji="😕"
|
||||
rating={1}
|
||||
label={tString(languages, 'was_this_helpful_neutral')}
|
||||
onClick={() => onSubmit(PageFeedbackRating.Ok)}
|
||||
/>
|
||||
<RatingButton
|
||||
emoji="😃"
|
||||
rating={2}
|
||||
label={tString(languages, 'was_this_helpful_positive')}
|
||||
onClick={() => onSubmit(PageFeedbackRating.Good)}
|
||||
/>
|
||||
@@ -53,8 +67,12 @@ export function PageFeedbackForm(props: { spaceId: string; pageId: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
function RatingButton(props: { emoji: string; label: string; onClick: () => void }) {
|
||||
const { emoji, label, onClick } = props;
|
||||
function RatingButton(props: { rating: number; label: string; onClick: () => void }) {
|
||||
const { rating, label, onClick } = props;
|
||||
|
||||
const ratingIcon =
|
||||
rating === 0 ? <Icon0 /> : rating === 1 ? <Icon1 /> : rating === 2 ? <Icon2 /> : null;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={tcls(
|
||||
@@ -63,21 +81,53 @@ function RatingButton(props: { emoji: string; label: string; onClick: () => void
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'p-2',
|
||||
'rounded',
|
||||
'text-base',
|
||||
'text-gray-500',
|
||||
'hover:text-gray-900',
|
||||
'focus:outline-none',
|
||||
'focus:ring',
|
||||
'focus:ring-gray-300',
|
||||
'focus:ring-offset-2',
|
||||
'focus:ring-offset-white',
|
||||
'rounded-full',
|
||||
'text-dark/6',
|
||||
'hover:bg-primary/4',
|
||||
'hover:text-primary-600',
|
||||
'dark:text-light/5',
|
||||
'dark:hover:text-primary-300',
|
||||
'dark:hover:bg-primary-300/2',
|
||||
)}
|
||||
aria-label={label}
|
||||
title={label}
|
||||
onClick={onClick}
|
||||
>
|
||||
{emoji}
|
||||
{ratingIcon}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
const Icon0 = () => (
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="9" cy="9" r="9" fill="currentColor" fill-opacity="0.24" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M5 8.25C4.58579 8.25 4.25 7.91421 4.25 7.5V6C4.25 5.58579 4.58579 5.25 5 5.25C5.41421 5.25 5.75 5.58579 5.75 6V7.5C5.75 7.91421 5.41421 8.25 5 8.25ZM4.66782 13.3737C4.87421 13.5572 5.19025 13.5386 5.3737 13.3322C7.30371 11.1609 10.6963 11.1609 12.6263 13.3322C12.8098 13.5386 13.1258 13.5572 13.3322 13.3737C13.5386 13.1902 13.5572 12.8742 13.3737 12.6678C11.0459 10.049 6.9541 10.049 4.6263 12.6678C4.44284 12.8742 4.46143 13.1902 4.66782 13.3737ZM12.25 7.5C12.25 7.91421 12.5858 8.25 13 8.25C13.4142 8.25 13.75 7.91421 13.75 7.5V6C13.75 5.58579 13.4142 5.25 13 5.25C12.5858 5.25 12.25 5.58579 12.25 6V7.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
const Icon1 = () => (
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="9" cy="9" r="9" fill="currentColor" fill-opacity="0.24" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M5 8.25C4.58579 8.25 4.25 7.91421 4.25 7.5V6C4.25 5.58579 4.58579 5.25 5 5.25C5.41421 5.25 5.75 5.58579 5.75 6V7.5C5.75 7.91421 5.41421 8.25 5 8.25ZM4.5 12C4.5 11.7239 4.72386 11.5 5 11.5H13C13.2761 11.5 13.5 11.7239 13.5 12C13.5 12.2761 13.2761 12.5 13 12.5H5C4.72386 12.5 4.5 12.2761 4.5 12ZM12.25 7.5C12.25 7.91421 12.5858 8.25 13 8.25C13.4142 8.25 13.75 7.91421 13.75 7.5V6C13.75 5.58579 13.4142 5.25 13 5.25C12.5858 5.25 12.25 5.58579 12.25 6V7.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
const Icon2 = () => (
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="9" cy="9" r="9" fill="currentColor" fill-opacity="0.24" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M5 8.25C4.58579 8.25 4.25 7.91421 4.25 7.5V6C4.25 5.58579 4.58579 5.25 5 5.25C5.41421 5.25 5.75 5.58579 5.75 6V7.5C5.75 7.91421 5.41421 8.25 5 8.25ZM4.66782 11.6263C4.87421 11.4428 5.19025 11.4614 5.3737 11.6678C7.30371 13.8391 10.6963 13.8391 12.6263 11.6678C12.8098 11.4614 13.1258 11.4428 13.3322 11.6263C13.5386 11.8098 13.5572 12.1258 13.3737 12.3322C11.0459 14.951 6.9541 14.951 4.6263 12.3322C4.44284 12.1258 4.46143 11.8098 4.66782 11.6263ZM12.25 7.5C12.25 7.91421 12.5858 8.25 13 8.25C13.4142 8.25 13.75 7.91421 13.75 7.5V6C13.75 5.58579 13.4142 5.25 13 5.25C12.5858 5.25 12.25 5.58579 12.25 6V7.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -3,11 +3,11 @@ import IconSearch from '@geist-ui/icons/search';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
|
||||
import { Loading } from '@/components/primitives';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { AskAnswerResult, askQuestion } from './server-actions';
|
||||
import { useSearch, useSearchLink } from './useSearch';
|
||||
import { Loading } from '../utils/Loading';
|
||||
|
||||
/**
|
||||
* Fetch and render the answers to a question.
|
||||
|
||||
@@ -33,6 +33,7 @@ export function SearchButton(props: { children?: React.ReactNode; style?: ClassV
|
||||
'justify-center',
|
||||
'items-center',
|
||||
'px-2',
|
||||
'text-dark/6',
|
||||
'min-h-[2.5rem]',
|
||||
'w-[2.5rem]',
|
||||
'rounded-lg',
|
||||
@@ -45,6 +46,7 @@ export function SearchButton(props: { children?: React.ReactNode; style?: ClassV
|
||||
'dark:bg-light/1',
|
||||
'dark:border-light/1',
|
||||
'dark:hover:bg-light/2',
|
||||
'dark:text-light/6',
|
||||
'[&>span]:hidden',
|
||||
'md:justify-between',
|
||||
'md:[&>span]:flex',
|
||||
@@ -54,7 +56,14 @@ export function SearchButton(props: { children?: React.ReactNode; style?: ClassV
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
<IconSearch className={tcls('w-4', 'h-4')} />
|
||||
<IconSearch
|
||||
className={tcls(
|
||||
'w-4',
|
||||
'h-4',
|
||||
'[color:color-mix(in_srgb,_rgb(var(--light)),_rgb(var(--dark))_32%)]',
|
||||
'dark:[color:color-mix(in_srgb,_rgb(var(--dark)),_rgb(var(--light))_32%)]',
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ export function Trademark(props: { space: Space; customization: CustomizationSet
|
||||
'rounded-md',
|
||||
'hover:backdrop-blur-sm',
|
||||
'lg:ring-0',
|
||||
'tracking-[-0.016em]',
|
||||
'dark:hover:bg-light/1',
|
||||
'dark:ring-light/1',
|
||||
'dark:font-normal',
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { tcls, ClassValue } from '@/lib/tailwind';
|
||||
|
||||
type ButtonProps = {
|
||||
onClick: () => void;
|
||||
children: React.ReactNode;
|
||||
variant?: 'primary' | 'secondary';
|
||||
size?: 'default' | 'small';
|
||||
className?: ClassValue;
|
||||
};
|
||||
|
||||
export function Button({
|
||||
onClick,
|
||||
children,
|
||||
variant = 'primary',
|
||||
size = 'default',
|
||||
className,
|
||||
}: ButtonProps) {
|
||||
const variantClasses =
|
||||
variant === 'primary'
|
||||
? //PRIMARY
|
||||
[
|
||||
'bg-primary-600',
|
||||
'text-white',
|
||||
'ring-dark/2',
|
||||
'hover:bg-primary-500',
|
||||
'dark:ring-light/3',
|
||||
'dark:bg-primary-500',
|
||||
'dark:hover:bg-primary-400',
|
||||
]
|
||||
: // SECONDARY
|
||||
[
|
||||
'bg-dark/2',
|
||||
'ring-dark/1',
|
||||
'hover:bg-dark/3',
|
||||
'dark:bg-light/2',
|
||||
'dark:ring-light/1',
|
||||
'dark:hover:bg-light/3',
|
||||
];
|
||||
|
||||
const sizeClasses =
|
||||
size === 'default'
|
||||
? // DEFAULT
|
||||
['text-base', 'px-4', 'py-2']
|
||||
: // SMALL
|
||||
['text-xs', 'px-3 py-2'];
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={tcls(
|
||||
'rounded',
|
||||
'place-self-start',
|
||||
'ring-1',
|
||||
'ring-inset',
|
||||
'grow-0',
|
||||
'shrink-0',
|
||||
variantClasses,
|
||||
sizeClasses,
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './Checkbox';
|
||||
export * from './Button';
|
||||
export * from './Loading';
|
||||
@@ -1,2 +1 @@
|
||||
export * from './Image';
|
||||
export * from './Checkbox';
|
||||
|
||||
Reference in New Issue
Block a user