mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 15:45:13 +00:00
Design pass (#8)
* first stab at bullet points * setup theming, custom opacities, use of brand UI colors * color key change * delete brand * setup initial defaults * Add opacity parameter to primary color * style TOC * styling pass header, footer, hints, toc, trademark * table pass * style pass, typo, quote, tabs, search, theme, toc
This commit is contained in:
+2
-2
@@ -1,6 +1,6 @@
|
||||
# Mode to serve only a single space
|
||||
# Ex: http://localhost:4000
|
||||
GITBOOK_MODE=single
|
||||
# GITBOOK_MODE=single
|
||||
GITBOOK_SPACE_ID=your_space_id
|
||||
GITBOOK_TOKEN=your_token_here
|
||||
|
||||
@@ -10,7 +10,7 @@ GITBOOK_MODE=multi
|
||||
|
||||
# Mode to serve multiple spaces with the space url passed as a path
|
||||
# Ex: http://localhost:4000/docs.mycompany.com/page1
|
||||
GITBOOK_MODE=multi-path
|
||||
# GITBOOK_MODE=multi-path
|
||||
|
||||
# Other options:
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Inter } from 'next/font/google';
|
||||
import shadesOf from 'tailwind-shades';
|
||||
import colors from 'tailwindcss/colors';
|
||||
|
||||
import { hexToRgb } from '@/components/utils/HexToRgb';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { ClientLayout } from './ClientLayout';
|
||||
@@ -24,30 +25,30 @@ export default async function SpaceRootLayout(props: {
|
||||
<head>
|
||||
<style>{`
|
||||
:root {
|
||||
${generateCSSVariable(
|
||||
${generateColorVariable(
|
||||
'primary-color',
|
||||
customization.styling.primaryColor.light,
|
||||
)}
|
||||
${generateCSSVariable(
|
||||
${generateColorVariable(
|
||||
'header-background',
|
||||
headerTheme.backgroundColor.light,
|
||||
)}
|
||||
${generateCSSVariable('header-link', headerTheme.linkColor.light)}
|
||||
${generateColorVariable('header-link', headerTheme.linkColor.light)}
|
||||
}
|
||||
.dark {
|
||||
${generateCSSVariable(
|
||||
${generateColorVariable(
|
||||
'primary-color',
|
||||
customization.styling.primaryColor.dark,
|
||||
)}
|
||||
${generateCSSVariable(
|
||||
${generateColorVariable(
|
||||
'header-background',
|
||||
headerTheme.backgroundColor.dark,
|
||||
)}
|
||||
${generateCSSVariable('header-link', headerTheme.linkColor.dark)}
|
||||
${generateColorVariable('header-link', headerTheme.linkColor.dark)}
|
||||
}
|
||||
`}</style>
|
||||
</head>
|
||||
<body className={tcls(inter.className, 'bg-white', 'dark:bg-slate-950')}>
|
||||
<body className={tcls(inter.className, 'bg-light', 'dark:bg-dark')}>
|
||||
<ClientLayout>{children}</ClientLayout>
|
||||
</body>
|
||||
</html>
|
||||
@@ -55,12 +56,14 @@ export default async function SpaceRootLayout(props: {
|
||||
}
|
||||
|
||||
type ColorInput = string | Record<string, string>;
|
||||
function generateCSSVariable(name: string, color: ColorInput) {
|
||||
function generateColorVariable(name: string, color: ColorInput) {
|
||||
const shades: Record<string, string> = typeof color === 'string' ? shadesOf(color) : color;
|
||||
|
||||
return Object.entries(shades)
|
||||
.map(([key, value]) => {
|
||||
return `--${name}-${key}: ${value};`;
|
||||
// Check the original hex value
|
||||
const rgbValue = hexToRgb(value);
|
||||
return `--${name}-${key}: ${rgbValue};`;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
@@ -2,5 +2,39 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
@layer base {
|
||||
:root {
|
||||
--dark: 24 28 31;
|
||||
--vanta: 2 12 13;
|
||||
--light: 242 247 247;
|
||||
--metal: 222 229 229;
|
||||
--yellow: 244 226 141;
|
||||
--teal: 63 137 161;
|
||||
--pomegranate: 242 91 58;
|
||||
--periwinkle: 172 198 238;
|
||||
}
|
||||
h1 {
|
||||
@apply tracking-tighter;
|
||||
@apply text-dark dark:text-light;
|
||||
}
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
@apply tracking-tight;
|
||||
@apply text-dark dark:text-light;
|
||||
}
|
||||
body {
|
||||
@apply text-dark/9 dark:text-light/9;
|
||||
}
|
||||
p {
|
||||
@apply text-dark/9 dark:text-light/8;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.linear-mask-gradient {
|
||||
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 96px, rgba(0, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,12 +23,14 @@ export async function BlockContentRef(props: BlockProps<DocumentBlockContentRef>
|
||||
'items-center',
|
||||
'rounded',
|
||||
'border',
|
||||
'border-slate-200',
|
||||
'hover:border-slate-300',
|
||||
'border-dark/2',
|
||||
'hover:border-dark/4',
|
||||
'px-5',
|
||||
'py-2',
|
||||
'text-slate-500',
|
||||
'hover:text-slate-700',
|
||||
'hover:text-dark',
|
||||
'dark:border-light/2',
|
||||
'dark:hover:text-light',
|
||||
'dark:hover:border-light/4',
|
||||
style,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -21,23 +21,9 @@ export function Blocks<T extends DocumentBlock, Tag extends React.ElementType =
|
||||
|
||||
/** Style passed to all blocks */
|
||||
blockStyle?: ClassValue;
|
||||
|
||||
/** Style passed to the first block */
|
||||
firstBlockStyle?: ClassValue;
|
||||
|
||||
/** Style passed to the last block */
|
||||
lastBlockStyle?: ClassValue;
|
||||
},
|
||||
) {
|
||||
const {
|
||||
nodes,
|
||||
tag: Tag = 'div',
|
||||
style,
|
||||
blockStyle = ['mt-6'],
|
||||
firstBlockStyle = ['mt-0'],
|
||||
lastBlockStyle,
|
||||
...contextProps
|
||||
} = props;
|
||||
const { nodes, tag: Tag = 'div', style, blockStyle, ...contextProps } = props;
|
||||
|
||||
return (
|
||||
<Tag className={tcls(style)}>
|
||||
@@ -45,13 +31,7 @@ export function Blocks<T extends DocumentBlock, Tag extends React.ElementType =
|
||||
<Block
|
||||
key={node.key}
|
||||
block={node}
|
||||
style={[
|
||||
'max-w-3xl', // Default max size for blocks, can be overridden in the block implementation
|
||||
'mx-auto',
|
||||
blockStyle,
|
||||
index === 0 && firstBlockStyle,
|
||||
index === nodes.length - 1 && lastBlockStyle,
|
||||
]}
|
||||
style={['max-w-3xl', 'w-full', 'mx-auto', 'leading-relaxed', blockStyle]}
|
||||
{...contextProps}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -21,7 +21,9 @@ export function Heading(props: BlockProps<DocumentBlockHeading>) {
|
||||
<div
|
||||
className={tcls(
|
||||
'absolute',
|
||||
' -ml-8',
|
||||
'-left-7',
|
||||
'ml-full',
|
||||
'w-7',
|
||||
'hidden',
|
||||
'items-center',
|
||||
'border-0',
|
||||
@@ -41,21 +43,22 @@ export function Heading(props: BlockProps<DocumentBlockHeading>) {
|
||||
'w-6',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'rounded-md',
|
||||
'text-slate-400',
|
||||
'shadow-sm',
|
||||
'ring-1',
|
||||
'ring-slate-900/5',
|
||||
'hover:text-slate-700',
|
||||
'hover:shadow',
|
||||
'hover:ring-slate-900/10',
|
||||
'dark:bg-slate-700',
|
||||
'dark:text-slate-300',
|
||||
'transition-colors',
|
||||
'dark:text-light/3',
|
||||
'dark:shadow-none',
|
||||
'dark:ring-0',
|
||||
)}
|
||||
>
|
||||
<IconHash className={tcls('w-4', 'h-4')} />
|
||||
<IconHash
|
||||
className={tcls(
|
||||
'w-4',
|
||||
'h-4',
|
||||
'stroke-dark/5',
|
||||
'group-hover:stroke-dark/6',
|
||||
'dark:stroke-light/2',
|
||||
'dark:group-hover:stroke-light/4',
|
||||
)}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -22,8 +22,7 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
|
||||
'flex-row',
|
||||
'px-4',
|
||||
'py-4',
|
||||
'bg-slate-50',
|
||||
'dark:bg-slate-900',
|
||||
'transition-colors',
|
||||
'rounded',
|
||||
'border-l-4',
|
||||
hintStyle.style,
|
||||
@@ -36,6 +35,7 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
|
||||
'items-center',
|
||||
'justify-center',
|
||||
'pr-3',
|
||||
|
||||
firstLine.lineHeight,
|
||||
hintStyle.iconStyle,
|
||||
)}
|
||||
@@ -46,7 +46,7 @@ export function Hint(props: BlockProps<DocumentBlockHint>) {
|
||||
{...contextProps}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
nodes={block.nodes}
|
||||
style={['flex-1']}
|
||||
style={['flex-1', 'space-y-8']}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -61,22 +61,22 @@ const HINT_STYLES: {
|
||||
} = {
|
||||
info: {
|
||||
icon: IconInfo,
|
||||
iconStyle: ['text-blue-500'],
|
||||
style: ['border-blue-500'],
|
||||
iconStyle: ['text-current'],
|
||||
style: ['border-dark/2', 'bg-dark/2', 'dark:bg-light/1', 'dark:border-light/3'],
|
||||
},
|
||||
warning: {
|
||||
icon: IconAlertCircle,
|
||||
iconStyle: ['text-orange-500'],
|
||||
style: ['border-orange-500'],
|
||||
iconStyle: ['text-yellow, dark:text-yellow/1, dark:text-yellow/8'],
|
||||
style: ['border-yellow/4', 'bg-yellow', 'dark:bg-yellow/1'],
|
||||
},
|
||||
danger: {
|
||||
icon: IconAlertTriangle,
|
||||
iconStyle: ['text-red-500'],
|
||||
style: ['border-red-500'],
|
||||
iconStyle: ['text-pomegranate'],
|
||||
style: ['border-pomegranate/4', 'bg-pomegranate/2', 'dark:bg-pomegranate/1'],
|
||||
},
|
||||
success: {
|
||||
icon: IconCheckInCircle,
|
||||
iconStyle: ['text-green-500'],
|
||||
style: ['border-green-500'],
|
||||
iconStyle: ['text-teal'],
|
||||
style: ['border-teal/4', 'bg-teal/2', 'dark:bg-teal/2'],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -22,6 +22,7 @@ export function Images(props: BlockProps<DocumentBlockImages>) {
|
||||
block.data.align === 'left' && 'justify-start',
|
||||
style,
|
||||
block.data.fullWidth ? 'max-w-full' : null,
|
||||
'justify-center',
|
||||
)}
|
||||
>
|
||||
{block.nodes.map((node: any, i: number) => (
|
||||
|
||||
@@ -20,10 +20,7 @@ export async function Link(props: InlineProps<DocumentInlineLink>) {
|
||||
}
|
||||
|
||||
return (
|
||||
<NextLink
|
||||
href={resolved.href}
|
||||
className="underline text-primary-600 hover:text-primary-800"
|
||||
>
|
||||
<NextLink href={resolved.href} className="underline text-primary hover:text-primary-700">
|
||||
<Inlines context={context} nodes={inline.nodes} />
|
||||
</NextLink>
|
||||
);
|
||||
|
||||
@@ -13,13 +13,14 @@ export function ListItem(props: BlockProps<DocumentBlockListItem>) {
|
||||
return (
|
||||
<li className={tcls('text-base', 'font-normal')}>
|
||||
{isTaskList ? (
|
||||
<div className={tcls('flex', 'flex-row')}>
|
||||
<div className={tcls('flex', 'flex-row', 'items-baseline')}>
|
||||
<input type="checkbox" disabled checked={block.data?.checked} />
|
||||
<Blocks
|
||||
{...contextProps}
|
||||
nodes={block.nodes}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
blockStyle={tcls('mt-6', 'first:mt-0', 'last:mt-0', 'flex-1')}
|
||||
blockStyle={tcls('flex-1')}
|
||||
style={tcls('ml-2', 'space-y-2')}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
@@ -27,7 +28,7 @@ export function ListItem(props: BlockProps<DocumentBlockListItem>) {
|
||||
{...contextProps}
|
||||
nodes={block.nodes}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
blockStyle={tcls('mt-6', 'first:mt-0', 'last:mt-0')}
|
||||
style={tcls('space-y-2')}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
|
||||
@@ -12,7 +12,7 @@ export function ListOrdered(props: BlockProps<DocumentBlockListOrdered>) {
|
||||
tag="ol"
|
||||
nodes={block.nodes}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
style={['list-decimal', 'ps-8', style]}
|
||||
style={['list-decimal', 'ps-8', 'space-y-2', style]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export function ListTasks(props: BlockProps<DocumentBlockListTasks>) {
|
||||
tag="ul"
|
||||
nodes={block.nodes}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
style={['list-disc', 'ps-8', style]}
|
||||
style={['list-none', 'ps-4', 'space-y-2', style]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export function ListUnordered(props: BlockProps<DocumentBlockListUnordered>) {
|
||||
tag="ul"
|
||||
nodes={block.nodes}
|
||||
ancestorBlocks={[...ancestorBlocks, block]}
|
||||
style={['list-disc', 'ps-8', style]}
|
||||
style={['list-disc', 'ps-8', 'space-y-2', style]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export function Paragraph(props: BlockProps<DocumentBlockParagraph>) {
|
||||
const { block, style, ...contextProps } = props;
|
||||
|
||||
return (
|
||||
<p className={tcls('text-base', 'font-normal', style)}>
|
||||
<p className={tcls('font-normal', style)}>
|
||||
<Inlines {...contextProps} nodes={block.nodes} />
|
||||
</p>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,16 @@ export async function RecordRow(
|
||||
<tr>
|
||||
{view.columns.map((column) => {
|
||||
return (
|
||||
<td key={column} className={tcls('border', 'border-slate-700', 'px-2', 'py-1')}>
|
||||
<td
|
||||
key={column}
|
||||
className={tcls(
|
||||
'border',
|
||||
'border-dark/2',
|
||||
'px-2',
|
||||
'py-1',
|
||||
'dark:border-light/2',
|
||||
)}
|
||||
>
|
||||
<RecordColumnValue key={column} {...props} column={column} />
|
||||
</td>
|
||||
);
|
||||
|
||||
@@ -14,9 +14,6 @@ export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
|
||||
style,
|
||||
'table-auto',
|
||||
'w-full',
|
||||
'border-collapse',
|
||||
'border',
|
||||
'border-slate-500',
|
||||
block.data.fullWidth ? ['max-w-full'] : null,
|
||||
)}
|
||||
>
|
||||
@@ -27,7 +24,13 @@ export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
|
||||
return (
|
||||
<th
|
||||
key={column}
|
||||
className={tcls('border', 'border-slate-700', 'px-2', 'py-1')}
|
||||
className={tcls(
|
||||
'border',
|
||||
'border-dark/4',
|
||||
'px-2',
|
||||
'py-1',
|
||||
'dark:border-light/4',
|
||||
)}
|
||||
>
|
||||
{block.data.definition[column].title}
|
||||
</th>
|
||||
|
||||
@@ -20,8 +20,18 @@ export function DynamicTabs(props: {
|
||||
const [active, setActive] = React.useState<null | string>(null);
|
||||
|
||||
return (
|
||||
<div className={tcls('rounded', 'border', 'border-slate-200', 'flex', 'flex-col', style)}>
|
||||
<div role="tablist" className={tcls('flex', 'flex-row', 'p-4')}>
|
||||
<div
|
||||
className={tcls(
|
||||
'rounded-md',
|
||||
'border',
|
||||
'border-dark/2',
|
||||
'flex',
|
||||
'flex-col',
|
||||
'dark:border-light/3',
|
||||
style,
|
||||
)}
|
||||
>
|
||||
<div role="tablist" className={tcls('flex', 'flex-row', 'p-4', 'space-x-2')}>
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
@@ -33,14 +43,19 @@ export function DynamicTabs(props: {
|
||||
setActive(tab.id);
|
||||
}}
|
||||
className={tcls(
|
||||
'bg-slate-50',
|
||||
'bg-transparent',
|
||||
'text-sm',
|
||||
'textslate-700',
|
||||
'rounded',
|
||||
'rounded-full',
|
||||
'px-4',
|
||||
'py-2',
|
||||
'me-2',
|
||||
active === tab.id ? ['bg-slate-100'] : null,
|
||||
'transition-colors',
|
||||
'hover:bg-dark/1',
|
||||
'font-semibold',
|
||||
'text-dark/7',
|
||||
'dark:text-light/6',
|
||||
active === tab.id
|
||||
? ['text-dark', 'bg-dark/2', 'dark:bg-light/2', 'dark:text-light']
|
||||
: null,
|
||||
)}
|
||||
>
|
||||
{tab.title}
|
||||
|
||||
@@ -21,11 +21,11 @@ export function Footer(props: {
|
||||
<div
|
||||
className={tcls(
|
||||
'border-t',
|
||||
'border-slate-200',
|
||||
'dark:border-slate-800',
|
||||
'bg-slate-50',
|
||||
'dark:bg-slate-900',
|
||||
'border-dark/3',
|
||||
'px-4',
|
||||
'bg-metal/5',
|
||||
'dark:border-light/3',
|
||||
'dark:bg-vanta/4',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import IconChevronDown from '@geist-ui/icons/chevronDown';
|
||||
import { Collection, Space } from '@gitbook/api';
|
||||
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
@@ -41,17 +41,17 @@ export function Header(props: {
|
||||
'top-0',
|
||||
'z-10',
|
||||
'w-full',
|
||||
'backdrop-blur',
|
||||
'backdrop-blur-lg',
|
||||
'flex-none',
|
||||
'transition-colors',
|
||||
'duration-500',
|
||||
'lg:z-10',
|
||||
'lg:border-b',
|
||||
'lg:border-slate-900/10',
|
||||
'dark:border-slate-50/[0.06]',
|
||||
'bg-header-background-500',
|
||||
'lg:border-dark/3',
|
||||
'bg-light/8',
|
||||
'supports-backdrop-blur:bg-white/60',
|
||||
'dark:bg-transparent',
|
||||
'dark:border-light/2',
|
||||
'dark:bg-dark/8',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -36,7 +36,7 @@ export function PageAside(props: {
|
||||
>
|
||||
{sections.length > 0 ? (
|
||||
<>
|
||||
<div className={tcls('text-sm', 'text-slate-500', 'font-semibold', 'pb-3')}>
|
||||
<div className={tcls('text-sm', 'font-semibold', 'pb-3')}>
|
||||
{t({ space }, 'on_this_page')}
|
||||
</div>
|
||||
<div className={tcls('overflow-auto', 'flex-1')}>
|
||||
|
||||
@@ -26,10 +26,10 @@ export function ScrollSectionsList(props: { sections: DocumentSection[] }) {
|
||||
'flex-row',
|
||||
'text-sm',
|
||||
section.depth > 1
|
||||
? ['font-light', 'ps-3', 'py-1', 'text-slate-500']
|
||||
: ['py-2', 'text-slate-700'],
|
||||
? ['font-light', 'ps-3', 'py-1', 'text-light/5']
|
||||
: ['py-2', 'text-light/7'],
|
||||
|
||||
activeId === section.id ? 'text-primary-500' : 'hover:text-slate-900',
|
||||
activeId === section.id ? 'text-primary' : 'hover:text-light/9',
|
||||
)}
|
||||
>
|
||||
{section.depth > 1 ? (
|
||||
|
||||
@@ -19,7 +19,7 @@ export function PageBody(props: {
|
||||
<PageHeader page={page} />
|
||||
<DocumentView
|
||||
document={document}
|
||||
style={'mt-6'}
|
||||
style={['space-y-6']}
|
||||
context={{
|
||||
space,
|
||||
revision,
|
||||
|
||||
@@ -62,22 +62,29 @@ function NavigationCard(props: {
|
||||
'items-center',
|
||||
'p-4',
|
||||
'border',
|
||||
'border-slate-200',
|
||||
'border-dark/3',
|
||||
'rounded',
|
||||
'hover:shadow',
|
||||
'hover:border-primary-500',
|
||||
'hover:border-primary/6',
|
||||
'dark:border-light/2',
|
||||
'dark:hover:border-primary-300/4',
|
||||
)}
|
||||
>
|
||||
<span className={tcls('flex', 'flex-col', 'flex-1', reversed ? 'text-right' : null)}>
|
||||
<span className={tcls('text-xs', 'text-slate-400')}>{label}</span>
|
||||
<span className={tcls('text-xs')}>{label}</span>
|
||||
<span
|
||||
className={tcls('text-base', 'text-slate-700', 'group-hover:text-primary-600')}
|
||||
className={tcls('text-dark', 'dark:text-light/6', 'group-hover:text-primary')}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
</span>
|
||||
<IconCo
|
||||
className={tcls('w-6', 'h-6', 'text-slate-400', 'group-hover:text-primary-600')}
|
||||
className={tcls(
|
||||
'w-6',
|
||||
'h-6',
|
||||
'text-dark/5',
|
||||
'group-hover:text-primary',
|
||||
'dark:text-light/4',
|
||||
)}
|
||||
/>
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ export function HighlightQuery(props: {
|
||||
/** Style to apply on matching parts (default to primary) */
|
||||
highlight?: ClassValue;
|
||||
}): React.ReactElement {
|
||||
const { query, text, highlight = ['text-bold', 'text-primary-500'], ...textProps } = props;
|
||||
const { query, text, highlight = ['text-bold', 'text-primary'], ...textProps } = props;
|
||||
const matches = matchString(text, query);
|
||||
|
||||
return (
|
||||
|
||||
@@ -30,13 +30,12 @@ export function SearchButton(props: { children?: React.ReactNode; style?: ClassV
|
||||
'px-4',
|
||||
'py-1',
|
||||
'rounded',
|
||||
'bg-slate-50',
|
||||
'hover:bg-slate-100',
|
||||
'text-base',
|
||||
'text-slate-600',
|
||||
'hover:text-slate-900',
|
||||
'dark:bg-vanta/4',
|
||||
'hover:bg-light',
|
||||
'border',
|
||||
'border-slate-200',
|
||||
'border-dark/2',
|
||||
'shadow-lg',
|
||||
'dark:border-light/3',
|
||||
style,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -46,9 +46,9 @@ export function SearchModal(props: SearchModalProps) {
|
||||
'justify-center',
|
||||
'fixed',
|
||||
'inset-0',
|
||||
'bg-zinc-400/25',
|
||||
'backdrop-blur-sm',
|
||||
'dark:bg-black/40',
|
||||
'bg-dark/4',
|
||||
'backdrop-blur',
|
||||
'dark:bg-dark/9',
|
||||
'opacity-100',
|
||||
'z-30',
|
||||
'pt-24',
|
||||
@@ -109,17 +109,18 @@ function SearchModalBody(
|
||||
'bg-white',
|
||||
'w-[600px]',
|
||||
'max-h',
|
||||
'rounded',
|
||||
'rounded-3xl',
|
||||
'border-slate-500',
|
||||
'shadow-lg',
|
||||
'overflow-hidden',
|
||||
'dark:bg-metal',
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div className={tcls('flex', 'flex-row', 'items-center')}>
|
||||
<div className={tcls('text-slate-400', 'p-3')}>
|
||||
<div className={tcls('flex', 'flex-row', 'items-center', 'border-b', 'border-dark/2')}>
|
||||
<div className={tcls('text-dark/4', 'p-3')}>
|
||||
<IconSearch className={tcls('w-6', 'h-6')} />
|
||||
</div>
|
||||
<div className={tcls('flex-1')}>
|
||||
@@ -128,7 +129,13 @@ function SearchModalBody(
|
||||
value={query}
|
||||
onKeyDown={onKeyDown}
|
||||
onChange={onChange}
|
||||
className={tcls('w-full', 'p-2', 'text-slate-600', 'focus:outline-none')}
|
||||
className={tcls(
|
||||
'w-full',
|
||||
'p-2',
|
||||
'text-dark',
|
||||
'focus:outline-none',
|
||||
'bg-transparent',
|
||||
)}
|
||||
placeholder={inputPlaceholder}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -22,13 +22,16 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt
|
||||
href={item.href}
|
||||
className={tcls(
|
||||
'flex',
|
||||
'flex-col',
|
||||
'flex-row',
|
||||
'rounded',
|
||||
'px-3',
|
||||
'px-6',
|
||||
'py-3',
|
||||
'hover:bg-slate-50',
|
||||
'hover:bg-dark/1',
|
||||
'text-base',
|
||||
'text-slate-600',
|
||||
'text-dark',
|
||||
'font-semibold',
|
||||
'mt-6',
|
||||
'first:mt-0',
|
||||
active ? ['bg-primary-50'] : null,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -100,9 +100,9 @@ export const SearchResults = React.forwardRef(function SearchResults(
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={tcls('max-h-[60vh]', 'overflow-auto', 'px-3')}>
|
||||
<div className={tcls('max-h-[60vh]', 'overflow-auto', 'px')}>
|
||||
{results.length === 0 ? (
|
||||
<div className={tcls('text-sm', 'text-slate-400', 'p-6', 'text-center')}>
|
||||
<div className={tcls('text-sm', 'text-dark', 'p-6', 'text-center')}>
|
||||
{noResultsMessage}
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -19,24 +19,31 @@ export const SearchSectionResultItem = React.forwardRef(function SearchSectionRe
|
||||
className={tcls(
|
||||
'flex',
|
||||
'flex-col',
|
||||
'rounded',
|
||||
'px-3',
|
||||
'py-3',
|
||||
'pl-6',
|
||||
'hover:bg-slate-50',
|
||||
'hover:bg-dark/1',
|
||||
active ? ['bg-primary-50'] : null,
|
||||
)}
|
||||
>
|
||||
{item.title ? (
|
||||
<p className={tcls('text-base', 'text-slate-600')}>
|
||||
<HighlightQuery query={query} text={item.title} />
|
||||
</p>
|
||||
) : null}
|
||||
{item.body ? (
|
||||
<p className={tcls('text-sm', 'text-slate-400')}>
|
||||
<HighlightQuery query={query} text={item.body} />
|
||||
</p>
|
||||
) : null}
|
||||
<div className={tcls('border-l', 'p-3', 'border-dark/2')}>
|
||||
{item.title ? (
|
||||
<p className={tcls('text-base', 'text-dark/7', 'dark:text-dark/7')}>
|
||||
<HighlightQuery query={query} text={item.title} />
|
||||
</p>
|
||||
) : null}
|
||||
{item.body ? (
|
||||
<p
|
||||
className={tcls(
|
||||
'text-sm',
|
||||
'text-dark',
|
||||
'line-clamp-6',
|
||||
'linear-mask-gradient',
|
||||
'dark:text-dark',
|
||||
)}
|
||||
>
|
||||
<HighlightQuery query={query} text={item.body} />
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { pageHref } from '@/lib/links';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
import { PagesList } from './PagesList';
|
||||
import { ToggeableLinkItem } from './ToggeableLinkItem';
|
||||
import { ToggleableLinkItem } from './ToggleableLinkItem';
|
||||
|
||||
export function PageDocumentItem(props: {
|
||||
page: RevisionPageDocument;
|
||||
@@ -29,28 +29,48 @@ export function PageDocumentItem(props: {
|
||||
'transition-colors',
|
||||
'duration-100',
|
||||
activePage.id === page.id
|
||||
? ['bg-primary-100', 'text-primary-500', 'font-semibold']
|
||||
: ['hover:bg-slate-100', 'text-slate-500', 'font-normal'],
|
||||
? [
|
||||
'font-semibold',
|
||||
'text-primary',
|
||||
'bg-primary-500/3',
|
||||
'dark:text-primary-400',
|
||||
'dark:hover:bg-primary-500/5',
|
||||
]
|
||||
: [
|
||||
'font-normal',
|
||||
'text-dark/7',
|
||||
'hover:bg-dark/1',
|
||||
'dark:text-light/7',
|
||||
'dark:hover:bg-light/2',
|
||||
],
|
||||
),
|
||||
};
|
||||
|
||||
return (
|
||||
<li className={tcls('flex', 'flex-col', 'mb-0.5')}>
|
||||
{page.pages && page.pages.length ? (
|
||||
<ToggeableLinkItem
|
||||
<ToggleableLinkItem
|
||||
{...linkProps}
|
||||
descendants={
|
||||
<PagesList
|
||||
pages={page.pages}
|
||||
style={tcls('ms-2', 'ps-3', 'my-2', 'border-l', 'border-slate-200')}
|
||||
style={tcls(
|
||||
'ms-2',
|
||||
'ps-3',
|
||||
'my-2',
|
||||
'border-l',
|
||||
'border-dark/3',
|
||||
'dark:border-light/2',
|
||||
)}
|
||||
activePage={activePage}
|
||||
ancestors={ancestors}
|
||||
/>
|
||||
}
|
||||
defaultOpen={hasActiveDescendant || activePage.id === page.id}
|
||||
isActive={activePage.id === page.id}
|
||||
>
|
||||
{page.title}
|
||||
</ToggeableLinkItem>
|
||||
</ToggleableLinkItem>
|
||||
) : (
|
||||
<Link {...linkProps}>{page.title}</Link>
|
||||
)}
|
||||
|
||||
@@ -13,9 +13,7 @@ export function PageGroupItem(props: {
|
||||
|
||||
return (
|
||||
<li className={tcls('flex', 'flex-col', 'my-3')}>
|
||||
<div className={tcls('px-2', 'py-1.5', 'text-m', 'text-slate-900', 'font-medium')}>
|
||||
{page.title}
|
||||
</div>
|
||||
<div className={tcls('px-2', 'py-2', 'text', 'font-medium')}>{page.title}</div>
|
||||
{page.pages && page.pages.length ? (
|
||||
<PagesList pages={page.pages} activePage={activePage} ancestors={ancestors} />
|
||||
) : null}
|
||||
|
||||
@@ -30,8 +30,6 @@ export function TableOfContents(
|
||||
'shrink-0',
|
||||
'sticky',
|
||||
withHeaderOffset ? SIDE_COLUMN_WITH_HEADER : SIDE_COLUMN_WITHOUT_HEADER,
|
||||
'border-r',
|
||||
'border-slate-200',
|
||||
)}
|
||||
>
|
||||
{header ? <div className={tcls('pt-6', 'pb-3', 'pr-4')}>{header}</div> : null}
|
||||
|
||||
+10
-5
@@ -10,15 +10,16 @@ import { tcls } from '@/lib/tailwind';
|
||||
/**
|
||||
* Client component to allow toggling of a page's children.
|
||||
*/
|
||||
export function ToggeableLinkItem(
|
||||
export function ToggleableLinkItem(
|
||||
props: LinkProps & {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
descendants?: React.ReactNode;
|
||||
defaultOpen?: boolean;
|
||||
isActive?: boolean;
|
||||
},
|
||||
) {
|
||||
const { children, descendants, defaultOpen = false, ...linkProps } = props;
|
||||
const { children, descendants, defaultOpen = false, isActive = false, ...linkProps } = props;
|
||||
|
||||
const [open, setOpen] = React.useState(defaultOpen);
|
||||
const Chevron = open ? IconChevronDown : IconChevronRight;
|
||||
@@ -32,10 +33,14 @@ export function ToggeableLinkItem(
|
||||
'w-5',
|
||||
'h-5',
|
||||
'p-0.5',
|
||||
'text-slate-600',
|
||||
'text-current',
|
||||
'rounded',
|
||||
'hover:bg-slate-200',
|
||||
'hover:text-slate-700',
|
||||
'hover:bg-dark/2',
|
||||
'hover:text-current',
|
||||
'dark:hover:bg-light/2',
|
||||
'dark:hover:text-current',
|
||||
'transition-colors',
|
||||
isActive ? ['hover:bg-primary-500/5', 'dark:hover:bg-primary-500/5'] : [],
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
@@ -11,16 +11,19 @@ export function Trademark(props: IntlContext) {
|
||||
href="https://www.gitbook.com"
|
||||
className={tcls(
|
||||
'text-m',
|
||||
'text-slate-500',
|
||||
'text-dark/8',
|
||||
'font-normal',
|
||||
'px-4',
|
||||
'py-3',
|
||||
'mr-4',
|
||||
'my-4',
|
||||
'rounded-lg',
|
||||
'bg-white',
|
||||
'bg-slate-50',
|
||||
'hover:bg-slate-100',
|
||||
'bg-dark/1',
|
||||
'hover:bg-dark/2',
|
||||
'transition-colors',
|
||||
'dark:bg-vanta/5',
|
||||
'dark:text-light/5',
|
||||
'dark:hover:bg-vanta/6',
|
||||
)}
|
||||
>
|
||||
{t(props, 'powered_by_gitbook')}
|
||||
|
||||
@@ -52,10 +52,10 @@ export function ThemeToggler(props: IntlContext) {
|
||||
className={tcls(
|
||||
'flex',
|
||||
'flex-row',
|
||||
'rounded',
|
||||
'rounded-full',
|
||||
'border',
|
||||
'border-slate-200',
|
||||
'dark:border-slate-800',
|
||||
'border-dark/3',
|
||||
'dark:border-light/2',
|
||||
)}
|
||||
>
|
||||
<ThemeButton
|
||||
@@ -97,13 +97,20 @@ function ThemeButton(props: {
|
||||
className={tcls(
|
||||
'p-1',
|
||||
'm-1',
|
||||
'rounded',
|
||||
active ? ['bg-slate-200', 'dark:bg-slate-800'] : null,
|
||||
'text-slate-900',
|
||||
'dark:text-slate-400',
|
||||
'group',
|
||||
'rounded-full',
|
||||
active ? ['bg-primary-600/4', 'dark:bg-primary-400/2'] : null,
|
||||
'text-dark',
|
||||
'dark:text-light/7',
|
||||
)}
|
||||
>
|
||||
<Icon className={tcls('w-4', 'h-4')} />
|
||||
<Icon
|
||||
className={tcls(
|
||||
'w-4',
|
||||
'h-4',
|
||||
active ? ['stroke-primary-600', 'dark:stroke-primary-400'] : null,
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export function hexToRgb(hex: string): string {
|
||||
// Remove the hash at the beginning of the hex string if it exists
|
||||
let sanitizedHex = hex.replace('#', '');
|
||||
|
||||
// If the hex is shorthand (3 characters), expand it to 6 characters
|
||||
if (sanitizedHex.length === 3) {
|
||||
sanitizedHex = sanitizedHex
|
||||
.split('')
|
||||
.map((char) => char + char)
|
||||
.join('');
|
||||
}
|
||||
|
||||
// Convert the hex to an integer and extract the RGB components
|
||||
const intVal = parseInt(sanitizedHex, 16);
|
||||
const r = (intVal >> 16) & 255;
|
||||
const g = (intVal >> 8) & 255;
|
||||
const b = intVal & 255;
|
||||
|
||||
// Return the RGB values separated by spaces
|
||||
return `${r} ${g} ${b}`;
|
||||
}
|
||||
+22
-2
@@ -1,11 +1,22 @@
|
||||
import type { Config } from 'tailwindcss';
|
||||
|
||||
const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
|
||||
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];
|
||||
|
||||
function generateShades(varName: string) {
|
||||
return shades.reduce(
|
||||
(acc, shade) => {
|
||||
acc[shade] = `var(--${varName}-${shade})`;
|
||||
acc[shade] = `rgb(var(--${varName}-${shade}) / <alpha-value>)`;
|
||||
return acc;
|
||||
},
|
||||
{ DEFAULT: `rgb(var(--${varName}-500) / <alpha-value>)` } as Record<string, string>,
|
||||
);
|
||||
}
|
||||
|
||||
function opacity() {
|
||||
return opacities.reduce(
|
||||
(acc, opacity, index) => {
|
||||
acc[index + 1] = `${opacity / 100}`;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
@@ -24,10 +35,19 @@ const config: Config = {
|
||||
colors: {
|
||||
// Dynamic colors matching the customization settings
|
||||
primary: generateShades('primary-color'),
|
||||
dark: 'rgb(var(--dark) / <alpha-value>)',
|
||||
light: 'rgb(var(--light) / <alpha-value>)',
|
||||
vanta: 'rgb(var(--vanta) / <alpha-value>)',
|
||||
metal: 'rgb(var(--metal) / <alpha-value>)',
|
||||
yellow: 'rgb(var(--yellow) / <alpha-value>)',
|
||||
periwinkle: 'rgb(var(--periwinkle) / <alpha-value>)',
|
||||
pomegranate: 'rgb(var(--pomegranate) / <alpha-value>)',
|
||||
teal: 'rgb(var(--teal) / <alpha-value>)',
|
||||
'header-background': generateShades('header-background'),
|
||||
'header-link': generateShades('header-link'),
|
||||
},
|
||||
},
|
||||
opacity: opacity(),
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
Vendored
+3
-12
@@ -1,16 +1,7 @@
|
||||
declare module 'tailwind-shades' {
|
||||
export type Shade = {
|
||||
50: string;
|
||||
100: string;
|
||||
200: string;
|
||||
300: string;
|
||||
400: string;
|
||||
500: string;
|
||||
600: string;
|
||||
700: string;
|
||||
800: string;
|
||||
900: string;
|
||||
};
|
||||
import { shades } from '../tailwind.config';
|
||||
|
||||
export type Shade = Record<(typeof shades)[number], string>;
|
||||
|
||||
function shadesOf(color: string): Shade;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user