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:
Sebastian Graz
2023-11-21 18:29:36 +01:00
committed by GitHub
parent a4ec3c8c53
commit aab7d0bd7a
39 changed files with 314 additions and 181 deletions
+2 -2
View File
@@ -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:
+12 -9
View File
@@ -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');
}
+35 -1
View File
@@ -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,
)}
>
+2 -22
View File
@@ -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}
/>
))}
+15 -12
View File
@@ -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>
+11 -11
View File
@@ -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'],
},
};
+1
View File
@@ -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) => (
+1 -4
View File
@@ -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>
);
+4 -3
View File
@@ -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>
+1 -1
View File
@@ -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]}
/>
);
}
+1 -1
View File
@@ -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]}
/>
);
}
+1 -1
View File
@@ -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}
+4 -4
View File
@@ -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';
+5 -5
View File
@@ -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
+1 -1
View File
@@ -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 ? (
+1 -1
View File
@@ -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>
);
+1 -1
View File
@@ -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 (
+5 -6
View File
@@ -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,
)}
>
+14 -7
View File
@@ -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,
)}
>
+2 -2
View File
@@ -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,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();
+7 -4
View File
@@ -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')}
+15 -8
View File
@@ -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>
);
}
+21
View File
@@ -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
View File
@@ -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: [],
};
+3 -12
View File
@@ -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;