v6.0.35

Spotlight

Command center for your application
Import
License

Installation

Package depends on @asuikit/core and @asuikit/hooks.

Install with yarn:

yarn add @asuikit/spotlight

Install with npm:

npm install @asuikit/spotlight

Usage

Spotlight component lets you build a popup search interface which can be triggered with keyboard shortcut or programmatically from anywhere inside your application. To get started, wrap your application with SpotlightProvider component:

import { Button, Group } from '@asuikit/core';
import { SpotlightProvider, spotlight } from '@asuikit/spotlight';
import type { SpotlightAction } from '@asuikit/spotlight';
import { IconHome, IconDashboard, IconFileText, IconSearch } from '@tabler/icons-react';
function SpotlightControl() {
return (
<Group position="center">
<Button onClick={spotlight.open}>Open spotlight</Button>
</Group>
);
}
const actions: SpotlightAction[] = [
{
title: 'Home',
description: 'Get to home page',
onTrigger: () => console.log('Home'),
icon: <IconHome size="1.2rem" />,
},
{
title: 'Dashboard',
description: 'Get full information about current system status',
onTrigger: () => console.log('Dashboard'),
icon: <IconDashboard size="1.2rem" />,
},
{
title: 'Documentation',
description: 'Visit documentation to lean more about all features',
onTrigger: () => console.log('Documentation'),
icon: <IconFileText size="1.2rem" />,
},
];
function Demo() {
return (
<SpotlightProvider
actions={actions}
searchIcon={<IconSearch size="1.2rem" />}
searchPlaceholder="Search..."
shortcut="mod + shift + 1"
nothingFoundMessage="Nothing found..."
>
<SpotlightControl />
</SpotlightProvider>
);
}

Note that if you are using MantineProvider, SpotlightProvider must be used as its child:

import { MantineProvider } from '@asuikit/core';
import { SpotlightProvider } from '@asuikit/spotlight';
function Demo() {
return (
<MantineProvider>
<SpotlightProvider actions={[]}>
<App />
</SpotlightProvider>
</MantineProvider>
);
}

Keyboard shortcuts

SpotlightProvider uses use-hotkeys hook to add keyboard shortcuts, the default shortcut to trigger popup is mod + K, it means that it will be shown when users press ⌘ + K on MacOS and Ctrl + K on any other os.

You can setup multiple shortcuts, for example, Mantine documentation uses the following setup:

import { SpotlightProvider } from '@asuikit/spotlight';
function Demo() {
return (
<SpotlightProvider shortcut={['mod + P', 'mod + K', '/']} actions={[]}>
<App />
</SpotlightProvider>
);
}

It means that user will be able to open documentation search with the following shortcuts:

  • ⌘ + K / Ctrl + K
  • ⌘ + P / Ctrl + P
  • / – single keys are also supported

Note that provided shortcuts will prevent the default behavior, for example, mod + P will disable "Print page" native browser function, choose those shortcuts that will not interfere with desired default browser behavior.

Keyboard shortcuts will not work if:

  • focus is not on current page
  • input, textarea or select elements are focused (these can be overriden with the tagsToIgnore arg)
  • elements have contentEditable=true (can be overriden with the triggerOnContentEditable arg)

To disabled keyboard shortcuts set shortcut={null}:

import { SpotlightProvider } from '@asuikit/spotlight';
function Demo() {
return (
<SpotlightProvider shortcut={null} actions={[]}>
<App />
</SpotlightProvider>
);
}

Event based functions

@asuikit/spotlight exports spotlight object with functions which can be used to perform certain actions from any part of your application:

// All functions can be called from anywhere (not only from components)
import { spotlight } from '@asuikit/spotlight';
// Opens spotlight
spotlight.open();
// Closes spotlight
spotlight.close();
// Toggles spotlight opened state
spotlight.toggle();
// triggers action with given id
spotlight.triggerAction('action-id');
// registers additional actions
spotlight.registerActions([
{
id: 'secret-action-1',
title: 'Secret action',
onTrigger: () => console.log('Secret'),
},
{
id: 'secret-action-2',
title: 'Another secret action',
onTrigger: () => console.log('Secret'),
},
]);
// removes actions
spotlight.removeActions(['secret-action-1', 'secret-action-2']);

use-spotlight hook

useSpotlight hook lets you control spotlight from anywhere in your application. For example, it can be used to open spotlight with button click:

import { useSpotlight } from '@asuikit/spotlight';
import { Button, Group } from '@asuikit/core';
function SpotlightControl() {
const spotlight = useSpotlight();
return (
<Group position="center">
<Button onClick={() => spotlight.openSpotlight()}>Open spotlight</Button>
</Group>
);
}

