@etoile-dev/react 0.2.3 → 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.
Files changed (74) hide show
  1. package/README.md +341 -206
  2. package/dist/Searchbar.d.ts +315 -0
  3. package/dist/Searchbar.js +207 -0
  4. package/dist/context.d.ts +57 -0
  5. package/dist/context.js +32 -0
  6. package/dist/hooks/useEtoileSearch.d.ts +122 -0
  7. package/dist/hooks/useEtoileSearch.js +138 -0
  8. package/dist/index.d.ts +44 -19
  9. package/dist/index.js +37 -12
  10. package/dist/primitives/Content.d.ts +34 -0
  11. package/dist/primitives/Content.js +108 -0
  12. package/dist/primitives/Empty.d.ts +25 -0
  13. package/dist/primitives/Empty.js +25 -0
  14. package/dist/primitives/Error.d.ts +29 -0
  15. package/dist/primitives/Error.js +26 -0
  16. package/dist/primitives/Group.d.ts +30 -0
  17. package/dist/primitives/Group.js +22 -0
  18. package/dist/primitives/Icon.d.ts +21 -0
  19. package/dist/primitives/Icon.js +14 -0
  20. package/dist/primitives/Input.d.ts +32 -0
  21. package/dist/primitives/Input.js +70 -0
  22. package/dist/primitives/Item.d.ts +61 -0
  23. package/dist/primitives/Item.js +76 -0
  24. package/dist/primitives/Kbd.d.ts +20 -0
  25. package/dist/primitives/Kbd.js +13 -0
  26. package/dist/primitives/List.d.ts +35 -0
  27. package/dist/primitives/List.js +37 -0
  28. package/dist/primitives/Loading.d.ts +25 -0
  29. package/dist/primitives/Loading.js +26 -0
  30. package/dist/primitives/Modal.d.ts +39 -0
  31. package/dist/primitives/Modal.js +37 -0
  32. package/dist/primitives/ModalInput.d.ts +61 -0
  33. package/dist/primitives/ModalInput.js +33 -0
  34. package/dist/primitives/Overlay.d.ts +21 -0
  35. package/dist/primitives/Overlay.js +41 -0
  36. package/dist/primitives/Portal.d.ts +28 -0
  37. package/dist/primitives/Portal.js +30 -0
  38. package/dist/primitives/Root.d.ts +116 -0
  39. package/dist/primitives/Root.js +413 -0
  40. package/dist/primitives/Separator.d.ts +19 -0
  41. package/dist/primitives/Separator.js +18 -0
  42. package/dist/primitives/Thumbnail.d.ts +31 -0
  43. package/dist/primitives/Thumbnail.js +59 -0
  44. package/dist/primitives/Trigger.d.ts +28 -0
  45. package/dist/primitives/Trigger.js +35 -0
  46. package/dist/store.d.ts +38 -0
  47. package/dist/store.js +63 -0
  48. package/dist/styles.css +480 -133
  49. package/dist/types.d.ts +3 -31
  50. package/dist/utils/composeRefs.d.ts +12 -0
  51. package/dist/utils/composeRefs.js +27 -0
  52. package/dist/utils/slot.d.ts +22 -0
  53. package/dist/utils/slot.js +58 -0
  54. package/package.json +9 -5
  55. package/dist/Search.d.ts +0 -39
  56. package/dist/Search.js +0 -31
  57. package/dist/components/SearchIcon.d.ts +0 -22
  58. package/dist/components/SearchIcon.js +0 -17
  59. package/dist/components/SearchInput.d.ts +0 -30
  60. package/dist/components/SearchInput.js +0 -59
  61. package/dist/components/SearchKbd.d.ts +0 -30
  62. package/dist/components/SearchKbd.js +0 -24
  63. package/dist/components/SearchResult.d.ts +0 -31
  64. package/dist/components/SearchResult.js +0 -40
  65. package/dist/components/SearchResultThumbnail.d.ts +0 -38
  66. package/dist/components/SearchResultThumbnail.js +0 -38
  67. package/dist/components/SearchResults.d.ts +0 -39
  68. package/dist/components/SearchResults.js +0 -53
  69. package/dist/components/SearchRoot.d.ts +0 -44
  70. package/dist/components/SearchRoot.js +0 -132
  71. package/dist/context/SearchContext.d.ts +0 -55
  72. package/dist/context/SearchContext.js +0 -36
  73. package/dist/hooks/useSearch.d.ts +0 -56
  74. package/dist/hooks/useSearch.js +0 -116
package/dist/types.d.ts CHANGED
@@ -1,33 +1,5 @@
1
1
  /**
2
- * Search result data returned from Étoile API.
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.2.3",
4
- "description": "Official React primitives for Étoile - Headless, composable search components",
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,16 +43,20 @@
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.3.0"
56
+ "@etoile-dev/client": "^0.5.0"
53
57
  },
54
58
  "devDependencies": {
55
59
  "@types/react": "^18.0.0",
56
60
  "typescript": "^5.0.0"
57
61
  }
58
- }
62
+ }
package/dist/Search.d.ts DELETED
@@ -1,39 +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
- /** Debounce delay in milliseconds before triggering search (default: 100) */
11
- debounceMs?: number;
12
- /** Placeholder text for the search input */
13
- placeholder?: string;
14
- /** Additional CSS class name (e.g., "dark" for dark mode) */
15
- className?: string;
16
- /** Custom render function for each result (optional) */
17
- renderResult?: (result: SearchResultData) => React.ReactNode;
18
- baseUrl?: string;
19
- };
20
- /**
21
- * All-in-one search component with sensible defaults.
22
- *
23
- * Provides a complete, polished search experience out of the box including
24
- * search icon, keyboard shortcut badge, and result thumbnails. Just import
25
- * `@etoile-dev/react/styles.css` for styling - no wrapper needed.
26
- *
27
- * @param props - Component props
28
- *
29
- * @example
30
- * ```tsx
31
- * <Search apiKey="your-api-key" collections={["paintings"]} />
32
- * ```
33
- *
34
- * @example Dark mode
35
- * ```tsx
36
- * <Search apiKey="your-api-key" collections={["paintings"]} className="dark" />
37
- * ```
38
- */
39
- export declare const Search: ({ apiKey, collections, limit, debounceMs, 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, debounceMs, placeholder = "Search...", className, renderResult, baseUrl, }) => {
30
- return (_jsxs(SearchRoot, { apiKey: apiKey, collections: collections, limit: limit, debounceMs: debounceMs, 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;