Files
coder/site/src/components/CodeExample/CodeExample.tsx
T
Jaayden Halko 6c2900f138 refactor: use a global tooltip provider with a consistent 100 millisecond delay duration (#20869)
Part 1 of 2

- this sets up the TooltipProvider for Storybook in preview.tsx and for
the app in App.tsx along with removing TooltipProvider in some of the
usages of Tooltip
- I tested existing components that haven't had the TooltipProvider
removed and they still function correctly. So should be fine until the
2nd PR to complete the migration.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-24 18:22:54 -05:00

132 lines
3.5 KiB
TypeScript

import type { Interpolation, Theme } from "@emotion/react";
import { Button } from "components/Button/Button";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import { EyeIcon, EyeOffIcon } from "lucide-react";
import { type FC, useState } from "react";
import { MONOSPACE_FONT_FAMILY } from "theme/constants";
import { CopyButton } from "../CopyButton/CopyButton";
interface CodeExampleProps {
code: string;
/** Defaulting to true to be on the safe side; you should have to opt out of the secure option, not remember to opt in */
secret?: boolean;
/** Redact parts of the code if the user doesn't want to obfuscate the whole code */
redactPattern?: RegExp;
/** Replacement text for redacted content */
redactReplacement?: string;
/** Show a button to reveal the redacted parts of the code */
showRevealButton?: boolean;
className?: string;
}
/**
* Component to show single-line code examples, with a copy button
*/
export const CodeExample: FC<CodeExampleProps> = ({
code,
className,
secret = true,
redactPattern,
redactReplacement = "********",
showRevealButton,
}) => {
const [showFullValue, setShowFullValue] = useState(false);
const displayValue = secret
? obfuscateText(code)
: redactPattern && !showFullValue
? code.replace(redactPattern, redactReplacement)
: code;
const showButtonLabel = showFullValue
? "Hide sensitive data"
: "Show sensitive data";
const icon = showFullValue ? (
<EyeOffIcon className="h-4 w-4" />
) : (
<EyeIcon className="h-4 w-4" />
);
return (
<div css={styles.container} className={className}>
<code css={[styles.code, secret && styles.secret]}>
{secret ? (
<>
{/*
* Obfuscating text even though we have the characters replaced with
* discs in the CSS for two reasons:
* 1. The CSS property is non-standard and won't work everywhere;
* MDN warns you not to rely on it alone in production
* 2. Even with it turned on and supported, the plaintext is still
* readily available in the HTML itself
*/}
<span aria-hidden>{displayValue}</span>
<span className="sr-only">
Encrypted text. Please access via the copy button.
</span>
</>
) : (
displayValue
)}
</code>
<div className="flex items-center gap-1">
{showRevealButton && redactPattern && !secret && (
<Tooltip>
<TooltipTrigger asChild>
<Button
size="icon"
variant="subtle"
onClick={() => setShowFullValue(!showFullValue)}
>
{icon}
<span className="sr-only">{showButtonLabel}</span>
</Button>
</TooltipTrigger>
<TooltipContent>{showButtonLabel}</TooltipContent>
</Tooltip>
)}
<CopyButton text={code} label="Copy code" />
</div>
</div>
);
};
function obfuscateText(text: string): string {
return new Array(text.length).fill("*").join("");
}
const styles = {
container: (theme) => ({
cursor: "pointer",
display: "flex",
flexDirection: "row",
alignItems: "center",
color: theme.experimental.l1.text,
fontFamily: MONOSPACE_FONT_FAMILY,
fontSize: 14,
borderRadius: 8,
padding: 8,
lineHeight: "150%",
border: `1px solid ${theme.experimental.l1.outline}`,
"&:hover": {
backgroundColor: theme.experimental.l2.hover.background,
},
}),
code: {
padding: "0 8px",
flexGrow: 1,
wordBreak: "break-all",
},
secret: {
"-webkit-text-security": "disc", // also supported by firefox
},
} satisfies Record<string, Interpolation<Theme>>;