useSpotlight returns an object with the following properties:

interface UseSpotlight {
/** Opens spotlight */
openSpotlight(): void;
/** Closes spotlight */
closeSpotlight(): void;
/** Toggles spotlight opened state */
toggleSpotlight(): void;
/** Triggers action with given id */
triggerAction(actionId: string): void;
/** Registers additional actions */
registerActions(action: SpotlightAction[]): void;
/** Removes actions with given ids */
removeActions(actionIds: string[]): void;
/** Current opened state */
opened: boolean;
/** List of registered actions */
actions: SpotlightAction[];
/** Search query */
query: string;
}

Spotlight actions

actions is the only required prop of SpotlightProvider. Action shape:

interface SpotlightAction {
/** Action id, may be used to trigger action or find it in actions array,
* if not provided random string will be generated instead */
id?: string;
/** Action title, topmost large text, used for filtering */
title: string;
/** Action description, small text displayed after title, used for filtering */
description?: string;
/** Action group, used to render group label */
group?: string;
/** Keywords that are used for filtering, not displayed anywhere,
* can be a string: "react,router,javascript" or an array: ['react', 'router', 'javascript'] */
keywords?: string | string[];
/** Decorative icon */
icon?: ReactNode;
/** Function that is called when action is triggered */
onTrigger(action: SpotlightAction): void;
/** Any other properties that can be consumed by custom action component */
[key: string]: any;
}

You can import SpotlightAction type from @asuikit/spotlight package:

import type { SpotlightAction } from '@asuikit/spotlight';

Controlled query

Use query and onQueryChange props to control search query:

import { useState } from 'react';
import { Button, Group } from '@asuikit/core';
import { SpotlightProvider, spotlight, SpotlightAction } from '@asuikit/spotlight';
function SpotlightControl() {
return (
<Group position="center">
<Button onClick={spotlight.open}>Open spotlight</Button>
</Group>
);
}
function Demo() {
const [query, setQuery] = useState('');
const actions: SpotlightAction[] =
query !== '%%secret%%'
? [
{
title: 'Reveal secret actions',
description: 'Click this action to reveal secret actions',
onTrigger: () => setQuery('%%secret%%'),
closeOnTrigger: false,
},
]
: [
{ title: 'Super secret action', keywords: '%%secret%%', onTrigger: () => {} },
{
title: 'Rick roll',
description: 'Do not click',
keywords: '%%secret%%',
onTrigger: () => {
window.location.href = 'https://www.youtube.com/watch?v=dQw4w9WgXcQ';
},
},
];
return (
<SpotlightProvider
actions={actions}
query={query}
onQueryChange={setQuery}
searchIcon={<IconSearch size="1.2rem" />}
searchPlaceholder="Search..."
shortcut="mod + shift + 1"
nothingFoundMessage="Nothing found..."
>
<SpotlightControl />
</SpotlightProvider>
);
}

Actions filtering

When user searches, spotlight actions are filtered based on the following action properties:

  • titlestring
  • descriptionstring
  • keywordsstring | string[]

You can change filtering logic by setting filter prop on SpotlightProvider. The following example filters actions only by title:

import { SpotlightProvider } from '@asuikit/spotlight';
function Demo() {
return (
<SpotlightProvider
shortcut="mod + alt + V"
nothingFoundMessage="Nothing found..."
filter={(query, actions) =>
actions.filter((action) => action.title.toLowerCase().includes(query.toLowerCase()))
}
{...otherProps}
>
<App />
</SpotlightProvider>
);
}

Actions limit

If you have a large list of actions, most of them won't be presented in the initial list (when user have not entered any text yet). You can control how many actions are displayed at a time with the limit prop:

import { SpotlightProvider } from '@asuikit/spotlight';
import type { SpotlightAction } from '@asuikit/spotlight';
const actions: SpotlightAction[] = Array(100)
.fill(0)
.map((_, index) => ({
title: `Action ${index}`,
onTrigger: () => console.log(`Action ${index}`),
}));
function Demo() {
return (
<SpotlightProvider
actions={actions}
limit={7}
searchPlaceholder="Search..."
shortcut="mod + shift + H"
>
<App />
</SpotlightProvider>
);
}

Register additional actions

You can register any amount of additional actions with registerActions function on useSpotlight hook. To remove actions from the list use removeActions function. Note that to register/remove actions features to work you need to store actions array in state:

