@etoile-dev/react 0.2.2 → 1.0.0
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 +341 -205
- package/dist/Searchbar.d.ts +315 -0
- package/dist/Searchbar.js +207 -0
- package/dist/context.d.ts +57 -0
- package/dist/context.js +32 -0
- package/dist/hooks/useEtoileSearch.d.ts +122 -0
- package/dist/hooks/useEtoileSearch.js +138 -0
- package/dist/index.d.ts +44 -19
- package/dist/index.js +37 -12
- package/dist/primitives/Content.d.ts +34 -0
- package/dist/primitives/Content.js +108 -0
- package/dist/primitives/Empty.d.ts +25 -0
- package/dist/primitives/Empty.js +25 -0
- package/dist/primitives/Error.d.ts +29 -0
- package/dist/primitives/Error.js +26 -0
- package/dist/primitives/Group.d.ts +30 -0
- package/dist/primitives/Group.js +22 -0
- package/dist/primitives/Icon.d.ts +21 -0
- package/dist/primitives/Icon.js +14 -0
- package/dist/primitives/Input.d.ts +32 -0
- package/dist/primitives/Input.js +70 -0
- package/dist/primitives/Item.d.ts +61 -0
- package/dist/primitives/Item.js +76 -0
- package/dist/primitives/Kbd.d.ts +20 -0
- package/dist/primitives/Kbd.js +13 -0
- package/dist/primitives/List.d.ts +35 -0
- package/dist/primitives/List.js +37 -0
- package/dist/primitives/Loading.d.ts +25 -0
- package/dist/primitives/Loading.js +26 -0
- package/dist/primitives/Modal.d.ts +39 -0
- package/dist/primitives/Modal.js +37 -0
- package/dist/primitives/ModalInput.d.ts +61 -0
- package/dist/primitives/ModalInput.js +33 -0
- package/dist/primitives/Overlay.d.ts +21 -0
- package/dist/primitives/Overlay.js +41 -0
- package/dist/primitives/Portal.d.ts +28 -0
- package/dist/primitives/Portal.js +30 -0
- package/dist/primitives/Root.d.ts +116 -0
- package/dist/primitives/Root.js +413 -0
- package/dist/primitives/Separator.d.ts +19 -0
- package/dist/primitives/Separator.js +18 -0
- package/dist/primitives/Thumbnail.d.ts +31 -0
- package/dist/primitives/Thumbnail.js +59 -0
- package/dist/primitives/Trigger.d.ts +28 -0
- package/dist/primitives/Trigger.js +35 -0
- package/dist/store.d.ts +38 -0
- package/dist/store.js +63 -0
- package/dist/styles.css +480 -133
- package/dist/types.d.ts +3 -31
- package/dist/utils/composeRefs.d.ts +12 -0
- package/dist/utils/composeRefs.js +27 -0
- package/dist/utils/slot.d.ts +22 -0
- package/dist/utils/slot.js +58 -0
- package/package.json +8 -4
- package/dist/Search.d.ts +0 -37
- package/dist/Search.js +0 -31
- package/dist/components/SearchIcon.d.ts +0 -22
- package/dist/components/SearchIcon.js +0 -17
- package/dist/components/SearchInput.d.ts +0 -30
- package/dist/components/SearchInput.js +0 -59
- package/dist/components/SearchKbd.d.ts +0 -30
- package/dist/components/SearchKbd.js +0 -24
- package/dist/components/SearchResult.d.ts +0 -31
- package/dist/components/SearchResult.js +0 -40
- package/dist/components/SearchResultThumbnail.d.ts +0 -38
- package/dist/components/SearchResultThumbnail.js +0 -38
- package/dist/components/SearchResults.d.ts +0 -39
- package/dist/components/SearchResults.js +0 -53
- package/dist/components/SearchRoot.d.ts +0 -44
- package/dist/components/SearchRoot.js +0 -132
- package/dist/context/SearchContext.d.ts +0 -55
- package/dist/context/SearchContext.js +0 -36
- package/dist/hooks/useSearch.d.ts +0 -56
- package/dist/hooks/useSearch.js +0 -116
package/dist/types.d.ts
CHANGED
|
@@ -1,33 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* ```ts
|
|
6
|
-
* const result: SearchResultData = {
|
|
7
|
-
* external_id: "starry-night",
|
|
8
|
-
* title: "The Starry Night",
|
|
9
|
-
* collection: "paintings",
|
|
10
|
-
* score: 0.95,
|
|
11
|
-
* content: "A swirling night sky over a village...",
|
|
12
|
-
* metadata: {
|
|
13
|
-
* artist: "Vincent van Gogh",
|
|
14
|
-
* year: 1889,
|
|
15
|
-
* url: "https://example.com/starry-night"
|
|
16
|
-
* }
|
|
17
|
-
* };
|
|
18
|
-
* ```
|
|
2
|
+
* @deprecated Use `SearchResult` from `@etoile-dev/client` instead.
|
|
3
|
+
* This alias will be removed in a future major version.
|
|
19
4
|
*/
|
|
20
|
-
export type SearchResultData
|
|
21
|
-
/** Unique identifier for the result */
|
|
22
|
-
external_id: string;
|
|
23
|
-
/** Title of the result */
|
|
24
|
-
title: string;
|
|
25
|
-
/** Collection this result belongs to */
|
|
26
|
-
collection: string;
|
|
27
|
-
/** Relevance score (0-1, higher is more relevant) */
|
|
28
|
-
score: number;
|
|
29
|
-
/** Text content of the result */
|
|
30
|
-
content?: string;
|
|
31
|
-
/** Custom metadata attached to the result */
|
|
32
|
-
metadata: Record<string, unknown>;
|
|
33
|
-
};
|
|
5
|
+
export type { SearchResult as SearchResultData } from "@etoile-dev/client";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
type ReactRef<T> = React.RefCallback<T> | React.RefObject<T> | React.MutableRefObject<T> | null | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* Merges multiple refs into a single callback ref.
|
|
5
|
+
* Useful for combining a forwarded ref with an internal ref.
|
|
6
|
+
*/
|
|
7
|
+
export declare function composeRefs<T>(...refs: ReactRef<T>[]): React.RefCallback<T>;
|
|
8
|
+
/**
|
|
9
|
+
* Returns a stable composed ref via useMemo.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useComposeRefs<T>(...refs: ReactRef<T>[]): React.RefCallback<T>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
function assignRef(ref, value) {
|
|
3
|
+
if (!ref)
|
|
4
|
+
return;
|
|
5
|
+
if (typeof ref === "function") {
|
|
6
|
+
ref(value);
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
ref.current = value;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Merges multiple refs into a single callback ref.
|
|
14
|
+
* Useful for combining a forwarded ref with an internal ref.
|
|
15
|
+
*/
|
|
16
|
+
export function composeRefs(...refs) {
|
|
17
|
+
return (node) => {
|
|
18
|
+
refs.forEach((ref) => assignRef(ref, node));
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Returns a stable composed ref via useMemo.
|
|
23
|
+
*/
|
|
24
|
+
export function useComposeRefs(...refs) {
|
|
25
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
26
|
+
return React.useMemo(() => composeRefs(...refs), refs);
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slot — enables the `asChild` composition pattern (Radix-style).
|
|
3
|
+
*
|
|
4
|
+
* When a component renders `<Slot>`, it merges its own props onto the single
|
|
5
|
+
* child element instead of rendering its own DOM node. This lets callers swap
|
|
6
|
+
* out the underlying element while keeping all behavior props (event handlers,
|
|
7
|
+
* aria attributes, data-* attributes, ref, className…).
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const Button = ({ asChild, ...props }) => {
|
|
11
|
+
* const Comp = asChild ? Slot : 'button';
|
|
12
|
+
* return <Comp {...props} />;
|
|
13
|
+
* };
|
|
14
|
+
*
|
|
15
|
+
* // Consumer:
|
|
16
|
+
* <Button asChild><a href="/search">Search</a></Button>
|
|
17
|
+
* // Renders: <a href="/search" ...buttonProps>Search</a>
|
|
18
|
+
*/
|
|
19
|
+
import * as React from "react";
|
|
20
|
+
export declare const Slot: React.ForwardRefExoticComponent<{
|
|
21
|
+
children?: React.ReactNode;
|
|
22
|
+
} & React.HTMLAttributes<HTMLElement> & React.RefAttributes<HTMLElement>>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slot — enables the `asChild` composition pattern (Radix-style).
|
|
3
|
+
*
|
|
4
|
+
* When a component renders `<Slot>`, it merges its own props onto the single
|
|
5
|
+
* child element instead of rendering its own DOM node. This lets callers swap
|
|
6
|
+
* out the underlying element while keeping all behavior props (event handlers,
|
|
7
|
+
* aria attributes, data-* attributes, ref, className…).
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const Button = ({ asChild, ...props }) => {
|
|
11
|
+
* const Comp = asChild ? Slot : 'button';
|
|
12
|
+
* return <Comp {...props} />;
|
|
13
|
+
* };
|
|
14
|
+
*
|
|
15
|
+
* // Consumer:
|
|
16
|
+
* <Button asChild><a href="/search">Search</a></Button>
|
|
17
|
+
* // Renders: <a href="/search" ...buttonProps>Search</a>
|
|
18
|
+
*/
|
|
19
|
+
import * as React from "react";
|
|
20
|
+
import { composeRefs } from "./composeRefs.js";
|
|
21
|
+
export const Slot = React.forwardRef(({ children, ...slotProps }, forwardedRef) => {
|
|
22
|
+
if (!React.isValidElement(children)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const child = children;
|
|
26
|
+
return React.cloneElement(child, {
|
|
27
|
+
...mergeProps(slotProps, child.props),
|
|
28
|
+
ref: forwardedRef
|
|
29
|
+
? composeRefs(forwardedRef, child.ref)
|
|
30
|
+
: child.ref,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
Slot.displayName = "Slot";
|
|
34
|
+
// Merge Slot props onto child props — child props win for most things,
|
|
35
|
+
// but event handlers and classNames are composed.
|
|
36
|
+
function mergeProps(slotProps, childProps) {
|
|
37
|
+
const merged = { ...slotProps };
|
|
38
|
+
for (const key of Object.keys(childProps)) {
|
|
39
|
+
const slotVal = slotProps[key];
|
|
40
|
+
const childVal = childProps[key];
|
|
41
|
+
if (key === "className") {
|
|
42
|
+
merged[key] = [slotVal, childVal].filter(Boolean).join(" ") || undefined;
|
|
43
|
+
}
|
|
44
|
+
else if (key.startsWith("on") &&
|
|
45
|
+
typeof slotVal === "function" &&
|
|
46
|
+
typeof childVal === "function") {
|
|
47
|
+
merged[key] = (...args) => {
|
|
48
|
+
childVal(...args);
|
|
49
|
+
slotVal(...args);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Child prop wins (more specific)
|
|
54
|
+
merged[key] = childVal !== undefined ? childVal : slotVal;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return merged;
|
|
58
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etoile-dev/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Official React primitives for
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official React primitives for Etoile - Headless, composable search components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"etoile",
|
|
7
7
|
"react",
|
|
@@ -43,13 +43,17 @@
|
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsc && cp src/styles.css dist/styles.css",
|
|
45
45
|
"clean": "rm -rf dist",
|
|
46
|
-
"prepublishOnly": "npm run clean && npm run build"
|
|
46
|
+
"prepublishOnly": "npm run clean && npm run build",
|
|
47
|
+
"playground": "npm run dev --workspace=playground"
|
|
47
48
|
},
|
|
49
|
+
"workspaces": [
|
|
50
|
+
"playground"
|
|
51
|
+
],
|
|
48
52
|
"peerDependencies": {
|
|
49
53
|
"react": ">=18"
|
|
50
54
|
},
|
|
51
55
|
"dependencies": {
|
|
52
|
-
"@etoile-dev/client": "^0.
|
|
56
|
+
"@etoile-dev/client": "^0.5.0"
|
|
53
57
|
},
|
|
54
58
|
"devDependencies": {
|
|
55
59
|
"@types/react": "^18.0.0",
|
package/dist/Search.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import type { SearchResultData } from "./types.js";
|
|
3
|
-
export type SearchProps = {
|
|
4
|
-
/** Your Étoile API key. Get one at https://etoile.dev */
|
|
5
|
-
apiKey: string;
|
|
6
|
-
/** Collections to search in (e.g., ["paintings", "artists"]) */
|
|
7
|
-
collections: string[];
|
|
8
|
-
/** Maximum number of results to return (default: 10) */
|
|
9
|
-
limit?: number;
|
|
10
|
-
/** Placeholder text for the search input */
|
|
11
|
-
placeholder?: string;
|
|
12
|
-
/** Additional CSS class name (e.g., "dark" for dark mode) */
|
|
13
|
-
className?: string;
|
|
14
|
-
/** Custom render function for each result (optional) */
|
|
15
|
-
renderResult?: (result: SearchResultData) => React.ReactNode;
|
|
16
|
-
baseUrl?: string;
|
|
17
|
-
};
|
|
18
|
-
/**
|
|
19
|
-
* All-in-one search component with sensible defaults.
|
|
20
|
-
*
|
|
21
|
-
* Provides a complete, polished search experience out of the box including
|
|
22
|
-
* search icon, keyboard shortcut badge, and result thumbnails. Just import
|
|
23
|
-
* `@etoile-dev/react/styles.css` for styling - no wrapper needed.
|
|
24
|
-
*
|
|
25
|
-
* @param props - Component props
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```tsx
|
|
29
|
-
* <Search apiKey="your-api-key" collections={["paintings"]} />
|
|
30
|
-
* ```
|
|
31
|
-
*
|
|
32
|
-
* @example Dark mode
|
|
33
|
-
* ```tsx
|
|
34
|
-
* <Search apiKey="your-api-key" collections={["paintings"]} className="dark" />
|
|
35
|
-
* ```
|
|
36
|
-
*/
|
|
37
|
-
export declare const Search: ({ apiKey, collections, limit, placeholder, className, renderResult, baseUrl, }: SearchProps) => import("react/jsx-runtime").JSX.Element;
|
package/dist/Search.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { SearchRoot } from "./components/SearchRoot.js";
|
|
3
|
-
import { SearchInput } from "./components/SearchInput.js";
|
|
4
|
-
import { SearchResults } from "./components/SearchResults.js";
|
|
5
|
-
import { SearchResult } from "./components/SearchResult.js";
|
|
6
|
-
import { SearchResultThumbnail } from "./components/SearchResultThumbnail.js";
|
|
7
|
-
import { SearchIcon } from "./components/SearchIcon.js";
|
|
8
|
-
import { SearchKbd } from "./components/SearchKbd.js";
|
|
9
|
-
const DefaultResult = (result) => (_jsxs(SearchResult, { children: [_jsx(SearchResultThumbnail, {}), _jsxs("div", { className: "etoile-result-content", children: [_jsx("span", { className: "etoile-result-title", children: result.title }), _jsx("span", { className: "etoile-result-subtitle", children: result.collection })] })] }));
|
|
10
|
-
/**
|
|
11
|
-
* All-in-one search component with sensible defaults.
|
|
12
|
-
*
|
|
13
|
-
* Provides a complete, polished search experience out of the box including
|
|
14
|
-
* search icon, keyboard shortcut badge, and result thumbnails. Just import
|
|
15
|
-
* `@etoile-dev/react/styles.css` for styling - no wrapper needed.
|
|
16
|
-
*
|
|
17
|
-
* @param props - Component props
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```tsx
|
|
21
|
-
* <Search apiKey="your-api-key" collections={["paintings"]} />
|
|
22
|
-
* ```
|
|
23
|
-
*
|
|
24
|
-
* @example Dark mode
|
|
25
|
-
* ```tsx
|
|
26
|
-
* <Search apiKey="your-api-key" collections={["paintings"]} className="dark" />
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export const Search = ({ apiKey, collections, limit, placeholder = "Search...", className, renderResult, baseUrl, }) => {
|
|
30
|
-
return (_jsxs(SearchRoot, { apiKey: apiKey, collections: collections, limit: limit, className: className, baseUrl: baseUrl, children: [_jsxs("div", { className: "etoile-input-wrapper", children: [_jsx(SearchIcon, {}), _jsx(SearchInput, { placeholder: placeholder }), _jsx(SearchKbd, {})] }), _jsx(SearchResults, { children: renderResult ?? DefaultResult })] }));
|
|
31
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export type SearchIconProps = {
|
|
2
|
-
/** Width and height in pixels (default: 18) */
|
|
3
|
-
size?: number;
|
|
4
|
-
/** CSS class name for styling */
|
|
5
|
-
className?: string;
|
|
6
|
-
};
|
|
7
|
-
/**
|
|
8
|
-
* Search magnifying glass icon.
|
|
9
|
-
*
|
|
10
|
-
* A minimal SVG icon that works perfectly with the default theme.
|
|
11
|
-
*
|
|
12
|
-
* @param props - Component props
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```tsx
|
|
16
|
-
* <div className="etoile-input-wrapper">
|
|
17
|
-
* <SearchIcon />
|
|
18
|
-
* <SearchInput placeholder="Search..." />
|
|
19
|
-
* </div>
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
export declare const SearchIcon: ({ size, className }: SearchIconProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Search magnifying glass icon.
|
|
4
|
-
*
|
|
5
|
-
* A minimal SVG icon that works perfectly with the default theme.
|
|
6
|
-
*
|
|
7
|
-
* @param props - Component props
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```tsx
|
|
11
|
-
* <div className="etoile-input-wrapper">
|
|
12
|
-
* <SearchIcon />
|
|
13
|
-
* <SearchInput placeholder="Search..." />
|
|
14
|
-
* </div>
|
|
15
|
-
* ```
|
|
16
|
-
*/
|
|
17
|
-
export const SearchIcon = ({ size = 18, className }) => (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className: className, "aria-hidden": "true", children: [_jsx("path", { d: "m21 21-4.34-4.34" }), _jsx("circle", { cx: "11", cy: "11", r: "8" })] }));
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
export type SearchInputProps = {
|
|
3
|
-
/** Placeholder text for the input field */
|
|
4
|
-
placeholder?: string;
|
|
5
|
-
/** CSS class name for styling the input */
|
|
6
|
-
className?: string;
|
|
7
|
-
} & React.InputHTMLAttributes<HTMLInputElement>;
|
|
8
|
-
/**
|
|
9
|
-
* Search input component with built-in keyboard navigation and accessibility.
|
|
10
|
-
*
|
|
11
|
-
* Integrates with SearchRoot context to provide debouncing and keyboard controls
|
|
12
|
-
* (ArrowUp, ArrowDown, Enter, Escape). Implements ARIA combobox pattern.
|
|
13
|
-
* Accepts standard input props like aria-label and autoComplete.
|
|
14
|
-
*
|
|
15
|
-
* @param props - Component props
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```tsx
|
|
19
|
-
* <SearchInput />
|
|
20
|
-
* ```
|
|
21
|
-
*
|
|
22
|
-
* @example With placeholder and styling
|
|
23
|
-
* ```tsx
|
|
24
|
-
* <SearchInput
|
|
25
|
-
* placeholder="Search paintings..."
|
|
26
|
-
* className="px-4 py-2 border rounded-lg"
|
|
27
|
-
* />
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export declare const SearchInput: ({ placeholder, className, ...props }: SearchInputProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useSearchContext } from "../context/SearchContext.js";
|
|
3
|
-
/**
|
|
4
|
-
* Search input component with built-in keyboard navigation and accessibility.
|
|
5
|
-
*
|
|
6
|
-
* Integrates with SearchRoot context to provide debouncing and keyboard controls
|
|
7
|
-
* (ArrowUp, ArrowDown, Enter, Escape). Implements ARIA combobox pattern.
|
|
8
|
-
* Accepts standard input props like aria-label and autoComplete.
|
|
9
|
-
*
|
|
10
|
-
* @param props - Component props
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```tsx
|
|
14
|
-
* <SearchInput />
|
|
15
|
-
* ```
|
|
16
|
-
*
|
|
17
|
-
* @example With placeholder and styling
|
|
18
|
-
* ```tsx
|
|
19
|
-
* <SearchInput
|
|
20
|
-
* placeholder="Search paintings..."
|
|
21
|
-
* className="px-4 py-2 border rounded-lg"
|
|
22
|
-
* />
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
export const SearchInput = ({ placeholder, className, ...props }) => {
|
|
26
|
-
const { query, setQuery, results, isOpen, setOpen, selectedIndex, setSelectedIndex, listboxId, getResultId, handleKeyDown, autoFocus, } = useSearchContext();
|
|
27
|
-
const showResults = isOpen && results.length > 0;
|
|
28
|
-
const activeId = selectedIndex >= 0 && showResults ? getResultId(selectedIndex) : undefined;
|
|
29
|
-
return (_jsxs(_Fragment, { children: [_jsx("input", { ...props, type: "text", placeholder: placeholder, className: className, value: query, autoFocus: autoFocus, role: "combobox", "aria-expanded": showResults, "aria-controls": listboxId, "aria-activedescendant": activeId, "aria-autocomplete": "list", onChange: (event) => {
|
|
30
|
-
props.onChange?.(event);
|
|
31
|
-
const nextValue = event.target.value;
|
|
32
|
-
setQuery(nextValue);
|
|
33
|
-
if (nextValue.trim() !== "") {
|
|
34
|
-
setSelectedIndex(0);
|
|
35
|
-
}
|
|
36
|
-
}, onFocus: (event) => {
|
|
37
|
-
props.onFocus?.(event);
|
|
38
|
-
if (!event.defaultPrevented && query.trim() !== "" && results.length > 0) {
|
|
39
|
-
setOpen(true);
|
|
40
|
-
}
|
|
41
|
-
}, onKeyDown: (event) => {
|
|
42
|
-
props.onKeyDown?.(event);
|
|
43
|
-
if (!event.defaultPrevented) {
|
|
44
|
-
handleKeyDown(event);
|
|
45
|
-
}
|
|
46
|
-
} }), _jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", style: {
|
|
47
|
-
position: "absolute",
|
|
48
|
-
width: 1,
|
|
49
|
-
height: 1,
|
|
50
|
-
padding: 0,
|
|
51
|
-
margin: -1,
|
|
52
|
-
overflow: "hidden",
|
|
53
|
-
clip: "rect(0, 0, 0, 0)",
|
|
54
|
-
whiteSpace: "nowrap",
|
|
55
|
-
border: 0,
|
|
56
|
-
}, children: showResults
|
|
57
|
-
? `${results.length} result${results.length === 1 ? "" : "s"} available`
|
|
58
|
-
: "" })] }));
|
|
59
|
-
};
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
export type SearchKbdProps = {
|
|
3
|
-
/** Keyboard shortcut text (default: "⌘K") */
|
|
4
|
-
children?: React.ReactNode;
|
|
5
|
-
/** CSS class name for styling */
|
|
6
|
-
className?: string;
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* Keyboard shortcut badge for search.
|
|
10
|
-
*
|
|
11
|
-
* Displays a styled keyboard shortcut indicator. Works with the default theme.
|
|
12
|
-
*
|
|
13
|
-
* @param props - Component props
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* ```tsx
|
|
17
|
-
* <div className="etoile-input-wrapper">
|
|
18
|
-
* <SearchIcon />
|
|
19
|
-
* <SearchInput placeholder="Search..." />
|
|
20
|
-
* <SearchKbd />
|
|
21
|
-
* </div>
|
|
22
|
-
* ```
|
|
23
|
-
*
|
|
24
|
-
* @example Custom shortcut
|
|
25
|
-
* ```tsx
|
|
26
|
-
* <SearchKbd>/</SearchKbd>
|
|
27
|
-
* <SearchKbd>Ctrl K</SearchKbd>
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export declare const SearchKbd: ({ children, className, }: SearchKbdProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Keyboard shortcut badge for search.
|
|
4
|
-
*
|
|
5
|
-
* Displays a styled keyboard shortcut indicator. Works with the default theme.
|
|
6
|
-
*
|
|
7
|
-
* @param props - Component props
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```tsx
|
|
11
|
-
* <div className="etoile-input-wrapper">
|
|
12
|
-
* <SearchIcon />
|
|
13
|
-
* <SearchInput placeholder="Search..." />
|
|
14
|
-
* <SearchKbd />
|
|
15
|
-
* </div>
|
|
16
|
-
* ```
|
|
17
|
-
*
|
|
18
|
-
* @example Custom shortcut
|
|
19
|
-
* ```tsx
|
|
20
|
-
* <SearchKbd>/</SearchKbd>
|
|
21
|
-
* <SearchKbd>Ctrl K</SearchKbd>
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
export const SearchKbd = ({ children = "⌘K", className, }) => (_jsx("kbd", { className: className ? `etoile-kbd ${className}` : "etoile-kbd", children: children }));
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
export type SearchResultProps = {
|
|
3
|
-
/** CSS class name for styling the result item */
|
|
4
|
-
className?: string;
|
|
5
|
-
/** Content to render inside the result */
|
|
6
|
-
children: React.ReactNode;
|
|
7
|
-
} & React.HTMLAttributes<HTMLDivElement>;
|
|
8
|
-
/**
|
|
9
|
-
* Individual search result item with selection state and keyboard navigation.
|
|
10
|
-
*
|
|
11
|
-
* Manages selection state and accessibility attributes. Provides `data-selected`
|
|
12
|
-
* attribute for styling the active result. Must be used inside SearchResults.
|
|
13
|
-
* Accepts standard div props like onClick for custom behavior.
|
|
14
|
-
*
|
|
15
|
-
* @param props - Component props
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```tsx
|
|
19
|
-
* <SearchResult>{result.title}</SearchResult>
|
|
20
|
-
* ```
|
|
21
|
-
*
|
|
22
|
-
* @example With selection styling
|
|
23
|
-
* ```tsx
|
|
24
|
-
* <SearchResult className="result-item">
|
|
25
|
-
* <h3>{result.title}</h3>
|
|
26
|
-
* </SearchResult>
|
|
27
|
-
*
|
|
28
|
-
* // CSS: .result-item[data-selected="true"] { background: #f0f9ff; }
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export declare const SearchResult: ({ className, children, ...props }: SearchResultProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import { useSearchContext } from "../context/SearchContext.js";
|
|
4
|
-
import { SearchResultIndexContext } from "./SearchResults.js";
|
|
5
|
-
/**
|
|
6
|
-
* Individual search result item with selection state and keyboard navigation.
|
|
7
|
-
*
|
|
8
|
-
* Manages selection state and accessibility attributes. Provides `data-selected`
|
|
9
|
-
* attribute for styling the active result. Must be used inside SearchResults.
|
|
10
|
-
* Accepts standard div props like onClick for custom behavior.
|
|
11
|
-
*
|
|
12
|
-
* @param props - Component props
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```tsx
|
|
16
|
-
* <SearchResult>{result.title}</SearchResult>
|
|
17
|
-
* ```
|
|
18
|
-
*
|
|
19
|
-
* @example With selection styling
|
|
20
|
-
* ```tsx
|
|
21
|
-
* <SearchResult className="result-item">
|
|
22
|
-
* <h3>{result.title}</h3>
|
|
23
|
-
* </SearchResult>
|
|
24
|
-
*
|
|
25
|
-
* // CSS: .result-item[data-selected="true"] { background: #f0f9ff; }
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
export const SearchResult = ({ className, children, ...props }) => {
|
|
29
|
-
const { selectedIndex, registerResult, getResultId } = useSearchContext();
|
|
30
|
-
const index = React.useContext(SearchResultIndexContext);
|
|
31
|
-
if (index === null) {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
const isSelected = index === selectedIndex;
|
|
35
|
-
const id = React.useMemo(() => getResultId(index), [getResultId, index]);
|
|
36
|
-
const setRef = React.useCallback((node) => {
|
|
37
|
-
registerResult(index, node);
|
|
38
|
-
}, [index, registerResult]);
|
|
39
|
-
return (_jsx("div", { ...props, ref: setRef, id: id, role: "option", "aria-selected": isSelected, "data-selected": isSelected ? "true" : "false", "data-index": index, tabIndex: isSelected ? 0 : -1, className: className, children: children }));
|
|
40
|
-
};
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
export type SearchResultThumbnailProps = {
|
|
3
|
-
/** Image source URL (defaults to result.metadata.thumbnailUrl) */
|
|
4
|
-
src?: string;
|
|
5
|
-
/** Alt text for the image (defaults to result.title) */
|
|
6
|
-
alt?: string;
|
|
7
|
-
/** Width and height in pixels (default: 40) */
|
|
8
|
-
size?: number;
|
|
9
|
-
/** CSS class name for styling */
|
|
10
|
-
className?: string;
|
|
11
|
-
} & React.ImgHTMLAttributes<HTMLImageElement>;
|
|
12
|
-
/**
|
|
13
|
-
* Thumbnail image for search results with automatic source detection.
|
|
14
|
-
*
|
|
15
|
-
* Automatically uses `metadata.thumbnailUrl` if available. Returns null
|
|
16
|
-
* if no image source is found. Must be used inside SearchResults.
|
|
17
|
-
* Accepts standard img props like loading and decoding.
|
|
18
|
-
*
|
|
19
|
-
* @param props - Component props
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```tsx
|
|
23
|
-
* <SearchResults>
|
|
24
|
-
* {(result) => (
|
|
25
|
-
* <SearchResult>
|
|
26
|
-
* <SearchResultThumbnail />
|
|
27
|
-
* <span>{result.title}</span>
|
|
28
|
-
* </SearchResult>
|
|
29
|
-
* )}
|
|
30
|
-
* </SearchResults>
|
|
31
|
-
* ```
|
|
32
|
-
*
|
|
33
|
-
* @example With custom size and styling
|
|
34
|
-
* ```tsx
|
|
35
|
-
* <SearchResultThumbnail size={48} className="rounded-full" />
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
export declare const SearchResultThumbnail: ({ src, alt, size, className, ...props }: SearchResultThumbnailProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import { SearchResultDataContext } from "./SearchResults.js";
|
|
4
|
-
/**
|
|
5
|
-
* Thumbnail image for search results with automatic source detection.
|
|
6
|
-
*
|
|
7
|
-
* Automatically uses `metadata.thumbnailUrl` if available. Returns null
|
|
8
|
-
* if no image source is found. Must be used inside SearchResults.
|
|
9
|
-
* Accepts standard img props like loading and decoding.
|
|
10
|
-
*
|
|
11
|
-
* @param props - Component props
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```tsx
|
|
15
|
-
* <SearchResults>
|
|
16
|
-
* {(result) => (
|
|
17
|
-
* <SearchResult>
|
|
18
|
-
* <SearchResultThumbnail />
|
|
19
|
-
* <span>{result.title}</span>
|
|
20
|
-
* </SearchResult>
|
|
21
|
-
* )}
|
|
22
|
-
* </SearchResults>
|
|
23
|
-
* ```
|
|
24
|
-
*
|
|
25
|
-
* @example With custom size and styling
|
|
26
|
-
* ```tsx
|
|
27
|
-
* <SearchResultThumbnail size={48} className="rounded-full" />
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
export const SearchResultThumbnail = ({ src, alt, size = 40, className, ...props }) => {
|
|
31
|
-
const result = React.useContext(SearchResultDataContext);
|
|
32
|
-
const imageSrc = src ?? result?.metadata?.thumbnailUrl;
|
|
33
|
-
const imageAlt = alt ?? result?.title ?? "";
|
|
34
|
-
if (!imageSrc) {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
return (_jsx("img", { ...props, src: imageSrc, alt: imageAlt, width: size, height: size, className: className, draggable: false }));
|
|
38
|
-
};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import type { SearchResultData } from "../types.js";
|
|
3
|
-
export type SearchResultsProps = {
|
|
4
|
-
/** CSS class name for the results container */
|
|
5
|
-
className?: string;
|
|
6
|
-
/** Render function that receives each search result */
|
|
7
|
-
children: (result: SearchResultData) => React.ReactNode;
|
|
8
|
-
} & Omit<React.HTMLAttributes<HTMLDivElement>, "children">;
|
|
9
|
-
export declare const SearchResultIndexContext: React.Context<number | null>;
|
|
10
|
-
export declare const SearchResultDataContext: React.Context<SearchResultData | null>;
|
|
11
|
-
/**
|
|
12
|
-
* Container component for rendering search results with keyboard navigation.
|
|
13
|
-
*
|
|
14
|
-
* Accepts a render function that receives each result. Automatically hides
|
|
15
|
-
* when query is empty or no results found. Includes ARIA listbox role.
|
|
16
|
-
* Accepts standard div props like onScroll and style.
|
|
17
|
-
*
|
|
18
|
-
* @param props - Component props
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```tsx
|
|
22
|
-
* <SearchResults>
|
|
23
|
-
* {(result) => <SearchResult>{result.title}</SearchResult>}
|
|
24
|
-
* </SearchResults>
|
|
25
|
-
* ```
|
|
26
|
-
*
|
|
27
|
-
* @example With styling and metadata
|
|
28
|
-
* ```tsx
|
|
29
|
-
* <SearchResults className="mt-2 border rounded-lg">
|
|
30
|
-
* {(result) => (
|
|
31
|
-
* <SearchResult className="p-4 hover:bg-gray-50">
|
|
32
|
-
* <h3>{result.title}</h3>
|
|
33
|
-
* <p>{result.metadata?.artist}</p>
|
|
34
|
-
* </SearchResult>
|
|
35
|
-
* )}
|
|
36
|
-
* </SearchResults>
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export declare const SearchResults: ({ className, children, ...props }: SearchResultsProps) => import("react/jsx-runtime").JSX.Element | null;
|