mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 18:05:11 +03:00
## Context In the table editor when hovering over a row that has fixed columns, the wordings could appear like they're overlapping <img width="339" height="95" alt="image" src="https://github.com/user-attachments/assets/9de8252c-08fc-4dec-a2f2-21461e47691e" /> This is due to the `bg-surface-200` class that gets applied to rows on hover which use alpha values, hence why it's happening. Opting to use `bg-200` instead which doesn't have alpha values. Also small fix for the copy button in `CodeBlock` as well which is running into the same issue - opting to use a set of colors that doesn't have alpha values instead. <img width="127" height="115" alt="image" src="https://github.com/user-attachments/assets/4d773474-49e0-4c3b-bea1-042ff3102f9c" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Style** * Updated table header, row hover, and selected-row background colors for a more consistent appearance. * Enhanced the code block copy button’s dark-mode and hover background styling. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
286 lines
10 KiB
TypeScript
286 lines
10 KiB
TypeScript
'use client'
|
|
|
|
// @ts-ignore
|
|
import curl from 'highlightjs-curl'
|
|
import { noop } from 'lodash'
|
|
import { Check, Copy } from 'lucide-react'
|
|
import { useTheme } from 'next-themes'
|
|
import { Children, ReactNode, useState } from 'react'
|
|
import { Light as SyntaxHighlighter, SyntaxHighlighterProps } from 'react-syntax-highlighter'
|
|
import bash from 'react-syntax-highlighter/dist/cjs/languages/hljs/bash'
|
|
import csharp from 'react-syntax-highlighter/dist/cjs/languages/hljs/csharp'
|
|
import dart from 'react-syntax-highlighter/dist/cjs/languages/hljs/dart'
|
|
import go from 'react-syntax-highlighter/dist/cjs/languages/hljs/go'
|
|
import http from 'react-syntax-highlighter/dist/cjs/languages/hljs/http'
|
|
import ini from 'react-syntax-highlighter/dist/cjs/languages/hljs/ini'
|
|
import js from 'react-syntax-highlighter/dist/cjs/languages/hljs/javascript'
|
|
import json from 'react-syntax-highlighter/dist/cjs/languages/hljs/json'
|
|
import kotlin from 'react-syntax-highlighter/dist/cjs/languages/hljs/kotlin'
|
|
import pgsql from 'react-syntax-highlighter/dist/cjs/languages/hljs/pgsql'
|
|
import php from 'react-syntax-highlighter/dist/cjs/languages/hljs/php'
|
|
import {
|
|
default as py,
|
|
default as python,
|
|
} from 'react-syntax-highlighter/dist/cjs/languages/hljs/python'
|
|
import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql'
|
|
import swift from 'react-syntax-highlighter/dist/cjs/languages/hljs/swift'
|
|
import ts from 'react-syntax-highlighter/dist/cjs/languages/hljs/typescript'
|
|
import xml from 'react-syntax-highlighter/dist/cjs/languages/hljs/xml'
|
|
import yaml from 'react-syntax-highlighter/dist/cjs/languages/hljs/yaml'
|
|
import { Button, cn, copyToClipboard } from 'ui'
|
|
|
|
import { monokaiCustomTheme } from './CodeBlock.utils'
|
|
|
|
const codeBlockLangs = [
|
|
'js',
|
|
'jsx',
|
|
'sql',
|
|
'py',
|
|
'bash',
|
|
'ts',
|
|
'dart',
|
|
'json',
|
|
'csharp',
|
|
'kotlin',
|
|
'curl',
|
|
'http',
|
|
'php',
|
|
'python',
|
|
'go',
|
|
'pgsql',
|
|
'swift',
|
|
'yaml',
|
|
'toml',
|
|
'html',
|
|
] as const
|
|
|
|
export type CodeBlockLang = (typeof codeBlockLangs)[number]
|
|
|
|
export function isCodeBlockLang(lang: string): lang is CodeBlockLang {
|
|
return (codeBlockLangs as readonly string[]).includes(lang)
|
|
}
|
|
|
|
export interface CodeBlockProps {
|
|
title?: ReactNode
|
|
language?: CodeBlockLang
|
|
linesToHighlight?: number[]
|
|
highlightBorder?: boolean
|
|
styleConfig?: {
|
|
lineNumber?: string
|
|
highlightBackgroundColor?: string
|
|
highlightBorderColor?: string
|
|
}
|
|
hideCopy?: boolean
|
|
hideLineNumbers?: boolean
|
|
className?: string
|
|
wrapperClassName?: string
|
|
value?: string
|
|
theme?: any
|
|
children?: string
|
|
wrapLines?: boolean
|
|
focusable?: boolean
|
|
renderer?: SyntaxHighlighterProps['renderer']
|
|
handleCopy?: (value?: string) => void
|
|
onCopyCallback?: () => void
|
|
}
|
|
|
|
/**
|
|
* CodeBlock component for displaying syntax-highlighted code.
|
|
* @param {ReactNode} [props.title] - Optional title for the code block.
|
|
* @param {string} [props.language] - The programming language of the code.
|
|
* @param {number[]} [props.linesToHighlight=[]] - Array of line numbers to highlight.
|
|
* @param {boolean} [props.highlightBorder] - Whether to show a border on highlighted lines.
|
|
* @param {Object} [props.styleConfig] - Custom style configurations.
|
|
* @param {string} [props.className] - Additional CSS classes for the code block.
|
|
* @param {string} [props.wrapperClassName] - CSS classes for the wrapper div.
|
|
* @param {string} [props.value] - The code content as a string.
|
|
* @param {any} [props.theme] - Custom theme for syntax highlighting.
|
|
* @param {string} [props.children] - The code content as children.
|
|
* @param {boolean} [props.hideCopy=false] - Whether to hide the copy button.
|
|
* @param {boolean} [props.hideLineNumbers=false] - Whether to hide line numbers.
|
|
* @param {SyntaxHighlighterProps['renderer']} [props.renderer] - Custom renderer for syntax highlighting.
|
|
* @param {boolean} [props.focusable=true] - Whether the code block is focusable. When true, users can focus the code block to select text or use ⌘A (Cmd+A) to select all. This is so we don't need to load Monaco Editor.
|
|
* @param {function} [props.handleCopy] - Optional override behaviour for copying value. For e.g if the code block contains obfuscated values, but the copy behaviour should reveal those values instead.
|
|
*/
|
|
export const CodeBlock = ({
|
|
title,
|
|
language,
|
|
linesToHighlight = [],
|
|
highlightBorder,
|
|
styleConfig,
|
|
className,
|
|
wrapperClassName,
|
|
value,
|
|
theme,
|
|
children,
|
|
hideCopy = false,
|
|
hideLineNumbers = false,
|
|
wrapLines = true,
|
|
renderer,
|
|
focusable = true,
|
|
onCopyCallback = noop,
|
|
handleCopy,
|
|
}: CodeBlockProps) => {
|
|
const { resolvedTheme } = useTheme()
|
|
const isDarkTheme = resolvedTheme?.includes('dark')!
|
|
const monokaiTheme = theme ?? monokaiCustomTheme(isDarkTheme)
|
|
|
|
const [copied, setCopied] = useState(false)
|
|
|
|
const onSelectCopy = (value?: string) => {
|
|
if (value) {
|
|
if (!!handleCopy) {
|
|
handleCopy(value)
|
|
} else {
|
|
copyToClipboard(value)
|
|
}
|
|
}
|
|
setCopied(true)
|
|
onCopyCallback()
|
|
setTimeout(() => setCopied(false), 1000)
|
|
}
|
|
|
|
// Extract string when `children` has a single string node
|
|
const childrenArray = Children.toArray(children)
|
|
const [singleChild] = childrenArray.length === 1 ? childrenArray : []
|
|
const singleString = typeof singleChild === 'string' ? singleChild : undefined
|
|
|
|
let codeValue = value ?? singleString ?? children
|
|
codeValue = codeValue?.trimEnd?.() ?? codeValue
|
|
|
|
// check the length of the string inside the <code> tag
|
|
// if it's fewer than 70 characters, add a white-space: pre so it doesn't wrap
|
|
const shortCodeBlockClasses =
|
|
typeof codeValue === 'string' && codeValue.length < 70 ? 'short-inline-codeblock' : ''
|
|
|
|
let lang = language ? language : className ? className.replace('language-', '') : 'js'
|
|
// force jsx to be js highlighted
|
|
if (lang === 'jsx') lang = 'js'
|
|
SyntaxHighlighter.registerLanguage('js', js)
|
|
SyntaxHighlighter.registerLanguage('ts', ts)
|
|
SyntaxHighlighter.registerLanguage('py', py)
|
|
SyntaxHighlighter.registerLanguage('sql', sql)
|
|
SyntaxHighlighter.registerLanguage('bash', bash)
|
|
SyntaxHighlighter.registerLanguage('dart', dart)
|
|
SyntaxHighlighter.registerLanguage('csharp', csharp)
|
|
SyntaxHighlighter.registerLanguage('json', json)
|
|
SyntaxHighlighter.registerLanguage('kotlin', kotlin)
|
|
SyntaxHighlighter.registerLanguage('curl', curl)
|
|
SyntaxHighlighter.registerLanguage('http', http)
|
|
SyntaxHighlighter.registerLanguage('php', php)
|
|
SyntaxHighlighter.registerLanguage('python', python)
|
|
SyntaxHighlighter.registerLanguage('go', go)
|
|
SyntaxHighlighter.registerLanguage('pgsql', pgsql)
|
|
SyntaxHighlighter.registerLanguage('swift', swift)
|
|
SyntaxHighlighter.registerLanguage('html', xml)
|
|
SyntaxHighlighter.registerLanguage('toml', ini)
|
|
SyntaxHighlighter.registerLanguage('yaml', yaml)
|
|
|
|
const large = false
|
|
// don't show line numbers if bash == lang
|
|
if (lang === 'bash' || lang === 'sh') hideLineNumbers = true
|
|
const showLineNumbers = !hideLineNumbers
|
|
|
|
return (
|
|
<>
|
|
{title && (
|
|
<div className="text-sm rounded-t-md bg-surface-100 py-2 px-4 border border-b-0 border-default font-sans">
|
|
{title}
|
|
</div>
|
|
)}
|
|
{className ? (
|
|
<div
|
|
className={cn(
|
|
'group relative max-w-[90vw] md:max-w-none overflow-auto',
|
|
wrapperClassName
|
|
)}
|
|
>
|
|
{/* @ts-ignore */}
|
|
<SyntaxHighlighter
|
|
suppressContentEditableWarning
|
|
language={lang}
|
|
wrapLines={wrapLines}
|
|
style={monokaiTheme}
|
|
className={cn(
|
|
'code-block border border-surface p-4 w-full my-0! !bg-surface-100 outline-hidden focus:border-foreground-lighter/50',
|
|
`${!title ? 'rounded-md' : 'rounded-t-none rounded-b-md'}`,
|
|
`${!showLineNumbers ? 'pl-6' : ''}`,
|
|
className
|
|
)}
|
|
customStyle={{
|
|
fontSize: large ? 18 : 13,
|
|
lineHeight: large ? 1.5 : 1.4,
|
|
}}
|
|
showLineNumbers={showLineNumbers}
|
|
lineProps={(lineNumber) => {
|
|
if (linesToHighlight.includes(lineNumber)) {
|
|
return {
|
|
style: {
|
|
display: 'block',
|
|
backgroundColor: styleConfig?.highlightBackgroundColor
|
|
? styleConfig?.highlightBackgroundColor
|
|
: 'var(--background-selection)',
|
|
borderLeft: highlightBorder
|
|
? `1px solid ${styleConfig?.highlightBorderColor ? styleConfig?.highlightBorderColor : 'var(--foreground-default)'}`
|
|
: undefined,
|
|
},
|
|
class: 'hljs-line-highlight',
|
|
}
|
|
}
|
|
return {}
|
|
}}
|
|
lineNumberContainerStyle={{
|
|
paddingTop: '128px',
|
|
}}
|
|
lineNumberStyle={{
|
|
minWidth: '44px',
|
|
paddingLeft: '4px',
|
|
paddingRight: '4px',
|
|
marginRight: '12px',
|
|
color: styleConfig?.lineNumber ?? '#828282',
|
|
textAlign: 'center',
|
|
fontSize: large ? 14 : 12,
|
|
paddingTop: '4px',
|
|
paddingBottom: '4px',
|
|
}}
|
|
renderer={renderer}
|
|
contentEditable={focusable}
|
|
onBeforeInput={(e: any) => {
|
|
e.preventDefault()
|
|
return false
|
|
}}
|
|
onKeyDown={(e: any) => {
|
|
if (e.code === 'Backspace') {
|
|
e.preventDefault()
|
|
return false
|
|
}
|
|
}}
|
|
>
|
|
{codeValue}
|
|
</SyntaxHighlighter>
|
|
{!hideCopy && (value || children) && className ? (
|
|
<div
|
|
className={[
|
|
'absolute right-2 top-2',
|
|
'opacity-0 group-hover:opacity-100 transition',
|
|
`${isDarkTheme ? 'dark' : ''}`,
|
|
].join(' ')}
|
|
>
|
|
<Button
|
|
variant="default"
|
|
className="px-1.5 dark:bg-200! dark:hover:bg-button! hover:bg-alternative!"
|
|
icon={copied ? <Check /> : <Copy />}
|
|
onClick={() => onSelectCopy(value || children)}
|
|
>
|
|
{copied ? 'Copied' : ''}
|
|
</Button>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
) : (
|
|
<code className={shortCodeBlockClasses}>{value || children}</code>
|
|
)}
|
|
</>
|
|
)
|
|
}
|