Style PR (#34)

* codeblock styling

* fix hover rounding

* checkpoint layout

* improve highlight algo

* v1 syntax theme

* fix table cell add title style

* color tweaks

* opaque sticky numbers

* fix collection spaces

* card aspect fix

* adding back full-width tables

* num tabs

* Add v1 more table types

* format
This commit is contained in:
Sebastian Graz
2023-12-14 16:17:13 +01:00
committed by GitHub
parent 87c38f9091
commit dae7f704fa
14 changed files with 313 additions and 133 deletions
+3
View File
@@ -81,6 +81,9 @@
.linear-mask-gradient {
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 1) 96px, rgba(0, 0, 0, 0));
}
.linear-mask-util {
mask-image: linear-gradient(to bottom, white, white);
}
.grid-area-1-1 {
grid-area: 1/1;
}
@@ -21,37 +21,108 @@ export async function CodeBlock(props: BlockProps<DocumentBlockCode>) {
const withLineNumbers = !!block.data.lineNumbers && block.nodes.length > 1;
const withWrap = block.data.overflow === 'wrap';
const title = block.data.title;
const fullWidth = block.data.fullWidth;
const fullWidthStyle = fullWidth ? 'max-w-4xl' : 'max-w-3xl';
const titleRounding = title ? ['rounded-md', 'rounded-ss-none'] : ['rounded-md'];
return (
<pre className={tcls('relative', style)}>
<code
id={id}
<div className={tcls('grid', 'grid-flow-col', style, fullWidthStyle)}>
<div
className={tcls(
'flex',
'flex-col',
'flex-wrap',
'py-4',
'rounded-md',
'bg-light',
'[counter-reset:line]',
withWrap ? 'whitespace-pre-wrap' : 'overflow-x-scroll',
'items-center',
'justify-start',
'[grid-area:1/1]',
/* 'mb-2', */
'text-sm',
'gap-2',
)}
>
{lines.map((line, index) => (
<CodeHighlightLine
block={block}
key={index}
line={line}
lineIndex={index + 1}
isLast={index === lines.length - 1}
withLineNumbers={withLineNumbers}
withWrap={withWrap}
context={context}
/>
))}
</code>
<CopyCodeButton codeId={id} style={['absolute', 'top-2', 'right-2']} />
</pre>
{title ? (
<div
className={tcls(
'text-xs',
'tracking-wide',
'text-dark/7',
'leading-none',
'inline-flex',
'items-center',
'justify-center',
'[background-color:color-mix(in_srgb,_rgb(var(--light)),_rgb(var(--dark))_3%)]',
'rounded-t',
'px-3',
'py-2',
'dark:bg-light/1',
'dark:text-light/7',
)}
>
{title}
</div>
) : null}
</div>
<CopyCodeButton
codeId={id}
style={[
'text-xs',
'[grid-area:2/1]',
'z-[1]',
'justify-self-end',
'backdrop-blur-md',
'self-start',
'ring-1',
'ring-dark/2',
'text-dark/7',
'bg-transparent',
'rounded-md',
'mr-2',
'mt-2',
'p-1',
'hover:ring-dark/3',
'dark:ring-light/2',
'dark:text-light/7',
'dark:hover:ring-light/3',
]}
/>
<pre
className={tcls(
'[grid-area:2/1]',
'relative',
'overflow-auto',
'linear-mask-util',
'[background-color:color-mix(in_srgb,_rgb(var(--light)),_rgb(var(--dark))_3%)]',
'dark:bg-light/1',
titleRounding,
)}
>
<code
id={id}
className={tcls(
'min-w-full',
'table',
'py-2',
'px-2',
'[counter-reset:line]',
withWrap ? 'whitespace-pre-wrap' : '',
)}
>
{lines.map((line, index) => (
<CodeHighlightLine
block={block}
key={index}
line={line}
lineIndex={index + 1}
isLast={index === lines.length - 1}
withLineNumbers={withLineNumbers}
withWrap={withWrap}
context={context}
/>
))}
</code>
</pre>
</div>
);
}
@@ -65,30 +136,77 @@ function CodeHighlightLine(props: {
context: ContentRefContext;
}) {
const { block, line, isLast, withLineNumbers, context } = props;
return (
<span
className={tcls(
'flex',
'flex-row',
'px-4',
line.highlighted ? 'bg-dark/5' : null,
withLineNumbers
? [
'before:shrink-0',
'before:absolute',
'before:left-0',
'before:pl-4',
line.highlighted ? 'before:bg-primary-200' : 'before:bg-primary-100',
'before:text-primary-400',
'before:content-[counter(line)]',
'[counter-increment:line]',
getLineNumberGutterWidth(block),
]
: [],
'overflow-hidden',
'table-row',
'relative',
'linear-mask-util',
'ring-2',
/* 'ring-inset', */
'ring-transparent',
'hover:ring-dark/2',
'dark:hover:ring-light/2',
'rounded',
'mask-[linear-gradient(to right, transparent, #000 1rem, #000 100%)]',
//first child
'[&.highlighted:first-child]:rounded-t-md',
'[&.highlighted:first-child>*]:mt-1',
//last child
'[&.highlighted:last-child]:rounded-b-md',
'[&.highlighted:last-child>*]:mb-1',
//is only child, dont hover effect line
'[&:only-child]:hover:ring-transparent',
//select all highlighted
'[&.highlighted]:rounded-none',
//select first in group
'[&:not(.highlighted)_+_.highlighted]:rounded-t-md',
'[&:not(.highlighted)_+_.highlighted>*]:mt-1',
//select last in group
'[&.highlighted:has(+:not(.highlighted))]:rounded-b-md',
'[&.highlighted:has(+:not(.highlighted))>*]:mb-1',
//select if highlight is singular in group
'[&:not(.highlighted)_+_.highlighted:has(+:not(.highlighted))]:rounded-md',
line.highlighted ? ['highlighted', 'bg-dark/1', 'dark:bg-sky/1'] : null,
)}
>
<span className="flex-1">
{withLineNumbers ? (
<span
className={tcls(
'table-cell',
'w-[0.01ch]',
'text-right',
'pr-3',
'pl-2',
'sticky',
'left-0',
'[background:linear-gradient(to_right,_color-mix(in_srgb,_rgb(var(--light)),_rgb(var(--dark))_3%)_60%,_transparent)]',
'dark:[background:linear-gradient(to_right,_color-mix(in_srgb,_rgb(var(--dark)),_rgb(var(--light))_4%)_60%,_transparent)]',
/* '[background-color:color-mix(in_oklab,_rgb(var(--light)),_rgb(var(--dark))_97%)]', */
withLineNumbers
? [
'before:text-dark/5',
'before:content-[counter(line)]',
'[counter-increment:line]',
'dark:before:text-light/4',
line.highlighted
? [
'before:text-dark/6',
'dark:before:text-light/8',
'[background:linear-gradient(to_right,_color-mix(in_srgb,_rgb(var(--light)),_rgb(var(--dark))_7%)_60%,_transparent)]',
'dark:[background:linear-gradient(to_right,_color-mix(in_srgb,_rgb(var(--dark)),_rgb(var(--sky))_9%)_60%,_transparent)]',
]
: null,
]
: [],
)}
></span>
) : null}
<span className={tcls('ml-3', 'block')}>
<CodeHighlightTokens tokens={line.tokens} context={context} />
{isLast ? null : !withLineNumbers && line.tokens.length === 0 && 0 ? (
<span className="ew">{'\u200B'}</span>
@@ -133,18 +251,3 @@ function CodeHighlightToken(props: { token: HighlightToken; context: ContentRefC
return <span style={{ color: colorToCSSVar[token.token.color] }}>{token.token.content}</span>;
}
/**
* Compute the width ofthe gutter with the line number to align the code.
*/
function getLineNumberGutterWidth(block: DocumentBlockCode): ClassValue {
if (block.nodes.length < 10) {
return ['before:w-8', 'ml-8'];
} else if (block.nodes.length < 100) {
return ['before:w-10', 'ml-10'];
} else if (block.nodes.length < 1000) {
return ['before:w-12', 'ml-12'];
} else {
return ['before:w-14', 'ml-14'];
}
}
@@ -39,7 +39,7 @@ export function CopyCodeButton(props: { codeId: string; style: ClassValue }) {
};
return (
<button onClick={onClick} className={tcls(style, 'text-sm')}>
<button onClick={onClick} className={tcls(style)}>
{copied ? 'Copied!' : 'Copy'}
</button>
);
+20 -22
View File
@@ -1,27 +1,25 @@
:root {
--shiki-color-text: theme('colors.zinc.700');
--shiki-color-background: theme('colors.white');
--shiki-token-constant: theme('colors.emerald.700');
--shiki-token-string: theme('colors.emerald.700');
--shiki-token-comment: theme('colors.zinc.500');
--shiki-token-keyword: theme('colors.cyan.700');
--shiki-token-parameter: theme('colors.pink.700');
--shiki-token-function: theme('colors.violet.700');
--shiki-token-string-expression: theme('colors.emerald.700');
--shiki-token-punctuation: theme('colors.zinc.800');
--shiki-token-link: theme('colors.zinc.700');
--shiki-color-text: theme('colors.dark/.88');
--shiki-token-constant: #0a6355;
--shiki-token-string: #8b6d32;
--shiki-token-comment: theme('colors.teal.700/.64');
--shiki-token-keyword: theme('colors.pomegranate.600');
--shiki-token-parameter: #0a3069;
--shiki-token-function: #8250df;
--shiki-token-string-expression: #6a4906;
--shiki-token-punctuation: theme('colors.pomegranate.700/.92');
--shiki-token-link: theme('colors.dark');
}
html.dark {
--shiki-color-text: theme('colors.zinc.700');
--shiki-color-background: theme('colors.white');
--shiki-token-constant: theme('colors.emerald.700');
--shiki-token-string: theme('colors.emerald.700');
--shiki-token-comment: theme('colors.zinc.500');
--shiki-token-keyword: theme('colors.cyan.700');
--shiki-token-parameter: theme('colors.pink.700');
--shiki-token-function: theme('colors.violet.700');
--shiki-token-string-expression: theme('colors.emerald.700');
--shiki-token-punctuation: theme('colors.zinc.800');
--shiki-token-link: theme('colors.zinc.700');
--shiki-color-text: theme('colors.light/.88');
--shiki-token-constant: #d19a66;
--shiki-token-string: theme('colors.pomegranate.300');
--shiki-token-comment: theme('colors.teal.300/.64');
--shiki-token-keyword: theme('colors.pomegranate.400');
--shiki-token-parameter: theme('colors.yellow.500');
--shiki-token-function: #56b6c2;
--shiki-token-string-expression: theme('colors.pomegranate.400');
--shiki-token-punctuation: theme('colors.periwinkle');
--shiki-token-link: theme('colors.pomegranate.400');
}
+1 -1
View File
@@ -9,7 +9,7 @@ export function Paragraph(props: BlockProps<DocumentBlockParagraph>) {
const { block, style, ...contextProps } = props;
return (
<p className={tcls('font-normal', style)}>
<p className={tcls(style)}>
<Inlines {...contextProps} nodes={block.nodes} />
</p>
);
@@ -28,9 +28,25 @@ export async function RecordCard(
const body = (
<div className={tcls('grid-area-1-1', 'z-0', 'relative')}>
{cover ? (
<img alt="Cover" src={cover.href} className={tcls('w-full', 'aspect-video')} />
<img
alt="Cover"
src={cover.href}
className={tcls('w-full', 'aspect-video', 'object-cover')}
/>
) : null}
<div className={tcls('flex', 'flex-col', 'p-4')}>
<div
className={tcls(
'flex',
'flex-col',
'p-4',
'text-sm',
'text-dark/8',
'transition-colors',
'group-hover:text-dark/10',
'dark:text-light/8',
'dark:group-hover:text-light/10',
)}
>
{view.columns.map((column) => {
return <RecordColumnValue key={column} {...props} column={column} />;
})}
@@ -1,11 +1,14 @@
import { DocumentBlockTable, DocumentTableViewCards } from '@gitbook/api';
import Link from 'next/link';
import { getNodeFragmentByName } from '@/lib/document';
import { resolveContentRef } from '@/lib/references';
import { ClassValue, tcls } from '@/lib/tailwind';
import { TableRecordKV, TableViewProps } from './Table';
import { TableRecordKV } from './Table';
import { BlockProps } from '../Block';
import { Blocks } from '../Blocks';
import { Checkbox } from '../Checkbox';
/**
* Render the value for a column in a record.
@@ -15,10 +18,9 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
tag?: Tag;
record: TableRecordKV;
column: string;
columnStyle?: ClassValue;
},
) {
const { tag: Tag = 'div', block, record, column, columnStyle, context } = props;
const { tag: Tag = 'div', block, record, column, context } = props;
const definition = block.data.definition[column];
const value = record[1].values[column];
@@ -28,13 +30,49 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
}
switch (definition.type) {
case 'content-ref':
const target = await resolveContentRef(value as any, context);
if (!target) {
return null;
}
return (
<Link
className={tcls(
'whitespace-nowrap',
'text-sm',
'text-primary-400',
'hover:text-primary-500',
)}
href={target.href}
>
{target.text}
</Link>
);
case 'checkbox':
return (
<Checkbox
className={tcls('w-5', 'h-5')}
checked={value as boolean}
disabled={true}
/>
);
case 'rating':
return (
<Tag className={tcls('text-base', 'tabular-nums', 'tracking-tighter')}>
{`${value}`}
</Tag>
);
case 'number':
return <Tag className={tcls('text-base', columnStyle)}>{`${value}`}</Tag>;
return (
<Tag
className={tcls('text-base', 'tabular-nums', 'tracking-tighter')}
>{`${value}`}</Tag>
);
case 'text':
// @ts-ignore
const fragment = getNodeFragmentByName(block, value);
if (!fragment) {
return <Tag className={tcls([columnStyle, 'w-full'])}>{''}</Tag>;
return <Tag className={tcls(['w-full'])}>{''}</Tag>;
}
return (
@@ -42,9 +80,9 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
tag={Tag}
ancestorBlocks={[]}
nodes={fragment.nodes}
style={[columnStyle, 'w-full', 'space-y-2', 'lg:space-y-3']}
style={['w-full', 'space-y-2', 'lg:space-y-3']}
context={context}
blockStyle={['w-full']}
blockStyle={['w-full', 'max-w-[unset]']}
/>
);
default:
@@ -18,14 +18,14 @@ export async function RecordRow(
: ['[&>*+*]:border-l', '[&>*+*]:pl-4'];
return (
<tr className={tcls(tableTR, 'border-dark/2', 'flex-row', 'dark:border-light/2')}>
<tr className={tcls(tableTR, 'border-dark/2', 'dark:border-light/2')}>
{view.columns.map((column) => {
return (
<td
key={column}
className={tcls(
'align-baseline',
'min-w-[8rem]',
'min-w-[10rem]',
'border-dark/2',
'py-3',
'text-sm',
+39 -26
View File
@@ -8,11 +8,11 @@ import styles from './table.module.css';
export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
const { block, view, records, style } = props;
const columnsLengthThreshold = view.columns.length >= 7;
const tableLayout = columnsLengthThreshold ? 'table-auto' : 'table-fixed';
const tableWrapper = columnsLengthThreshold
const columnsOverThreshold = view.columns.length >= 7;
const tableWrapper = columnsOverThreshold
? [
'max-w-4xl',
// has over X columns
'w-full',
'overflow-x-auto',
'overflow-y-hidden',
@@ -21,22 +21,47 @@ export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
'border',
'border-dark/2',
'dark:border-light/2',
styles.progressContainer,
block.data.fullWidth
? [
// has over X columns, and is full width
'max-w-full',
]
: [
// NOT full width, but has over X columns
'max-w-4xl',
],
]
: ['max-w-3xl', 'w-full', 'overflow-x-auto', 'overflow-y-hidden', 'mx-auto'];
: [
'w-full',
'overflow-x-auto',
'overflow-y-hidden',
'mx-auto',
// has under X columns
block.data.fullWidth
? [
// has under X columns, and is full width
'max-w-full',
]
: [
// NOT full width, but has under X columns
'max-w-3xl',
],
];
const tableTR = columnsLengthThreshold
const tableTR = columnsOverThreshold
? ['[&>*+*]:border-l', '[&>*]:px-4']
: ['[&>*+*]:border-l', '[&>*+*]:pl-4'];
const tableTH = columnsLengthThreshold ? ['py-3'] : ['py-1', 'pt-0'];
const tableTH = columnsOverThreshold ? ['py-3'] : ['py-1', 'pt-0'];
return (
<div className={`${tcls('relative', 'grid', tableWrapper)}`}>
<div
className={`${tcls(style, 'relative', 'grid', tableWrapper, styles.progressContainer)}`}
>
{/* ProgressScroller: */}
<div
className={`${styles.progressOpacitySharp} ${tcls(
columnsLengthThreshold ? 'grid' : 'hidden',
'grid',
'items-center',
'grid-area-1-1',
'w-[5rem]',
@@ -112,20 +137,7 @@ export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
</div>
{/* Table: */}
<table
className={tcls(
style,
'block',
'w-full',
'grid-area-1-1',
tableLayout,
block.data.fullWidth
? ['max-w-full']
: columnsLengthThreshold
? ['max-w-full']
: null,
)}
>
<table className={tcls('w-full', 'grid-area-1-1', 'table-auto')}>
{view.hideHeader ? null : (
<thead>
<tr className={tcls(tableTR)}>
@@ -138,11 +150,12 @@ export function ViewGrid(props: TableViewProps<DocumentTableViewGrid>) {
'align-baseline',
'textwrap-balance',
'border-b',
'border-l-light/2',
'border-b-dark/4',
'text-left',
'text-sm',
'lg:text-base',
'dark:border-b-light/6',
'dark:border-l-light/2',
'dark:border-b-light/5',
)}
>
{block.data.definition[column].title}
@@ -13,6 +13,7 @@
}
.progressOpacitySharp {
opacity: 0;
animation-name: opacityProgressSharp;
animation-duration: 1ms;
animation-direction: alternate;
@@ -17,6 +17,7 @@ export function CollectionSpacesDropdown(props: {
<div
{...buttonProps}
className={tcls(
'justify-self-start',
'flex',
'flex-row',
'items-center',
@@ -24,8 +25,9 @@ export function CollectionSpacesDropdown(props: {
'rounded-full',
'px-3',
'py-1',
'bg-header-background-400',
'text-header-link-500',
'bg-dark/2',
'dark:bg-light/2',
/* 'text-header-link-500', */
)}
>
{space.title}
+3 -1
View File
@@ -121,7 +121,9 @@ export function DropdownMenuItem(props: {
'px-3',
'py-1',
'rounded',
active ? ['bg-primary-50'] : ['hover:bg-dark/2', 'dark:hover:bg-light/2'],
active
? ['bg-primary-100', 'dark:bg-light/2']
: ['hover:bg-dark/2', 'dark:hover:bg-light/2'],
)}
>
{children}
+11 -9
View File
@@ -58,8 +58,8 @@ export function Header(props: {
className={tcls(
'gap-8',
'grid',
'grid-cols-[min-content_min-content]',
'sm:grid-cols-[min-content_1fr_min-content]',
'grid-flow-col',
'auto-cols-[auto_auto_1fr_auto]',
'h-16',
'items-center',
'align-center',
@@ -70,13 +70,15 @@ export function Header(props: {
)}
>
<HeaderLogo collection={collection} space={space} customization={customization} />
{collection ? (
<CollectionSpacesDropdown
space={space}
collection={collection}
collectionSpaces={collectionSpaces}
/>
) : null}
<span>
{collection ? (
<CollectionSpacesDropdown
space={space}
collection={collection}
collectionSpaces={collectionSpaces}
/>
) : null}
</span>
<HeaderLinks>
{customization.header.links.map((link, index) => {
return (
+3 -1
View File
@@ -11,10 +11,12 @@ export async function HeaderLinks({ children }: HeaderLinksProps) {
<div
className={tcls(
'@container/headerlinks',
'w-full',
'hidden',
'sm:flex',
'sm:inline-flex',
'justify-end',
'h-full',
'tracking-[-0.02em]',
//hide children
`[&>*]:hidden`,
//hide each child per container breakpoint