mirror of https://github.com/buster-so/buster.git
pass in read props
This commit is contained in:
parent
0c576b62e6
commit
d4075311a3
|
@ -22,6 +22,8 @@ export const BusterInput = ({
|
|||
variant = 'default',
|
||||
onChange,
|
||||
ariaLabel = 'Buster Input',
|
||||
readOnly,
|
||||
autoFocus,
|
||||
//suggestions
|
||||
suggestionItems,
|
||||
closeSuggestionOnSelect = true,
|
||||
|
@ -58,6 +60,7 @@ export const BusterInput = ({
|
|||
console.warn('Item is loading', params);
|
||||
return;
|
||||
}
|
||||
console.log('onSelectItem', addValueToInput, params);
|
||||
if (addValueToInput) setValue(inputValue ?? String(label));
|
||||
onClick?.();
|
||||
if (closeSuggestionOnSelect) setHasClickedSelect(true);
|
||||
|
@ -92,13 +95,13 @@ export const BusterInput = ({
|
|||
variant={variant}
|
||||
>
|
||||
<BusterMentionsInput
|
||||
autoFocus
|
||||
defaultValue={defaultValue}
|
||||
readOnly
|
||||
readOnly={readOnly}
|
||||
autoFocus={autoFocus}
|
||||
placeholder={placeholder}
|
||||
mentions={mentions}
|
||||
value={value}
|
||||
onChangeInputValue={onChangeInputValue}
|
||||
onChange={onChangeInputValue}
|
||||
shouldFilter={shouldFilter}
|
||||
filter={filter}
|
||||
onMentionItemClick={onMentionItemClick}
|
||||
|
|
|
@ -57,9 +57,11 @@ export type BusterInputProps<T = string> = {
|
|||
onChange?: (value: string) => void;
|
||||
submitting?: boolean;
|
||||
disabled?: boolean;
|
||||
readOnly?: boolean;
|
||||
onSubmit: (value: string) => void;
|
||||
onStop: () => void;
|
||||
variant?: 'default';
|
||||
autoFocus?: boolean;
|
||||
sendIcon?: React.ReactNode;
|
||||
secondaryActions?: React.ReactNode;
|
||||
placeholder?: string;
|
||||
|
@ -67,7 +69,7 @@ export type BusterInputProps<T = string> = {
|
|||
emptyComponent?: React.ReactNode | string | false; //if false, no empty component will be shown
|
||||
//mentions
|
||||
onMentionItemClick?: (params: MentionTriggerItem<T>) => void;
|
||||
mentions?: MentionSuggestionExtension[];
|
||||
mentions: MentionSuggestionExtension[];
|
||||
//suggestions
|
||||
suggestionItems: (
|
||||
| BusterInputDropdownItem<T>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/** biome-ignore-all lint/complexity/noUselessFragments: Intersting bug when NOT using fragments */
|
||||
import { Command } from 'cmdk';
|
||||
import React from 'react';
|
||||
import { cn } from '@/lib/classMerge';
|
||||
import { MentionInput, type MentionInputProps } from '../MentionInput';
|
||||
import type { BusterInputProps } from './BusterInput.types';
|
||||
|
||||
export type BusterMentionsInputProps = Pick<
|
||||
|
@ -14,33 +14,29 @@ export type BusterMentionsInputProps = Pick<
|
|||
| 'filter'
|
||||
| 'onMentionItemClick'
|
||||
> & {
|
||||
onChangeInputValue: (value: string) => void;
|
||||
} & React.ComponentPropsWithoutRef<typeof Command.Input>;
|
||||
onChange: MentionInputProps['onChange'];
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
autoFocus?: boolean;
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
export const BusterMentionsInput = ({
|
||||
children,
|
||||
value: valueProp,
|
||||
placeholder,
|
||||
defaultValue,
|
||||
mentions,
|
||||
value,
|
||||
onChangeInputValue,
|
||||
className,
|
||||
style,
|
||||
...props
|
||||
}: BusterMentionsInputProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<textarea />
|
||||
|
||||
<MentionInput mentions={mentions} defaultValue={value} {...props} />
|
||||
<Command.Input
|
||||
value={value}
|
||||
{...props}
|
||||
autoFocus={false}
|
||||
className="absolute -top-5 left-0 w-full outline-1 outline-amber-200 pointer-events-none"
|
||||
>
|
||||
{children}
|
||||
</Command.Input>
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { faker } from '@faker-js/faker';
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { fn } from 'storybook/test';
|
||||
import { createMentionSuggestionExtension } from './createMentionSuggestionOption';
|
||||
import { MentionInput } from './MentionInput';
|
||||
import type { MentionInputTriggerItem } from './MentionInput.types';
|
||||
|
@ -55,48 +56,6 @@ const looneyTunesCharacters: MentionInputTriggerItem[] = [
|
|||
},
|
||||
];
|
||||
|
||||
const theSimpsonsCharacters: MentionInputTriggerItem[] = [
|
||||
{
|
||||
value: 'Homer Simpson',
|
||||
label: 'Homer Simpson',
|
||||
},
|
||||
{
|
||||
value: 'Marge Simpson',
|
||||
label: 'Marge Simpson',
|
||||
},
|
||||
{
|
||||
value: 'Bart Simpson',
|
||||
label: 'Bart Simpson',
|
||||
},
|
||||
{
|
||||
value: 'Lisa Simpson',
|
||||
label: 'Lisa Simpson',
|
||||
},
|
||||
{
|
||||
value: 'Maggie Simpson',
|
||||
label: 'Maggie Simpson',
|
||||
},
|
||||
{
|
||||
value: 'Ned Flanders',
|
||||
label: 'Ned Flanders',
|
||||
},
|
||||
].map((item) => ({
|
||||
...item,
|
||||
label: (
|
||||
<span className="gap-x-1 space-x-1">
|
||||
<img
|
||||
src={faker.image.url({
|
||||
width: 16,
|
||||
height: 16,
|
||||
})}
|
||||
alt=""
|
||||
className="w-3 h-3 rounded-full bg-item-active inline-block align-middle"
|
||||
/>
|
||||
<span className="inline-block">{item.label}</span>
|
||||
</span>
|
||||
),
|
||||
}));
|
||||
|
||||
const arthurCharacters: MentionInputTriggerItem[] = [
|
||||
{
|
||||
value: 'Arthur Read',
|
||||
|
@ -138,7 +97,7 @@ const arthurCharacters: MentionInputTriggerItem[] = [
|
|||
),
|
||||
}));
|
||||
|
||||
export const looneyTunesSuggestions = createMentionSuggestionExtension({
|
||||
const looneyTunesSuggestions = createMentionSuggestionExtension({
|
||||
trigger: '@',
|
||||
items: looneyTunesCharacters,
|
||||
popoverContent: (props) => {
|
||||
|
@ -150,10 +109,52 @@ export const looneyTunesSuggestions = createMentionSuggestionExtension({
|
|||
},
|
||||
},
|
||||
onChangeTransform: (v) => {
|
||||
return `[@${String(v.label)}](${String(v.value)})`;
|
||||
return `We can totally transform this into anything we want. The original value was [@${String(v.label)}](${String(v.value)})`;
|
||||
},
|
||||
});
|
||||
|
||||
const theSimpsonsCharacters: MentionInputTriggerItem[] = [
|
||||
{
|
||||
value: 'Homer Simpson',
|
||||
label: 'Homer Simpson',
|
||||
},
|
||||
{
|
||||
value: 'Marge Simpson',
|
||||
label: 'Marge Simpson',
|
||||
},
|
||||
{
|
||||
value: 'Bart Simpson',
|
||||
label: 'Bart Simpson',
|
||||
},
|
||||
{
|
||||
value: 'Lisa Simpson',
|
||||
label: 'Lisa Simpson',
|
||||
},
|
||||
{
|
||||
value: 'Maggie Simpson',
|
||||
label: 'Maggie Simpson',
|
||||
},
|
||||
{
|
||||
value: 'Ned Flanders',
|
||||
label: 'Ned Flanders',
|
||||
},
|
||||
].map((item) => ({
|
||||
...item,
|
||||
label: (
|
||||
<span className="gap-x-1 space-x-1">
|
||||
<img
|
||||
src={faker.image.url({
|
||||
width: 16,
|
||||
height: 16,
|
||||
})}
|
||||
alt=""
|
||||
className="w-3 h-3 rounded-full bg-item-active inline-block align-middle"
|
||||
/>
|
||||
<span className="inline-block">{item.label}</span>
|
||||
</span>
|
||||
),
|
||||
}));
|
||||
|
||||
const theSimpsonsSuggestions = createMentionSuggestionExtension({
|
||||
trigger: '#',
|
||||
items: theSimpsonsCharacters,
|
||||
|
@ -301,6 +302,7 @@ export const Default: Story = {
|
|||
arthurSuggestions,
|
||||
spongebobSuggestions,
|
||||
],
|
||||
onChange: fn(),
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
|
|
|
@ -3,18 +3,19 @@ import Document from '@tiptap/extension-document';
|
|||
import Paragraph from '@tiptap/extension-paragraph';
|
||||
import Text from '@tiptap/extension-text';
|
||||
import {
|
||||
type Editor,
|
||||
EditorContent,
|
||||
EditorContext,
|
||||
type EditorEvents,
|
||||
type NodeType,
|
||||
type TextType,
|
||||
useEditor,
|
||||
} from '@tiptap/react';
|
||||
import { useMemo } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { MentionExtension } from './MentionExtension';
|
||||
import type {
|
||||
MentionArrayItem,
|
||||
MentionOnChangeParams,
|
||||
MentionInputProps,
|
||||
MentionSuggestionExtension,
|
||||
} from './MentionInput.types';
|
||||
import type { MentionPillAttributes } from './MentionPill';
|
||||
|
@ -25,13 +26,12 @@ export const MentionInput = ({
|
|||
defaultValue = '',
|
||||
onFocus,
|
||||
onBlur,
|
||||
}: {
|
||||
mentions: MentionSuggestionExtension[];
|
||||
onChange?: MentionOnChangeParams;
|
||||
onFocus?: (v: EditorEvents['focus']) => void;
|
||||
onBlur?: (v: EditorEvents['blur']) => void;
|
||||
defaultValue?: string;
|
||||
}) => {
|
||||
autoFocus,
|
||||
style,
|
||||
className,
|
||||
readOnly,
|
||||
disabled,
|
||||
}: MentionInputProps) => {
|
||||
const mentionsByTrigger = useMemo(() => {
|
||||
return mentions.reduce(
|
||||
(acc, mention) => {
|
||||
|
@ -47,43 +47,18 @@ export const MentionInput = ({
|
|||
const editor = useEditor({
|
||||
extensions: [Document, Paragraph, Text, MentionExtension(mentions)],
|
||||
content: defaultValue,
|
||||
autofocus: true,
|
||||
autofocus: autoFocus,
|
||||
editable: !disabled && !readOnly,
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: 'p-1',
|
||||
class: cn('p-1 border rounded outline-0', className),
|
||||
},
|
||||
},
|
||||
onUpdate: ({ editor }) => {
|
||||
const editorText = editor.getText();
|
||||
const editorJson = editor.getJSON();
|
||||
const transformedJson: MentionArrayItem[] = editorJson.content.reduce<MentionArrayItem[]>(
|
||||
(acc, item) => {
|
||||
if (item.type === 'paragraph') {
|
||||
item.content?.forEach((item) => {
|
||||
if (item.type === 'text') {
|
||||
const _item = item as TextType;
|
||||
acc.push({ type: 'text', text: _item.text });
|
||||
} else if (item.type === 'mention') {
|
||||
const _item = item as NodeType<'mention', MentionPillAttributes>;
|
||||
acc.push({ type: 'mention', attrs: _item.attrs });
|
||||
}
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
const transformedValue = transformedJson.reduce((acc, item) => {
|
||||
if (item.type === 'text') {
|
||||
return acc + item.text;
|
||||
}
|
||||
if (item.type === 'mention') {
|
||||
const onChangeTransform = mentionsByTrigger[item.attrs.trigger]?.onChangeTransform;
|
||||
if (onChangeTransform) return acc + onChangeTransform(item.attrs);
|
||||
return acc + item.attrs.label;
|
||||
}
|
||||
return acc;
|
||||
}, '');
|
||||
const { transformedValue, transformedJson, editorText } = onUpdateTransformer({
|
||||
editor,
|
||||
mentionsByTrigger,
|
||||
});
|
||||
onChange?.(transformedValue, transformedJson, editorText);
|
||||
},
|
||||
onFocus: onFocus,
|
||||
|
@ -95,8 +70,53 @@ export const MentionInput = ({
|
|||
return (
|
||||
<ClientOnly>
|
||||
<EditorContext.Provider value={providerValue}>
|
||||
<EditorContent className="rounded p-1 border outline-1 min-w-120" editor={editor} />
|
||||
<EditorContent className="outline-0" editor={editor} style={style} />
|
||||
</EditorContext.Provider>
|
||||
</ClientOnly>
|
||||
);
|
||||
};
|
||||
|
||||
const onUpdateTransformer = ({
|
||||
editor,
|
||||
mentionsByTrigger,
|
||||
}: {
|
||||
editor: Editor;
|
||||
mentionsByTrigger: Record<string, MentionSuggestionExtension>;
|
||||
}) => {
|
||||
const editorText = editor.getText();
|
||||
const editorJson = editor.getJSON();
|
||||
const transformedJson: MentionArrayItem[] = editorJson.content.reduce<MentionArrayItem[]>(
|
||||
(acc, item) => {
|
||||
if (item.type === 'paragraph') {
|
||||
item.content?.forEach((item) => {
|
||||
if (item.type === 'text') {
|
||||
const _item = item as TextType;
|
||||
acc.push({ type: 'text', text: _item.text });
|
||||
} else if (item.type === 'mention') {
|
||||
const _item = item as NodeType<'mention', MentionPillAttributes>;
|
||||
acc.push({ type: 'mention', attrs: _item.attrs });
|
||||
}
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
const transformedValue = transformedJson.reduce((acc, item) => {
|
||||
if (item.type === 'text') {
|
||||
return acc + item.text;
|
||||
}
|
||||
if (item.type === 'mention') {
|
||||
const onChangeTransform = mentionsByTrigger[item.attrs.trigger]?.onChangeTransform;
|
||||
if (onChangeTransform) return acc + onChangeTransform(item.attrs);
|
||||
return acc + item.attrs.label;
|
||||
}
|
||||
return acc;
|
||||
}, '');
|
||||
|
||||
return {
|
||||
transformedValue,
|
||||
transformedJson,
|
||||
editorText,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { MentionNodeAttrs, MentionOptions } from '@tiptap/extension-mention';
|
||||
import type { EditorEvents } from '@tiptap/react';
|
||||
import type { MentionPillAttributes } from './MentionPill';
|
||||
|
||||
export type MentionOnSelectParams<T = unknown> = {
|
||||
|
@ -86,6 +87,19 @@ export type MentionOnChangeParams = (
|
|||
rawValue: string
|
||||
) => void;
|
||||
|
||||
export type MentionInputProps = {
|
||||
mentions: MentionSuggestionExtension[];
|
||||
onChange: MentionOnChangeParams;
|
||||
onFocus?: (v: EditorEvents['focus']) => void;
|
||||
onBlur?: (v: EditorEvents['blur']) => void;
|
||||
defaultValue?: string;
|
||||
autoFocus?: boolean;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
readOnly?: boolean;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Storage {
|
||||
mention: {
|
||||
|
|
Loading…
Reference in New Issue