claritykit-svelte
Version:
A comprehensive Svelte component library focused on accessibility, ADHD-optimized design, developer experience, and full SSR compatibility
189 lines (188 loc) • 7.85 kB
JavaScript
import tippy, {} from 'tippy.js';
import MentionList from './MentionList.svelte';
export function createMentionSuggestion(searchUsers, onMentionSelect) {
return {
suggestion: {
items: async ({ query }) => {
try {
const users = await searchUsers(query);
return users.slice(0, 10); // Limit to 10 results
}
catch (error) {
console.error('Error searching users for mentions:', error);
return [];
}
},
render: () => {
let component;
let componentElement;
let popup;
let selectedIndex = 0;
return {
onStart: (props) => {
selectedIndex = 0;
// Create a container for the Svelte component
componentElement = document.createElement('div');
// Mount the Svelte component
component = new MentionList({
target: componentElement,
props: {
...props,
selectedIndex,
command: (item) => {
onMentionSelect?.(item);
props.command(item);
},
},
});
if (!props.clientRect) {
return;
}
popup = tippy('body', {
getReferenceClientRect: props.clientRect,
appendTo: () => document.body,
content: componentElement,
showOnCreate: true,
interactive: true,
trigger: 'manual',
placement: 'bottom-start',
theme: 'mention-dropdown',
maxWidth: 'none',
offset: [0, 8],
popperOptions: {
modifiers: [
{
name: 'flip',
options: {
fallbackPlacements: ['top-start', 'bottom-start'],
},
},
{
name: 'preventOverflow',
options: {
boundary: 'viewport',
padding: 8,
},
},
],
},
});
},
onUpdate(props) {
if (component && component.$set) {
component.$set({
...props,
selectedIndex,
command: (item) => {
onMentionSelect?.(item);
props.command(item);
},
});
}
if (!props.clientRect) {
return;
}
if (popup && popup[0]) {
popup[0].setProps({
getReferenceClientRect: props.clientRect,
});
}
},
onKeyDown(props) {
if (props.event.key === 'Escape') {
if (popup && popup[0]) {
popup[0].hide();
}
return true;
}
if (props.event.key === 'ArrowUp') {
selectedIndex = Math.max(0, selectedIndex - 1);
if (component && component.$set) {
component.$set({ selectedIndex });
}
return true;
}
if (props.event.key === 'ArrowDown') {
selectedIndex = Math.min(props.items.length - 1, selectedIndex + 1);
if (component && component.$set) {
component.$set({ selectedIndex });
}
return true;
}
if (props.event.key === 'Enter') {
const item = props.items[selectedIndex];
if (item) {
onMentionSelect?.(item);
props.command({
id: item.id,
label: item.name,
userId: item.id,
userName: item.name,
userAvatar: item.avatar,
userRole: item.role,
});
}
return true;
}
return false;
},
onExit() {
if (popup && popup[0]) {
popup[0].destroy();
}
if (component && component.$destroy) {
component.$destroy();
}
},
};
},
char: '@',
allowSpaces: false,
startOfLine: false,
},
};
}
// Default user search function (can be overridden)
export async function defaultUserSearch(query) {
// This would typically make an API call to search users
// For now, return mock data
const mockUsers = [
{
id: 'user1',
name: 'Alice Johnson',
email: 'alice@example.com',
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Alice',
role: 'Developer',
status: 'online',
},
{
id: 'user2',
name: 'Bob Smith',
email: 'bob@example.com',
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Bob',
role: 'Designer',
status: 'away',
},
{
id: 'user3',
name: 'Carol Davis',
email: 'carol@example.com',
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Carol',
role: 'Product Manager',
status: 'busy',
},
{
id: 'user4',
name: 'David Wilson',
email: 'david@example.com',
avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=David',
role: 'QA Engineer',
status: 'offline',
},
];
// Filter users based on query
return mockUsers.filter(user => user.name.toLowerCase().includes(query.toLowerCase()) ||
user.email.toLowerCase().includes(query.toLowerCase()) ||
(user.role && user.role.toLowerCase().includes(query.toLowerCase())));
}
export default createMentionSuggestion;