fix: keep the active tag row in view and document the full tag set

The 19-row menu is taller than its 280px max-height, so arrow-key
navigation past the fold lost its highlight. Scroll the active row into
view on index change, list the delivery tags in the README, and give
[sarcastic] and [whispering] emoji that are not already used by
[chuckle] and [shush].
This commit is contained in:
jamiepine
2026-10-03 23:59:50 +00:00
committed by capy-ai-staging[bot]
parent 50d8d6a34f
commit 214adc5ff2
2 changed files with 15 additions and 3 deletions
+3 -1
View File
@@ -130,7 +130,9 @@ literally as text.
With **Chatterbox Turbo** selected, type `/` in the text input to open the tag With **Chatterbox Turbo** selected, type `/` in the text input to open the tag
inserter and add expressive tags inline with speech: inserter and add expressive tags inline with speech:
`[laugh]` `[chuckle]` `[gasp]` `[cough]` `[sigh]` `[groan]` `[sniff]` `[shush]` `[clear throat]` Sounds: `[laugh]` `[chuckle]` `[gasp]` `[cough]` `[sigh]` `[groan]` `[sniff]` `[shush]` `[clear throat]`
Delivery: `[angry]` `[crying]` `[dramatic]` `[fear]` `[happy]` `[narration]` `[sarcastic]` `[surprised]` `[whispering]` `[advertisement]`
### Post-Processing Effects ### Post-Processing Effects
@@ -29,9 +29,9 @@ const PARALINGUISTIC_TAGS = [
{ tag: '[fear]', label: 'fear', emoji: '\u{1F628}' }, { tag: '[fear]', label: 'fear', emoji: '\u{1F628}' },
{ tag: '[happy]', label: 'happy', emoji: '\u{1F60A}' }, { tag: '[happy]', label: 'happy', emoji: '\u{1F60A}' },
{ tag: '[narration]', label: 'narration', emoji: '\u{1F4D6}' }, { tag: '[narration]', label: 'narration', emoji: '\u{1F4D6}' },
{ tag: '[sarcastic]', label: 'sarcastic', emoji: '\u{1F60F}' }, { tag: '[sarcastic]', label: 'sarcastic', emoji: '\u{1F644}' },
{ tag: '[surprised]', label: 'surprised', emoji: '\u{1F632}' }, { tag: '[surprised]', label: 'surprised', emoji: '\u{1F632}' },
{ tag: '[whispering]', label: 'whispering', emoji: '\u{1F92B}' }, { tag: '[whispering]', label: 'whispering', emoji: '\u{1F62F}' },
{ tag: '[advertisement]', label: 'advertisement', emoji: '\u{1F4E2}' }, { tag: '[advertisement]', label: 'advertisement', emoji: '\u{1F4E2}' },
] as const; ] as const;
@@ -147,6 +147,7 @@ export const ParalinguisticInput = forwardRef<ParalinguisticInputRef, Paralingui
left: 0, left: 0,
}); });
const triggerRangeRef = useRef<Range | null>(null); const triggerRangeRef = useRef<Range | null>(null);
const menuListRef = useRef<HTMLDivElement | null>(null);
const lastSerializedRef = useRef<string>(''); const lastSerializedRef = useRef<string>('');
const isComposingRef = useRef(false); const isComposingRef = useRef(false);
@@ -155,6 +156,14 @@ export const ParalinguisticInput = forwardRef<ParalinguisticInputRef, Paralingui
element: editorRef.current, element: editorRef.current,
})); }));
// The list is taller than the menu's max-height, so keep the keyboard
// highlight visible as ArrowUp/ArrowDown move it.
useEffect(() => {
if (!showMenu) return;
const item = menuListRef.current?.children[menuIndex] as HTMLElement | undefined;
item?.scrollIntoView({ block: 'nearest' });
}, [showMenu, menuIndex]);
// Filtered tag list for the autocomplete menu // Filtered tag list for the autocomplete menu
const filteredTags = PARALINGUISTIC_TAGS.filter((t) => const filteredTags = PARALINGUISTIC_TAGS.filter((t) =>
t.label.toLowerCase().includes(menuFilter.toLowerCase()), t.label.toLowerCase().includes(menuFilter.toLowerCase()),
@@ -392,6 +401,7 @@ export const ParalinguisticInput = forwardRef<ParalinguisticInputRef, Paralingui
createPortal( createPortal(
<AnimatePresence> <AnimatePresence>
<motion.div <motion.div
ref={menuListRef}
initial={{ opacity: 0, y: 4 }} initial={{ opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 4 }} exit={{ opacity: 0, y: 4 }}