Skip to content

Commit d3b3d3d

Browse files
committed
More intuitive selection and clipboard support
1 parent 9fb922c commit d3b3d3d

File tree

2 files changed

+31
-5
lines changed

2 files changed

+31
-5
lines changed

packages/web/app/src/components/ui/code.tsx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ComponentProps, FC } from 'react';
1+
import { useRef, type ComponentProps, type FC } from 'react';
22
import cn from 'clsx';
33
import { useHover } from '@/lib/hooks/use-hover';
44
import { useTimed } from '@/lib/hooks/use-timed';
@@ -7,28 +7,46 @@ import { CheckIcon, CopyIcon } from './icon';
77
export const Code: FC<ComponentProps<'code'>> = ({ children, className, ...props }) => {
88
const [copied, startCopyTimer] = useTimed(1500);
99
const [ref, hovering] = useHover();
10+
const codeRef = useRef<HTMLElement | null>(null)
11+
// in case this browser does not support this newer API...
12+
const navigatorClipboardSupport = typeof navigator.clipboard?.writeText === 'function';
1013
return (
1114
<span
1215
ref={ref}
13-
className="relative flex items-center gap-2 break-all rounded-md border border-gray-600 bg-black p-4 pr-14 font-mono text-sm"
16+
className="relative flex items-center gap-2 break-all rounded-md border border-gray-600 bg-black p-4 pr-14 font-mono text-sm cursor-text"
17+
// Make this element able to be focused by setting tabIndex.
18+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
19+
tabIndex={0}
20+
onFocus={() => {
21+
if (codeRef.current) {
22+
const selection = window.getSelection();
23+
const range = document.createRange();
24+
range.setStart(codeRef.current, 0);
25+
range.setEnd(codeRef.current, codeRef.current.childNodes.length);
26+
selection?.removeAllRanges();
27+
selection?.addRange(range);
28+
}
29+
}}
1430
>
1531
<code
16-
className={cn('whitespace-pre-line', 'cursor-text', className)}
32+
ref={codeRef}
33+
className={cn('whitespace-pre-line', className)}
1734
// always show code blocks in ltr
1835
dir="ltr"
1936
{...props}
2037
>
2138
{children}
2239
</code>
2340
<button
41+
hidden={!navigatorClipboardSupport}
2442
data-hovering={hovering || copied}
2543
className="absolute right-3 top-2 cursor-pointer rounded-md border border-gray-600 p-2 opacity-0 hover:text-orange-600 data-[hovering=true]:opacity-100 data-[hovering=true]:transition-opacity"
2644
onClick={async ev => {
2745
const value = children?.valueOf().toString();
2846
if (value) {
2947
ev.preventDefault();
30-
startCopyTimer();
3148
await navigator.clipboard.writeText(value);
49+
startCopyTimer();
3250
}
3351
}}
3452
title="Copy to clipboard"

packages/web/app/src/lib/hooks/use-timed.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1-
import { useState } from 'react';
1+
import { useEffect, useState } from 'react';
22

33
export function useTimed(wait: number = 1000) {
44
const [timer, setTimer] = useState<NodeJS.Timeout | null>(null);
5+
useEffect(() => {
6+
return () => {
7+
if (timer) {
8+
clearTimeout(timer);
9+
}
10+
};
11+
}, [timer]);
12+
513
const handler = () => {
614
if (timer) {
715
clearTimeout(timer);

0 commit comments

Comments
 (0)