CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-mantine--spotlight

Command center components for react and Mantine

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

utilities.mddocs/

Utility Functions

Mantine Spotlight provides utility functions for type checking and custom filter implementations to support advanced use cases.

Capabilities

Type Guards

Type guard functions for distinguishing between different action data structures.

/**
 * Checks if an item is an actions group rather than a single action
 * @param item - The item to check (action or group)
 * @returns Type predicate indicating if item is SpotlightActionGroupData
 */
function isActionsGroup(
  item: SpotlightActionData | SpotlightActionGroupData
): item is SpotlightActionGroupData;

Usage Example:

import { isActionsGroup } from "@mantine/spotlight";

function processActions(actions: SpotlightActions[]) {
  actions.forEach(item => {
    if (isActionsGroup(item)) {
      console.log(`Group: ${item.group} has ${item.actions.length} actions`);
      item.actions.forEach(action => {
        console.log(`  - ${action.label}`);
      });
    } else {
      console.log(`Action: ${item.label}`);
    }
  });
}

Filter Functions

The Spotlight component uses an internal default filter function that provides smart searching across action labels, descriptions, and keywords. This function is not exported from the package but you can create custom filter functions with similar behavior.

type SpotlightFilterFunction = (
  query: string,
  actions: SpotlightActions[]
) => SpotlightActions[];

Custom Filter Examples:

import { SpotlightFilterFunction, isActionsGroup } from "@mantine/spotlight";

// Simple filter example
const simpleFilter: SpotlightFilterFunction = (query, actions) => {
  if (!query.trim()) return actions;
  
  return actions.filter(action => {
    if (isActionsGroup(action)) {
      // Filter group actions
      const filteredActions = action.actions.filter(subAction =>
        subAction.label?.toLowerCase().includes(query.toLowerCase())
      );
      return filteredActions.length > 0;
    } else {
      // Filter individual action
      return action.label?.toLowerCase().includes(query.toLowerCase());
    }
  });
};

// Priority-based filter (similar to internal default)
const priorityFilter: SpotlightFilterFunction = (query, actions) => {
  if (!query.trim()) return actions;
  
  const queryLower = query.toLowerCase();
  const labelMatches: SpotlightActions[] = [];
  const descriptionMatches: SpotlightActions[] = [];
  
  actions.forEach(action => {
    if (isActionsGroup(action)) {
      const labelMatched = action.actions.filter(subAction =>
        subAction.label?.toLowerCase().includes(queryLower)
      );
      const descMatched = action.actions.filter(subAction =>
        subAction.description?.toLowerCase().includes(queryLower) ||
        (typeof subAction.keywords === 'string' && 
         subAction.keywords.toLowerCase().includes(queryLower)) ||
        (Array.isArray(subAction.keywords) && 
         subAction.keywords.some(k => k.toLowerCase().includes(queryLower)))
      );
      
      if (labelMatched.length > 0) {
        labelMatches.push({ ...action, actions: labelMatched });
      } else if (descMatched.length > 0) {
        descriptionMatches.push({ ...action, actions: descMatched });
      }
    } else {
      if (action.label?.toLowerCase().includes(queryLower)) {
        labelMatches.push(action);
      } else if (
        action.description?.toLowerCase().includes(queryLower) ||
        (typeof action.keywords === 'string' && 
         action.keywords.toLowerCase().includes(queryLower)) ||
        (Array.isArray(action.keywords) && 
         action.keywords.some(k => k.toLowerCase().includes(queryLower)))
      ) {
        descriptionMatches.push(action);
      }
    }
  });
  
  return [...labelMatches, ...descriptionMatches];
};

Action Data Types

Type definitions for working with actions and action groups.

interface SpotlightActionData extends SpotlightActionProps {
  /** Unique identifier for the action */
  id: string;
  /** Optional group name for organizing actions */
  group?: string;
}

interface SpotlightActionGroupData {
  /** Group label displayed in the interface */
  group: string;
  /** Array of actions belonging to this group */
  actions: SpotlightActionData[];
}

type SpotlightActions = SpotlightActionData | SpotlightActionGroupData;

Advanced Filter Implementation

You can create custom filter functions for specialized search behavior:

import { SpotlightFilterFunction, isActionsGroup } from "@mantine/spotlight";

