basefn 1.8.0 → 1.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basefn",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/brnrdog/basefn.git"
package/src/Basefn.res CHANGED
@@ -62,6 +62,7 @@ type hoverCardAlign = Basefn__HoverCard.align
62
62
  type alertDialogVariant = Basefn__AlertDialog.variant
63
63
  type contextMenuItem = Basefn__ContextMenu.menuItem
64
64
  type contextMenuContent = Basefn__ContextMenu.menuContent
65
+ type spotlightItem = Basefn__Spotlight.spotlightItem
65
66
  type gridColumns = Basefn__Grid.columns
66
67
  type gridRows = Basefn__Grid.rows
67
68
  type gridAutoFlow = Basefn__Grid.autoFlow
@@ -227,6 +228,9 @@ module AlertDialog = {
227
228
  module ContextMenu = {
228
229
  include Basefn__ContextMenu
229
230
  }
231
+ module Spotlight = {
232
+ include Basefn__Spotlight
233
+ }
230
234
 
231
235
  // Responsive Utilities
232
236
  module Responsive = {
@@ -36,6 +36,7 @@ import * as Basefn__Accordion from "./components/Basefn__Accordion.res.mjs";
36
36
  import * as Basefn__AppLayout from "./components/Basefn__AppLayout.res.mjs";
37
37
  import * as Basefn__HoverCard from "./components/Basefn__HoverCard.res.mjs";
38
38
  import * as Basefn__Separator from "./components/Basefn__Separator.res.mjs";
39
+ import * as Basefn__Spotlight from "./components/Basefn__Spotlight.res.mjs";
39
40
  import * as Basefn__Breadcrumb from "./components/Basefn__Breadcrumb.res.mjs";
40
41
  import * as Basefn__Responsive from "./Basefn__Responsive.res.mjs";
41
42
  import * as Basefn__ScrollArea from "./components/Basefn__ScrollArea.res.mjs";
@@ -140,6 +141,8 @@ let AlertDialog = Basefn__AlertDialog;
140
141
 
141
142
  let ContextMenu = Basefn__ContextMenu;
142
143
 
144
+ let Spotlight = Basefn__Spotlight;
145
+
143
146
  let Responsive = Basefn__Responsive;
144
147
 
145
148
  export {
@@ -188,6 +191,7 @@ export {
188
191
  HoverCard,
189
192
  AlertDialog,
190
193
  ContextMenu,
194
+ Spotlight,
191
195
  Responsive,
192
196
  }
193
197
  /* Not a pure module */
@@ -0,0 +1,207 @@
1
+ @import '../styles/variables.css';
2
+
3
+ .basefn-spotlight-backdrop {
4
+ position: fixed;
5
+ inset: 0;
6
+ background-color: var(--basefn-surface-overlay);
7
+ display: flex;
8
+ align-items: flex-start;
9
+ justify-content: center;
10
+ padding-top: 20vh;
11
+ z-index: 1100;
12
+ animation: basefn-spotlight-fade-in 0.15s ease-out;
13
+ }
14
+
15
+ .basefn-spotlight {
16
+ background: var(--basefn-bg-primary);
17
+ border-radius: var(--basefn-radius-xl);
18
+ box-shadow: var(--basefn-shadow-lg);
19
+ width: 560px;
20
+ max-width: 90vw;
21
+ max-height: 60vh;
22
+ display: flex;
23
+ flex-direction: column;
24
+ overflow: hidden;
25
+ animation: basefn-spotlight-slide-in 0.2s ease-out;
26
+ }
27
+
28
+ /* Search input area */
29
+ .basefn-spotlight__input-wrapper {
30
+ display: flex;
31
+ align-items: center;
32
+ gap: var(--basefn-spacing-md);
33
+ padding: var(--basefn-spacing-md) var(--basefn-spacing-lg);
34
+ border-bottom: 1px solid var(--basefn-border-primary);
35
+ }
36
+
37
+ .basefn-spotlight__icon {
38
+ flex-shrink: 0;
39
+ color: var(--basefn-text-tertiary);
40
+ width: 20px;
41
+ height: 20px;
42
+ }
43
+
44
+ .basefn-spotlight__input {
45
+ flex: 1;
46
+ border: none;
47
+ outline: none;
48
+ background: transparent;
49
+ font-family: var(--basefn-font-family);
50
+ font-size: var(--basefn-font-size-base);
51
+ color: var(--basefn-text-primary);
52
+ line-height: var(--basefn-line-height-normal);
53
+ padding: var(--basefn-spacing-sm) 0;
54
+ }
55
+
56
+ .basefn-spotlight__input::placeholder {
57
+ color: var(--basefn-text-muted);
58
+ }
59
+
60
+ /* Results list */
61
+ .basefn-spotlight__results {
62
+ overflow-y: auto;
63
+ padding: var(--basefn-spacing-sm) 0;
64
+ }
65
+
66
+ .basefn-spotlight__empty {
67
+ padding: var(--basefn-spacing-xl) var(--basefn-spacing-lg);
68
+ text-align: center;
69
+ color: var(--basefn-text-muted);
70
+ font-size: var(--basefn-font-size-sm);
71
+ }
72
+
73
+ .basefn-spotlight__group-label {
74
+ padding: var(--basefn-spacing-sm) var(--basefn-spacing-lg);
75
+ font-size: var(--basefn-font-size-xs);
76
+ font-weight: var(--basefn-font-weight-semibold);
77
+ color: var(--basefn-text-muted);
78
+ text-transform: uppercase;
79
+ letter-spacing: 0.05em;
80
+ }
81
+
82
+ .basefn-spotlight__item {
83
+ display: flex;
84
+ align-items: center;
85
+ gap: var(--basefn-spacing-md);
86
+ width: 100%;
87
+ padding: var(--basefn-spacing-sm) var(--basefn-spacing-lg);
88
+ margin: 0;
89
+ font-family: var(--basefn-font-family);
90
+ font-size: var(--basefn-font-size-sm);
91
+ color: var(--basefn-text-primary);
92
+ background: none;
93
+ border: none;
94
+ cursor: pointer;
95
+ text-align: left;
96
+ transition: background-color var(--basefn-transition-fast);
97
+ border-radius: 0;
98
+ }
99
+
100
+ .basefn-spotlight__item:hover,
101
+ .basefn-spotlight__item--active {
102
+ background-color: var(--basefn-color-primary);
103
+ color: #ffffff;
104
+ }
105
+
106
+ .basefn-spotlight__item-icon {
107
+ flex-shrink: 0;
108
+ width: 16px;
109
+ height: 16px;
110
+ color: var(--basefn-text-tertiary);
111
+ }
112
+
113
+ .basefn-spotlight__item:hover .basefn-spotlight__item-icon,
114
+ .basefn-spotlight__item--active .basefn-spotlight__item-icon {
115
+ color: #ffffff;
116
+ }
117
+
118
+ .basefn-spotlight__item-content {
119
+ flex: 1;
120
+ min-width: 0;
121
+ }
122
+
123
+ .basefn-spotlight__item-label {
124
+ font-weight: var(--basefn-font-weight-medium);
125
+ white-space: nowrap;
126
+ overflow: hidden;
127
+ text-overflow: ellipsis;
128
+ }
129
+
130
+ .basefn-spotlight__item-description {
131
+ font-size: var(--basefn-font-size-xs);
132
+ color: var(--basefn-text-muted);
133
+ white-space: nowrap;
134
+ overflow: hidden;
135
+ text-overflow: ellipsis;
136
+ margin-top: 1px;
137
+ }
138
+
139
+ .basefn-spotlight__item:hover .basefn-spotlight__item-description,
140
+ .basefn-spotlight__item--active .basefn-spotlight__item-description {
141
+ color: rgba(255, 255, 255, 0.7);
142
+ }
143
+
144
+ /* Footer with keyboard hints */
145
+ .basefn-spotlight__footer {
146
+ display: flex;
147
+ align-items: center;
148
+ gap: var(--basefn-spacing-lg);
149
+ padding: var(--basefn-spacing-sm) var(--basefn-spacing-lg);
150
+ border-top: 1px solid var(--basefn-border-primary);
151
+ font-size: var(--basefn-font-size-xs);
152
+ color: var(--basefn-text-muted);
153
+ }
154
+
155
+ .basefn-spotlight__footer-hint {
156
+ display: flex;
157
+ align-items: center;
158
+ gap: var(--basefn-spacing-xs);
159
+ }
160
+
161
+ .basefn-spotlight__footer-key {
162
+ display: inline-flex;
163
+ align-items: center;
164
+ justify-content: center;
165
+ min-width: 1.25rem;
166
+ height: 1.25rem;
167
+ padding: 0 0.25rem;
168
+ border-radius: var(--basefn-radius-sm);
169
+ border: 1px solid var(--basefn-border-secondary);
170
+ background: var(--basefn-bg-secondary);
171
+ font-size: 0.6875rem;
172
+ font-family: var(--basefn-font-family);
173
+ line-height: 1;
174
+ }
175
+
176
+ /* Animations */
177
+ @keyframes basefn-spotlight-fade-in {
178
+ from { opacity: 0; }
179
+ to { opacity: 1; }
180
+ }
181
+
182
+ @keyframes basefn-spotlight-slide-in {
183
+ from {
184
+ opacity: 0;
185
+ transform: scale(0.98) translateY(-8px);
186
+ }
187
+ to {
188
+ opacity: 1;
189
+ transform: scale(1) translateY(0);
190
+ }
191
+ }
192
+
193
+ /* Mobile */
194
+ @media (max-width: 640px) {
195
+ .basefn-spotlight-backdrop {
196
+ padding-top: 10vh;
197
+ }
198
+
199
+ .basefn-spotlight {
200
+ max-width: 95vw;
201
+ max-height: 70vh;
202
+ }
203
+
204
+ .basefn-spotlight__footer {
205
+ display: none;
206
+ }
207
+ }
@@ -0,0 +1,203 @@
1
+ %%raw(`import './Basefn__Spotlight.css'`)
2
+
3
+ open Xote
4
+
5
+ @get external key: Dom.event => string = "key"
6
+ @send external focus: Dom.element => unit = "focus"
7
+ @send external querySelector: (Dom.element, string) => Nullable.t<Dom.element> = "querySelector"
8
+
9
+ type spotlightItem = {
10
+ id: string,
11
+ label: string,
12
+ description?: string,
13
+ group?: string,
14
+ onSelect: unit => unit,
15
+ }
16
+
17
+ @jsx.component
18
+ let make = (
19
+ ~isOpen: Signal.t<bool>,
20
+ ~onClose: unit => unit,
21
+ ~items: array<spotlightItem>,
22
+ ~placeholder: string="Search...",
23
+ ~emptyMessage: string="No results found.",
24
+ ~filterFn: option<(string, spotlightItem) => bool>=?,
25
+ ) => {
26
+ let query = Signal.make("")
27
+ let activeIndex = Signal.make(0)
28
+
29
+ let defaultFilter = (q: string, item: spotlightItem) => {
30
+ let q = String.toLowerCase(q)
31
+ String.toLowerCase(item.label)->String.includes(q) ||
32
+ switch item.description {
33
+ | Some(desc) => String.toLowerCase(desc)->String.includes(q)
34
+ | None => false
35
+ }
36
+ }
37
+
38
+ let filterItem = switch filterFn {
39
+ | Some(fn) => fn
40
+ | None => defaultFilter
41
+ }
42
+
43
+ let filteredItems = Computed.make(() => {
44
+ let q = Signal.get(query)
45
+ if q === "" {
46
+ items
47
+ } else {
48
+ items->Array.filter(item => filterItem(q, item))
49
+ }
50
+ })
51
+
52
+ let handleSelect = (item: spotlightItem) => {
53
+ item.onSelect()
54
+ Signal.set(query, "")
55
+ Signal.set(activeIndex, 0)
56
+ onClose()
57
+ }
58
+
59
+ let handleKeyDown = (evt: Dom.event) => {
60
+ let k = key(evt)
61
+ let currentItems = Signal.get(filteredItems)
62
+ let len = Array.length(currentItems)
63
+
64
+ switch k {
65
+ | "ArrowDown" => {
66
+ let _ = Basefn__Dom.preventDefault(evt)
67
+ Signal.update(activeIndex, i => mod(i + 1, max(len, 1)))
68
+ }
69
+ | "ArrowUp" => {
70
+ let _ = Basefn__Dom.preventDefault(evt)
71
+ Signal.update(activeIndex, i => mod(i - 1 + max(len, 1), max(len, 1)))
72
+ }
73
+ | "Enter" =>
74
+ if len > 0 {
75
+ let idx = Signal.get(activeIndex)
76
+ switch currentItems->Array.get(idx) {
77
+ | Some(item) => handleSelect(item)
78
+ | None => ()
79
+ }
80
+ }
81
+ | "Escape" => {
82
+ Signal.set(query, "")
83
+ Signal.set(activeIndex, 0)
84
+ onClose()
85
+ }
86
+ | _ => ()
87
+ }
88
+ }
89
+
90
+ let handleInput = (evt: Dom.event) => {
91
+ let value = Basefn__Dom.target(evt)["value"]
92
+ Signal.set(query, value)
93
+ Signal.set(activeIndex, 0)
94
+ }
95
+
96
+ let handleBackdropClick = evt => {
97
+ let target = Obj.magic(evt)["target"]
98
+ let currentTarget = Obj.magic(evt)["currentTarget"]
99
+ if target === currentTarget {
100
+ Signal.set(query, "")
101
+ Signal.set(activeIndex, 0)
102
+ onClose()
103
+ }
104
+ }
105
+
106
+ // Auto-focus input when opened using requestAnimationFrame for reliable timing
107
+ let _ = Effect.run(() => {
108
+ if Signal.get(isOpen) {
109
+ let _ = %raw(`requestAnimationFrame(() => {
110
+ requestAnimationFrame(() => {
111
+ const el = document.querySelector(".basefn-spotlight__input");
112
+ if (el) el.focus();
113
+ })
114
+ })`)
115
+ }
116
+ None
117
+ })
118
+
119
+ let renderResults = () => {
120
+ let currentItems = Signal.get(filteredItems)
121
+
122
+ if Array.length(currentItems) === 0 {
123
+ <div class="basefn-spotlight__empty"> {Component.text(emptyMessage)} </div>
124
+ } else {
125
+ let lastGroup: ref<option<string>> = ref(None)
126
+ let elements: array<Component.node> = []
127
+
128
+ currentItems->Array.forEachWithIndex((item, index) => {
129
+ switch item.group {
130
+ | Some(group) if Some(group) !== lastGroup.contents => {
131
+ lastGroup := Some(group)
132
+ let _ = elements->Array.push(
133
+ <div key={"group-" ++ group} class="basefn-spotlight__group-label">
134
+ {Component.text(group)}
135
+ </div>,
136
+ )
137
+ }
138
+ | _ => ()
139
+ }
140
+
141
+ let itemClass =
142
+ "basefn-spotlight__item" ++
143
+ (index === Signal.get(activeIndex) ? " basefn-spotlight__item--active" : "")
144
+
145
+ let _ = elements->Array.push(
146
+ <button key={item.id} class={itemClass} onClick={_ => handleSelect(item)}>
147
+ <div class="basefn-spotlight__item-content">
148
+ <div class="basefn-spotlight__item-label"> {Component.text(item.label)} </div>
149
+ {switch item.description {
150
+ | Some(desc) =>
151
+ <div class="basefn-spotlight__item-description"> {Component.text(desc)} </div>
152
+ | None => <> </>
153
+ }}
154
+ </div>
155
+ </button>,
156
+ )
157
+ })
158
+
159
+ elements->Component.fragment
160
+ }
161
+ }
162
+
163
+ let content = Computed.make(() => {
164
+ if Signal.get(isOpen) {
165
+ [
166
+ <div class="basefn-spotlight-backdrop" onClick={handleBackdropClick}>
167
+ <div class="basefn-spotlight" onKeyDown={handleKeyDown}>
168
+ <div class="basefn-spotlight__input-wrapper">
169
+ <Basefn__Icon name={Basefn__Icon.Search} size={Basefn__Icon.Sm} />
170
+ <input
171
+ class="basefn-spotlight__input"
172
+ type_="text"
173
+ placeholder
174
+ value={ReactiveProp.reactive(query)}
175
+ onInput={handleInput}
176
+ />
177
+ </div>
178
+ <div class="basefn-spotlight__results"> {renderResults()} </div>
179
+ <div class="basefn-spotlight__footer">
180
+ <span class="basefn-spotlight__footer-hint">
181
+ <span class="basefn-spotlight__footer-key"> {Component.text("\u2191")} </span>
182
+ <span class="basefn-spotlight__footer-key"> {Component.text("\u2193")} </span>
183
+ {Component.text("to navigate")}
184
+ </span>
185
+ <span class="basefn-spotlight__footer-hint">
186
+ <span class="basefn-spotlight__footer-key"> {Component.text("\u21b5")} </span>
187
+ {Component.text("to select")}
188
+ </span>
189
+ <span class="basefn-spotlight__footer-hint">
190
+ <span class="basefn-spotlight__footer-key"> {Component.text("esc")} </span>
191
+ {Component.text("to close")}
192
+ </span>
193
+ </div>
194
+ </div>
195
+ </div>,
196
+ ]
197
+ } else {
198
+ []
199
+ }
200
+ })
201
+
202
+ Component.signalFragment(content)
203
+ }
@@ -0,0 +1,231 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Xote from "xote/src/Xote.res.mjs";
4
+ import * as Xote__JSX from "xote/src/Xote__JSX.res.mjs";
5
+ import * as Basefn__Dom from "../Basefn__Dom.res.mjs";
6
+ import * as Basefn__Icon from "./Basefn__Icon.res.mjs";
7
+ import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
8
+
9
+ import './Basefn__Spotlight.css'
10
+ ;
11
+
12
+ function Basefn__Spotlight(props) {
13
+ let filterFn = props.filterFn;
14
+ let __emptyMessage = props.emptyMessage;
15
+ let __placeholder = props.placeholder;
16
+ let items = props.items;
17
+ let onClose = props.onClose;
18
+ let isOpen = props.isOpen;
19
+ let placeholder = __placeholder !== undefined ? __placeholder : "Search...";
20
+ let emptyMessage = __emptyMessage !== undefined ? __emptyMessage : "No results found.";
21
+ let query = Xote.Signal.make("", undefined, undefined);
22
+ let activeIndex = Xote.Signal.make(0, undefined, undefined);
23
+ let defaultFilter = (q, item) => {
24
+ let q$1 = q.toLowerCase();
25
+ if (item.label.toLowerCase().includes(q$1)) {
26
+ return true;
27
+ }
28
+ let desc = item.description;
29
+ if (desc !== undefined) {
30
+ return desc.toLowerCase().includes(q$1);
31
+ } else {
32
+ return false;
33
+ }
34
+ };
35
+ let filterItem = filterFn !== undefined ? filterFn : defaultFilter;
36
+ let filteredItems = Xote.Computed.make(() => {
37
+ let q = Xote.Signal.get(query);
38
+ if (q === "") {
39
+ return items;
40
+ } else {
41
+ return items.filter(item => filterItem(q, item));
42
+ }
43
+ }, undefined);
44
+ let handleSelect = item => {
45
+ item.onSelect();
46
+ Xote.Signal.set(query, "");
47
+ Xote.Signal.set(activeIndex, 0);
48
+ onClose();
49
+ };
50
+ let handleKeyDown = evt => {
51
+ let k = evt.key;
52
+ let currentItems = Xote.Signal.get(filteredItems);
53
+ let len = currentItems.length;
54
+ switch (k) {
55
+ case "ArrowDown" :
56
+ Basefn__Dom.preventDefault(evt);
57
+ return Xote.Signal.update(activeIndex, i => Primitive_int.mod_(i + 1 | 0, Primitive_int.max(len, 1)));
58
+ case "ArrowUp" :
59
+ Basefn__Dom.preventDefault(evt);
60
+ return Xote.Signal.update(activeIndex, i => Primitive_int.mod_((i - 1 | 0) + Primitive_int.max(len, 1) | 0, Primitive_int.max(len, 1)));
61
+ case "Enter" :
62
+ if (len <= 0) {
63
+ return;
64
+ }
65
+ let idx = Xote.Signal.get(activeIndex);
66
+ let item = currentItems[idx];
67
+ if (item !== undefined) {
68
+ return handleSelect(item);
69
+ } else {
70
+ return;
71
+ }
72
+ case "Escape" :
73
+ Xote.Signal.set(query, "");
74
+ Xote.Signal.set(activeIndex, 0);
75
+ return onClose();
76
+ default:
77
+ return;
78
+ }
79
+ };
80
+ let handleInput = evt => {
81
+ let value = Basefn__Dom.target(evt).value;
82
+ Xote.Signal.set(query, value);
83
+ Xote.Signal.set(activeIndex, 0);
84
+ };
85
+ let handleBackdropClick = evt => {
86
+ let target = evt.target;
87
+ let currentTarget = evt.currentTarget;
88
+ if (target === currentTarget) {
89
+ Xote.Signal.set(query, "");
90
+ Xote.Signal.set(activeIndex, 0);
91
+ return onClose();
92
+ }
93
+ };
94
+ Xote.Effect.run(() => {
95
+ if (Xote.Signal.get(isOpen)) {
96
+ ((requestAnimationFrame(() => {
97
+ requestAnimationFrame(() => {
98
+ const el = document.querySelector(".basefn-spotlight__input");
99
+ if (el) el.focus();
100
+ })
101
+ })));
102
+ }
103
+ }, undefined);
104
+ let renderResults = () => {
105
+ let currentItems = Xote.Signal.get(filteredItems);
106
+ if (currentItems.length === 0) {
107
+ return Xote__JSX.Elements.jsx("div", {
108
+ class: "basefn-spotlight__empty",
109
+ children: Xote.Component.text(emptyMessage)
110
+ });
111
+ }
112
+ let lastGroup = {
113
+ contents: undefined
114
+ };
115
+ let elements = [];
116
+ currentItems.forEach((item, index) => {
117
+ let group = item.group;
118
+ if (group !== undefined && group !== lastGroup.contents) {
119
+ lastGroup.contents = group;
120
+ elements.push(Xote__JSX.Elements.jsxKeyed("div", {
121
+ class: "basefn-spotlight__group-label",
122
+ children: Xote.Component.text(group)
123
+ }, "group-" + group, undefined));
124
+ }
125
+ let itemClass = "basefn-spotlight__item" + (
126
+ index === Xote.Signal.get(activeIndex) ? " basefn-spotlight__item--active" : ""
127
+ );
128
+ let desc = item.description;
129
+ elements.push(Xote__JSX.Elements.jsxKeyed("button", {
130
+ class: itemClass,
131
+ onClick: param => handleSelect(item),
132
+ children: Xote__JSX.Elements.jsxs("div", {
133
+ class: "basefn-spotlight__item-content",
134
+ children: Xote__JSX.array([
135
+ Xote__JSX.Elements.jsx("div", {
136
+ class: "basefn-spotlight__item-label",
137
+ children: Xote.Component.text(item.label)
138
+ }),
139
+ desc !== undefined ? Xote__JSX.Elements.jsx("div", {
140
+ class: "basefn-spotlight__item-description",
141
+ children: Xote.Component.text(desc)
142
+ }) : Xote__JSX.jsx(Xote__JSX.jsxFragment, {})
143
+ ])
144
+ })
145
+ }, item.id, undefined));
146
+ });
147
+ return Xote.Component.fragment(elements);
148
+ };
149
+ return Xote.Component.signalFragment(Xote.Computed.make(() => {
150
+ if (Xote.Signal.get(isOpen)) {
151
+ return [Xote__JSX.Elements.jsx("div", {
152
+ class: "basefn-spotlight-backdrop",
153
+ onClick: handleBackdropClick,
154
+ children: Xote__JSX.Elements.jsxs("div", {
155
+ class: "basefn-spotlight",
156
+ onKeyDown: handleKeyDown,
157
+ children: Xote__JSX.array([
158
+ Xote__JSX.Elements.jsxs("div", {
159
+ class: "basefn-spotlight__input-wrapper",
160
+ children: Xote__JSX.array([
161
+ Xote__JSX.jsx(Basefn__Icon.make, {
162
+ name: "Search",
163
+ size: "Sm"
164
+ }),
165
+ Xote__JSX.Elements.jsx("input", {
166
+ class: "basefn-spotlight__input",
167
+ type: "text",
168
+ value: Xote.ReactiveProp.reactive(query),
169
+ placeholder: placeholder,
170
+ onInput: handleInput
171
+ })
172
+ ])
173
+ }),
174
+ Xote__JSX.Elements.jsx("div", {
175
+ class: "basefn-spotlight__results",
176
+ children: renderResults()
177
+ }),
178
+ Xote__JSX.Elements.jsxs("div", {
179
+ class: "basefn-spotlight__footer",
180
+ children: Xote__JSX.array([
181
+ Xote__JSX.Elements.jsxs("span", {
182
+ class: "basefn-spotlight__footer-hint",
183
+ children: Xote__JSX.array([
184
+ Xote__JSX.Elements.jsx("span", {
185
+ class: "basefn-spotlight__footer-key",
186
+ children: Xote.Component.text("\u2191")
187
+ }),
188
+ Xote__JSX.Elements.jsx("span", {
189
+ class: "basefn-spotlight__footer-key",
190
+ children: Xote.Component.text("\u2193")
191
+ }),
192
+ Xote.Component.text("to navigate")
193
+ ])
194
+ }),
195
+ Xote__JSX.Elements.jsxs("span", {
196
+ class: "basefn-spotlight__footer-hint",
197
+ children: Xote__JSX.array([
198
+ Xote__JSX.Elements.jsx("span", {
199
+ class: "basefn-spotlight__footer-key",
200
+ children: Xote.Component.text("\u21b5")
201
+ }),
202
+ Xote.Component.text("to select")
203
+ ])
204
+ }),
205
+ Xote__JSX.Elements.jsxs("span", {
206
+ class: "basefn-spotlight__footer-hint",
207
+ children: Xote__JSX.array([
208
+ Xote__JSX.Elements.jsx("span", {
209
+ class: "basefn-spotlight__footer-key",
210
+ children: Xote.Component.text("esc")
211
+ }),
212
+ Xote.Component.text("to close")
213
+ ])
214
+ })
215
+ ])
216
+ })
217
+ ])
218
+ })
219
+ })];
220
+ } else {
221
+ return [];
222
+ }
223
+ }, undefined));
224
+ }
225
+
226
+ let make = Basefn__Spotlight;
227
+
228
+ export {
229
+ make,
230
+ }
231
+ /* Not a pure module */