0
# Utility Functions
1
2
Mantine Spotlight provides utility functions for type checking and custom filter implementations to support advanced use cases.
3
4
## Capabilities
5
6
### Type Guards
7
8
Type guard functions for distinguishing between different action data structures.
9
10
```typescript { .api }
11
/**
12
* Checks if an item is an actions group rather than a single action
13
* @param item - The item to check (action or group)
14
* @returns Type predicate indicating if item is SpotlightActionGroupData
15
*/
16
function isActionsGroup(
17
item: SpotlightActionData | SpotlightActionGroupData
18
): item is SpotlightActionGroupData;
19
```
20
21
**Usage Example:**
22
23
```typescript
24
import { isActionsGroup } from "@mantine/spotlight";
25
26
function processActions(actions: SpotlightActions[]) {
27
actions.forEach(item => {
28
if (isActionsGroup(item)) {
29
console.log(`Group: ${item.group} has ${item.actions.length} actions`);
30
item.actions.forEach(action => {
31
console.log(` - ${action.label}`);
32
});
33
} else {
34
console.log(`Action: ${item.label}`);
35
}
36
});
37
}
38
```
39
40
### Filter Functions
41
42
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.
43
44
```typescript { .api }
45
type SpotlightFilterFunction = (
46
query: string,
47
actions: SpotlightActions[]
48
) => SpotlightActions[];
49
```
50
51
**Custom Filter Examples:**
52
53
```typescript
54
import { SpotlightFilterFunction, isActionsGroup } from "@mantine/spotlight";
55
56
// Simple filter example
57
const simpleFilter: SpotlightFilterFunction = (query, actions) => {
58
if (!query.trim()) return actions;
59
60
return actions.filter(action => {
61
if (isActionsGroup(action)) {
62
// Filter group actions
63
const filteredActions = action.actions.filter(subAction =>
64
subAction.label?.toLowerCase().includes(query.toLowerCase())
65
);
66
return filteredActions.length > 0;
67
} else {
68
// Filter individual action
69
return action.label?.toLowerCase().includes(query.toLowerCase());
70
}
71
});
72
};
73
74
// Priority-based filter (similar to internal default)
75
const priorityFilter: SpotlightFilterFunction = (query, actions) => {
76
if (!query.trim()) return actions;
77
78
const queryLower = query.toLowerCase();
79
const labelMatches: SpotlightActions[] = [];
80
const descriptionMatches: SpotlightActions[] = [];
81
82
actions.forEach(action => {
83
if (isActionsGroup(action)) {
84
const labelMatched = action.actions.filter(subAction =>
85
subAction.label?.toLowerCase().includes(queryLower)
86
);
87
const descMatched = action.actions.filter(subAction =>
88
subAction.description?.toLowerCase().includes(queryLower) ||
89
(typeof subAction.keywords === 'string' &&
90
subAction.keywords.toLowerCase().includes(queryLower)) ||
91
(Array.isArray(subAction.keywords) &&
92
subAction.keywords.some(k => k.toLowerCase().includes(queryLower)))
93
);
94
95
if (labelMatched.length > 0) {
96
labelMatches.push({ ...action, actions: labelMatched });
97
} else if (descMatched.length > 0) {
98
descriptionMatches.push({ ...action, actions: descMatched });
99
}
100
} else {
101
if (action.label?.toLowerCase().includes(queryLower)) {
102
labelMatches.push(action);
103
} else if (
104
action.description?.toLowerCase().includes(queryLower) ||
105
(typeof action.keywords === 'string' &&
106
action.keywords.toLowerCase().includes(queryLower)) ||
107
(Array.isArray(action.keywords) &&
108
action.keywords.some(k => k.toLowerCase().includes(queryLower)))
109
) {
110
descriptionMatches.push(action);
111
}
112
}
113
});
114
115
return [...labelMatches, ...descriptionMatches];
116
};
117
```
118
119
### Action Data Types
120
121
Type definitions for working with actions and action groups.
122
123
```typescript { .api }
124
interface SpotlightActionData extends SpotlightActionProps {
125
/** Unique identifier for the action */
126
id: string;
127
/** Optional group name for organizing actions */
128
group?: string;
129
}
130
131
interface SpotlightActionGroupData {
132
/** Group label displayed in the interface */
133
group: string;
134
/** Array of actions belonging to this group */
135
actions: SpotlightActionData[];
136
}
137
138
type SpotlightActions = SpotlightActionData | SpotlightActionGroupData;
139
```
140
141
## Advanced Filter Implementation
142
143
You can create custom filter functions for specialized search behavior:
144
145
```typescript
146
import { SpotlightFilterFunction, isActionsGroup } from "@mantine/spotlight";
147
148
// Fuzzy search filter
149
const fuzzyFilter: SpotlightFilterFunction = (query, actions) => {
150
if (!query.trim()) return actions;
151
152
const fuzzyMatch = (text: string, query: string): boolean => {
153
const queryLower = query.toLowerCase();
154
const textLower = text.toLowerCase();
155
let queryIndex = 0;
156
157
for (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) {
158
if (textLower[i] === queryLower[queryIndex]) {
159
queryIndex++;
160
}
161
}
162
163
return queryIndex === queryLower.length;
164
};
165
166
return actions.filter(action => {
167
if (isActionsGroup(action)) {
168
const filteredActions = action.actions.filter(subAction =>
169
fuzzyMatch(subAction.label || "", query) ||
170
fuzzyMatch(subAction.description || "", query)
171
);
172
if (filteredActions.length > 0) {
173
return { ...action, actions: filteredActions };
174
}
175
return false;
176
} else {
177
return fuzzyMatch(action.label || "", query) ||
178
fuzzyMatch(action.description || "", query);
179
}
180
});
181
};
182
183
// Weighted search filter
184
const weightedFilter: SpotlightFilterFunction = (query, actions) => {
185
if (!query.trim()) return actions;
186
187
const queryLower = query.toLowerCase();
188
const scoredActions: Array<{ action: SpotlightActions; score: number }> = [];
189
190
actions.forEach(action => {
191
if (isActionsGroup(action)) {
192
let groupScore = 0;
193
const filteredActions = action.actions.filter(subAction => {
194
let score = 0;
195
if (subAction.label?.toLowerCase().includes(queryLower)) score += 10;
196
if (subAction.description?.toLowerCase().includes(queryLower)) score += 5;
197
if (typeof subAction.keywords === 'string' &&
198
subAction.keywords.toLowerCase().includes(queryLower)) score += 3;
199
if (Array.isArray(subAction.keywords) &&
200
subAction.keywords.some(k => k.toLowerCase().includes(queryLower))) score += 3;
201
202
if (score > 0) groupScore += score;
203
return score > 0;
204
});
205
206
if (filteredActions.length > 0) {
207
scoredActions.push({
208
action: { ...action, actions: filteredActions },
209
score: groupScore
210
});
211
}
212
} else {
213
let score = 0;
214
if (action.label?.toLowerCase().includes(queryLower)) score += 10;
215
if (action.description?.toLowerCase().includes(queryLower)) score += 5;
216
if (typeof action.keywords === 'string' &&
217
action.keywords.toLowerCase().includes(queryLower)) score += 3;
218
if (Array.isArray(action.keywords) &&
219
action.keywords.some(k => k.toLowerCase().includes(queryLower))) score += 3;
220
221
if (score > 0) {
222
scoredActions.push({ action, score });
223
}
224
}
225
});
226
227
// Sort by score (highest first) and return actions
228
return scoredActions
229
.sort((a, b) => b.score - a.score)
230
.map(item => item.action);
231
};
232
```
233
234
## Action Data Utilities
235
236
Helper functions for working with action data structures:
237
238
```typescript
239
import { SpotlightActions, isActionsGroup } from "@mantine/spotlight";
240
241
// Flatten grouped actions into a single array
242
function flattenActions(actions: SpotlightActions[]): SpotlightActionData[] {
243
const flattened: SpotlightActionData[] = [];
244
245
actions.forEach(action => {
246
if (isActionsGroup(action)) {
247
flattened.push(...action.actions);
248
} else {
249
flattened.push(action);
250
}
251
});
252
253
return flattened;
254
}
255
256
// Group individual actions by their group property
257
function groupActions(actions: SpotlightActionData[]): SpotlightActions[] {
258
const groups: Record<string, SpotlightActionData[]> = {};
259
const ungrouped: SpotlightActionData[] = [];
260
261
actions.forEach(action => {
262
if (action.group) {
263
if (!groups[action.group]) {
264
groups[action.group] = [];
265
}
266
groups[action.group].push(action);
267
} else {
268
ungrouped.push(action);
269
}
270
});
271
272
const result: SpotlightActions[] = [];
273
274
// Add grouped actions
275
Object.entries(groups).forEach(([group, groupActions]) => {
276
result.push({ group, actions: groupActions });
277
});
278
279
// Add ungrouped actions
280
result.push(...ungrouped);
281
282
return result;
283
}
284
285
// Count total actions including those in groups
286
function countActions(actions: SpotlightActions[]): number {
287
return actions.reduce((count, action) => {
288
if (isActionsGroup(action)) {
289
return count + action.actions.length;
290
} else {
291
return count + 1;
292
}
293
}, 0);
294
}
295
```
296
297
## Filter Performance Optimization
298
299
For large action datasets, consider implementing optimized filtering:
300
301
```typescript
302
import { SpotlightFilterFunction } from "@mantine/spotlight";
303
304
// Memoized filter for better performance
305
function createMemoizedFilter(baseFilter: SpotlightFilterFunction): SpotlightFilterFunction {
306
const cache = new Map<string, SpotlightActions[]>();
307
308
return (query: string, actions: SpotlightActions[]) => {
309
const cacheKey = `${query}_${actions.length}`;
310
311
if (cache.has(cacheKey)) {
312
return cache.get(cacheKey)!;
313
}
314
315
const result = baseFilter(query, actions);
316
cache.set(cacheKey, result);
317
318
// Limit cache size
319
if (cache.size > 100) {
320
const firstKey = cache.keys().next().value;
321
cache.delete(firstKey);
322
}
323
324
return result;
325
};
326
}
327
328
const memoizedFilter = createMemoizedFilter(simpleFilter);
329
```