// Fuzzy search filter
const fuzzyFilter: SpotlightFilterFunction = (query, actions) => {
  if (!query.trim()) return actions;
  
  const fuzzyMatch = (text: string, query: string): boolean => {
    const queryLower = query.toLowerCase();
    const textLower = text.toLowerCase();
    let queryIndex = 0;
    
    for (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) {
      if (textLower[i] === queryLower[queryIndex]) {
        queryIndex++;
      }
    }
    
    return queryIndex === queryLower.length;
  };

  return actions.filter(action => {
    if (isActionsGroup(action)) {
      const filteredActions = action.actions.filter(subAction =>
        fuzzyMatch(subAction.label || "", query) ||
        fuzzyMatch(subAction.description || "", query)
      );
      if (filteredActions.length > 0) {
        return { ...action, actions: filteredActions };
      }
      return false;
    } else {
      return fuzzyMatch(action.label || "", query) ||
             fuzzyMatch(action.description || "", query);
    }
  });
};

// Weighted search filter
const weightedFilter: SpotlightFilterFunction = (query, actions) => {
  if (!query.trim()) return actions;
  
  const queryLower = query.toLowerCase();
  const scoredActions: Array<{ action: SpotlightActions; score: number }> = [];
  
  actions.forEach(action => {
    if (isActionsGroup(action)) {
      let groupScore = 0;
      const filteredActions = action.actions.filter(subAction => {
        let score = 0;
        if (subAction.label?.toLowerCase().includes(queryLower)) score += 10;
        if (subAction.description?.toLowerCase().includes(queryLower)) score += 5;
        if (typeof subAction.keywords === 'string' && 
            subAction.keywords.toLowerCase().includes(queryLower)) score += 3;
        if (Array.isArray(subAction.keywords) && 
            subAction.keywords.some(k => k.toLowerCase().includes(queryLower))) score += 3;
        
        if (score > 0) groupScore += score;
        return score > 0;
      });
      
      if (filteredActions.length > 0) {
        scoredActions.push({
          action: { ...action, actions: filteredActions },
          score: groupScore
        });
      }
    } else {
      let score = 0;
      if (action.label?.toLowerCase().includes(queryLower)) score += 10;
      if (action.description?.toLowerCase().includes(queryLower)) score += 5;
      if (typeof action.keywords === 'string' && 
          action.keywords.toLowerCase().includes(queryLower)) score += 3;
      if (Array.isArray(action.keywords) && 
          action.keywords.some(k => k.toLowerCase().includes(queryLower))) score += 3;
      
      if (score > 0) {
        scoredActions.push({ action, score });
      }
    }
  });
  
  // Sort by score (highest first) and return actions
  return scoredActions
    .sort((a, b) => b.score - a.score)
    .map(item => item.action);
};

Action Data Utilities

Helper functions for working with action data structures:

import { SpotlightActions, isActionsGroup } from "@mantine/spotlight";

// Flatten grouped actions into a single array
function flattenActions(actions: SpotlightActions[]): SpotlightActionData[] {
  const flattened: SpotlightActionData[] = [];
  
  actions.forEach(action => {
    if (isActionsGroup(action)) {
      flattened.push(...action.actions);
    } else {
      flattened.push(action);
    }
  });
  
  return flattened;
}

// Group individual actions by their group property
function groupActions(actions: SpotlightActionData[]): SpotlightActions[] {
  const groups: Record<string, SpotlightActionData[]> = {};
  const ungrouped: SpotlightActionData[] = [];
  
  actions.forEach(action => {
    if (action.group) {
      if (!groups[action.group]) {
        groups[action.group] = [];
      }
      groups[action.group].push(action);
    } else {
      ungrouped.push(action);
    }
  });
  
  const result: SpotlightActions[] = [];
  
  // Add grouped actions
  Object.entries(groups).forEach(([group, groupActions]) => {
    result.push({ group, actions: groupActions });
  });
  
  // Add ungrouped actions
  result.push(...ungrouped);
  
  return result;
}

// Count total actions including those in groups
function countActions(actions: SpotlightActions[]): number {
  return actions.reduce((count, action) => {
    if (isActionsGroup(action)) {
      return count + action.actions.length;
    } else {
      return count + 1;
    }
  }, 0);
}

Filter Performance Optimization

For large action datasets, consider implementing optimized filtering:

import { SpotlightFilterFunction } from "@mantine/spotlight";

// Memoized filter for better performance
function createMemoizedFilter(baseFilter: SpotlightFilterFunction): SpotlightFilterFunction {
  const cache = new Map<string, SpotlightActions[]>();
  
  return (query: string, actions: SpotlightActions[]) => {
    const cacheKey = `${query}_${actions.length}`;
    
    if (cache.has(cacheKey)) {
      return cache.get(cacheKey)!;
    }
    
    const result = baseFilter(query, actions);
    cache.set(cacheKey, result);
    
    // Limit cache size
    if (cache.size > 100) {
      const firstKey = cache.keys().next().value;
      cache.delete(firstKey);
    }
    
    return result;
  };
}

const memoizedFilter = createMemoizedFilter(simpleFilter);

docs

compound-components.md

index.md

main-component.md

store-management.md

utilities.md

tile.json