import type { ElementType, KeyboardEvent, MouseEvent, ReactNode } from "react";
import type { AppOption } from "../AppSwitcher/AppSwitcherProps";
import type { LauncherSynonyms } from "./engine";
/** Rendered as ``, so any lucide-compatible component works. */
export type LauncherIcon = ElementType;
/** One destination the launcher can open; screens are what pins, Frequent and Recent refer to. */
export type LauncherScreen = {
/** Unique across all sections; the id that pins, Frequent and Recent refer to. */
id: string;
/** Row text; also what search matches first. */
label: string;
/** Destination of the row's link; Enter passes it to `onNavigate`. */
href: string;
/** Row icon; falls back to the module's icon in search results. */
icon?: LauncherIcon;
/** Count or short text shown on the row. */
badge?: number | string;
/** Extra terms the query may match; never rendered. */
keywords?: readonly string[];
/** Listed on the module's second line (first `keyScreenLimit` of them). */
isKey?: boolean;
};
/** A group of screens shown as one row in the browse view. */
export type LauncherModule = {
/** Unique across sections. */
id: string;
/** Row title; also what search matches first. */
label: string;
/** Normally its first visible screen's href. */
href: string;
/** Icon beside the title. */
icon?: LauncherIcon;
/** Count or short text shown on the module row. */
badge?: number | string;
/** Shows a small "New" tag after the name. */
isNew?: boolean;
/** Extra terms the query may match; never rendered. */
keywords?: readonly string[];
/** Pinning the module pins `screens[0]`; a module with no screens is not rendered. */
screens: readonly LauncherScreen[];
};
/** A labelled run of modules; a section with no renderable module is dropped. */
export type LauncherSection = {
/** Unique key of the section. */
id: string;
/** Section heading. */
label: string;
/** Modules in display order; modules with no screens are skipped. */
modules: readonly LauncherModule[];
};
/** One label/value pair of a result, e.g. PAN: ABCDE1234F; plain text only, never HTML. */
export type LauncherFact = {
/** Name shown before the value, e.g. "PAN". */
label: string;
/** Text shown, and copied when `copyable`. */
value: string;
/** Adds a Copy button; a blocked clipboard selects the value instead. */
copyable?: boolean;
};
/** Direct-answer payload, e.g. label "Next holiday", value "24 Oct — Diwali". */
export type LauncherAnswer = {
/** Heading of the answer card. */
label: string;
/** The answer itself, shown large; plain text. */
value: string;
/** Small line under the value. */
hint?: string;
};
/** A search row that is not a screen: an action (`run`) or an entity result (`href`). */
export type LauncherSearchItem = {
/** Unique within its list; prefixed internally with the group or "action". */
id: string;
/** Row text; also what search matches first. */
label: string;
/** Second line, e.g. the module a result belongs to. */
context?: string;
/** Row icon. */
icon?: LauncherIcon;
/** Extra terms the query may match; never rendered. Separate phrases with commas. */
keywords?: string;
/** Short text appended to `context` on the second line, separated by a dot. */
hint?: string;
/** Destination of an entity result; Enter passes it to `onNavigate`. */
href?: string;
/** Action to perform; the launcher closes first, then calls it. */
run?: () => void;
/** Shown in the Preview pane for the active result; searchable in spotlight. */
facts?: readonly LauncherFact[];
/** Read only for items passed in `answers`: the card's label, value and hint. */
answer?: LauncherAnswer;
};
/** A titled batch of remote results, rendered after Screens in the order given. */
export type LauncherResultGroup = {
/** Unique key of the group; also its default scope id. */
id: string;
/** Group heading. */
label: string;
/** Results in display order. */
items: readonly LauncherSearchItem[];
/** Scope chip this group belongs to; defaults to `id`. */
scope?: string;
/** Launcher filters and ranks these items itself; by default they show as given. */
matchLocally?: boolean;
};
/** A filter chip; "screens" and "actions" are built in, others match `LauncherResultGroup.scope`. */
export type LauncherScope = {
/** "all", "screens", "actions" or a `LauncherResultGroup.scope`. */
id: string;
/** Chip text. */
label: string;
/** Field placeholder while this chip is selected, e.g. "Search legal name, PAN, GSTIN…"; the field's accessible name stays `labels.searchPlaceholder`. */
placeholder?: string;
};
/** "classic": registry order, substring matching; "spotlight": relevance ranking plus a Top hit. */
export type LauncherSearchMode = "classic" | "spotlight";
/** Every visible string and accessible name; pass any subset, the rest fall back to English. */
export type LauncherLabels = {
/** Accessible name of the dialog and the results list; no longer drawn as a header pill. */
title: string;
/** Search field placeholder and accessible name. */
searchPlaceholder: string;
/** Accessible name of the clear-query button. */
clearSearch: string;
/** Accessible name of the close button. */
close: string;
/** Heading of the Pinned section. */
pinned: string;
/** Message shown in Pinned when nothing is pinned. */
pinnedEmpty: string;
/** Heading of the Frequently visited section. */
frequent: string;
/** Heading of the Recent section. */
recent: string;
/** Name for a Screens result group; NavLauncher does not read it, hosts pass it as `screensLabel` to `searchLauncher`. */
screensGroup: string;
/** Name of the group holding matching `actions` in search results. */
actionsGroup: string;
/** Accessible name of a pin button for the named screen, e.g. "Pin Leave". */
pin: (label: string) => string;
/** Accessible name of an unpin button for the named screen. */
unpin: (label: string) => string;
/** Accessible name of the new-tab arrow beside the named row. */
openInNewTab: (label: string) => string;
/** Heading of the empty state for the given query. */
noResults: (query: string) => string;
/** Line under the empty-state heading when no `suggestions` are given. */
noResultsHint: string;
/** Empty-state heading while `isSearching` and nothing local matched. */
searching: string;
/** Accessible name of the skeleton region while `isLoading`. */
loading: string;
/** Heading of the empty state shown when `sections` has nothing to render. */
empty: string;
/** Footer hint after the arrow caps; the legend hides below md and on touch. */
hintMove: string;
/** Hint after ENTER when it opens the active result: footer, answer card, preview pane. */
hintOpen: string;
/** Footer hint after the mod + ENTER key caps. */
hintNewTab: string;
/** Footer hint after the P key cap. */
hintPin: string;
/** Footer hint after the ESC key cap. */
hintClose: string;
/** @deprecated No longer rendered: the launcher is already open, so the open chord is not shown. */
hintOpenMenu?: string;
/** Footer hint after the mod key cap for a modified click, e.g. "+ click for new tab". */
hintClickNewTab: string;
/** Tooltip of the arrow link beside a row. */
newTabTip: string;
/** Muted count beside the search field and its polite announcement, e.g. "12 results". */
resultsCount: (count: number) => string;
/** Name of the Menu / Quick access / Apps tab list shown below `lg`. */
tabsLabel: string;
/** Tab showing every module (below `lg`). */
tabMenu: string;
/** Tab showing Pinned, Frequently visited and Recent (below `lg`). */
tabQuickAccess: string;
/** Tab showing the suite's apps (below `lg`). */
tabApps: string;
/** Heading and list name of the apps section. */
apps: string;
/** Per-section count shown while searching, e.g. "3 found". */
found: (count: number) => string;
/** Accessible name of the right-hand quick access rail. */
quickAccess: string;
/** Tag after a module marked `isNew`. */
newBadge: string;
/** Noun used in a module's key-screens group name, e.g. "Leave screens". */
screens: string;
/** Text on the Pin button of Frequently visited rows. */
pinShort: string;
/** Right-hand footer note. */
footerNote: string;
/** Line above the suggestion chips of the no-results state. */
suggestionsHint: string;
/** Tooltip on an unpinned star. */
pinTip: string;
/** Tooltip on a pinned star. */
unpinTip: string;
/** Heading of the best-match card in spotlight mode. */
topHit?: string;
/** Prefix of the Top hit line naming the keyword or fact that matched. */
matchedOn?: string;
/** Name of the answers group. */
answers?: string;
/** Hint after ENTER when it copies the active answer: footer, answer card, preview pane. */
hintCopy?: string;
/** Footer hint after ENTER while a Show more option is active. */
hintExpand?: string;
/** Name of the scope chip group. */
scopes?: string;
/** Chip prepended to `scopes` when the list has no "all". */
scopeAll?: string;
/** Polite announcement when the scope changes. */
scopeChanged?: (label: string) => string;
/** Accessible name of a chip while a query is active, e.g. "People, 3 results". */
scopeCount?: (label: string, count: number) => string;
/** Shown when a chip is picked with an empty query and its scope has nothing to list yet. */
scopeEmpty?: (label: string) => string;
/** Name of the quick actions row. */
quickActions?: string;
/** Option that expands a collapsed result group. */
showMore?: (count: number) => string;
/** Name of the preview pane. */
preview?: string;
/** Accessible name and tooltip of a fact's Copy button. */
copy?: (label: string) => string;
/** Announced after a copy; shown for 1.5 s in place of the Enter hint. */
copied?: string;
/** Announcement when the clipboard is blocked and the value was selected instead. */
copySelected?: string;
};
/** Passed to `onNavigate`; `newTab` is true for mod+Enter. */
export type LauncherNavigateOptions = {
/** True when the open was requested in a new tab (mod+Enter). */
newTab: boolean;
};
/** Full-height navigation launcher: browse every screen, search, pins, Frequent, Recent and optionally the suite's apps. */
export interface NavLauncherProps {
/** Shows the launcher; the query resets when it closes. */
isOpen: boolean;
/** Called on Escape with an empty query, the close button, a plain-click navigation, `run` and app selection. */
onClose: () => void;
/** Already permission-filtered by the app. Empty sections/modules are dropped, never rendered as gaps. */
sections: readonly LauncherSection[];
/** Screen ids in display order; ids not in `sections` are ignored silently. */
pinnedIds: readonly string[];
/** Screen ids in display order; pinned ids are skipped and the list is cut at `frequentLimit`. */
frequentIds: readonly string[];
/** Screen ids in display order; ids not in `sections` are ignored silently. */
recentIds: readonly string[];
/** Omit to hide every pin toggle (read-only launcher). */
onTogglePin?: (screenId: string) => void;
/** Keyboard opens only (Enter / mod+Enter); pointer clicks follow the rendered links natively. */
onNavigate: (href: string, options: LauncherNavigateOptions) => void;
/** Anchor-like component receiving `href`, `target`, `rel`, `onClick`, `ref`. Defaults to `"a"`. */
linkAs?: ElementType;
/** Command rows matched by search under the Actions group; each has `run` or `href`. */
actions?: readonly LauncherSearchItem[];
/** Remote results, already filtered by the app; rendered after Screens in the given order. */
extraResults?: readonly LauncherResultGroup[];
/** Controlled query; omit for uncontrolled. Reset to "" on close either way. */
query?: string;
/** Fires on every change, including the reset on close. */
onQueryChange?: (query: string) => void;
/** Remote search in flight: shows the searching state when nothing local matched. */
isSearching?: boolean;
/** Sections not known yet (access loading): skeleton browse view. */
isLoading?: boolean;
/** Defaults to 3. */
keyScreenLimit?: number;
/** Most Frequently visited rows shown (pinned ones are skipped); defaults to 4. */
frequentLimit?: number;
/** Query chips offered by the no-results state; clicking one sets the query. */
suggestions?: readonly string[];
/** Key cap shown at the end of the search field, e.g. "/" when the app opens the launcher with it. */
searchHint?: string;
/** @deprecated No longer rendered: the launcher is already open. Cmd/Ctrl+K and "/" now focus the search field while it is open. */
openShortcutHint?: string;
/** Replaces the modifier cap of the mod+Enter hint; defaults to "⌘" on Apple platforms, "Ctrl" elsewhere. */
modKeyLabel?: string;
/** Rendered just before the close button, e.g. a help or customise icon button; the dialog name stays `labels.title` whatever is here. */
headerActions?: ReactNode;
/** Panel width at `lg`+; always full width below `lg`. Defaults to "1080px". */
maxWidth?: string;
/** Overrides for any `LauncherLabels` key; unset keys keep their English default. */
labels?: Partial;
/** Other Pixelize apps, shown as an "Apps" section at the bottom of the right rail. Same shape as AppSwitcher. */
apps?: AppOption[];
/** The app this UI runs inside: its row is marked and never a link. */
currentAppId?: string;
/** Same contract as AppSwitcher.onSelect; preventDefault keeps the app's own routing. */
onAppSelect?: (app: AppOption, event: MouseEvent | KeyboardEvent) => void;
/** Default "apps-on-compact": the section shows only below `md`, where the 9-dot is usually hidden. */
appsVisibility?: "always" | "apps-on-compact";
/** Default "classic"; "spotlight" adds typo-tolerant ranking, synonyms, usage boosts and a Top hit. */
searchMode?: LauncherSearchMode;
/** Spotlight only: equivalent terms, both directions, e.g. `{ pf: ["provident fund"] }`. */
synonyms?: LauncherSynonyms;
/** Filter chips in the header row (their own row below `md`), always shown, with live counts while a query is active; "all" is prepended unless listed. A chip other than All or Screens picked with an empty query lists that scope's items. */
scopes?: readonly LauncherScope[];
/** Controlled scope id; omit for uncontrolled ("all", reset on close). */
activeScope?: string;
/** Called with the new scope id whenever the user picks a chip, controlled or not. */
onScopeChange?: (scope: string) => void;
/** Up to 5 pill buttons at the top of the menu while the query is empty and the scope is All; each runs or links like an action. */
quickActions?: readonly LauncherSearchItem[];
/** Direct answers, matched against the query and shown above every result. */
answers?: readonly LauncherSearchItem[];
/** Rows per group before "Show N more"; default 5 in spotlight, unlimited in classic. */
groupLimit?: number;
/** Test id on the drawer content. */
"data-testid"?: string;
}
/** Props for the star pin toggle used on launcher rows and reusable on a landing page; shows only while its row is hovered or focused. */
export interface PinToggleProps {
/** Draws the star filled when true. */
isPinned: boolean;
/** Accessible name and tooltip, e.g. "Pin Leave". */
label: string;
/** Called when the star is pressed. */
onToggle: () => void;
/** Tooltip text; defaults to `label`. */
title?: string;
/** Star size in px; defaults to 13. */
size?: number;
/** Pass -1 where the row itself is the tab stop. */
tabIndex?: number;
}
/** Props for the small "open in new tab" anchor beside a launcher row. */
export interface NewTabLinkProps {
/** Link target, opened in a new tab. */
href: string;
/** Accessible name, e.g. "Open Leave in a new tab". */
label: string;
/** Visible tooltip text; defaults to `label`. */
tip?: string;
/** Anchor-like component receiving `href`, `target`, `rel`; defaults to `"a"`. */
linkAs?: ElementType;
/** Pass -1 where the row itself is the tab stop. */
tabIndex?: number;
}