UNPKG

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
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;