@etoile-dev/react 0.2.3 → 1.0.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 +344 -206
- 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 +136 -0
- package/dist/hooks/useEtoileSearch.js +187 -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 +9 -5
- package/dist/Search.d.ts +0 -39
- 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
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { Input } from "./Input.js";
|
|
4
|
+
import { Icon } from "./Icon.js";
|
|
5
|
+
import { Kbd } from "./Kbd.js";
|
|
6
|
+
/**
|
|
7
|
+
* Pre-composed input row for command palette mode.
|
|
8
|
+
*
|
|
9
|
+
* Renders a search icon, combobox input, and optional keyboard badge in a
|
|
10
|
+
* flex row. A separator border appears automatically when results are
|
|
11
|
+
* visible below (via `:has(+ [role="listbox"])`).
|
|
12
|
+
*
|
|
13
|
+
* Styled via `data-slot="searchbar-modal-input"` — fully independent from
|
|
14
|
+
* the inline `Searchbar` input wrapper.
|
|
15
|
+
*
|
|
16
|
+
* @example Basic usage
|
|
17
|
+
* ```tsx
|
|
18
|
+
* <Searchbar.Modal hotkey="mod+k">
|
|
19
|
+
* <Searchbar.ModalInput />
|
|
20
|
+
* <Searchbar.List>…</Searchbar.List>
|
|
21
|
+
* </Searchbar.Modal>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @example Custom placeholder, no kbd badge
|
|
25
|
+
* ```tsx
|
|
26
|
+
* <Searchbar.Modal>
|
|
27
|
+
* <Searchbar.ModalInput placeholder="Search paintings…" icon={null} kbd={null} />
|
|
28
|
+
* <Searchbar.List>…</Searchbar.List>
|
|
29
|
+
* </Searchbar.Modal>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const ModalInput = React.forwardRef(({ placeholder = "Search…", icon = _jsx(Icon, {}), kbd = "Esc", className, ...props }, ref) => (_jsxs("div", { ...props, ref: ref, "data-slot": "searchbar-modal-input", className: className, children: [icon, _jsx(Input, { autoFocus: true, placeholder: placeholder }), kbd !== null && _jsx(Kbd, { children: kbd })] })));
|
|
33
|
+
ModalInput.displayName = "Searchbar.ModalInput";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type SearchbarOverlayProps = {
|
|
3
|
+
className?: string;
|
|
4
|
+
asChild?: boolean;
|
|
5
|
+
} & React.HTMLAttributes<HTMLDivElement>;
|
|
6
|
+
/**
|
|
7
|
+
* Backdrop overlay for command palette / modal mode.
|
|
8
|
+
* Only renders when the search is open.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <Searchbar.Portal>
|
|
13
|
+
* <Searchbar.Overlay className="fixed inset-0 bg-black/40" />
|
|
14
|
+
* <Searchbar.Content>…</Searchbar.Content>
|
|
15
|
+
* </Searchbar.Portal>
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare const Overlay: React.ForwardRefExoticComponent<{
|
|
19
|
+
className?: string;
|
|
20
|
+
asChild?: boolean;
|
|
21
|
+
} & React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useSearchbarContext, useSearchbarStore } from "../context.js";
|
|
4
|
+
import { Slot } from "../utils/slot.js";
|
|
5
|
+
const PRESENCE_DURATION_MS = 300;
|
|
6
|
+
/**
|
|
7
|
+
* Backdrop overlay for command palette / modal mode.
|
|
8
|
+
* Only renders when the search is open.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <Searchbar.Portal>
|
|
13
|
+
* <Searchbar.Overlay className="fixed inset-0 bg-black/40" />
|
|
14
|
+
* <Searchbar.Content>…</Searchbar.Content>
|
|
15
|
+
* </Searchbar.Portal>
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export const Overlay = React.forwardRef(({ className, asChild = false, ...props }, forwardedRef) => {
|
|
19
|
+
const { store, rootId, rootClassName, setOpen } = useSearchbarContext();
|
|
20
|
+
const isOpen = useSearchbarStore(store, (s) => s.open);
|
|
21
|
+
const [present, setPresent] = React.useState(isOpen);
|
|
22
|
+
const mergedClassName = [rootClassName, className].filter(Boolean).join(" ") || undefined;
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
if (isOpen) {
|
|
25
|
+
setPresent(true);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const timeout = window.setTimeout(() => setPresent(false), PRESENCE_DURATION_MS);
|
|
29
|
+
return () => window.clearTimeout(timeout);
|
|
30
|
+
}, [isOpen]);
|
|
31
|
+
if (!present)
|
|
32
|
+
return null;
|
|
33
|
+
const Comp = asChild ? Slot : "div";
|
|
34
|
+
return (_jsx(Comp, { ...props, ref: forwardedRef, "aria-hidden": "true", className: mergedClassName, "data-state": isOpen ? "open" : "closed", "data-slot": "searchbar-overlay", "data-searchbar-root": rootId, onPointerDown: (event) => {
|
|
35
|
+
props.onPointerDown?.(event);
|
|
36
|
+
if (event.defaultPrevented)
|
|
37
|
+
return;
|
|
38
|
+
setOpen(false);
|
|
39
|
+
} }));
|
|
40
|
+
});
|
|
41
|
+
Overlay.displayName = "Searchbar.Overlay";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type SearchbarPortalProps = {
|
|
3
|
+
/** DOM node to portal into (default: document.body) */
|
|
4
|
+
container?: Element | null;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Renders children into a portal — useful for command palette / modal mode
|
|
9
|
+
* to escape stacking contexts.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <Searchbar.Root>
|
|
14
|
+
* <Searchbar.Trigger />
|
|
15
|
+
* <Searchbar.Portal>
|
|
16
|
+
* <Searchbar.Overlay />
|
|
17
|
+
* <Searchbar.Content>
|
|
18
|
+
* <Searchbar.Input />
|
|
19
|
+
* <Searchbar.List>…</Searchbar.List>
|
|
20
|
+
* </Searchbar.Content>
|
|
21
|
+
* </Searchbar.Portal>
|
|
22
|
+
* </Searchbar.Root>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare const Portal: {
|
|
26
|
+
({ container, children }: SearchbarPortalProps): React.ReactPortal | null;
|
|
27
|
+
displayName: string;
|
|
28
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
/**
|
|
4
|
+
* Renders children into a portal — useful for command palette / modal mode
|
|
5
|
+
* to escape stacking contexts.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* <Searchbar.Root>
|
|
10
|
+
* <Searchbar.Trigger />
|
|
11
|
+
* <Searchbar.Portal>
|
|
12
|
+
* <Searchbar.Overlay />
|
|
13
|
+
* <Searchbar.Content>
|
|
14
|
+
* <Searchbar.Input />
|
|
15
|
+
* <Searchbar.List>…</Searchbar.List>
|
|
16
|
+
* </Searchbar.Content>
|
|
17
|
+
* </Searchbar.Portal>
|
|
18
|
+
* </Searchbar.Root>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export const Portal = ({ container, children }) => {
|
|
22
|
+
const [mounted, setMounted] = React.useState(false);
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
setMounted(true);
|
|
25
|
+
}, []);
|
|
26
|
+
if (!mounted)
|
|
27
|
+
return null;
|
|
28
|
+
return createPortal(children, container ?? document.body);
|
|
29
|
+
};
|
|
30
|
+
Portal.displayName = "Searchbar.Portal";
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
export type SearchbarRootProps = {
|
|
3
|
+
/** Controlled open state */
|
|
4
|
+
open?: boolean;
|
|
5
|
+
defaultOpen?: boolean;
|
|
6
|
+
onOpenChange?: (open: boolean) => void;
|
|
7
|
+
/** Controlled search query */
|
|
8
|
+
search?: string;
|
|
9
|
+
defaultSearch?: string;
|
|
10
|
+
onSearchChange?: (search: string) => void;
|
|
11
|
+
/** Controlled selected item value */
|
|
12
|
+
value?: string | null;
|
|
13
|
+
defaultValue?: string | null;
|
|
14
|
+
onValueChange?: (value: string | null) => void;
|
|
15
|
+
/** Whether a search is currently in progress */
|
|
16
|
+
isLoading?: boolean;
|
|
17
|
+
/** Current error, if any */
|
|
18
|
+
error?: unknown;
|
|
19
|
+
/**
|
|
20
|
+
* Global keyboard shortcut.
|
|
21
|
+
* Use `"mod+k"` for ⌘K on Mac / Ctrl+K elsewhere, or `"/"` for the widely used search shortcut.
|
|
22
|
+
* Supports: `mod`, `ctrl`, `shift`, `alt` modifiers + any key (e.g. `"mod+k"`, `"/"`).
|
|
23
|
+
*
|
|
24
|
+
* @example `hotkey="mod+k"` — command palette (toggle modal)
|
|
25
|
+
* @example `hotkey="/"` with `hotkeyBehavior="focus"` — focus input (inline searchbar)
|
|
26
|
+
*/
|
|
27
|
+
hotkey?: string;
|
|
28
|
+
/**
|
|
29
|
+
* What the hotkey does: `"toggle"` (default) opens/closes the modal; `"focus"` focuses the input.
|
|
30
|
+
* Use `"focus"` for inline Searchbar so the hotkey focuses the input instead of toggling.
|
|
31
|
+
*/
|
|
32
|
+
hotkeyBehavior?: "focus" | "toggle";
|
|
33
|
+
/** Called when an item is selected. Receives the item's `value`. */
|
|
34
|
+
onSelect?: (value: string) => void;
|
|
35
|
+
children: React.ReactNode;
|
|
36
|
+
className?: string;
|
|
37
|
+
asChild?: boolean;
|
|
38
|
+
} & Omit<React.HTMLAttributes<HTMLDivElement>, "onSelect">;
|
|
39
|
+
/**
|
|
40
|
+
* Root of the Searchbar component tree. Manages all search state and provides
|
|
41
|
+
* it to child primitives via an external store (`useSyncExternalStore`).
|
|
42
|
+
*
|
|
43
|
+
* Supports fully controlled, fully uncontrolled, and mixed modes for `open`,
|
|
44
|
+
* `search`, and `value`. Handles keyboard navigation, selection, escape
|
|
45
|
+
* behavior, outside click close, and portal-aware focus boundaries.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* <Searchbar.Root onSelect={(value) => console.log(value)}>
|
|
50
|
+
* <Searchbar.Input />
|
|
51
|
+
* <Searchbar.List>
|
|
52
|
+
* {items.map((item) => (
|
|
53
|
+
* <Searchbar.Item key={item.id} value={item.id}>{item.title}</Searchbar.Item>
|
|
54
|
+
* ))}
|
|
55
|
+
* <Searchbar.Empty>No results</Searchbar.Empty>
|
|
56
|
+
* </Searchbar.List>
|
|
57
|
+
* </Searchbar.Root>
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @example Command palette
|
|
61
|
+
* ```tsx
|
|
62
|
+
* <Searchbar.Root hotkey="mod+k" className="etoile-search">
|
|
63
|
+
* <Searchbar.Trigger>
|
|
64
|
+
* <Searchbar.Icon />
|
|
65
|
+
* Search paintings…
|
|
66
|
+
* <Searchbar.Kbd />
|
|
67
|
+
* </Searchbar.Trigger>
|
|
68
|
+
* <Searchbar.Portal>
|
|
69
|
+
* <Searchbar.Overlay />
|
|
70
|
+
* <Searchbar.Content aria-label="Search paintings">
|
|
71
|
+
* <Searchbar.Input />
|
|
72
|
+
* <Searchbar.List>
|
|
73
|
+
* <Searchbar.Item value="starry-night">The Starry Night</Searchbar.Item>
|
|
74
|
+
* </Searchbar.List>
|
|
75
|
+
* </Searchbar.Content>
|
|
76
|
+
* </Searchbar.Portal>
|
|
77
|
+
* </Searchbar.Root>
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare const Root: React.ForwardRefExoticComponent<{
|
|
81
|
+
/** Controlled open state */
|
|
82
|
+
open?: boolean;
|
|
83
|
+
defaultOpen?: boolean;
|
|
84
|
+
onOpenChange?: (open: boolean) => void;
|
|
85
|
+
/** Controlled search query */
|
|
86
|
+
search?: string;
|
|
87
|
+
defaultSearch?: string;
|
|
88
|
+
onSearchChange?: (search: string) => void;
|
|
89
|
+
/** Controlled selected item value */
|
|
90
|
+
value?: string | null;
|
|
91
|
+
defaultValue?: string | null;
|
|
92
|
+
onValueChange?: (value: string | null) => void;
|
|
93
|
+
/** Whether a search is currently in progress */
|
|
94
|
+
isLoading?: boolean;
|
|
95
|
+
/** Current error, if any */
|
|
96
|
+
error?: unknown;
|
|
97
|
+
/**
|
|
98
|
+
* Global keyboard shortcut.
|
|
99
|
+
* Use `"mod+k"` for ⌘K on Mac / Ctrl+K elsewhere, or `"/"` for the widely used search shortcut.
|
|
100
|
+
* Supports: `mod`, `ctrl`, `shift`, `alt` modifiers + any key (e.g. `"mod+k"`, `"/"`).
|
|
101
|
+
*
|
|
102
|
+
* @example `hotkey="mod+k"` — command palette (toggle modal)
|
|
103
|
+
* @example `hotkey="/"` with `hotkeyBehavior="focus"` — focus input (inline searchbar)
|
|
104
|
+
*/
|
|
105
|
+
hotkey?: string;
|
|
106
|
+
/**
|
|
107
|
+
* What the hotkey does: `"toggle"` (default) opens/closes the modal; `"focus"` focuses the input.
|
|
108
|
+
* Use `"focus"` for inline Searchbar so the hotkey focuses the input instead of toggling.
|
|
109
|
+
*/
|
|
110
|
+
hotkeyBehavior?: "focus" | "toggle";
|
|
111
|
+
/** Called when an item is selected. Receives the item's `value`. */
|
|
112
|
+
onSelect?: (value: string) => void;
|
|
113
|
+
children: React.ReactNode;
|
|
114
|
+
className?: string;
|
|
115
|
+
asChild?: boolean;
|
|
116
|
+
} & Omit<React.HTMLAttributes<HTMLDivElement>, "onSelect"> & React.RefAttributes<HTMLDivElement>>;
|