import { useState } from 'react';
import { Group, Button } from '@asuikit/core';
import { SpotlightProvider, spotlight } from '@asuikit/spotlight';
import { IconAlien, IconSearch } from '@tabler/icons-react';
function SpotlightControls() {
const [registered, setRegistered] = useState(false);
return (
<Group position="center">
<Button onClick={spotlight.open}>Open spotlight</Button>
{registered ? (
<Button
variant="outline"
color="red"
onClick={() => {
setRegistered(false);
spotlight.removeActions(['secret-action-1', 'secret-action-2']);
}}
>
Remove extra actions
</Button>
) : (
<Button
variant="outline"
onClick={() => {
setRegistered(true);
spotlight.registerActions([
{
id: 'secret-action-1',
title: 'Secret action',
description: 'It was registered with a button click',
icon: <IconAlien size="1.2rem" />,
onTrigger: () => console.log('Secret'),
},
{
id: 'secret-action-2',
title: 'Another secret action',
description: 'You can register multiple actions with just one command',
icon: <IconAlien size="1.2rem" />,
onTrigger: () => console.log('Secret'),
},
]);
}}
>
Register extra actions
</Button>
)}
</Group>
);
}
export function Demo() {
// It is required to store actions in state for register/remove functions to work
const [actions, setActions] = useState([/* ... see in previous demos */]);
return (
<SpotlightProvider
actions={actions}
onActionsChange={setActions}
searchIcon={<IconSearch size="1.2rem" />}
searchPlaceholder="Search..."
shortcut="mod + shift + C"
>
<SpotlightControls />
</SpotlightProvider>
);
}

Group actions

import { SpotlightProvider, SpotlightAction } from '@asuikit/spotlight';
const onTrigger = () => {};
const actions: SpotlightAction[] = [
{ title: 'Home', group: 'main', onTrigger },
{ title: 'Docs', group: 'main', onTrigger },
{ title: 'Dashboard', group: 'main', onTrigger },
{ title: 'Component: Tabs', group: 'search', onTrigger },
{ title: 'Component: SegmentedControl', group: 'search', onTrigger },
{ title: 'Component: Button', group: 'search', onTrigger },
];
function Demo() {
return (
<SpotlightProvider
actions={actions}
searchPlaceholder="Search..."
shortcut="mod + shift + V"
>
<App />
</SpotlightProvider>
);
}

Custom action component

You can provide custom component to render actions, this feature can be used to customize how actions are displayed:

import { createStyles, UnstyledButton, Group, Text, Image, Center, Badge, rem } from '@asuikit/core';
import { SpotlightProvider, SpotlightAction, SpotlightActionProps } from '@asuikit/spotlight';
const actions: SpotlightAction[] = [
{
image: 'https://img.icons8.com/clouds/256/000000/futurama-bender.png',
title: 'Bender Bending Rodríguez',
description: 'Fascinated with cooking, though has no sense of taste',
new: true,
onTrigger: () => {},
},
{
image: 'https://img.icons8.com/clouds/256/000000/futurama-mom.png',
title: 'Carol Miller',
description: 'One of the richest people on Earth',
new: false,
onTrigger: () => {},
},
{
image: 'https://img.icons8.com/clouds/256/000000/homer-simpson.png',
title: 'Homer Simpson',
description: 'Overweight, lazy, and often ignorant',
new: false,
onTrigger: () => {},
},
{
image: 'https://img.icons8.com/clouds/256/000000/spongebob-squarepants.png',
title: 'Spongebob Squarepants',
description: 'Not just a sponge',
new: false,
onTrigger: () => {},
},
];
const useStyles = createStyles((theme) => ({
action: {
position: 'relative',
display: 'block',
width: '100%',
padding: `${rem(10)} ${rem(12)}`,
borderRadius: theme.radius.sm,
...theme.fn.hover({
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[4] : theme.colors.gray[1],
}),
'&[data-hovered]': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[4] : theme.colors.gray[1],
},
},
}));
function CustomAction({
action,
styles,
classNames,
hovered,
onTrigger,
...others
}: SpotlightActionProps) {
const { classes } = useStyles(null, { styles, classNames, name: 'Spotlight' });
return (
<UnstyledButton
className={classes.action}
data-hovered={hovered || undefined}
tabIndex={-1}
onMouseDown={(event) => event.preventDefault()}
onClick={onTrigger}
{...others}
>
<Group noWrap>
{action.image && (
<Center>
<Image src={action.image} alt={action.title} width={50} height={50} />
</Center>
)}
<div style={{ flex: 1 }}>
<Text>{action.title}</Text>
{action.description && (
<Text color="dimmed" size="xs">
{action.description}
</Text>
)}
</div>
{action.new && <Badge>new</Badge>}
</Group>
</UnstyledButton>
);
}
function Demo() {
return (
<SpotlightProvider
actions={actions}
actionComponent={CustomAction}
searchPlaceholder="Search..."
shortcut="mod + shift + I"
>
<App />
</SpotlightProvider>
);
}

