|
1 | 1 | import type { Editor } from '@tiptap/core' |
2 | | -import { type SyntheticEvent, useEffect, useRef, useState } from 'react' |
| 2 | +import { Icon } from 'copilot-design-system' |
| 3 | +import { type KeyboardEvent, useEffect, useRef, useState } from 'react' |
3 | 4 | import { fixUrl } from '@/utils/urls' |
4 | 5 |
|
5 | | -interface IframeBubbleInputProps { |
| 6 | +interface EmbedBubbleInputProps { |
6 | 7 | editor: Editor |
| 8 | + showEmbedInput: boolean |
| 9 | + setShowEmbedInput: (showEmbedInput: boolean) => void |
7 | 10 | } |
8 | 11 |
|
9 | | -export const IframeBubbleInput = ({ editor }: IframeBubbleInputProps) => { |
| 12 | +export const EmbedBubbleInput = ({ editor, showEmbedInput, setShowEmbedInput }: EmbedBubbleInputProps) => { |
10 | 13 | const [url, setUrl] = useState('') |
11 | | - |
12 | 14 | const urlInputRef = useRef<HTMLInputElement>(null) |
13 | 15 |
|
14 | | - const handleKeyDown = (event: SyntheticEvent<HTMLDivElement>) => { |
15 | | - //@ts-expect-error event should contain code |
| 16 | + const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => { |
16 | 17 | if (event.code === 'Escape') { |
17 | 18 | event.preventDefault() |
18 | | - // TODO: trigger setShowEmbedInput(false) |
| 19 | + setShowEmbedInput(false) |
| 20 | + setUrl('') |
| 21 | + return |
19 | 22 | } |
20 | 23 |
|
21 | | - //@ts-expect-error event should contain code |
22 | 24 | if (event.code === 'Enter') { |
23 | 25 | event.preventDefault() |
24 | | - console.info(fixUrl(url)) |
25 | 26 | editor |
26 | 27 | .chain() |
27 | 28 | .focus() |
28 | | - .setIframe({ src: fixUrl(url) }) |
| 29 | + .setEmbed({ src: fixUrl(url) }) |
29 | 30 | .run() |
30 | | - // TODO: trigger setShowEmbedInput(false) |
| 31 | + setShowEmbedInput(false) |
31 | 32 | setUrl('') |
32 | 33 | } |
33 | 34 | } |
34 | 35 |
|
35 | | - // biome-ignore lint/correctness/useExhaustiveDependencies: force side-effect here |
36 | 36 | useEffect(() => { |
37 | | - if (urlInputRef.current) { |
38 | | - urlInputRef.current.focus() |
39 | | - } |
40 | | - }, [urlInputRef.current]) |
| 37 | + if (!showEmbedInput) return |
| 38 | + |
| 39 | + // Wait until tippy has actually mounted/moved the node into the popper |
| 40 | + const raf = requestAnimationFrame(() => { |
| 41 | + urlInputRef.current?.focus() |
| 42 | + urlInputRef.current?.select() |
| 43 | + }) |
| 44 | + |
| 45 | + return () => cancelAnimationFrame(raf) |
| 46 | + }, [showEmbedInput]) |
41 | 47 |
|
42 | 48 | return ( |
43 | | - <div> |
| 49 | + <div className="flex items-center border border-border-gray bg-white p-2"> |
44 | 50 | <input |
| 51 | + ref={urlInputRef} |
45 | 52 | type="text" |
| 53 | + placeholder="Paste embed URL" |
| 54 | + value={url} |
46 | 55 | onChange={(e) => setUrl(e.target.value)} |
47 | | - ref={urlInputRef} |
48 | 56 | onKeyDown={handleKeyDown} |
49 | | - value={url} |
50 | | - /> |
51 | | - <button |
52 | | - type="reset" |
53 | | - onClick={() => { |
54 | | - // TODO: setShowEmbedInput(false) |
55 | | - console.info('cancelled') |
56 | | - }} |
| 57 | + className="px-2 focus:outline-none" |
57 | 58 | /> |
| 59 | + <button type="reset" onClick={() => setShowEmbedInput(false)}> |
| 60 | + <Icon |
| 61 | + icon="Cancel" |
| 62 | + width={16} |
| 63 | + height={16} |
| 64 | + className="text-text-primary transition-all duration-100 ease-in-out" |
| 65 | + /> |
| 66 | + </button> |
58 | 67 | </div> |
59 | 68 | ) |
60 | 69 | } |
0 commit comments