mirror of https://github.com/buster-so/buster.git
dictation so hot right now
This commit is contained in:
parent
d5db629b22
commit
d46383103d
|
@ -1,12 +1,15 @@
|
|||
import type { ListShortcutsResponse } from '@buster/server-shared/shortcuts';
|
||||
import type { GetSuggestedPromptsResponse } from '@buster/server-shared/user';
|
||||
import sampleSize from 'lodash/sampleSize';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import type { MentionSuggestionExtension } from '@/components/ui/inputs/MentionInput';
|
||||
import type { MentionInputSuggestionsProps } from '@/components/ui/inputs/MentionInputSuggestions';
|
||||
import type {
|
||||
MentionInputSuggestionsProps,
|
||||
MentionInputSuggestionsRef,
|
||||
} from '@/components/ui/inputs/MentionInputSuggestions';
|
||||
import { MentionInputSuggestions } from '@/components/ui/inputs/MentionInputSuggestions';
|
||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||
import { BusterChatInputButtons } from './BusterChatInputButtons';
|
||||
import { BusterChatInputButtons, type BusterChatInputMode } from './BusterChatInputButtons';
|
||||
|
||||
export type BusterChatInput = {
|
||||
defaultValue: string;
|
||||
|
@ -20,9 +23,12 @@ export type BusterChatInput = {
|
|||
|
||||
export const BusterChatInputBase: React.FC<BusterChatInput> = React.memo(
|
||||
({ defaultValue, onSubmit, onStop, submitting, disabled, shortcuts, suggestedPrompts }) => {
|
||||
const mentionInputSuggestionsRef = useRef<MentionInputSuggestionsRef>(null);
|
||||
const uniqueSuggestions = useUniqueSuggestions(suggestedPrompts);
|
||||
const shortcutsSuggestions = useShortcuts(shortcuts);
|
||||
|
||||
const [mode, setMode] = useState<BusterChatInputMode>('auto');
|
||||
|
||||
const suggestionItems: MentionInputSuggestionsProps['suggestionItems'] = useMemo(() => {
|
||||
const items: MentionInputSuggestionsProps['suggestionItems'] = [...uniqueSuggestions];
|
||||
|
||||
|
@ -43,6 +49,10 @@ export const BusterChatInputBase: React.FC<BusterChatInput> = React.memo(
|
|||
return [];
|
||||
}, [shortcuts]);
|
||||
|
||||
const onDictate = useMemoizedFn((transcript: string) => {
|
||||
mentionInputSuggestionsRef.current?.onChangeValue(transcript);
|
||||
});
|
||||
|
||||
const onSubmitPreflight = (value: string) => {
|
||||
if (submitting) {
|
||||
console.warn('Input is submitting');
|
||||
|
@ -65,6 +75,13 @@ export const BusterChatInputBase: React.FC<BusterChatInput> = React.memo(
|
|||
}
|
||||
);
|
||||
|
||||
const onSubmitButton = useMemoizedFn(() => {
|
||||
const value = mentionInputSuggestionsRef.current?.getValue();
|
||||
if (value) {
|
||||
onSubmitPreflight(value.transformedValue);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MentionInputSuggestions
|
||||
defaultValue={defaultValue}
|
||||
|
@ -72,15 +89,16 @@ export const BusterChatInputBase: React.FC<BusterChatInput> = React.memo(
|
|||
mentions={mentions}
|
||||
suggestionItems={suggestionItems}
|
||||
placeholder="Ask a question or type ‘/’ for shortcuts..."
|
||||
ref={mentionInputSuggestionsRef}
|
||||
>
|
||||
<BusterChatInputButtons
|
||||
onSubmit={() => {}}
|
||||
onStop={() => {}}
|
||||
onSubmit={onSubmitButton}
|
||||
onStop={onStop}
|
||||
submitting={submitting}
|
||||
disabled={disabled}
|
||||
mode="auto"
|
||||
onModeChange={() => {}}
|
||||
onDictate={() => {}}
|
||||
mode={mode}
|
||||
onModeChange={setMode}
|
||||
onDictate={onDictate}
|
||||
/>
|
||||
</MentionInputSuggestions>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import type React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import SpeechRecognition, { useSpeechRecognition } from 'react-speech-recognition';
|
||||
import { Button } from '@/components/ui/buttons';
|
||||
import { ArrowUp, Magnifier, Sparkle2 } from '@/components/ui/icons';
|
||||
|
@ -7,6 +6,7 @@ import Atom from '@/components/ui/icons/NucleoIconOutlined/atom';
|
|||
import Microphone from '@/components/ui/icons/NucleoIconOutlined/microphone';
|
||||
import { Popover } from '@/components/ui/popover';
|
||||
import { AppSegmented, type AppSegmentedProps } from '@/components/ui/segmented';
|
||||
import { AppTooltip } from '@/components/ui/tooltip';
|
||||
import { Text } from '@/components/ui/typography';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
|
@ -20,70 +20,84 @@ type BusterChatInputButtons = {
|
|||
mode: BusterChatInputMode;
|
||||
onModeChange: (mode: BusterChatInputMode) => void;
|
||||
onDictate: (transcript: string) => void;
|
||||
onDictateListeningChange?: (listening: boolean) => void;
|
||||
};
|
||||
|
||||
export const BusterChatInputButtons = ({
|
||||
onSubmit,
|
||||
onStop,
|
||||
submitting,
|
||||
disabled,
|
||||
mode,
|
||||
onModeChange,
|
||||
onDictate,
|
||||
}: BusterChatInputButtons) => {
|
||||
const { transcript, listening, resetTranscript, browserSupportsSpeechRecognition } =
|
||||
useSpeechRecognition();
|
||||
export const BusterChatInputButtons = React.memo(
|
||||
({
|
||||
onSubmit,
|
||||
onStop,
|
||||
submitting,
|
||||
disabled,
|
||||
mode,
|
||||
onModeChange,
|
||||
onDictate,
|
||||
onDictateListeningChange,
|
||||
}: BusterChatInputButtons) => {
|
||||
const { transcript, listening, resetTranscript, browserSupportsSpeechRecognition } =
|
||||
useSpeechRecognition();
|
||||
|
||||
const startListening = () => {
|
||||
SpeechRecognition.startListening({ continuous: true });
|
||||
};
|
||||
const startListening = () => {
|
||||
SpeechRecognition.startListening({ continuous: true });
|
||||
};
|
||||
|
||||
const stopListening = () => {
|
||||
SpeechRecognition.stopListening();
|
||||
};
|
||||
const stopListening = () => {
|
||||
SpeechRecognition.stopListening();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (listening && transcript) {
|
||||
onDictate(transcript);
|
||||
}
|
||||
}, [listening, transcript, onDictate]);
|
||||
useEffect(() => {
|
||||
if (listening && transcript) {
|
||||
onDictate(transcript);
|
||||
}
|
||||
}, [listening, transcript, onDictate]);
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center gap-2">
|
||||
<AppSegmented value={mode} options={modesOptions} onChange={(v) => onModeChange(v.value)} />
|
||||
useEffect(() => {
|
||||
onDictateListeningChange?.(listening);
|
||||
}, [listening, onDictateListeningChange]);
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{browserSupportsSpeechRecognition && (
|
||||
<Button
|
||||
rounding={'large'}
|
||||
variant={'ghost'}
|
||||
prefix={<Microphone />}
|
||||
onClick={listening ? stopListening : startListening}
|
||||
loading={submitting}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'origin-center transform-gpu transition-all duration-300 ease-out will-change-transform text-text-secondary',
|
||||
!disabled && 'hover:scale-110 active:scale-95',
|
||||
listening && 'bg-item-select text-foreground'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
rounding={'large'}
|
||||
variant={'default'}
|
||||
prefix={<ArrowUp />}
|
||||
onClick={submitting ? onStop : onSubmit}
|
||||
loading={submitting}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'origin-center transform-gpu transition-all duration-300 ease-out will-change-transform',
|
||||
!disabled && 'hover:scale-110 active:scale-95'
|
||||
return (
|
||||
<div className="flex justify-between items-center gap-2">
|
||||
<AppSegmented value={mode} options={modesOptions} onChange={(v) => onModeChange(v.value)} />
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
{browserSupportsSpeechRecognition && (
|
||||
<AppTooltip title={listening ? 'Stop Dictation...' : 'Press to Dictate...'}>
|
||||
<Button
|
||||
rounding={'large'}
|
||||
variant={'ghost'}
|
||||
prefix={<Microphone />}
|
||||
onClick={listening ? stopListening : startListening}
|
||||
loading={submitting}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'origin-center transform-gpu transition-all duration-300 ease-out will-change-transform text-text-secondary',
|
||||
!disabled && 'hover:scale-110 active:scale-95',
|
||||
listening && 'bg-item-select text-foreground animate-pulse'
|
||||
)}
|
||||
/>
|
||||
</AppTooltip>
|
||||
)}
|
||||
/>
|
||||
<AppTooltip title={'Submit'}>
|
||||
<Button
|
||||
rounding={'large'}
|
||||
variant={'default'}
|
||||
prefix={<ArrowUp />}
|
||||
onClick={submitting ? onStop : onSubmit}
|
||||
loading={submitting}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'origin-center transform-gpu transition-all duration-300 ease-out will-change-transform',
|
||||
!disabled && 'hover:scale-110 active:scale-95'
|
||||
)}
|
||||
/>
|
||||
</AppTooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
BusterChatInputButtons.displayName = 'BusterChatInputButtons';
|
||||
|
||||
const ModePopoverContent = ({
|
||||
title,
|
||||
|
|
|
@ -58,6 +58,13 @@ export const MentionInput = forwardRef<MentionInputRef, MentionInputProps>(
|
|||
);
|
||||
}, [mentions]);
|
||||
|
||||
const getValue = () => {
|
||||
return onUpdateTransformer({
|
||||
editor,
|
||||
mentionsByTrigger,
|
||||
});
|
||||
};
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
Document,
|
||||
|
@ -94,6 +101,7 @@ export const MentionInput = forwardRef<MentionInputRef, MentionInputProps>(
|
|||
ref,
|
||||
() => ({
|
||||
editor,
|
||||
getValue,
|
||||
}),
|
||||
[editor]
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { MentionNodeAttrs, MentionOptions } from '@tiptap/extension-mention';
|
||||
import type { Editor, EditorEvents } from '@tiptap/react';
|
||||
import type { MentionPillAttributes } from './MentionPill';
|
||||
import type { onUpdateTransformer } from './update-transformers';
|
||||
|
||||
export type MentionOnSelectParams<T = unknown> = {
|
||||
value: T;
|
||||
|
@ -106,6 +107,7 @@ export type MentionInputProps = {
|
|||
|
||||
export type MentionInputRef = {
|
||||
editor: Editor | null;
|
||||
getValue: () => ReturnType<typeof onUpdateTransformer>;
|
||||
};
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { Command } from 'cmdk';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { MentionInputProps, MentionInputRef } from '../MentionInput';
|
||||
import type {
|
||||
MentionInputSuggestionsOnSelectParams,
|
||||
MentionInputSuggestionsProps,
|
||||
MentionInputSuggestionsRef,
|
||||
} from './MentionInputSuggestions.types';
|
||||
import { MentionInputSuggestionsContainer } from './MentionInputSuggestionsContainer';
|
||||
import { MentionInputSuggestionsEmpty } from './MentionInputSuggestionsEmpty';
|
||||
|
@ -13,156 +14,191 @@ import { MentionInputSuggestionsItemsSelector } from './MentionInputSuggestionsI
|
|||
import { MentionInputSuggestionsList } from './MentionInputSuggestionsList';
|
||||
import { MentionInputSuggestionsMentionsInput } from './MentionInputSuggestionsMentionsInput';
|
||||
|
||||
export const MentionInputSuggestions = ({
|
||||
placeholder,
|
||||
defaultValue,
|
||||
value: valueProp,
|
||||
emptyComponent,
|
||||
submitting,
|
||||
onPressEnter,
|
||||
disabled = false,
|
||||
onChange,
|
||||
ariaLabel = 'Mention Input Suggestions',
|
||||
readOnly,
|
||||
autoFocus,
|
||||
children,
|
||||
//container
|
||||
className,
|
||||
inputContainerClassName,
|
||||
suggestionsContainerClassName,
|
||||
//suggestions
|
||||
suggestionItems,
|
||||
closeSuggestionOnSelect = true,
|
||||
addSuggestionValueToInput = true,
|
||||
onSuggestionItemClick,
|
||||
filter,
|
||||
shouldFilter,
|
||||
//mentions
|
||||
onMentionItemClick,
|
||||
mentions,
|
||||
}: MentionInputSuggestionsProps) => {
|
||||
const [hasClickedSelect, setHasClickedSelect] = useState(false);
|
||||
const [value, setValue] = useState(valueProp ?? defaultValue);
|
||||
const [hasResults, setHasResults] = useState(!!suggestionItems.length);
|
||||
export const MentionInputSuggestions = forwardRef<
|
||||
MentionInputSuggestionsRef,
|
||||
MentionInputSuggestionsProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
placeholder,
|
||||
defaultValue,
|
||||
value: valueProp,
|
||||
emptyComponent,
|
||||
submitting,
|
||||
onPressEnter,
|
||||
disabled = false,
|
||||
onChange,
|
||||
ariaLabel = 'Mention Input Suggestions',
|
||||
readOnly,
|
||||
autoFocus,
|
||||
children,
|
||||
//container
|
||||
className,
|
||||
inputContainerClassName,
|
||||
suggestionsContainerClassName,
|
||||
//suggestions
|
||||
suggestionItems,
|
||||
closeSuggestionOnSelect = true,
|
||||
addSuggestionValueToInput = true,
|
||||
onSuggestionItemClick,
|
||||
filter,
|
||||
shouldFilter,
|
||||
//mentions
|
||||
onMentionItemClick,
|
||||
mentions,
|
||||
}: MentionInputSuggestionsProps,
|
||||
ref
|
||||
) => {
|
||||
const [hasClickedSelect, setHasClickedSelect] = useState(false);
|
||||
const [value, setValue] = useState(valueProp ?? defaultValue);
|
||||
const [hasResults, setHasResults] = useState(!!suggestionItems.length);
|
||||
|
||||
const commandListNavigatedRef = useRef(false);
|
||||
const commandRef = useRef<HTMLDivElement>(null);
|
||||
const mentionsInputRef = useRef<MentionInputRef>(null);
|
||||
const commandListNavigatedRef = useRef(false);
|
||||
const commandRef = useRef<HTMLDivElement>(null);
|
||||
const mentionsInputRef = useRef<MentionInputRef>(null);
|
||||
|
||||
const showSuggestionList = !hasClickedSelect && suggestionItems.length > 0;
|
||||
const showSuggestionList = !hasClickedSelect && suggestionItems.length > 0;
|
||||
|
||||
const onChangeInputValue: MentionInputProps['onChange'] = useCallback(
|
||||
(transformedValue, arrayValue, rawValue) => {
|
||||
setValue(value);
|
||||
setHasClickedSelect(false);
|
||||
// Reset command list navigation when user types
|
||||
commandListNavigatedRef.current = false;
|
||||
onChange?.(transformedValue, arrayValue, rawValue);
|
||||
},
|
||||
[]
|
||||
);
|
||||
const onChangeInputValue: MentionInputProps['onChange'] = useCallback(
|
||||
(transformedValue, arrayValue, rawValue) => {
|
||||
setValue(transformedValue);
|
||||
onChange?.(transformedValue, arrayValue, rawValue);
|
||||
commandListNavigatedRef.current = false;
|
||||
setHasClickedSelect(false);
|
||||
},
|
||||
[onChange, setHasClickedSelect]
|
||||
);
|
||||
|
||||
const onSelectItem = useMemoizedFn(
|
||||
({ onClick, ...params }: MentionInputSuggestionsOnSelectParams) => {
|
||||
const { addValueToInput, loading, inputValue, label, disabled } = params;
|
||||
if (disabled) {
|
||||
console.warn('Item is disabled', params);
|
||||
return;
|
||||
//this is used to change the value of the input from outside the component
|
||||
const onChangeValue = useMemoizedFn((v: string | ((prevState: string) => string)) => {
|
||||
if (typeof v === 'function') {
|
||||
setValue((prevState) => {
|
||||
const newState = v(prevState);
|
||||
mentionsInputRef.current?.editor?.commands.setContent(newState);
|
||||
return newState;
|
||||
});
|
||||
} else {
|
||||
setValue(v);
|
||||
mentionsInputRef.current?.editor?.commands.setContent(v);
|
||||
}
|
||||
if (submitting) {
|
||||
console.warn('Input is submitting');
|
||||
return;
|
||||
}
|
||||
if (loading) {
|
||||
console.warn('Item is loading', params);
|
||||
return;
|
||||
}
|
||||
if (addValueToInput) {
|
||||
const stringValue = inputValue ?? String(label);
|
||||
mentionsInputRef.current?.editor?.commands.setContent(stringValue);
|
||||
setValue(stringValue);
|
||||
}
|
||||
onClick?.();
|
||||
if (closeSuggestionOnSelect) setHasClickedSelect(true);
|
||||
onSuggestionItemClick?.(params);
|
||||
setHasResults(false);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Track arrow key navigation in the command list
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (showSuggestionList && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {
|
||||
commandListNavigatedRef.current = true;
|
||||
}
|
||||
|
||||
// If Enter is pressed and command list was navigated, manually trigger selection
|
||||
if (showSuggestionList && event.key === 'Enter' && commandListNavigatedRef.current) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
// Find the currently selected item and trigger its click
|
||||
const selectedItem = commandElement?.querySelector('[data-selected="true"]') as HTMLElement;
|
||||
if (selectedItem) {
|
||||
selectedItem.click();
|
||||
const onSelectItem = useMemoizedFn(
|
||||
({ onClick, ...params }: MentionInputSuggestionsOnSelectParams) => {
|
||||
const { addValueToInput, loading, inputValue, label, disabled } = params;
|
||||
if (disabled) {
|
||||
console.warn('Item is disabled', params);
|
||||
return;
|
||||
}
|
||||
if (submitting) {
|
||||
console.warn('Input is submitting');
|
||||
return;
|
||||
}
|
||||
if (loading) {
|
||||
console.warn('Item is loading', params);
|
||||
return;
|
||||
}
|
||||
if (addValueToInput) {
|
||||
const stringValue = inputValue ?? String(label);
|
||||
mentionsInputRef.current?.editor?.commands.setContent(stringValue);
|
||||
setValue(stringValue);
|
||||
}
|
||||
onClick?.();
|
||||
if (closeSuggestionOnSelect) setHasClickedSelect(true);
|
||||
onSuggestionItemClick?.(params);
|
||||
setHasResults(false);
|
||||
}
|
||||
};
|
||||
const commandElement = commandRef.current;
|
||||
if (commandElement) {
|
||||
commandElement.addEventListener('keydown', handleKeyDown, true); // Use capture phase
|
||||
return () => {
|
||||
commandElement.removeEventListener('keydown', handleKeyDown, true);
|
||||
);
|
||||
|
||||
const getValue = useMemoizedFn(() => {
|
||||
return mentionsInputRef.current?.getValue();
|
||||
});
|
||||
|
||||
// Track arrow key navigation in the command list
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (showSuggestionList && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {
|
||||
commandListNavigatedRef.current = true;
|
||||
}
|
||||
|
||||
// If Enter is pressed and command list was navigated, manually trigger selection
|
||||
if (showSuggestionList && event.key === 'Enter' && commandListNavigatedRef.current) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
// Find the currently selected item and trigger its click
|
||||
const selectedItem = commandElement?.querySelector(
|
||||
'[data-selected="true"]'
|
||||
) as HTMLElement;
|
||||
if (selectedItem) {
|
||||
selectedItem.click();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [showSuggestionList]);
|
||||
const commandElement = commandRef.current;
|
||||
if (commandElement) {
|
||||
commandElement.addEventListener('keydown', handleKeyDown, true); // Use capture phase
|
||||
return () => {
|
||||
commandElement.removeEventListener('keydown', handleKeyDown, true);
|
||||
};
|
||||
}
|
||||
}, [showSuggestionList]);
|
||||
|
||||
useImperativeHandle(ref, () => ({}), []);
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
value,
|
||||
onChangeValue,
|
||||
getValue,
|
||||
}),
|
||||
[value]
|
||||
);
|
||||
|
||||
return (
|
||||
<Command
|
||||
ref={commandRef}
|
||||
value={value}
|
||||
label={ariaLabel}
|
||||
className={cn('relative border rounded overflow-hidden bg-background shadow', className)}
|
||||
shouldFilter={shouldFilter}
|
||||
filter={filter}
|
||||
>
|
||||
<MentionInputSuggestionsContainer className={inputContainerClassName}>
|
||||
<MentionInputSuggestionsMentionsInput
|
||||
ref={mentionsInputRef}
|
||||
defaultValue={defaultValue}
|
||||
readOnly={readOnly}
|
||||
autoFocus={autoFocus}
|
||||
placeholder={placeholder}
|
||||
mentions={mentions}
|
||||
value={value}
|
||||
onChange={onChangeInputValue}
|
||||
onMentionItemClick={onMentionItemClick}
|
||||
onPressEnter={onPressEnter}
|
||||
commandListNavigatedRef={commandListNavigatedRef}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{children && <div className="mt-3">{children}</div>}
|
||||
</MentionInputSuggestionsContainer>
|
||||
{hasResults && <div className="border-b mb-1.5" />}
|
||||
<MentionInputSuggestionsList
|
||||
show={showSuggestionList}
|
||||
className={cn(suggestionsContainerClassName, hasResults && 'pb-1.5')}
|
||||
return (
|
||||
<Command
|
||||
ref={commandRef}
|
||||
value={value}
|
||||
label={ariaLabel}
|
||||
className={cn('relative border rounded overflow-hidden bg-background shadow', className)}
|
||||
shouldFilter={shouldFilter}
|
||||
filter={filter}
|
||||
>
|
||||
<MentionInputSuggestionsItemsSelector
|
||||
suggestionItems={suggestionItems}
|
||||
onSelect={onSelectItem}
|
||||
addValueToInput={addSuggestionValueToInput}
|
||||
closeOnSelect={closeSuggestionOnSelect}
|
||||
hasResults={hasResults}
|
||||
setHasResults={setHasResults}
|
||||
/>
|
||||
<MentionInputSuggestionsContainer className={inputContainerClassName}>
|
||||
<MentionInputSuggestionsMentionsInput
|
||||
ref={mentionsInputRef}
|
||||
defaultValue={defaultValue}
|
||||
readOnly={readOnly}
|
||||
autoFocus={autoFocus}
|
||||
placeholder={placeholder}
|
||||
mentions={mentions}
|
||||
value={value}
|
||||
onChange={onChangeInputValue}
|
||||
onMentionItemClick={onMentionItemClick}
|
||||
onPressEnter={onPressEnter}
|
||||
commandListNavigatedRef={commandListNavigatedRef}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{children && <div className="mt-3">{children}</div>}
|
||||
</MentionInputSuggestionsContainer>
|
||||
{hasResults && <div className="border-b mb-1.5" />}
|
||||
<MentionInputSuggestionsList
|
||||
show={showSuggestionList}
|
||||
className={cn(suggestionsContainerClassName, hasResults && 'pb-1.5')}
|
||||
>
|
||||
<MentionInputSuggestionsItemsSelector
|
||||
suggestionItems={suggestionItems}
|
||||
onSelect={onSelectItem}
|
||||
addValueToInput={addSuggestionValueToInput}
|
||||
closeOnSelect={closeSuggestionOnSelect}
|
||||
hasResults={hasResults}
|
||||
setHasResults={setHasResults}
|
||||
/>
|
||||
|
||||
<MentionInputSuggestionsEmpty
|
||||
setHasResults={setHasResults}
|
||||
emptyComponent={emptyComponent}
|
||||
/>
|
||||
</MentionInputSuggestionsList>
|
||||
</Command>
|
||||
);
|
||||
};
|
||||
<MentionInputSuggestionsEmpty
|
||||
setHasResults={setHasResults}
|
||||
emptyComponent={emptyComponent}
|
||||
/>
|
||||
</MentionInputSuggestionsList>
|
||||
</Command>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -2,6 +2,7 @@ import type { Command } from 'cmdk';
|
|||
import type React from 'react';
|
||||
import type { MentionSuggestionExtension } from '../MentionInput';
|
||||
import type { MentionInputProps, MentionTriggerItem } from '../MentionInput/MentionInput.types';
|
||||
import type { onUpdateTransformer } from '../MentionInput/update-transformers';
|
||||
|
||||
/**
|
||||
* @description Override the addValueToInput and closeOnSelect props for the item based on the group props
|
||||
|
@ -81,6 +82,12 @@ export type MentionInputSuggestionsProps<T = string> = {
|
|||
suggestionsContainerClassName?: string;
|
||||
} & Pick<React.ComponentProps<typeof Command>, 'filter' | 'shouldFilter'>;
|
||||
|
||||
export type MentionInputSuggestionsRef = {
|
||||
value: string;
|
||||
onChangeValue: React.Dispatch<React.SetStateAction<string>>;
|
||||
getValue: () => ReturnType<typeof onUpdateTransformer> | undefined;
|
||||
};
|
||||
|
||||
export type MentionInputSuggestionsContainerProps = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
|
|
Loading…
Reference in New Issue