Custom actions wrapper component

With custom actions wrapper component you can customize how actions list is rendered, for example, you can add a footer:

import { SpotlightProvider } from '@asuikit/spotlight';
import { Group, Text, Anchor, rem } from '@asuikit/core';
function ActionsWrapper({ children }: { children: React.ReactNode }) {
return (
<div>
{children}
<Group
position="apart"
px={15}
py="xs"
sx={(theme) => ({
borderTop: `${rem(1)} solid ${
theme.colorScheme === 'dark' ? theme.colors.dark[4] : theme.colors.gray[2]
}`,
})}
>
<Text size="xs" color="dimmed">
Search powered by Mantine
</Text>
<Anchor size="xs" href="#">
Learn more
</Anchor>
</Group>
</div>
);
}
function Demo() {
return (
<SpotlightProvider
shortcut="mod + alt + T"
nothingFoundMessage="Nothing found..."
actionsWrapperComponent={ActionsWrapper}
{...otherProps}
>
<App />
</SpotlightProvider>
);
}

Close spotlight on action trigger

By default, spotlight will be closed when any action is triggered with mouse click or Enter key. To change this behavior set closeOnActionTrigger={false} prop on SpotlightProvider:

import { SpotlightProvider } from '@asuikit/spotlight';
function Demo() {
return (
<SpotlightProvider
shortcut="mod + shift + G"
closeOnActionTrigger={false}
{...otherProps}
>
<App />
</SpotlightProvider>
);
}

Close spotlight on specific action trigger

Other than with the global closeOnActionTrigger property, the closeOnTrigger property can be defined for specific actions. The action will then ignore the closeOnActionTrigger property and use its own definition.

import { SpotlightProvider, SpotlightAction } from '@asuikit/spotlight';
const onTrigger = () => {};
const actions: SpotlightAction[] = [
{ title: 'Will stay open', onTrigger, closeOnTrigger: false },
{ title: 'Will close', onTrigger, closeOnTrigger: true },
];
function Demo() {
return (
<SpotlightProvider
actions={actions}
searchPlaceholder="Search..."
shortcut="mod + shift + 5"
>
<App />
</SpotlightProvider>
);
}

Highlight query

Default action component supports highlighting search query with Highlight component. To enable this option set highlightQuery on SpotlightProvider:

import { SpotlightProvider } from '@asuikit/spotlight';
function Demo() {
return (
<SpotlightProvider
shortcut="mod + alt + L"
highlightQuery
{...otherProps}
>
<App />
</SpotlightProvider>
);
}

Change transitions

Component presence is animated with Transition component, it supports all premade and custom transitions. To change transition set Transition component props with transitionProps:

import { SpotlightProvider } from '@asuikit/spotlight';
function Demo() {
return (
<SpotlightProvider
shortcut="mod + shift + 2"
transitionProps={{ duration: 300, transition: 'slide-down' }}
{...otherProps}
>
<App />
</SpotlightProvider>
);
}

To disable transitions set transitionProps={{ duration: 0 }}:

import { SpotlightProvider } from '@asuikit/spotlight';
function Demo() {
return (
<SpotlightProvider
shortcut="mod + shift + 2"
transitionProps={{ duration: 0 }}
{...otherProps}
>
<App />
</SpotlightProvider>
);
}

Fixed elements offset

SpotlightProvider component uses react-remove-scroll package to lock scroll. To properly size these elements add a className to them (documentation):

import { RemoveScroll } from '@asuikit/core';
// to make "width: 100%"
<div className={RemoveScroll.classNames.fullWidth} />
// to make "right: 0"
<div className={RemoveScroll.classNames.zeroRight} />

SpotlightProvider component props

