Improve rendering of File column in tables. (#2297)

This commit is contained in:
Steven H
2024-04-19 16:58:46 +01:00
committed by GitHub
parent 4a8097d968
commit 2b63bc158a
5 changed files with 100 additions and 50 deletions
+6 -41
View File
@@ -1,12 +1,10 @@
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 { getSimplifiedContentType } from '@/lib/files';
import { tcls } from '@/lib/tailwind';
import { BlockProps } from './Block';
import { FileIcon } from './FileIcon';
export async function File(props: BlockProps<DocumentBlockFile>) {
const { block, context, style } = props;
@@ -18,42 +16,7 @@ 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 />;
}
})();
const contentType = getSimplifiedContentType(file.contentType);
return (
<a
@@ -103,7 +66,9 @@ export async function File(props: BlockProps<DocumentBlockFile>) {
'dark:border-light/2',
)}
>
<div className={tcls('*:w-5', '*:h-5', '*:stroke-primary')}>{icon}</div>
<div className={tcls('*:w-5', '*:h-5', '*:stroke-primary')}>
<FileIcon contentType={contentType} />
</div>
<div
className={tcls(
'text-xs',
+24
View File
@@ -0,0 +1,24 @@
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 { SimplifiedFileType } from '@/lib/files';
/**
* Render an appropriate icon for a file.
*/
export function FileIcon(props: { contentType: SimplifiedFileType | null }) {
const { contentType } = props;
switch (contentType) {
case 'pdf':
return <IconFileText />;
case 'image':
return <IconImage />;
case 'archive':
return <IconPaperClip />;
default:
return <IconDownload />;
}
}
@@ -4,7 +4,9 @@ import assertNever from 'assert-never';
import { Checkbox, Emoji } from '@/components/primitives';
import { StyledLink } from '@/components/primitives';
import { Image } from '@/components/utils';
import { getNodeFragmentByName } from '@/lib/document';
import { getSimplifiedContentType } from '@/lib/files';
import { tcls } from '@/lib/tailwind';
import { filterOutNullable } from '@/lib/typescript';
@@ -12,6 +14,7 @@ import { TableRecordKV } from './Table';
import { getColumnAlignment } from './utils';
import { BlockProps } from '../Block';
import { Blocks } from '../Blocks';
import { FileIcon } from '../FileIcon';
/**
* Render the value for a column in a record.
@@ -125,11 +128,41 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
return (
<Tag className={tcls('text-base')}>
{files.filter(filterOutNullable).map((file, index) => (
<StyledLink key={index} href={file.href}>
{file.text}
</StyledLink>
))}
{files.filter(filterOutNullable).map((ref, index) => {
const contentType = ref.file
? getSimplifiedContentType(ref.file.contentType)
: null;
return (
<StyledLink
key={index}
href={ref.href}
target="_blank"
style={['flex', 'flex-row', 'items-center', 'gap-2']}
>
{contentType === 'image' ? (
<Image
style={['max-h-[1lh]', 'h-[1lh]']}
alt={ref.text}
sizes={[{ width: 24 }]}
sources={{
light: {
src: ref.href,
size: {
width: 24,
height: 24,
},
},
}}
priority="lazy"
/>
) : (
<FileIcon contentType={contentType} />
)}
{ref.text}
</StyledLink>
);
})}
</Tag>
);
case 'content-ref': {
@@ -208,4 +241,4 @@ export async function RecordColumnValue<Tag extends React.ElementType = 'div'>(
default:
assertNever(definition);
}
}
}
+6 -3
View File
@@ -1,14 +1,16 @@
import { tcls } from '@/lib/tailwind';
import { ClassValue, tcls } from '@/lib/tailwind';
import { Link, LinkProps } from '../primitives/Link';
/**
* Styled version of Link component.
*/
export function StyledLink(props: LinkProps) {
export function StyledLink(props: Omit<LinkProps, 'style'> & { style?: ClassValue }) {
const { style, ...rest } = props;
return (
<Link
{...props}
{...rest}
className={tcls(
'underline',
'underline-offset-2',
@@ -16,6 +18,7 @@ export function StyledLink(props: LinkProps) {
'text-primary',
'hover:text-primary-700',
'transition-colors',
style,
)}
>
{props.children}
+25
View File
@@ -0,0 +1,25 @@
export type SimplifiedFileType = 'image' | 'pdf' | 'archive';
/**
* Get a simplified content type for the given mime type.
*/
export function getSimplifiedContentType(mimeType: string): SimplifiedFileType | null {
if (mimeType.startsWith('image')) {
return 'image';
}
switch (mimeType) {
case 'application/pdf':
case 'application/x-pdf':
return 'pdf';
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;
}
}