@charcuterie/ui 4.25.0 → 4.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/dist/CommandPalette/CommandPalette.d.ts +43 -0
- package/dist/CommandPalette/CommandPalette.d.ts.map +1 -0
- package/dist/CommandPalette/CommandPalette.js +53 -0
- package/dist/CommandPalette/CommandPalette.js.map +1 -0
- package/dist/CommandPalette/CommandPaletteFooter.d.ts +16 -0
- package/dist/CommandPalette/CommandPaletteFooter.d.ts.map +1 -0
- package/dist/CommandPalette/CommandPaletteFooter.js +14 -0
- package/dist/CommandPalette/CommandPaletteFooter.js.map +1 -0
- package/dist/CommandPalette/CommandPaletteOption.d.ts +44 -0
- package/dist/CommandPalette/CommandPaletteOption.d.ts.map +1 -0
- package/dist/CommandPalette/CommandPaletteOption.js +72 -0
- package/dist/CommandPalette/CommandPaletteOption.js.map +1 -0
- package/dist/CommandPalette/CommandPalettePanel.d.ts +52 -0
- package/dist/CommandPalette/CommandPalettePanel.d.ts.map +1 -0
- package/dist/CommandPalette/CommandPalettePanel.js +168 -0
- package/dist/CommandPalette/CommandPalettePanel.js.map +1 -0
- package/dist/CommandPalette/commandPaletteSections.d.ts +93 -0
- package/dist/CommandPalette/commandPaletteSections.d.ts.map +1 -0
- package/dist/CommandPalette/commandPaletteSections.js +94 -0
- package/dist/CommandPalette/commandPaletteSections.js.map +1 -0
- package/dist/CommandPalette/useCommandPalette.d.ts +32 -0
- package/dist/CommandPalette/useCommandPalette.d.ts.map +1 -0
- package/dist/CommandPalette/useCommandPalette.js +40 -0
- package/dist/CommandPalette/useCommandPalette.js.map +1 -0
- package/dist/CommandPalette/useCommandPaletteHotkeys.d.ts +35 -0
- package/dist/CommandPalette/useCommandPaletteHotkeys.d.ts.map +1 -0
- package/dist/CommandPalette/useCommandPaletteHotkeys.js +114 -0
- package/dist/CommandPalette/useCommandPaletteHotkeys.js.map +1 -0
- package/dist/TableViewport/TableViewport.d.ts.map +1 -1
- package/dist/TableViewport/TableViewport.js +62 -10
- package/dist/TableViewport/TableViewport.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/CommandPalette/CommandPalette.mdx +129 -0
- package/src/CommandPalette/CommandPalette.stories.tsx +413 -0
- package/src/CommandPalette/CommandPalette.test.tsx +553 -0
- package/src/CommandPalette/CommandPalette.tsx +91 -0
- package/src/CommandPalette/CommandPaletteFooter.tsx +45 -0
- package/src/CommandPalette/CommandPaletteOption.tsx +200 -0
- package/src/CommandPalette/CommandPalettePanel.tsx +411 -0
- package/src/CommandPalette/commandPalette.storyHelpers.tsx +248 -0
- package/src/CommandPalette/commandPaletteSections.test.ts +156 -0
- package/src/CommandPalette/commandPaletteSections.ts +219 -0
- package/src/CommandPalette/useCommandPalette.ts +75 -0
- package/src/CommandPalette/useCommandPaletteHotkeys.ts +158 -0
- package/src/TableViewport/TableViewport.mdx +2 -0
- package/src/TableViewport/TableViewport.test.tsx +89 -1
- package/src/TableViewport/TableViewport.tsx +82 -9
- package/src/index.ts +14 -0
- package/src/sourceRules.test.ts +2 -1
package/README.md
CHANGED
|
@@ -147,6 +147,19 @@ own `aria-valuemin`/`aria-valuemax`. What is genuinely shared is the arithmetic
|
|
|
147
147
|
same extraction `tileStyles.ts` is), compared as **computed** styles in the test
|
|
148
148
|
([decision](../../docs/decisions/2026-09-01-a-two-thumb-range-is-its-own-component-and-shares-the-bar.md)).
|
|
149
149
|
|
|
150
|
+
**`CommandPalette` is the launcher** — a search field over a grouped list in a `Modal`, built
|
|
151
|
+
here first because a start page, Docket and Folio all want one and nothing in the fleet had
|
|
152
|
+
one. It lists the caller's `suggestions` before anything is typed, which is the defect it
|
|
153
|
+
exists to fix: the third-party launcher it replaces showed nothing until you typed. Typing
|
|
154
|
+
matches **word prefixes and initials, never subsequences** (`ha` finds `Home Automation`,
|
|
155
|
+
not `Chat`), through `rankWordPrefixMatches` in `@charcuterie/logic`. A short prefix and a
|
|
156
|
+
space (`au lego`) routes the rest of the query to a caller's target, and the caller's
|
|
157
|
+
fallback row ends every other list. The library owns the shape and no site names. The global
|
|
158
|
+
keys are an opt-in hook, `useCommandPaletteHotkeys`, and `useCommandPalette()` assembles them
|
|
159
|
+
with the open state
|
|
160
|
+
([matching](../../docs/decisions/2026-10-16-a-command-palette-matches-word-prefixes.md),
|
|
161
|
+
[keys](../../docs/decisions/2026-10-16-command-palette-hotkeys-are-an-opt-in-hook.md)).
|
|
162
|
+
|
|
150
163
|
**The unified app shell adds four**: `Shell`, `Header`, `Rail`, `Main` — the fleet's largest
|
|
151
164
|
single duplication, where ten of twelve UI repos hand-roll the page chrome and three of them
|
|
152
165
|
have a file named `AppShell.tsx` (two of those headers are a *byte-identical* class string,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type CommandPalettePanelProps } from "./CommandPalettePanel.tsx";
|
|
3
|
+
export type CommandPaletteProps = Omit<CommandPalettePanelProps, "inputRef" | "onChoose"> & {
|
|
4
|
+
className?: string;
|
|
5
|
+
isVisible: boolean;
|
|
6
|
+
/** Every route out: Escape, a press outside, and a chosen row. */
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The launcher: a search field over a grouped list, opened over the
|
|
11
|
+
* page.
|
|
12
|
+
*
|
|
13
|
+
* - **It shows something before anything is typed** — the caller's
|
|
14
|
+
* `suggestions` ("Pinned", "Recently opened").
|
|
15
|
+
* - **Typing filters `items` by word prefix**: every typed word must
|
|
16
|
+
* start a word of the title, the description or a keyword, and the
|
|
17
|
+
* initials count (`ha` → `Home Automation`). Matches are grouped by
|
|
18
|
+
* each item's `group`.
|
|
19
|
+
* - **A prefix command** — `au lego` — routes the rest of the query
|
|
20
|
+
* to one of the caller's `prefixes`.
|
|
21
|
+
* - **The last row is the caller's fallback** (`getFallbackItem`), so
|
|
22
|
+
* a query nothing matches still goes somewhere.
|
|
23
|
+
*
|
|
24
|
+
* The library owns the shape. Every name, link and target is data the
|
|
25
|
+
* app passes in.
|
|
26
|
+
*
|
|
27
|
+
* ### Open state
|
|
28
|
+
*
|
|
29
|
+
* `isVisible` and `onClose`, as on `Modal`: the app holds the state,
|
|
30
|
+
* usually in `useCommandPalette()`, which also wires the global keys.
|
|
31
|
+
* The query is controlled (`query` + `onQueryChange`) or uncontrolled
|
|
32
|
+
* (`defaultQuery`, read each time it opens), as on `Combobox`.
|
|
33
|
+
*
|
|
34
|
+
* ### Keys
|
|
35
|
+
*
|
|
36
|
+
* Arrow keys, Home and End move the active row; Enter opens it;
|
|
37
|
+
* `Ctrl Enter` or `⌘ Enter` opens an `href` row in a new tab; Escape
|
|
38
|
+
* closes. Focus stays in the field the whole time and the active row
|
|
39
|
+
* is its `aria-activedescendant`. Focus is trapped while it is open and
|
|
40
|
+
* goes back to where it was when it closes.
|
|
41
|
+
*/
|
|
42
|
+
export declare const CommandPalette: ({ className, isVisible, label, onClose, ...panelProps }: CommandPaletteProps) => ReactNode;
|
|
43
|
+
//# sourceMappingURL=CommandPalette.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandPalette.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPalette.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,2BAA2B,CAAA;AAElC,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,wBAAwB,EACxB,UAAU,GAAG,UAAU,CACxB,GAAG;IACF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,OAAO,CAAA;IAClB,kEAAkE;IAClE,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CAAA;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,cAAc,GAAI,yDAM5B,mBAAmB,KAAG,SAmBxB,CAAA"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
import { Modal } from "../Modal/Modal.js";
|
|
4
|
+
import { toClassName } from "../toClassName.js";
|
|
5
|
+
import { CommandPalettePanel, } from "./CommandPalettePanel.js";
|
|
6
|
+
/**
|
|
7
|
+
* Top-anchored on a wide window, so the list grows downward from a
|
|
8
|
+
* fixed field instead of re-centering as it fills; nearly the whole
|
|
9
|
+
* screen in the Narrow View, where a phone keyboard takes the bottom.
|
|
10
|
+
*
|
|
11
|
+
* `self-start` because `OverlayPanel` centers its child in a flex
|
|
12
|
+
* box. `dvh` for the reason `Dialog` gives: on a phone `vh` includes
|
|
13
|
+
* the address bar.
|
|
14
|
+
*/
|
|
15
|
+
const PALETTE_BOX_CLASS = "mt-[12dvh] max-h-[76dvh] w-[calc(100vw-2rem)] max-w-[45rem] self-start max-sm:mt-2 max-sm:h-[calc(100dvh-1rem)] max-sm:max-h-[calc(100dvh-1rem)] max-sm:w-[calc(100vw-1rem)]";
|
|
16
|
+
/**
|
|
17
|
+
* The launcher: a search field over a grouped list, opened over the
|
|
18
|
+
* page.
|
|
19
|
+
*
|
|
20
|
+
* - **It shows something before anything is typed** — the caller's
|
|
21
|
+
* `suggestions` ("Pinned", "Recently opened").
|
|
22
|
+
* - **Typing filters `items` by word prefix**: every typed word must
|
|
23
|
+
* start a word of the title, the description or a keyword, and the
|
|
24
|
+
* initials count (`ha` → `Home Automation`). Matches are grouped by
|
|
25
|
+
* each item's `group`.
|
|
26
|
+
* - **A prefix command** — `au lego` — routes the rest of the query
|
|
27
|
+
* to one of the caller's `prefixes`.
|
|
28
|
+
* - **The last row is the caller's fallback** (`getFallbackItem`), so
|
|
29
|
+
* a query nothing matches still goes somewhere.
|
|
30
|
+
*
|
|
31
|
+
* The library owns the shape. Every name, link and target is data the
|
|
32
|
+
* app passes in.
|
|
33
|
+
*
|
|
34
|
+
* ### Open state
|
|
35
|
+
*
|
|
36
|
+
* `isVisible` and `onClose`, as on `Modal`: the app holds the state,
|
|
37
|
+
* usually in `useCommandPalette()`, which also wires the global keys.
|
|
38
|
+
* The query is controlled (`query` + `onQueryChange`) or uncontrolled
|
|
39
|
+
* (`defaultQuery`, read each time it opens), as on `Combobox`.
|
|
40
|
+
*
|
|
41
|
+
* ### Keys
|
|
42
|
+
*
|
|
43
|
+
* Arrow keys, Home and End move the active row; Enter opens it;
|
|
44
|
+
* `Ctrl Enter` or `⌘ Enter` opens an `href` row in a new tab; Escape
|
|
45
|
+
* closes. Focus stays in the field the whole time and the active row
|
|
46
|
+
* is its `aria-activedescendant`. Focus is trapped while it is open and
|
|
47
|
+
* goes back to where it was when it closes.
|
|
48
|
+
*/
|
|
49
|
+
export const CommandPalette = ({ className, isVisible, label = "Search", onClose, ...panelProps }) => {
|
|
50
|
+
const inputRef = useRef(null);
|
|
51
|
+
return (_jsx(Modal, { "aria-label": label, className: toClassName(PALETTE_BOX_CLASS, className), initialFocus: inputRef, isVisible: isVisible, onClose: onClose, children: _jsx(CommandPalettePanel, { ...panelProps, inputRef: inputRef, label: label, onChoose: onClose }) }));
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=CommandPalette.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandPalette.js","sourceRoot":"","sources":["../../src/CommandPalette/CommandPalette.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAoB,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EACL,mBAAmB,GAEpB,MAAM,0BAA2B,CAAA;AAYlC;;;;;;;;GAQG;AACH,MAAM,iBAAiB,GACrB,8KAA8K,CAAA;AAEhL;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,EAC7B,SAAS,EACT,SAAS,EACT,KAAK,GAAG,QAAQ,EAChB,OAAO,EACP,GAAG,UAAU,EACO,EAAa,EAAE;IACnC,MAAM,QAAQ,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAA;IAE/C,OAAO,CACL,KAAC,KAAK,kBACQ,KAAK,EACjB,SAAS,EAAE,WAAW,CAAC,iBAAiB,EAAE,SAAS,CAAC,EACpD,YAAY,EAAE,QAAQ,EACtB,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,YAEhB,KAAC,mBAAmB,OACd,UAAU,EACd,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,OAAO,GACjB,GACI,CACT,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { CommandPalettePrefix } from "./commandPaletteSections.ts";
|
|
3
|
+
/**
|
|
4
|
+
* The default strip under the list: the keys, and each prefix with
|
|
5
|
+
* its target.
|
|
6
|
+
*
|
|
7
|
+
* The keys are words — `Up`, `Down` — not arrow glyphs, because a font
|
|
8
|
+
* without the glyph paints nothing
|
|
9
|
+
* (`2026-07-29-ship-no-icons-and-no-symbol-glyphs.md`). In the Narrow
|
|
10
|
+
* View the key hints give way, since a phone has no arrow keys, and
|
|
11
|
+
* the prefixes stay: they work from a phone keyboard too.
|
|
12
|
+
*/
|
|
13
|
+
export declare const CommandPaletteFooter: ({ prefixes, }: {
|
|
14
|
+
prefixes: readonly CommandPalettePrefix[];
|
|
15
|
+
}) => ReactNode;
|
|
16
|
+
//# sourceMappingURL=CommandPaletteFooter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandPaletteFooter.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPaletteFooter.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAA;AAQvE;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,GAAI,eAElC;IACD,QAAQ,EAAE,SAAS,oBAAoB,EAAE,CAAA;CAC1C,KAAG,SAoBH,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
const renderKey = (name) => (_jsx("kbd", { className: "rounded-sm border border-border-default bg-surface-raised px-1 font-mono text-content-secondary text-xs", children: name }));
|
|
3
|
+
/**
|
|
4
|
+
* The default strip under the list: the keys, and each prefix with
|
|
5
|
+
* its target.
|
|
6
|
+
*
|
|
7
|
+
* The keys are words — `Up`, `Down` — not arrow glyphs, because a font
|
|
8
|
+
* without the glyph paints nothing
|
|
9
|
+
* (`2026-07-29-ship-no-icons-and-no-symbol-glyphs.md`). In the Narrow
|
|
10
|
+
* View the key hints give way, since a phone has no arrow keys, and
|
|
11
|
+
* the prefixes stay: they work from a phone keyboard too.
|
|
12
|
+
*/
|
|
13
|
+
export const CommandPaletteFooter = ({ prefixes, }) => (_jsxs(_Fragment, { children: [_jsxs("span", { className: "max-sm:hidden", children: [renderKey("Up"), " ", renderKey("Down"), " move"] }), _jsxs("span", { className: "max-sm:hidden", children: [renderKey("Enter"), " open"] }), _jsxs("span", { className: "max-sm:hidden", children: [renderKey("Ctrl"), " ", renderKey("Enter"), " new tab"] }), prefixes.map(({ label, prefix }) => (_jsxs("span", { children: [renderKey(prefix), " ", label] }, prefix))), _jsxs("span", { className: "max-sm:hidden", children: [renderKey("Esc"), " close"] })] }));
|
|
14
|
+
//# sourceMappingURL=CommandPaletteFooter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandPaletteFooter.js","sourceRoot":"","sources":["../../src/CommandPalette/CommandPaletteFooter.tsx"],"names":[],"mappings":";AAIA,MAAM,SAAS,GAAG,CAAC,IAAY,EAAa,EAAE,CAAC,CAC7C,cAAK,SAAS,EAAC,yGAAyG,YACrH,IAAI,GACD,CACP,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,EACnC,QAAQ,GAGT,EAAa,EAAE,CAAC,CACf,8BACE,gBAAM,SAAS,EAAC,eAAe,aAC5B,SAAS,CAAC,IAAI,CAAC,OAAG,SAAS,CAAC,MAAM,CAAC,aAC/B,EACP,gBAAM,SAAS,EAAC,eAAe,aAC5B,SAAS,CAAC,OAAO,CAAC,aACd,EACP,gBAAM,SAAS,EAAC,eAAe,aAC5B,SAAS,CAAC,MAAM,CAAC,OAAG,SAAS,CAAC,OAAO,CAAC,gBAClC,EACN,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CACnC,2BACG,SAAS,CAAC,MAAM,CAAC,OAAG,KAAK,KADjB,MAAM,CAEV,CACR,CAAC,EACF,gBAAM,SAAS,EAAC,eAAe,aAC5B,SAAS,CAAC,KAAK,CAAC,cACZ,IACN,CACJ,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { TextMatchRange } from "@charcuterie/logic";
|
|
2
|
+
import type { ControlSize } from "@charcuterie/tokens";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import type { CommandPaletteItem } from "./commandPaletteSections.ts";
|
|
5
|
+
export type CommandPaletteOptionProps = {
|
|
6
|
+
/** The DOM id `aria-activedescendant` points at. */
|
|
7
|
+
id: string;
|
|
8
|
+
isActive: boolean;
|
|
9
|
+
item: CommandPaletteItem;
|
|
10
|
+
itemSize: ControlSize;
|
|
11
|
+
/** A click on the row: the link has navigated (or will), so close and run `onSelect`. */
|
|
12
|
+
onChoose: () => void;
|
|
13
|
+
/** The pointer moved over the row. */
|
|
14
|
+
onPoint: () => void;
|
|
15
|
+
titleRanges: readonly TextMatchRange[];
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* One row of the palette's listbox.
|
|
19
|
+
*
|
|
20
|
+
* **Focus never comes here.** The input keeps it, and the row the
|
|
21
|
+
* arrows have reached is announced through the input's
|
|
22
|
+
* `aria-activedescendant` — the APG combobox pattern `Combobox` uses
|
|
23
|
+
* too. So the row is `tabIndex={-1}`, and a mouse press is prevented
|
|
24
|
+
* from moving focus (`onMouseDown`), which keeps the caret in the
|
|
25
|
+
* input while the click still lands.
|
|
26
|
+
*
|
|
27
|
+
* **An `href` item is a real `<a href>`.** It goes through
|
|
28
|
+
* `UnstyledLink`, so an app's injected router link handles a plain
|
|
29
|
+
* click, and middle-click, ctrl-click and "copy link address" all
|
|
30
|
+
* work, because it is a link. Enter in the input clicks this same
|
|
31
|
+
* element, so the keyboard and the mouse take one path. An item
|
|
32
|
+
* without one is a `<button>`, as in `Combobox`.
|
|
33
|
+
*
|
|
34
|
+
* **The pointer moves the active row on `pointermove`, not
|
|
35
|
+
* `pointerenter`.** Arrowing scrolls the list under a mouse that has
|
|
36
|
+
* not moved, and an enter event would then hand the active row to
|
|
37
|
+
* whatever slid under the pointer.
|
|
38
|
+
*
|
|
39
|
+
* **One highlight.** The active row is tinted and nothing else is —
|
|
40
|
+
* no separate `:hover` tint — because the pointer makes a row active,
|
|
41
|
+
* and two tinted rows would leave Enter's target ambiguous.
|
|
42
|
+
*/
|
|
43
|
+
export declare const CommandPaletteOption: ({ id, isActive, item, itemSize, onChoose, onPoint, titleRanges, }: CommandPaletteOptionProps) => ReactNode;
|
|
44
|
+
//# sourceMappingURL=CommandPaletteOption.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandPaletteOption.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPaletteOption.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAEV,SAAS,EACV,MAAM,OAAO,CAAA;AAKd,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAErE,MAAM,MAAM,yBAAyB,GAAG;IACtC,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,OAAO,CAAA;IACjB,IAAI,EAAE,kBAAkB,CAAA;IACxB,QAAQ,EAAE,WAAW,CAAA;IACrB,yFAAyF;IACzF,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,sCAAsC;IACtC,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,WAAW,EAAE,SAAS,cAAc,EAAE,CAAA;CACvC,CAAA;AAkDD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,oBAAoB,GAAI,mEAQlC,yBAAyB,KAAG,SA4F9B,CAAA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { PANEL_ITEM_SIZE_CLASS } from "../controlStyles.js";
|
|
3
|
+
import { UnstyledLink } from "../RouterLink/UnstyledLink.js";
|
|
4
|
+
import { toClassName } from "../toClassName.js";
|
|
5
|
+
const ICON_SIZE_CLASS = {
|
|
6
|
+
lg: "size-7 [&>img]:size-7 [&>svg]:size-5",
|
|
7
|
+
md: "size-6 [&>img]:size-6 [&>svg]:size-4",
|
|
8
|
+
sm: "size-5 [&>img]:size-5 [&>svg]:size-4",
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* The title with each matched range wrapped in a `<mark>`. Plain
|
|
12
|
+
* text in, so nothing the caller typed is ever read as markup.
|
|
13
|
+
*/
|
|
14
|
+
const HighlightedTitle = ({ ranges, title, }) => {
|
|
15
|
+
if (ranges.length === 0) {
|
|
16
|
+
return title;
|
|
17
|
+
}
|
|
18
|
+
const pieces = [];
|
|
19
|
+
let cursor = 0;
|
|
20
|
+
for (const { end, start } of ranges) {
|
|
21
|
+
if (start > cursor) {
|
|
22
|
+
pieces.push(title.slice(cursor, start));
|
|
23
|
+
}
|
|
24
|
+
pieces.push(_jsx("mark", { className: "bg-transparent font-semibold text-intent-accent-content", children: title.slice(start, end) }, start));
|
|
25
|
+
cursor = end;
|
|
26
|
+
}
|
|
27
|
+
if (cursor < title.length) {
|
|
28
|
+
pieces.push(title.slice(cursor));
|
|
29
|
+
}
|
|
30
|
+
return pieces;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* One row of the palette's listbox.
|
|
34
|
+
*
|
|
35
|
+
* **Focus never comes here.** The input keeps it, and the row the
|
|
36
|
+
* arrows have reached is announced through the input's
|
|
37
|
+
* `aria-activedescendant` — the APG combobox pattern `Combobox` uses
|
|
38
|
+
* too. So the row is `tabIndex={-1}`, and a mouse press is prevented
|
|
39
|
+
* from moving focus (`onMouseDown`), which keeps the caret in the
|
|
40
|
+
* input while the click still lands.
|
|
41
|
+
*
|
|
42
|
+
* **An `href` item is a real `<a href>`.** It goes through
|
|
43
|
+
* `UnstyledLink`, so an app's injected router link handles a plain
|
|
44
|
+
* click, and middle-click, ctrl-click and "copy link address" all
|
|
45
|
+
* work, because it is a link. Enter in the input clicks this same
|
|
46
|
+
* element, so the keyboard and the mouse take one path. An item
|
|
47
|
+
* without one is a `<button>`, as in `Combobox`.
|
|
48
|
+
*
|
|
49
|
+
* **The pointer moves the active row on `pointermove`, not
|
|
50
|
+
* `pointerenter`.** Arrowing scrolls the list under a mouse that has
|
|
51
|
+
* not moved, and an enter event would then hand the active row to
|
|
52
|
+
* whatever slid under the pointer.
|
|
53
|
+
*
|
|
54
|
+
* **One highlight.** The active row is tinted and nothing else is —
|
|
55
|
+
* no separate `:hover` tint — because the pointer makes a row active,
|
|
56
|
+
* and two tinted rows would leave Enter's target ambiguous.
|
|
57
|
+
*/
|
|
58
|
+
export const CommandPaletteOption = ({ id, isActive, item, itemSize, onChoose, onPoint, titleRanges, }) => {
|
|
59
|
+
const { description, hint, href, icon, target, title } = item;
|
|
60
|
+
const className = toClassName("flex w-full cursor-pointer items-center rounded-md text-start text-content-primary no-underline", PANEL_ITEM_SIZE_CLASS[itemSize], isActive && "bg-intent-neutral-surface-hover");
|
|
61
|
+
const content = (_jsxs(_Fragment, { children: [icon === undefined ? null : (_jsx("span", { "aria-hidden": "true", className: toClassName("flex shrink-0 items-center justify-center overflow-hidden rounded-sm text-content-secondary [&>img]:object-contain", ICON_SIZE_CLASS[itemSize]), children: icon })), _jsxs("span", { className: "flex min-w-0 flex-1 flex-wrap items-baseline gap-x-2", children: [_jsx("span", { className: "min-w-0 wrap-anywhere", children: _jsx(HighlightedTitle, { ranges: titleRanges, title: title }) }), description === undefined ? null : (_jsx("span", { className: "min-w-0 text-content-muted text-sm wrap-anywhere", children: description }))] }), hint === undefined ? null : (_jsx("span", { className: "shrink-0 text-content-muted text-sm", children: hint }))] }));
|
|
62
|
+
const keepFocusInInput = (mouseEvent) => {
|
|
63
|
+
mouseEvent.preventDefault();
|
|
64
|
+
};
|
|
65
|
+
if (href !== undefined) {
|
|
66
|
+
return (_jsx(UnstyledLink, { "aria-selected": isActive, className: className, href: href, id: id, onClick: onChoose, onMouseDown: keepFocusInInput, onPointerMove: onPoint, rel: target === "_blank"
|
|
67
|
+
? "noopener noreferrer"
|
|
68
|
+
: undefined, role: "option", tabIndex: -1, target: target, children: content }));
|
|
69
|
+
}
|
|
70
|
+
return (_jsx("button", { "aria-selected": isActive, className: className, id: id, onClick: onChoose, onMouseDown: keepFocusInInput, onPointerMove: onPoint, role: "option", tabIndex: -1, type: "button", children: content }));
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=CommandPaletteOption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandPaletteOption.js","sourceRoot":"","sources":["../../src/CommandPalette/CommandPaletteOption.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ControlSize } from "@charcuterie/tokens";
|
|
2
|
+
import type { ReactNode, RefObject } from "react";
|
|
3
|
+
import { type CommandPaletteGroup, type CommandPaletteItem, type CommandPalettePrefix } from "./commandPaletteSections.ts";
|
|
4
|
+
export type CommandPalettePanelProps = {
|
|
5
|
+
/** The glyph inside the clear button. Without one, there is no clear button — Escape closes. */
|
|
6
|
+
clearIcon?: ReactNode;
|
|
7
|
+
/** The query the palette opens with, when the caller does not own `query`. Read once per open. */
|
|
8
|
+
defaultQuery?: string;
|
|
9
|
+
/** Shown when a query matches nothing and there is no fallback row. Default "No matches". */
|
|
10
|
+
emptyLabel?: string;
|
|
11
|
+
/**
|
|
12
|
+
* The strip under the list. Default: the keys, and each prefix
|
|
13
|
+
* with its target. `null` draws none.
|
|
14
|
+
*/
|
|
15
|
+
footer?: ReactNode;
|
|
16
|
+
/** The title of a prefix command's row. Default `Search <label> for "<query>"`. */
|
|
17
|
+
formatPrefixTitle?: (label: string, query: string) => string;
|
|
18
|
+
/** The last row for any query that is not a prefix command — the "search the web" row. */
|
|
19
|
+
getFallbackItem?: (query: string) => CommandPaletteItem | null | undefined;
|
|
20
|
+
/** Ref to the search field, for the dialog's initial focus. */
|
|
21
|
+
inputRef?: RefObject<HTMLInputElement | null>;
|
|
22
|
+
/** How tall each row is — and the search field. Default `lg`, stepped down on a short window. */
|
|
23
|
+
itemSize?: ControlSize;
|
|
24
|
+
/** Everything a query can match. Folder members included, flattened. */
|
|
25
|
+
items: readonly CommandPaletteItem[];
|
|
26
|
+
/** The accessible name of the search field and the list. Default "Search". */
|
|
27
|
+
label?: string;
|
|
28
|
+
/** At most this many matches under one heading. Default: no cap. */
|
|
29
|
+
maxResultsPerGroup?: number;
|
|
30
|
+
/** After a row is chosen. `CommandPalette` closes here. */
|
|
31
|
+
onChoose?: () => void;
|
|
32
|
+
/** Every change to the query. With `query`, the caller owns it. */
|
|
33
|
+
onQueryChange?: (query: string) => void;
|
|
34
|
+
placeholder?: string;
|
|
35
|
+
/** The heading over a prefix command's row. Default "Search a site". */
|
|
36
|
+
prefixGroupLabel?: string;
|
|
37
|
+
prefixes?: readonly CommandPalettePrefix[];
|
|
38
|
+
/** Controlled query. Uncontrolled (from `defaultQuery`) when absent. */
|
|
39
|
+
query?: string;
|
|
40
|
+
/** What the palette lists before anything is typed — "Pinned", "Recently opened". */
|
|
41
|
+
suggestions?: readonly CommandPaletteGroup[];
|
|
42
|
+
/** The heading over matches whose item has no `group`. Default "Results". */
|
|
43
|
+
ungroupedLabel?: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* The palette's contents — the search field, the grouped listbox and
|
|
47
|
+
* the footer — without the dialog around them. `CommandPalette` is
|
|
48
|
+
* this inside a `Modal`; the stories also lay it out inline, so every
|
|
49
|
+
* state can sit on one board.
|
|
50
|
+
*/
|
|
51
|
+
export declare const CommandPalettePanel: ({ clearIcon, defaultQuery, emptyLabel, footer, formatPrefixTitle, getFallbackItem, inputRef: externalInputRef, itemSize: requestedItemSize, items, label, maxResultsPerGroup, onChoose, onQueryChange, placeholder, prefixGroupLabel, prefixes, query, suggestions, ungroupedLabel, }: CommandPalettePanelProps) => ReactNode;
|
|
52
|
+
//# sourceMappingURL=CommandPalettePanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandPalettePanel.d.ts","sourceRoot":"","sources":["../../src/CommandPalette/CommandPalettePanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAEV,SAAS,EACT,SAAS,EACV,MAAM,OAAO,CAAA;AAQd,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EAE1B,MAAM,6BAA6B,CAAA;AAEpC,MAAM,MAAM,wBAAwB,GAAG;IACrC,gGAAgG;IAChG,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,kGAAkG;IAClG,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6FAA6F;IAC7F,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,mFAAmF;IACnF,iBAAiB,CAAC,EAAE,CAClB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,KACV,MAAM,CAAA;IACX,0FAA0F;IAC1F,eAAe,CAAC,EAAE,CAChB,KAAK,EAAE,MAAM,KACV,kBAAkB,GAAG,IAAI,GAAG,SAAS,CAAA;IAC1C,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,SAAS,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IAC7C,iGAAiG;IACjG,QAAQ,CAAC,EAAE,WAAW,CAAA;IACtB,wEAAwE;IACxE,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACpC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oEAAoE;IACpE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,mEAAmE;IACnE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAA;IAC1C,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qFAAqF;IACrF,WAAW,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAA;IAC5C,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AA2BD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,uRAoBjC,wBAAwB,KAAG,SAkS7B,CAAA"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useUniqueId } from "@charcuterie/logic";
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { usePanelItemSize } from "../Overlay/usePanelItemSize.js";
|
|
5
|
+
import { SearchInput } from "../SearchInput/SearchInput.js";
|
|
6
|
+
import { toClassName } from "../toClassName.js";
|
|
7
|
+
import { CommandPaletteFooter } from "./CommandPaletteFooter.js";
|
|
8
|
+
import { CommandPaletteOption } from "./CommandPaletteOption.js";
|
|
9
|
+
import { buildCommandPaletteSections, } from "./commandPaletteSections.js";
|
|
10
|
+
const defaultFormatPrefixTitle = (label, query) => `Search ${label} for “${query}”`;
|
|
11
|
+
const GROUP_HEADING_INSET_CLASS = {
|
|
12
|
+
lg: "px-(--control-padding-inline-lg)",
|
|
13
|
+
md: "px-(--control-padding-inline-md)",
|
|
14
|
+
sm: "px-(--control-padding-inline-sm)",
|
|
15
|
+
};
|
|
16
|
+
const SEARCH_SIZE = {
|
|
17
|
+
lg: "lg",
|
|
18
|
+
md: "md",
|
|
19
|
+
sm: "md",
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The palette's contents — the search field, the grouped listbox and
|
|
23
|
+
* the footer — without the dialog around them. `CommandPalette` is
|
|
24
|
+
* this inside a `Modal`; the stories also lay it out inline, so every
|
|
25
|
+
* state can sit on one board.
|
|
26
|
+
*/
|
|
27
|
+
export const CommandPalettePanel = ({ clearIcon, defaultQuery = "", emptyLabel = "No matches", footer, formatPrefixTitle = defaultFormatPrefixTitle, getFallbackItem, inputRef: externalInputRef, itemSize: requestedItemSize = "lg", items, label = "Search", maxResultsPerGroup, onChoose, onQueryChange, placeholder = "Search…", prefixGroupLabel = "Search a site", prefixes = [], query, suggestions = [], ungroupedLabel = "Results", }) => {
|
|
28
|
+
const itemSize = usePanelItemSize(requestedItemSize);
|
|
29
|
+
const listId = useUniqueId();
|
|
30
|
+
const ownInputRef = useRef(null);
|
|
31
|
+
const inputRef = externalInputRef ?? ownInputRef;
|
|
32
|
+
const [internalQuery, setInternalQuery] = useState(defaultQuery);
|
|
33
|
+
const currentQuery = query ?? internalQuery;
|
|
34
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
35
|
+
const sections = useMemo(() => buildCommandPaletteSections({
|
|
36
|
+
formatPrefixTitle,
|
|
37
|
+
getFallbackItem,
|
|
38
|
+
items,
|
|
39
|
+
maxResultsPerGroup,
|
|
40
|
+
prefixGroupLabel,
|
|
41
|
+
prefixes,
|
|
42
|
+
query: currentQuery,
|
|
43
|
+
suggestions,
|
|
44
|
+
ungroupedLabel,
|
|
45
|
+
}), [
|
|
46
|
+
currentQuery,
|
|
47
|
+
formatPrefixTitle,
|
|
48
|
+
getFallbackItem,
|
|
49
|
+
items,
|
|
50
|
+
maxResultsPerGroup,
|
|
51
|
+
prefixGroupLabel,
|
|
52
|
+
prefixes,
|
|
53
|
+
suggestions,
|
|
54
|
+
ungroupedLabel,
|
|
55
|
+
]);
|
|
56
|
+
const indexedSections = useMemo(() => {
|
|
57
|
+
let index = 0;
|
|
58
|
+
return sections.map((section) => ({
|
|
59
|
+
...section,
|
|
60
|
+
headingId: `${listId}-${section.key}`,
|
|
61
|
+
rows: section.rows.map((row) => ({
|
|
62
|
+
...row,
|
|
63
|
+
id: `${listId}-${section.key}-${row.item.key}`,
|
|
64
|
+
index: index++,
|
|
65
|
+
})),
|
|
66
|
+
}));
|
|
67
|
+
}, [listId, sections]);
|
|
68
|
+
const rows = indexedSections.flatMap((section) => section.rows);
|
|
69
|
+
const resolvedActiveIndex = rows.length === 0
|
|
70
|
+
? -1
|
|
71
|
+
: Math.min(activeIndex, rows.length - 1);
|
|
72
|
+
const activeRow = rows[resolvedActiveIndex];
|
|
73
|
+
const setQuery = (nextQuery) => {
|
|
74
|
+
if (query === undefined) {
|
|
75
|
+
setInternalQuery(nextQuery);
|
|
76
|
+
}
|
|
77
|
+
setActiveIndex(0);
|
|
78
|
+
onQueryChange?.(nextQuery);
|
|
79
|
+
};
|
|
80
|
+
// The arrows can walk the active row out of the scrolled list; bring
|
|
81
|
+
// it back. `nearest` is a no-op for a row the pointer is already on.
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (activeRow === undefined) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
document
|
|
87
|
+
.getElementById(activeRow.id)
|
|
88
|
+
?.scrollIntoView?.({ block: "nearest" });
|
|
89
|
+
}, [activeRow]);
|
|
90
|
+
/**
|
|
91
|
+
* Opening with a typed character puts the caret AFTER it, so the
|
|
92
|
+
* next key extends the query instead of landing in front of it.
|
|
93
|
+
* Once per mount: a later focus (back from the clear button) keeps
|
|
94
|
+
* wherever the person left the caret.
|
|
95
|
+
*/
|
|
96
|
+
const hasPlacedCaret = useRef(false);
|
|
97
|
+
const choose = (row) => {
|
|
98
|
+
onChoose?.();
|
|
99
|
+
row.item.onSelect?.();
|
|
100
|
+
};
|
|
101
|
+
const activate = (row, isNewTab) => {
|
|
102
|
+
const { href } = row.item;
|
|
103
|
+
if (href === undefined) {
|
|
104
|
+
choose(row);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (isNewTab) {
|
|
108
|
+
window.open(href, "_blank", "noopener,noreferrer");
|
|
109
|
+
choose(row);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// The row is the `<a href>` itself, so clicking it takes the path
|
|
113
|
+
// a mouse would — the injected router link, `target`, all of it —
|
|
114
|
+
// and its `onClick` runs `choose`.
|
|
115
|
+
document.getElementById(row.id)?.click();
|
|
116
|
+
};
|
|
117
|
+
const handleKeyDown = (keyboardEvent) => {
|
|
118
|
+
const lastIndex = rows.length - 1;
|
|
119
|
+
switch (keyboardEvent.key) {
|
|
120
|
+
case "ArrowDown": {
|
|
121
|
+
keyboardEvent.preventDefault();
|
|
122
|
+
setActiveIndex(Math.min(resolvedActiveIndex + 1, lastIndex));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
case "ArrowUp": {
|
|
126
|
+
keyboardEvent.preventDefault();
|
|
127
|
+
setActiveIndex(Math.max(resolvedActiveIndex - 1, 0));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
case "Home": {
|
|
131
|
+
keyboardEvent.preventDefault();
|
|
132
|
+
setActiveIndex(0);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
case "End": {
|
|
136
|
+
keyboardEvent.preventDefault();
|
|
137
|
+
setActiveIndex(Math.max(lastIndex, 0));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
case "Enter": {
|
|
141
|
+
if (activeRow === undefined) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
keyboardEvent.preventDefault();
|
|
145
|
+
activate(activeRow, keyboardEvent.ctrlKey || keyboardEvent.metaKey);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
default:
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const hasRows = rows.length > 0;
|
|
152
|
+
const isQueryEmpty = currentQuery.trim().length === 0;
|
|
153
|
+
const footerContent = footer === undefined ? (_jsx(CommandPaletteFooter, { prefixes: prefixes })) : (footer);
|
|
154
|
+
return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [_jsx("div", { className: "shrink-0 border-border-subtle border-b p-2", children: _jsx(SearchInput, { "aria-activedescendant": activeRow?.id, "aria-autocomplete": "list", "aria-controls": hasRows ? listId : undefined, "aria-expanded": hasRows, "aria-label": label, autoCapitalize: "off", autoComplete: "off", autoCorrect: "off", clearIcon: clearIcon ?? null, enterKeyHint: "go", isClearVisible: clearIcon !== undefined &&
|
|
155
|
+
currentQuery.length > 0, onChange: (changeEvent) => setQuery(changeEvent.currentTarget.value), onClear: () => setQuery(""), onFocus: (focusEvent) => {
|
|
156
|
+
if (hasPlacedCaret.current) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
hasPlacedCaret.current = true;
|
|
160
|
+
const { length } = focusEvent.currentTarget.value;
|
|
161
|
+
focusEvent.currentTarget.setSelectionRange(length, length);
|
|
162
|
+
}, onKeyDown: handleKeyDown, placeholder: placeholder, ref: inputRef, role: "combobox", size: SEARCH_SIZE[itemSize], spellCheck: false, value: currentQuery }) }), hasRows ? (_jsx("div", { "aria-label": label, className: "charcuterie-scrollbar min-h-0 flex-1 overflow-y-auto overscroll-contain p-1.5", id: listId, role: "listbox", children: indexedSections.map((section) => (_jsxs("div", { "aria-labelledby": section.headingId, className: "not-first:mt-1", role: "group", children: [_jsx("div", { className: toClassName(GROUP_HEADING_INSET_CLASS[itemSize], "pt-1.5 pb-0.5 font-medium text-content-secondary text-xs"), id: section.headingId, children: section.label }), section.rows.map((row) => (_jsx(CommandPaletteOption, { id: row.id, isActive: row.index === resolvedActiveIndex, item: row.item, itemSize: itemSize, onChoose: () => choose(row), onPoint: () => {
|
|
163
|
+
if (row.index !== resolvedActiveIndex) {
|
|
164
|
+
setActiveIndex(row.index);
|
|
165
|
+
}
|
|
166
|
+
}, titleRanges: row.titleRanges }, row.id)))] }, section.key))) })) : isQueryEmpty ? null : (_jsx("p", { className: "m-0 px-4 py-6 text-center text-content-muted text-sm", children: emptyLabel })), footerContent === null ? null : (_jsx("div", { className: "flex shrink-0 flex-wrap items-center gap-x-4 gap-y-1 border-border-subtle border-t px-4 py-2 text-content-muted text-xs", children: footerContent }))] }));
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=CommandPalettePanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CommandPalettePanel.js","sourceRoot":"","sources":["../../src/CommandPalette/CommandPalettePanel.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAOhD,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,+BAAgC,CAAA;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA4B,CAAA;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA4B,CAAA;AACjE,OAAO,EACL,2BAA2B,GAK5B,MAAM,6BAA6B,CAAA;AAiDpC,MAAM,wBAAwB,GAAG,CAC/B,KAAa,EACb,KAAa,EACb,EAAE,CAAC,UAAU,KAAK,SAAS,KAAK,GAAG,CAAA;AAErC,MAAM,yBAAyB,GAG3B;IACF,EAAE,EAAE,kCAAkC;IACtC,EAAE,EAAE,kCAAkC;IACtC,EAAE,EAAE,kCAAkC;CACvC,CAAA;AAED,MAAM,WAAW,GAAqC;IACpD,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;CACT,CAAA;AAOD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,EAClC,SAAS,EACT,YAAY,GAAG,EAAE,EACjB,UAAU,GAAG,YAAY,EACzB,MAAM,EACN,iBAAiB,GAAG,wBAAwB,EAC5C,eAAe,EACf,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,iBAAiB,GAAG,IAAI,EAClC,KAAK,EACL,KAAK,GAAG,QAAQ,EAChB,kBAAkB,EAClB,QAAQ,EACR,aAAa,EACb,WAAW,GAAG,SAAS,EACvB,gBAAgB,GAAG,eAAe,EAClC,QAAQ,GAAG,EAAE,EACb,KAAK,EACL,WAAW,GAAG,EAAE,EAChB,cAAc,GAAG,SAAS,GACD,EAAa,EAAE;IACxC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,iBAAiB,CAAC,CAAA;IAEpD,MAAM,MAAM,GAAG,WAAW,EAAE,CAAA;IAE5B,MAAM,WAAW,GAAG,MAAM,CAAmB,IAAI,CAAC,CAAA;IAClD,MAAM,QAAQ,GAAG,gBAAgB,IAAI,WAAW,CAAA;IAEhD,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GACrC,QAAQ,CAAC,YAAY,CAAC,CAAA;IAExB,MAAM,YAAY,GAAG,KAAK,IAAI,aAAa,CAAA;IAE3C,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;IAEjD,MAAM,QAAQ,GAAG,OAAO,CACtB,GAAG,EAAE,CACH,2BAA2B,CAAC;QAC1B,iBAAiB;QACjB,eAAe;QACf,KAAK;QACL,kBAAkB;QAClB,gBAAgB;QAChB,QAAQ;QACR,KAAK,EAAE,YAAY;QACnB,WAAW;QACX,cAAc;KACf,CAAC,EACJ;QACE,YAAY;QACZ,iBAAiB;QACjB,eAAe;QACf,KAAK;QACL,kBAAkB;QAClB,gBAAgB;QAChB,QAAQ;QACR,WAAW;QACX,cAAc;KACf,CACF,CAAA;IAED,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,EAAE;QACnC,IAAI,KAAK,GAAG,CAAC,CAAA;QAEb,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAChC,GAAG,OAAO;YACV,SAAS,EAAE,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,EAAE;YACrC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CACpB,CAAC,GAAG,EAAc,EAAE,CAAC,CAAC;gBACpB,GAAG,GAAG;gBACN,EAAE,EAAE,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE;gBAC9C,KAAK,EAAE,KAAK,EAAE;aACf,CAAC,CACH;SACF,CAAC,CAAC,CAAA;IACL,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;IAEtB,MAAM,IAAI,GAAG,eAAe,CAAC,OAAO,CAClC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAC1B,CAAA;IAED,MAAM,mBAAmB,GACvB,IAAI,CAAC,MAAM,KAAK,CAAC;QACf,CAAC,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAE5C,MAAM,SAAS,GAAG,IAAI,CAAC,mBAAmB,CAAC,CAAA;IAE3C,MAAM,QAAQ,GAAG,CAAC,SAAiB,EAAE,EAAE;QACrC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,gBAAgB,CAAC,SAAS,CAAC,CAAA;QAC7B,CAAC;QAED,cAAc,CAAC,CAAC,CAAC,CAAA;QAEjB,aAAa,EAAE,CAAC,SAAS,CAAC,CAAA;IAC5B,CAAC,CAAA;IAED,qEAAqE;IACrE,qEAAqE;IACrE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAM;QACR,CAAC;QAED,QAAQ;aACL,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7B,EAAE,cAAc,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;IAC5C,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAA;IAEf;;;;;OAKG;IACH,MAAM,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAEpC,MAAM,MAAM,GAAG,CAAC,GAAe,EAAE,EAAE;QACjC,QAAQ,EAAE,EAAE,CAAA;QACZ,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAA;IACvB,CAAC,CAAA;IAED,MAAM,QAAQ,GAAG,CAAC,GAAe,EAAE,QAAiB,EAAE,EAAE;QACtD,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,CAAA;QAEzB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,CAAC,CAAA;YAEX,OAAM;QACR,CAAC;QAED,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CAAA;YAClD,MAAM,CAAC,GAAG,CAAC,CAAA;YAEX,OAAM;QACR,CAAC;QAED,kEAAkE;QAClE,kEAAkE;QAClE,mCAAmC;QACnC,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAA;IAC1C,CAAC,CAAA;IAED,MAAM,aAAa,GAAG,CACpB,aAAmD,EACnD,EAAE;QACF,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;QAEjC,QAAQ,aAAa,CAAC,GAAG,EAAE,CAAC;YAC1B,KAAK,WAAW,CAAC,CAAC,CAAC;gBACjB,aAAa,CAAC,cAAc,EAAE,CAAA;gBAC9B,cAAc,CACZ,IAAI,CAAC,GAAG,CAAC,mBAAmB,GAAG,CAAC,EAAE,SAAS,CAAC,CAC7C,CAAA;gBAED,OAAM;YACR,CAAC;YACD,KAAK,SAAS,CAAC,CAAC,CAAC;gBACf,aAAa,CAAC,cAAc,EAAE,CAAA;gBAC9B,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,mBAAmB,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;gBAEpD,OAAM;YACR,CAAC;YACD,KAAK,MAAM,CAAC,CAAC,CAAC;gBACZ,aAAa,CAAC,cAAc,EAAE,CAAA;gBAC9B,cAAc,CAAC,CAAC,CAAC,CAAA;gBAEjB,OAAM;YACR,CAAC;YACD,KAAK,KAAK,CAAC,CAAC,CAAC;gBACX,aAAa,CAAC,cAAc,EAAE,CAAA;gBAC9B,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAA;gBAEtC,OAAM;YACR,CAAC;YACD,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;oBAC5B,OAAM;gBACR,CAAC;gBAED,aAAa,CAAC,cAAc,EAAE,CAAA;gBAC9B,QAAQ,CACN,SAAS,EACT,aAAa,CAAC,OAAO,IAAI,aAAa,CAAC,OAAO,CAC/C,CAAA;gBAED,OAAM;YACR,CAAC;YACD,QAAQ;QACV,CAAC;IACH,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;IAC/B,MAAM,YAAY,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CAAA;IAErD,MAAM,aAAa,GACjB,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,CACrB,KAAC,oBAAoB,IAAC,QAAQ,EAAE,QAAQ,GAAI,CAC7C,CAAC,CAAC,CAAC,CACF,MAAM,CACP,CAAA;IAEH,OAAO,CACL,eAAK,SAAS,EAAC,8BAA8B,aAC3C,cAAK,SAAS,EAAC,4CAA4C,YACzD,KAAC,WAAW,6BACa,SAAS,EAAE,EAAE,uBAClB,MAAM,mBACT,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,mBAC5B,OAAO,gBACV,KAAK,EACjB,cAAc,EAAC,KAAK,EACpB,YAAY,EAAC,KAAK,EAClB,WAAW,EAAC,KAAK,EACjB,SAAS,EAAE,SAAS,IAAI,IAAI,EAC5B,YAAY,EAAC,IAAI,EACjB,cAAc,EACZ,SAAS,KAAK,SAAS;wBACvB,YAAY,CAAC,MAAM,GAAG,CAAC,EAEzB,QAAQ,EAAE,CAAC,WAAW,EAAE,EAAE,CACxB,QAAQ,CAAC,WAAW,CAAC,aAAa,CAAC,KAAK,CAAC,EAE3C,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAC3B,OAAO,EAAE,CAAC,UAAU,EAAE,EAAE;wBACtB,IAAI,cAAc,CAAC,OAAO,EAAE,CAAC;4BAC3B,OAAM;wBACR,CAAC;wBAED,cAAc,CAAC,OAAO,GAAG,IAAI,CAAA;wBAE7B,MAAM,EAAE,MAAM,EAAE,GACd,UAAU,CAAC,aAAa,CAAC,KAAK,CAAA;wBAEhC,UAAU,CAAC,aAAa,CAAC,iBAAiB,CACxC,MAAM,EACN,MAAM,CACP,CAAA;oBACH,CAAC,EACD,SAAS,EAAE,aAAa,EACxB,WAAW,EAAE,WAAW,EACxB,GAAG,EAAE,QAAQ,EACb,IAAI,EAAC,UAAU,EACf,IAAI,EAAE,WAAW,CAAC,QAAQ,CAAC,EAC3B,UAAU,EAAE,KAAK,EACjB,KAAK,EAAE,YAAY,GACnB,GACE,EAEL,OAAO,CAAC,CAAC,CAAC,CACT,4BACc,KAAK,EACjB,SAAS,EAAC,+EAA+E,EACzF,EAAE,EAAE,MAAM,EACV,IAAI,EAAC,SAAS,YAEb,eAAe,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAEhC,kCACmB,OAAO,CAAC,SAAS,EAClC,SAAS,EAAC,gBAAgB,EAE1B,IAAI,EAAC,OAAO,aAEZ,cACE,SAAS,EAAE,WAAW,CACpB,yBAAyB,CAAC,QAAQ,CAAC,EACnC,0DAA0D,CAC3D,EACD,EAAE,EAAE,OAAO,CAAC,SAAS,YAEpB,OAAO,CAAC,KAAK,GACV,EAEL,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CACzB,KAAC,oBAAoB,IACnB,EAAE,EAAE,GAAG,CAAC,EAAE,EACV,QAAQ,EACN,GAAG,CAAC,KAAK,KAAK,mBAAmB,EAEnC,IAAI,EAAE,GAAG,CAAC,IAAI,EACd,QAAQ,EAAE,QAAQ,EAElB,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAC3B,OAAO,EAAE,GAAG,EAAE;gCACZ,IAAI,GAAG,CAAC,KAAK,KAAK,mBAAmB,EAAE,CAAC;oCACtC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;gCAC3B,CAAC;4BACH,CAAC,EACD,WAAW,EAAE,GAAG,CAAC,WAAW,IAPvB,GAAG,CAAC,EAAE,CAQX,CACH,CAAC,KA9BG,OAAO,CAAC,GAAG,CA+BZ,CACP,CAAC,GACE,CACP,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CACxB,YAAG,SAAS,EAAC,sDAAsD,YAChE,UAAU,GACT,CACL,EAEA,aAAa,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAC/B,cAAK,SAAS,EAAC,yHAAyH,YACrI,aAAa,GACV,CACP,IACG,CACP,CAAA;AACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type TextMatchRange } from "@charcuterie/logic";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* One thing the palette can open or run.
|
|
5
|
+
*
|
|
6
|
+
* A folder's members are items like any other: the caller flattens
|
|
7
|
+
* them into `items`, and puts the folder's name in `description` so a
|
|
8
|
+
* hit inside a folder still says where it lives.
|
|
9
|
+
*/
|
|
10
|
+
export type CommandPaletteItem = {
|
|
11
|
+
/** Secondary text after the title — a folder, a section, a host. Matched, never highlighted. */
|
|
12
|
+
description?: string;
|
|
13
|
+
/** Shown under this heading when the palette lists matches. Items without one share `ungroupedLabel`. */
|
|
14
|
+
group?: string;
|
|
15
|
+
/** Opens as a link: a real `<a href>`, routed through `RouterLinkProvider`. */
|
|
16
|
+
href?: string;
|
|
17
|
+
/** The app's own glyph or image. Decorative; the title is the name. */
|
|
18
|
+
icon?: ReactNode;
|
|
19
|
+
/** Matched as extra words and never shown: an old name, an abbreviation, a product the app hosts. */
|
|
20
|
+
keywords?: readonly string[];
|
|
21
|
+
/** Unique within the list it is in. */
|
|
22
|
+
key: string;
|
|
23
|
+
/** Trailing text at the end of the row: a kind ("Bookmark") or a status. */
|
|
24
|
+
hint?: ReactNode;
|
|
25
|
+
/** Runs on choice. With an `href` too, it runs as well as the navigation — a place to record a visit. */
|
|
26
|
+
onSelect?: () => void;
|
|
27
|
+
/** Passed to the link. `_blank` opens a new tab on a plain Enter or click. */
|
|
28
|
+
target?: string;
|
|
29
|
+
/** What is typed for and highlighted. */
|
|
30
|
+
title: string;
|
|
31
|
+
/** Breaks a tie inside a match tier, higher first: an open count, say. */
|
|
32
|
+
weight?: number;
|
|
33
|
+
};
|
|
34
|
+
/** A heading and its items, shown before anything is typed. */
|
|
35
|
+
export type CommandPaletteGroup = {
|
|
36
|
+
items: readonly CommandPaletteItem[];
|
|
37
|
+
key: string;
|
|
38
|
+
label: string;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A short prefix that sends the rest of the query to one target:
|
|
42
|
+
* `{ prefix: "au", label: "Auction Site", getHref: (query) => … }`
|
|
43
|
+
* turns `au lego` into one row that searches that site.
|
|
44
|
+
*/
|
|
45
|
+
export type CommandPalettePrefix = {
|
|
46
|
+
/** The link the row opens, given what follows the prefix. */
|
|
47
|
+
getHref?: (query: string) => string;
|
|
48
|
+
icon?: ReactNode;
|
|
49
|
+
/** The target's name, in the row's title and in the footer. */
|
|
50
|
+
label: string;
|
|
51
|
+
/** Runs instead of, or as well as, the link. */
|
|
52
|
+
onSelect?: (query: string) => void;
|
|
53
|
+
/** What is typed before the space: `au`. Matched without case. */
|
|
54
|
+
prefix: string;
|
|
55
|
+
};
|
|
56
|
+
export type CommandPaletteRow = {
|
|
57
|
+
item: CommandPaletteItem;
|
|
58
|
+
/** Where the query matched the title; empty for a suggestion. */
|
|
59
|
+
titleRanges: readonly TextMatchRange[];
|
|
60
|
+
};
|
|
61
|
+
export type CommandPaletteSection = {
|
|
62
|
+
key: string;
|
|
63
|
+
label: string;
|
|
64
|
+
rows: readonly CommandPaletteRow[];
|
|
65
|
+
};
|
|
66
|
+
export type CommandPaletteSectionsOptions = {
|
|
67
|
+
formatPrefixTitle: (label: string, query: string) => string;
|
|
68
|
+
getFallbackItem?: (query: string) => CommandPaletteItem | null | undefined;
|
|
69
|
+
items: readonly CommandPaletteItem[];
|
|
70
|
+
maxResultsPerGroup?: number;
|
|
71
|
+
prefixGroupLabel: string;
|
|
72
|
+
prefixes: readonly CommandPalettePrefix[];
|
|
73
|
+
query: string;
|
|
74
|
+
suggestions: readonly CommandPaletteGroup[];
|
|
75
|
+
ungroupedLabel: string;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* What the palette lists for a query, as headed sections in order.
|
|
79
|
+
*
|
|
80
|
+
* Three cases, decided in this order:
|
|
81
|
+
*
|
|
82
|
+
* 1. **A prefix command** (`au lego`) lists one row, for that target.
|
|
83
|
+
* 2. **Nothing typed** lists the caller's `suggestions` — the shape
|
|
84
|
+
* the palette exists for: something to choose the moment it opens.
|
|
85
|
+
* 3. **Anything else** lists the word-prefix matches from `items`,
|
|
86
|
+
* under each item's `group`, and then the caller's fallback row.
|
|
87
|
+
*
|
|
88
|
+
* Matched groups are ordered by their best member, so the first row
|
|
89
|
+
* — the one Enter opens — is always the best match, whichever group
|
|
90
|
+
* it belongs to.
|
|
91
|
+
*/
|
|
92
|
+
export declare const buildCommandPaletteSections: ({ formatPrefixTitle, getFallbackItem, items, maxResultsPerGroup, prefixGroupLabel, prefixes, query, suggestions, ungroupedLabel, }: CommandPaletteSectionsOptions) => CommandPaletteSection[];
|
|
93
|
+
//# sourceMappingURL=commandPaletteSections.d.ts.map
|