Files
James Ritchie 244988e212 New Help & Feedback menu and tab-selected styles (#1374)
* Added a new dropdown help and feedback menu to the side menu

* Added a shortcut to the popover menu

* Removed dev cli connected button for now

* Contact us form uses original Feedback component to prevent broken links

* Improved the messaging when selecting different options in the email form

* buttons style tweak

* SideMenuItem supports the trailingIconClassName

* Adding a consistent focus-visible states

* Removing tooltips for now

* Squashed commit of the following:

commit 7d11123c0a
Author: Eric Goldman <eric@sequin.io>
Date:   Mon Sep 30 17:54:06 2024 -0700

    Add sequin guide (#1368)

    Co-authored-by: James Ritchie <james@trigger.dev>

commit 8da495ac00
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Mon Sep 30 13:42:22 2024 +0100

    Improve checkpoint reliability and cleanup of temp files (#1367)

    * improve cleanup reliability

    * improve logging

    * bye-bye execa

    * fix for trailing newlines

    * prettier errors

    * trim args and log output by default

    * fix archive cleanup

    * prevent potential memleak

    * more cleanup debug logs

    * ignore abort during cleanup

    * rename checkpoint dir env var and move to helper

    * add global never throw override

    * add tmp cleaner

    * also clean up checkpoint dir by default

    * split by any whitespace, not just tabs

    * only create tmp cleaner if paths to clean

commit 69ec68ee31
Author: Eric Allam <eallam@icloud.com>
Date:   Sun Sep 29 19:18:39 2024 -0700

    Release 3.0.9

commit a6ea8444c9
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Sun Sep 29 19:17:26 2024 -0700

    chore: Update version for release (#1366)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

commit 4c1ee3d6ea
Author: Eric Allam <eallam@icloud.com>
Date:   Sun Sep 29 19:09:38 2024 -0700

    fix: run metadata not working when using npx/pnpm dlx

* More support for custom-focus

* More custom focus styles added

* Support for focus-visible style for the Segmented control

* Fixed table triple dot menu z-index issue

* Improved help menu wording

* When you submit the help form, close the modal

* focus-visible style for radio buttons

* button prop is now optional in the SideMenu component

* focus styling for a text link

* Deleted unused sequin files
2024-10-10 14:28:18 +01:00

397 lines
11 KiB
TypeScript

import { Clipboard, ClipboardCheck } from "lucide-react";
import type { Language, PrismTheme } from "prism-react-renderer";
import { Highlight, Prism } from "prism-react-renderer";
import { forwardRef, ReactNode, useCallback, useState } from "react";
import { cn } from "~/utils/cn";
import { Paragraph } from "../primitives/Paragraph";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip";
//This is a fork of https://github.com/mantinedev/mantine/blob/master/src/mantine-prism/src/Prism/Prism.tsx
//it didn't support highlighting lines by dimming the rest of the code, or animations on the highlighting
async function setup() {
(typeof global !== "undefined" ? global : window).Prism = Prism;
//@ts-ignore
await import("prismjs/components/prism-json");
//@ts-ignore
await import("prismjs/components/prism-typescript");
}
setup();
type CodeBlockProps = {
/** Code which will be highlighted */
code: string;
/** Programming language that should be highlighted */
language?: Language;
/** Show copy to clipboard button */
showCopyButton?: boolean;
/** Display line numbers */
showLineNumbers?: boolean;
/** Highlight line at given line number with color from theme.colors */
highlightedRanges?: [number, number][];
/** Add/override classes on the overall element */
className?: string;
/** Add/override code theme */
theme?: PrismTheme;
/** Max lines */
maxLines?: number;
/** Whether to show the chrome, if you provide a string it will be used as the title, */
showChrome?: boolean;
/** filename */
fileName?: string;
/** title text for the Title row */
rowTitle?: string;
};
const dimAmount = 0.5;
const extraLinesWhenClipping = 0.35;
const defaultTheme: PrismTheme = {
plain: {
color: "#9C9AF2",
backgroundColor: "rgba(0, 0, 0, 0)",
},
styles: [
{
types: ["comment", "prolog", "doctype", "cdata"],
style: {
color: "#5F6570",
},
},
{
types: ["punctuation"],
style: {
color: "#878C99",
},
},
{
types: ["property", "tag", "boolean", "number", "constant", "symbol", "deleted"],
style: {
color: "#9B99FF",
},
},
{
types: ["selector", "attr-name", "string", "char", "builtin", "inserted"],
style: {
color: "#AFEC73",
},
},
{
types: ["operator", "entity", "url"],
style: {
color: "#D4D4D4",
},
},
{
types: ["variable"],
style: {
color: "#CCCBFF",
},
},
{
types: ["atrule", "attr-value", "keyword"],
style: {
color: "#E888F8",
},
},
{
types: ["function", "class-name"],
style: {
color: "#D9F07C",
},
},
{
types: ["regex"],
style: {
color: "#d16969",
},
},
{
types: ["important", "bold"],
style: {
fontWeight: "bold",
},
},
{
types: ["italic"],
style: {
fontStyle: "italic",
},
},
{
types: ["namespace"],
style: {
opacity: 0.7,
},
},
{
types: ["deleted"],
style: {
color: "#F85149",
},
},
{
types: ["boolean"],
style: {
color: "#9B99FF",
},
},
{
types: ["char"],
style: {
color: "#b5cea8",
},
},
{
types: ["tag"],
style: {
color: "#D7BA7D",
},
},
{
types: ["keyword.operator"],
style: {
color: "#8271ED",
},
},
{
types: ["meta.template.expression"],
style: {
color: "#d4d4d4",
},
},
],
};
export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
(
{
showCopyButton = true,
showLineNumbers = true,
highlightedRanges,
code,
className,
language = "typescript",
theme = defaultTheme,
maxLines,
showChrome = false,
fileName,
rowTitle,
...props
}: CodeBlockProps,
ref
) => {
const [mouseOver, setMouseOver] = useState(false);
const [copied, setCopied] = useState(false);
const onCopied = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
event.stopPropagation();
navigator.clipboard.writeText(code);
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 1500);
},
[code]
);
code = code.trim();
const lineCount = code.split("\n").length;
const maxLineWidth = lineCount.toString().length;
let maxHeight: string | undefined = undefined;
if (maxLines && lineCount > maxLines) {
maxHeight = `calc(${(maxLines + extraLinesWhenClipping) * 0.75 * 1.625}rem + 1.5rem )`;
}
const highlightLines = highlightedRanges?.flatMap(([start, end]) =>
Array.from({ length: end - start + 1 }, (_, i) => start + i)
);
// if there are more than 1000 lines, don't highlight
const shouldHighlight = lineCount <= 1000;
return (
<div
className={cn("relative overflow-hidden rounded-md border border-grid-bright", className)}
style={{
backgroundColor: theme.plain.backgroundColor,
}}
ref={ref}
{...props}
translate="no"
>
{showChrome && <Chrome title={fileName} />}
{rowTitle && <TitleRow title={rowTitle} />}
{showCopyButton && (
<TooltipProvider>
<Tooltip open={copied || mouseOver}>
<TooltipTrigger
onClick={onCopied}
onMouseEnter={() => setMouseOver(true)}
onMouseLeave={() => setMouseOver(false)}
className={cn(
"absolute right-3 z-50 transition-colors duration-100 focus-custom hover:cursor-pointer",
showChrome ? "top-10" : "top-2.5",
copied ? "text-emerald-500" : "text-charcoal-500 hover:text-charcoal-300"
)}
>
{copied ? <ClipboardCheck className="size-4" /> : <Clipboard className="size-4" />}
</TooltipTrigger>
<TooltipContent side="left" className="text-xs">
{copied ? "Copied" : "Copy"}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{shouldHighlight ? (
<Highlight theme={theme} code={code} language={language}>
{({
className: inheritedClassName,
style: inheritedStyle,
tokens,
getLineProps,
getTokenProps,
}) => (
<div
dir="ltr"
className="overflow-auto px-2 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
style={{
maxHeight,
}}
>
<pre
className={cn(
"relative mr-2 font-mono text-xs leading-relaxed",
inheritedClassName
)}
style={inheritedStyle}
dir="ltr"
>
{tokens
.map((line, index) => {
if (
index === tokens.length - 1 &&
line.length === 1 &&
line[0].content === "\n"
) {
return null;
}
const lineNumber = index + 1;
const lineProps = getLineProps({ line, key: index });
let hasAnyHighlights = highlightLines ? highlightLines.length > 0 : false;
let shouldDim = hasAnyHighlights;
if (hasAnyHighlights && highlightLines?.includes(lineNumber)) {
shouldDim = false;
}
return (
<div
key={lineNumber}
{...lineProps}
className={cn(
"flex w-full justify-start transition-opacity duration-500",
lineProps.className
)}
style={{
opacity: shouldDim ? dimAmount : undefined,
...lineProps.style,
}}
>
{showLineNumbers && (
<div
className={
"mr-2 flex-none select-none text-right text-charcoal-500 transition-opacity duration-500"
}
style={{
width: `calc(8 * ${maxLineWidth / 16}rem)`,
}}
>
{lineNumber}
</div>
)}
<div className="flex-1">
{line.map((token, key) => {
const tokenProps = getTokenProps({ token, key });
return (
<span
key={key}
{...tokenProps}
style={{
color: tokenProps?.style?.color as string,
...tokenProps.style,
}}
/>
);
})}
</div>
<div className="w-4 flex-none" />
</div>
);
})
.filter(Boolean)}
</pre>
</div>
)}
</Highlight>
) : (
<div
dir="ltr"
className="overflow-auto px-2 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
style={{
maxHeight,
}}
>
<pre className="relative mr-2 p-2 font-mono text-xs leading-relaxed" dir="ltr">
{code}
</pre>
</div>
)}
</div>
);
}
);
CodeBlock.displayName = "CodeBlock";
function Chrome({ title }: { title?: string }) {
return (
<div className="grid h-7 grid-cols-[100px_auto_100px] border-b border-charcoal-800 bg-charcoal-900">
<div className="ml-2 flex items-center gap-2">
<div className="h-3 w-3 rounded-full bg-charcoal-700" />
<div className="h-3 w-3 rounded-full bg-charcoal-700" />
<div className="h-3 w-3 rounded-full bg-charcoal-700" />
</div>
<div className="flex items-center justify-center">
<div className={cn("rounded-sm px-3 py-0.5 text-xs text-charcoal-500")}>{title}</div>
</div>
<div></div>
</div>
);
}
export function TitleRow({ title }: { title: ReactNode }) {
return (
<div className="flex items-center justify-between px-3">
<Paragraph variant="small/bright" className="w-full border-b border-grid-dimmed py-2">
{title}
</Paragraph>
</div>
);
}