v3: editor fixes

This commit is contained in:
khannurien
2026-03-23 17:57:25 +00:00
parent b96879a556
commit cd4076343b
5 changed files with 54 additions and 320 deletions

View File

@@ -22,6 +22,7 @@ import { ErrorCard } from "./ErrorCard.tsx";
import { FileDropZone } from "./FileDropZone.tsx";
import { friendlyFetchError } from "../utils/apiError.ts";
import { MAX_FILE_SIZE } from "../config/upload.ts";
import { TextEditor } from "./TextEditor.tsx";
type Mode = "url" | "file";
type Phase = "create" | "playlist";
@@ -98,10 +99,10 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) {
};
}, []);
// Escape key to close
// Escape key to close (skip if a picker/dropdown already handled it)
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
if (e.key === "Escape" && !e.defaultPrevented) onClose();
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
@@ -389,10 +390,10 @@ export function DumpCreateModal({ onClose }: DumpCreateModalProps) {
<label htmlFor="dc-comment">
Why are you dumping this?
</label>
<textarea
<TextEditor
id="dc-comment"
value={comment}
onChange={(e) => setComment(e.target.value)}
onChange={setComment}
disabled={submitting}
placeholder="Tell the community what makes this worth their time..."
rows={3}

View File

@@ -4,6 +4,7 @@ import type { Playlist, RawPlaylist } from "../model.ts";
import { deserializePlaylist } from "../model.ts";
import { useAuth } from "../hooks/useAuth.ts";
import { ErrorCard } from "./ErrorCard.tsx";
import { TextEditor } from "./TextEditor.tsx";
interface PlaylistCreateFormProps {
/** If provided, the new playlist will have this dump added to it. */
@@ -67,10 +68,10 @@ export function PlaylistCreateForm(
autoFocus
required
/>
<textarea
<TextEditor
placeholder="Description (optional)"
value={description}
onChange={(e) => setDescription(e.target.value)}
onChange={setDescription}
rows={3}
/>
<div className="visibility-toggle">

View File

@@ -144,11 +144,21 @@ export const TextEditor = forwardRef<TextEditorHandle, TextEditorProps>(
<EmojiPicker.Root
onEmojiSelect={(e) => handleEmojiSelect(e.emoji)}
>
<EmojiPicker.Search
ref={emojiSearchRef}
defaultValue={emojiQuery}
placeholder="Search emoji…"
/>
<div className="emoji-picker-search-row">
<EmojiPicker.Search
ref={emojiSearchRef}
defaultValue={emojiQuery}
placeholder="Search emoji…"
/>
<button
type="button"
className="emoji-picker-close-btn"
onClick={closeEmoji}
aria-label="Close emoji picker"
>
</button>
</div>
<EmojiPicker.Viewport
ref={emojiViewportRef}
// tabIndex={-1} makes the div programmatically focusable so