NameTypeDescription
actionComponent
FC<DefaultActionProps>
Component that is used to render actions
actions *
SpotlightAction[]
Actions list
actionsWrapperComponent
string | FC<{ children: ReactNode; }>
Component that is used to wrap actions list
centered
boolean
Determines whether the modal should be centered vertically, false by default
children
ReactNode
Your application
cleanQueryOnClose
boolean
Should search be cleared when spotlight closes
closeButtonProps
ModalBaseCloseButtonProps
Props added to close button
closeOnActionTrigger
boolean
Should spotlight be closed when action is triggered
closeOnClickOutside
boolean
Determines whether the modal/drawer should be closed when user clicks on the overlay, true by default
closeOnEscape
boolean
Determines whether onClose should be called when user presses escape key, true by default
disabled
boolean
Spotlight will not render if disabled is set to true
filter
(query: string, actions: SpotlightAction[]) => SpotlightAction[]
Function used to determine how actions will be filtered based on user input
fullScreen
boolean
Determines whether the modal should take the entire screen
highlightColor
MantineColor
The highlight color
highlightQuery
boolean
Should user query be highlighted in actions title
id
string
Id used to connect modal/drawer with body and title
keepMounted
boolean
If set modal/drawer will not be unmounted from the DOM when it is hidden, display: none styles will be added instead
limit
number
Number of actions displayed at a time
lockScroll
boolean
Determines whether scroll should be locked when opened={true}, defaults to true
nothingFoundMessage
ReactNode
Message displayed when actions were not found
onActionsChange
(actions: SpotlightAction[]) => void
Called when actions change (registered or removed)
onQueryChange
(query: string) => void
Called when user enters text in search input
onSpotlightClose
() => void
Called when spotlight closes
onSpotlightOpen
() => void
Called when spotlight opens
overlayProps
ModalBaseOverlayProps
Props added to Overlay component, use configure opacity, background color, styles and other properties
padding
number | "xs" | "sm" | "md" | "lg" | "xl"
Key of theme.spacing or any valid CSS value to set content, header and footer padding, 'md' by default
portalProps
Omit<PortalProps, "children" | "target" | "withinPortal">
Props to pass down to the portal when withinPortal is true
query
string
Controlled search query
radius
number | "xs" | "sm" | "md" | "lg" | "xl"
Key of theme.radius or any valid CSS value to set border-radius, theme.defaultRadius by default
returnFocus
boolean
Determines whether focus should be returned to the last active element onClose is called, true by default
scrollAreaComponent
FC<{ children: ReactNode; }>
Component used as scrollable container for actions list, defaults to ScrollArea.Autosize
searchIcon
ReactNode
Search input icon
searchInputProps
TextInputProps
Props spread to search input
searchPlaceholder
string
Search input placeholder
shadow
MantineShadow
Key of theme.shadows or any valid css box-shadow value, 'xl' by default
shortcut
string | string[]
Keyboard shortcut or list of shortcuts to trigger spotlight
size
number | "xs" | "sm" | "md" | "lg" | "xl"
Controls content width, 'md' by default
tagsToIgnore
string[]
Tags to ignore shortcut hotkeys on.
target
string | HTMLElement
Target element or selector where Portal should be rendered, by default new element is created and appended to the document.body
transitionProps
Partial<Omit<TransitionProps, "mounted">>
Props added to Transition component that used to animate overlay and body, use to configure duration and animation type, { duration: 200, transition: 'pop' } by default
trapFocus
boolean
Determines whether focus should be trapped, true by default
triggerOnContentEditable
boolean
Whether shortcuts should trigger based on contentEditable.
withOverlay
boolean
Determines whether overlay should be rendered, true by default
withinPortal
boolean
Determines whether component should be rendered inside Portal, true by default
xOffset
MarginLeft<string | number>
Left/right modal offset, 5vw by default
yOffset
MarginTop<string | number>
Top/bottom modal offset, 5vh by default
zIndex
number
z-index CSS property of root element, 200 by default

SpotlightProvider component Styles API

NameStatic selectorDescription
root.asuikit-SpotlightProvider-rootRoot element
inner.asuikit-SpotlightProvider-innerElement used to center spotlight, has fixed position, takes entire screen
content.asuikit-SpotlightProvider-contentSpotlight content root element
overlay.asuikit-SpotlightProvider-overlayOverlay displayed under the Spotlight
body.asuikit-SpotlightProvider-bodySpotlight body, displayed after header
searchInput.asuikit-SpotlightProvider-searchInputSearch input
nothingFound.asuikit-SpotlightProvider-nothingFoundNothing found message
actions.asuikit-SpotlightProvider-actionsActions list
actionsGroup.asuikit-SpotlightProvider-actionsGroupActions group label
action.asuikit-SpotlightProvider-actionDefault action
actionIcon.asuikit-SpotlightProvider-actionIconDefault action icon wrapper
actionBody.asuikit-SpotlightProvider-actionBodyDefault action body
actionDescription.asuikit-SpotlightProvider-actionDescriptionDefault action description
actionHighlight.asuikit-SpotlightProvider-actionHighlightHighlighted query in default action title