mirror of https://github.com/buster-so/buster.git
Add great meta data
This commit is contained in:
parent
4819a4aa4d
commit
5ef9acd931
|
@ -1,25 +1,44 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { BusterInput } from './BusterInput';
|
||||
import type { BusterInputProps } from './BusterInput.types';
|
||||
|
||||
const meta: Meta<typeof BusterInput> = {
|
||||
title: 'UI/Inputs/BusterInput',
|
||||
component: BusterInput,
|
||||
tags: ['autodocs'],
|
||||
args: {},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="p-4">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof BusterInput>;
|
||||
|
||||
const items: BusterInputProps['items'] = [
|
||||
...Array.from({ length: 3 }, (_, i) => ({
|
||||
label: `Item ${i + 1}`,
|
||||
value: `item${i + 1}`,
|
||||
})),
|
||||
{
|
||||
label: 'Item (disabled)',
|
||||
value: 'item-disabled',
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
label: 'Item (loading)',
|
||||
value: 'item-loading',
|
||||
loading: true,
|
||||
},
|
||||
{
|
||||
label: 'Item do not close on select',
|
||||
value: 'asdf',
|
||||
closeOnSelect: false,
|
||||
},
|
||||
];
|
||||
|
||||
const mentions = [];
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
value: 'Sample text value',
|
||||
items,
|
||||
mentions: [],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,127 +1,86 @@
|
|||
import { Command } from 'cmdk';
|
||||
import type React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Mention, MentionsInput } from 'react-mentions';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { BusterInputProps } from './BusterInput.types';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||
import type { BusterInputProps, BusterOnSelectParams } from './BusterInput.types';
|
||||
import { BusterInputEmpty } from './BusterInputEmpty';
|
||||
import { BusterInputSeparator } from './BusterInputSeparator';
|
||||
import { DEFAULT_MENTION_MARKUP } from './parse-input';
|
||||
import { BusterInputList } from './BusterInputList';
|
||||
import { BusterItemsSelector } from './BusterItemSelector';
|
||||
import { BusterMentionsInput } from './BusterMentionsInput';
|
||||
|
||||
const users = [
|
||||
{ id: '1', display: 'BigNate' },
|
||||
{ id: '2', display: 'ReactFan42' },
|
||||
{ id: '3', display: 'NextJSDev' },
|
||||
];
|
||||
export const BusterInput = ({
|
||||
placeholder,
|
||||
mentions,
|
||||
defaultValue,
|
||||
value: valueProp,
|
||||
|
||||
export const BusterInput = ({ defaultValue, value: valueProp, onChange }: BusterInputProps) => {
|
||||
emptyComponent,
|
||||
items,
|
||||
closeOnSelect = true,
|
||||
addValueToInput = true,
|
||||
submitting,
|
||||
onSubmit,
|
||||
onStop,
|
||||
sendIcon,
|
||||
secondaryActions,
|
||||
variant = 'default',
|
||||
onChange,
|
||||
onItemClick,
|
||||
ariaLabel = 'Buster Input',
|
||||
}: BusterInputProps) => {
|
||||
const [hasClickedSelect, setHasClickedSelect] = useState(false);
|
||||
const [value, setValue] = useState(valueProp ?? defaultValue);
|
||||
|
||||
const showList = !hasClickedSelect && items.length > 0;
|
||||
|
||||
const onChangeInputValue = useCallback((value: string) => {
|
||||
setValue(value);
|
||||
setHasClickedSelect(false);
|
||||
// onChange?.(value);
|
||||
}, []);
|
||||
|
||||
const onSelectItem = useMemoizedFn(({ onClick, ...params }: BusterOnSelectParams) => {
|
||||
const { addValueToInput, value, 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) setValue(inputValue ?? String(label));
|
||||
onClick?.();
|
||||
if (closeOnSelect) setHasClickedSelect(true);
|
||||
onItemClick?.(params);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<MentionsInput
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
setValue(e.target.value);
|
||||
console.log(e.target.value);
|
||||
}}
|
||||
placeholder="Type @ to mention…"
|
||||
style={{
|
||||
control: { fontSize: 16, minHeight: 46 },
|
||||
highlighter: { padding: 8 },
|
||||
input: { padding: 8 },
|
||||
}}
|
||||
classNames={{
|
||||
highlighter: 'bg-red-500/10',
|
||||
suggestions: 'bg-blue-500/20 border',
|
||||
item: 'text-red-500',
|
||||
}}
|
||||
>
|
||||
{/* Always render a valid Mention node */}
|
||||
<Mention
|
||||
trigger="@"
|
||||
markup={DEFAULT_MENTION_MARKUP}
|
||||
data={users}
|
||||
displayTransform={(_, display) => `@${display}`}
|
||||
appendSpaceOnAdd
|
||||
renderSuggestion={(d) => {
|
||||
return d.display;
|
||||
}}
|
||||
/>
|
||||
</MentionsInput>
|
||||
|
||||
<div className="w-full h-px bg-border" />
|
||||
|
||||
<Command
|
||||
label="Command Menu"
|
||||
onValueChange={(e) => {
|
||||
console.log(e);
|
||||
}}
|
||||
>
|
||||
<Command.Input
|
||||
<Command label={ariaLabel}>
|
||||
<BusterMentionsInput
|
||||
className="w-full outline-1 outline-amber-600"
|
||||
value={value}
|
||||
onValueChange={setValue}
|
||||
asChild
|
||||
autoFocus
|
||||
defaultValue={defaultValue}
|
||||
readOnly
|
||||
placeholder="Type @ to mention…"
|
||||
>
|
||||
<textarea />
|
||||
</Command.Input>
|
||||
<Command.List>
|
||||
<BusterInputEmpty>No results found.</BusterInputEmpty>
|
||||
|
||||
<CommandGroup heading="Letters">
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
setValue('a');
|
||||
}}
|
||||
>
|
||||
a
|
||||
</CommandItem>
|
||||
<CommandItem>b</CommandItem>
|
||||
<BusterInputSeparator />
|
||||
<CommandItem>c</CommandItem>
|
||||
</CommandGroup>
|
||||
|
||||
<CommandItem>Apple</CommandItem>
|
||||
</Command.List>
|
||||
placeholder={placeholder}
|
||||
mentions={mentions}
|
||||
value={value}
|
||||
onChangeInputValue={onChangeInputValue}
|
||||
/>
|
||||
<BusterInputList show={showList}>
|
||||
<BusterItemsSelector
|
||||
items={items}
|
||||
onSelect={onSelectItem}
|
||||
addValueToInput={addValueToInput}
|
||||
closeOnSelect={closeOnSelect}
|
||||
/>
|
||||
{emptyComponent && <BusterInputEmpty>{emptyComponent}</BusterInputEmpty>}
|
||||
</BusterInputList>
|
||||
</Command>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandGroup = ({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<typeof Command.Group>) => {
|
||||
return (
|
||||
<Command.Group
|
||||
className={cn(
|
||||
'text-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium',
|
||||
props.className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Command.Group>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandItem = ({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<typeof Command.Item>) => {
|
||||
return (
|
||||
<Command.Item
|
||||
className={cn(
|
||||
'data-[selected=true]:bg-item-hover data-[selected=true]:text-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-base outline-none select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
props.className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Command.Item>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
import type { Command } from 'cmdk';
|
||||
import type React from 'react';
|
||||
import type { DisplayTransformFunc } from 'react-mentions';
|
||||
|
||||
/**
|
||||
* @description Override the addValueToInput and closeOnSelect props for the item based on the group props
|
||||
*/
|
||||
export type GroupOverrideProps = {
|
||||
addValueToInput: boolean | undefined;
|
||||
closeOnSelect: boolean | undefined;
|
||||
};
|
||||
|
||||
export type BusterInputDropdownItem<T = string> = {
|
||||
value: T;
|
||||
inputValue?: string; //if this is undefined, the label will be used (string casted), must have addValueToInput set to true
|
||||
label: string | React.ReactNode;
|
||||
shortcut?: string;
|
||||
icon?: React.ReactNode;
|
||||
|
@ -10,22 +20,48 @@ export type BusterInputDropdownItem<T = string> = {
|
|||
disabled?: boolean;
|
||||
loading?: boolean;
|
||||
closeOnSelect?: boolean; //defaults to parent
|
||||
type?: 'item';
|
||||
addValueToInput?: boolean; //defaults to group addValueToInput
|
||||
};
|
||||
|
||||
export type BusterOnSelectParams = NonNullable<
|
||||
Pick<
|
||||
NonNullable<BusterInputDropdownItem>,
|
||||
| 'value'
|
||||
| 'label'
|
||||
| 'addValueToInput'
|
||||
| 'onClick'
|
||||
| 'inputValue'
|
||||
| 'closeOnSelect'
|
||||
| 'disabled'
|
||||
| 'loading'
|
||||
>
|
||||
>;
|
||||
|
||||
export type BusterOnMentionClickParams = Pick<BusterMentionItem, 'value' | 'label'>;
|
||||
|
||||
export type BusterInputDropdownGroup<T = string> = {
|
||||
label: string | React.ReactNode;
|
||||
items: BusterInputDropdownItem<T>[];
|
||||
addValueToInput?: boolean;
|
||||
closeOnSelect?: boolean;
|
||||
type: 'group';
|
||||
};
|
||||
|
||||
export type BusterMentionItem<V = string> = {
|
||||
export type BusterInputSeperator = {
|
||||
type: 'separator';
|
||||
};
|
||||
|
||||
export type BusterMentionItem<V = string, M = unknown> = {
|
||||
value: V;
|
||||
parsedValue?: string; //if this is undefined, the value will be used
|
||||
label: string | React.ReactNode;
|
||||
selected?: boolean;
|
||||
meta?: M;
|
||||
};
|
||||
|
||||
export type BusterMentionItems<V = string, T = string> = {
|
||||
items: BusterMentionItem<V>[];
|
||||
export type BusterMentionItems<T = string, V = string, M = unknown> = {
|
||||
items: BusterMentionItem<V, M>[];
|
||||
displayTransform?: DisplayTransformFunc;
|
||||
style?: React.CSSProperties;
|
||||
appendSpaceOnAdd?: boolean; //defaults to true
|
||||
|
@ -33,22 +69,34 @@ export type BusterMentionItems<V = string, T = string> = {
|
|||
popoverContent?: (v: BusterMentionItem<V>) => React.ReactNode;
|
||||
};
|
||||
|
||||
export type BusterMentionRecords<V = string, T extends string = string> = {
|
||||
[K in T]: BusterMentionItems<V, T>;
|
||||
};
|
||||
export type BusterMentions<V = string, T extends string = string, M = unknown> = BusterMentionItems<
|
||||
V,
|
||||
T,
|
||||
M
|
||||
>[];
|
||||
|
||||
export type BusterInputProps<T = string> = {
|
||||
export type BusterInputProps<
|
||||
T = string,
|
||||
TMention = string,
|
||||
VMention extends string = string,
|
||||
MMention = unknown,
|
||||
> = {
|
||||
defaultValue: string;
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
submitting?: boolean;
|
||||
onSubmit: (value: string) => void;
|
||||
onStop: () => void;
|
||||
onItemClick?: (value: string) => void;
|
||||
items: (BusterInputDropdownItem<T> | BusterInputDropdownGroup<T>)[];
|
||||
mentions?: BusterMentionRecords<T>;
|
||||
onItemClick?: (params: Omit<BusterOnSelectParams, 'onClick'>) => void;
|
||||
onMentionClick?: (params: BusterMentionItem<T>) => void;
|
||||
items: (BusterInputDropdownItem<T> | BusterInputDropdownGroup<T> | BusterInputSeperator)[];
|
||||
mentions?: BusterMentions<TMention, VMention, MMention>;
|
||||
variant?: 'default';
|
||||
sendIcon?: React.ReactNode;
|
||||
secondaryActions?: React.ReactNode;
|
||||
addValueToInput?: boolean; //defaults to true
|
||||
closeOnSelect?: boolean; //defaults to true
|
||||
};
|
||||
placeholder?: string;
|
||||
ariaLabel?: string;
|
||||
emptyComponent?: React.ReactNode | string | false; //if false, no empty component will be shown
|
||||
} & Pick<React.ComponentProps<typeof Command>, 'filter' | 'shouldFilter'>;
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import { Command } from 'cmdk';
|
||||
import type React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { BusterInputDropdownGroup, BusterOnSelectParams } from './BusterInput.types';
|
||||
import { BusterItemsSelector } from './BusterItemSelector';
|
||||
|
||||
export type BusterInputGroupProps = BusterInputDropdownGroup & {
|
||||
onSelect: (params: BusterOnSelectParams) => void;
|
||||
} & {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export const BusterInputGroup = ({
|
||||
items,
|
||||
label,
|
||||
onSelect,
|
||||
addValueToInput,
|
||||
className,
|
||||
closeOnSelect,
|
||||
style,
|
||||
}: BusterInputGroupProps) => {
|
||||
return (
|
||||
<Command.Group
|
||||
className={cn(
|
||||
'text-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium',
|
||||
className
|
||||
)}
|
||||
style={style}
|
||||
heading={label}
|
||||
>
|
||||
<BusterItemsSelector
|
||||
items={items}
|
||||
onSelect={onSelect}
|
||||
addValueToInput={addValueToInput}
|
||||
closeOnSelect={closeOnSelect}
|
||||
/>
|
||||
</Command.Group>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
import { Command } from 'cmdk';
|
||||
import type React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { BusterInputDropdownItem, BusterOnSelectParams } from './BusterInput.types';
|
||||
|
||||
export type BusterInputItemProps = {
|
||||
onSelect: (d: BusterOnSelectParams) => void;
|
||||
} & BusterInputDropdownItem & {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export const BusterInputItem = ({
|
||||
value,
|
||||
inputValue,
|
||||
label,
|
||||
shortcut,
|
||||
icon,
|
||||
onClick,
|
||||
disabled,
|
||||
loading,
|
||||
closeOnSelect,
|
||||
type,
|
||||
addValueToInput,
|
||||
onSelect,
|
||||
...props
|
||||
}: BusterInputItemProps) => {
|
||||
return (
|
||||
<Command.Item
|
||||
className={cn(
|
||||
'data-[selected=true]:bg-item-hover data-[selected=true]:text-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-base outline-none select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
props.className
|
||||
)}
|
||||
{...props}
|
||||
onSelect={() => {
|
||||
onSelect({
|
||||
value,
|
||||
inputValue,
|
||||
label,
|
||||
onClick,
|
||||
addValueToInput,
|
||||
closeOnSelect,
|
||||
disabled,
|
||||
loading,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Command.Item>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
import { Command } from 'cmdk';
|
||||
import type React from 'react';
|
||||
|
||||
interface BusterInputListProps {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
show?: boolean;
|
||||
}
|
||||
|
||||
export const BusterInputList = ({
|
||||
className,
|
||||
style,
|
||||
children,
|
||||
show = true,
|
||||
}: BusterInputListProps) => {
|
||||
if (!show) return null;
|
||||
return (
|
||||
<Command.List className={className} style={style}>
|
||||
{children}
|
||||
</Command.List>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,62 @@
|
|||
import type {
|
||||
BusterInputProps,
|
||||
BusterOnSelectParams,
|
||||
GroupOverrideProps,
|
||||
} from './BusterInput.types';
|
||||
import { BusterInputGroup } from './BusterInputGroup';
|
||||
import { BusterInputItem } from './BusterInputItem';
|
||||
import { BusterInputSeparator } from './BusterInputSeparator';
|
||||
|
||||
export const BusterItemSelector = ({
|
||||
item,
|
||||
onSelect,
|
||||
addValueToInput,
|
||||
closeOnSelect,
|
||||
}: {
|
||||
item: BusterInputProps['items'][number];
|
||||
onSelect: (params: BusterOnSelectParams) => void;
|
||||
} & GroupOverrideProps) => {
|
||||
if (item.type === 'separator') {
|
||||
return <BusterInputSeparator />;
|
||||
}
|
||||
|
||||
if (item.type === 'group') {
|
||||
return <BusterInputGroup {...item} onSelect={onSelect} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<BusterInputItem
|
||||
{...item}
|
||||
onSelect={onSelect}
|
||||
addValueToInput={item?.addValueToInput ?? addValueToInput}
|
||||
closeOnSelect={item?.closeOnSelect ?? closeOnSelect}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const BusterItemsSelector = ({
|
||||
items,
|
||||
onSelect,
|
||||
addValueToInput,
|
||||
closeOnSelect,
|
||||
}: {
|
||||
items: BusterInputProps['items'];
|
||||
onSelect: (params: BusterOnSelectParams) => void;
|
||||
} & GroupOverrideProps) => {
|
||||
if (!items) return null;
|
||||
return items.map((item, index) => (
|
||||
<BusterItemSelector
|
||||
key={keySelector(item, index)}
|
||||
item={item}
|
||||
onSelect={onSelect}
|
||||
addValueToInput={addValueToInput}
|
||||
closeOnSelect={closeOnSelect}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
const keySelector = (item: BusterInputProps['items'][number], index: number) => {
|
||||
if (item.type === 'separator') return `separator${index}`;
|
||||
if (item.type === 'group') return `${item.label} + index`;
|
||||
return item.value;
|
||||
};
|
|
@ -0,0 +1,81 @@
|
|||
/** biome-ignore-all lint/complexity/noUselessFragments: Intersting bug when NOT using fragments */
|
||||
import { Command } from 'cmdk';
|
||||
import React, { useState } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { Mention, type MentionProps, MentionsInput } from 'react-mentions';
|
||||
import type { BusterInputProps } from './BusterInput.types';
|
||||
import { DEFAULT_MENTION_MARKUP } from './parse-input';
|
||||
|
||||
export type BusterMentionsInputProps = Pick<
|
||||
BusterInputProps,
|
||||
'mentions' | 'value' | 'placeholder' | 'defaultValue'
|
||||
> & {
|
||||
onChangeInputValue: (value: string) => void;
|
||||
} & React.ComponentPropsWithoutRef<typeof Command.Input>;
|
||||
|
||||
export const BusterMentionsInput = ({
|
||||
children,
|
||||
value: valueProp,
|
||||
placeholder,
|
||||
defaultValue,
|
||||
mentions,
|
||||
value,
|
||||
onChangeInputValue,
|
||||
...props
|
||||
}: BusterMentionsInputProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<MentionsInput
|
||||
value={value}
|
||||
onChange={(e) => onChangeInputValue(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
style={{
|
||||
control: { fontSize: 16, minHeight: 46 },
|
||||
highlighter: { padding: 8 },
|
||||
input: { padding: 8 },
|
||||
}}
|
||||
className="swag"
|
||||
classNames={{
|
||||
highlighter: 'bg-red-500/10',
|
||||
suggestions: 'bg-blue-500/20 border',
|
||||
item: 'text-red-500',
|
||||
}}
|
||||
>
|
||||
{mentions?.length ? (
|
||||
mentions.map((mention) => <FormattedMention key={mention.trigger} {...mention} />)
|
||||
) : (
|
||||
<Mention trigger="" markup={DEFAULT_MENTION_MARKUP} data={[]} appendSpaceOnAdd />
|
||||
)}
|
||||
</MentionsInput>
|
||||
|
||||
<Command.Input value={value} {...props}>
|
||||
{children}
|
||||
</Command.Input>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const FormattedMention = React.memo(
|
||||
(
|
||||
mention: NonNullable<BusterInputProps['mentions']>[number]
|
||||
): React.ReactElement<MentionProps> => {
|
||||
const formattedItems = mention.items.map((item) => ({
|
||||
id: String(item.value),
|
||||
display: typeof item.label === 'string' ? item.label : String(item.value),
|
||||
}));
|
||||
|
||||
return (
|
||||
<Mention
|
||||
key={mention.trigger}
|
||||
trigger={mention.trigger}
|
||||
markup={DEFAULT_MENTION_MARKUP}
|
||||
data={formattedItems}
|
||||
displayTransform={mention.displayTransform}
|
||||
appendSpaceOnAdd={mention.appendSpaceOnAdd ?? true}
|
||||
renderSuggestion={(d) => d.display}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
FormattedMention.displayName = 'FormattedMention';
|
|
@ -21,12 +21,12 @@ describe('parseMarkupInput', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const mockItemsRecord = {
|
||||
'@': {
|
||||
const mockItemsRecord = [
|
||||
{
|
||||
items: mockItems,
|
||||
trigger: '@' as const,
|
||||
},
|
||||
};
|
||||
];
|
||||
|
||||
it('should replace mention markup with parsedValue when available', () => {
|
||||
const input = 'Hello @[BigNate](1) how are you?';
|
||||
|
|
|
@ -3,10 +3,10 @@ import type { BusterMentionItems } from './BusterInput.types';
|
|||
export const DEFAULT_MENTION_MARKUP = '@[__display__](__id__)';
|
||||
const DEFAULT_MENTION_REGEX = /@\[([^\]]+)\]\(([^)]+)\)/g;
|
||||
|
||||
type BusterMentionItemsRecord<V = string, T extends string = string> = Record<
|
||||
T,
|
||||
Pick<BusterMentionItems<V, T>, 'items' | 'trigger'>
|
||||
>;
|
||||
type BusterMentionItemsRecord<V = string, T extends string = string> = Pick<
|
||||
BusterMentionItems<V, T>,
|
||||
'items' | 'trigger'
|
||||
>[];
|
||||
|
||||
export const parseMarkupInput = <V = string, T extends string = string>({
|
||||
input,
|
||||
|
|
Loading…
Reference in New Issue