@shipfox/react-ui 0.30.0 → 0.31.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/dist/components/code-block/code-content.js +5 -2
- package/dist/components/dashboard/components/kpi-card.js +2 -2
- package/dist/components/shipql-editor/index.d.ts +1 -1
- package/dist/components/shipql-editor/index.js +1 -1
- package/dist/components/shipql-editor/lexical/leaf-close-overlay.js +65 -2
- package/dist/components/shipql-editor/lexical/shipql-plugin.js +10 -1
- package/dist/components/shipql-editor/shipql-editor-inner.d.ts +1 -1
- package/dist/components/shipql-editor/shipql-editor-inner.js +148 -41
- package/dist/components/shipql-editor/shipql-editor.d.ts +12 -0
- package/dist/components/shipql-editor/shipql-editor.js +1 -1
- package/dist/components/shipql-editor/suggestions/generate-suggestions.d.ts +22 -0
- package/dist/components/shipql-editor/suggestions/generate-suggestions.js +170 -0
- package/dist/components/shipql-editor/suggestions/shipql-range-facet-panel.d.ts +9 -0
- package/dist/components/shipql-editor/suggestions/shipql-range-facet-panel.js +376 -0
- package/dist/components/shipql-editor/suggestions/shipql-suggestion-item.d.ts +11 -0
- package/dist/components/shipql-editor/suggestions/shipql-suggestion-item.js +40 -0
- package/dist/components/shipql-editor/suggestions/shipql-suggestions-dropdown.d.ts +19 -0
- package/dist/components/shipql-editor/suggestions/shipql-suggestions-dropdown.js +128 -0
- package/dist/components/shipql-editor/suggestions/shipql-suggestions-footer.d.ts +11 -0
- package/dist/components/shipql-editor/suggestions/shipql-suggestions-footer.js +123 -0
- package/dist/components/shipql-editor/suggestions/shipql-suggestions-plugin.d.ts +27 -0
- package/dist/components/shipql-editor/suggestions/shipql-suggestions-plugin.js +407 -0
- package/dist/components/shipql-editor/suggestions/types.d.ts +20 -0
- package/dist/components/shipql-editor/suggestions/types.js +3 -0
- package/dist/styles.css +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '../../../components/button/index.js';
|
|
3
|
+
import { Icon } from '../../../components/icon/index.js';
|
|
4
|
+
import { Slider } from '../../../components/slider/index.js';
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
|
+
import { cn } from '../../../utils/cn.js';
|
|
7
|
+
const RECENT_MAX = 5;
|
|
8
|
+
const INPUT_CLASSES = 'w-40 shrink-0 rounded-4 border border-border-neutral-base-component bg-background-field-base shadow-button-neutral transition-[color,box-shadow] outline-none px-4 py-2 text-center text-xs text-foreground-neutral-base focus-visible:shadow-border-interactive-with-active [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none';
|
|
9
|
+
function getRecentKey(facetName) {
|
|
10
|
+
return `shipql-range-recent-${facetName}`;
|
|
11
|
+
}
|
|
12
|
+
function loadRecent(facetName) {
|
|
13
|
+
try {
|
|
14
|
+
const raw = localStorage.getItem(getRecentKey(facetName));
|
|
15
|
+
if (!raw) return [];
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function saveRecent(facetName, value) {
|
|
22
|
+
const existing = loadRecent(facetName).filter((v)=>v !== value);
|
|
23
|
+
const next = [
|
|
24
|
+
value,
|
|
25
|
+
...existing
|
|
26
|
+
].slice(0, RECENT_MAX);
|
|
27
|
+
try {
|
|
28
|
+
localStorage.setItem(getRecentKey(facetName), JSON.stringify(next));
|
|
29
|
+
} catch {
|
|
30
|
+
// ignore storage errors
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function clearRecent(facetName) {
|
|
34
|
+
try {
|
|
35
|
+
localStorage.removeItem(getRecentKey(facetName));
|
|
36
|
+
} catch {
|
|
37
|
+
// ignore storage errors
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function PresetRow({ value, onClick, isRecent, isHighlighted, rowRef }) {
|
|
41
|
+
return /*#__PURE__*/ _jsxs("button", {
|
|
42
|
+
ref: rowRef,
|
|
43
|
+
type: "button",
|
|
44
|
+
className: cn('flex w-full items-center gap-12 rounded-none px-8 py-6 h-28 text-left transition-colors duration-75 cursor-pointer', isHighlighted ? 'bg-background-button-transparent-hover' : 'hover:bg-background-button-transparent-hover'),
|
|
45
|
+
onMouseDown: (e)=>{
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
onClick(value);
|
|
48
|
+
},
|
|
49
|
+
children: [
|
|
50
|
+
/*#__PURE__*/ _jsx(Icon, {
|
|
51
|
+
name: isRecent ? 'timeLine' : 'arrowRightLongFill',
|
|
52
|
+
className: "size-16 shrink-0 text-foreground-neutral-subtle"
|
|
53
|
+
}),
|
|
54
|
+
/*#__PURE__*/ _jsx("span", {
|
|
55
|
+
className: "flex-1 truncate text-sm text-foreground-neutral-subtle",
|
|
56
|
+
children: value
|
|
57
|
+
})
|
|
58
|
+
]
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export function ShipQLRangeFacetPanel({ facetName, config, onApply, isSelectingRef }) {
|
|
62
|
+
const absMin = Number(config.min);
|
|
63
|
+
const absMax = Number(config.max);
|
|
64
|
+
const [sliderValues, setSliderValues] = useState([
|
|
65
|
+
absMin,
|
|
66
|
+
absMax
|
|
67
|
+
]);
|
|
68
|
+
const [recentValues, setRecentValues] = useState(()=>loadRecent(facetName));
|
|
69
|
+
const [selectedPresetIndex, setSelectedPresetIndex] = useState(-1);
|
|
70
|
+
const selectedPresetIndexRef = useRef(-1);
|
|
71
|
+
selectedPresetIndexRef.current = selectedPresetIndex;
|
|
72
|
+
const presetRowRefs = useRef([]);
|
|
73
|
+
// Local string state for inputs so mid-edit typing isn't clamped
|
|
74
|
+
const [minText, setMinText] = useState(String(absMin));
|
|
75
|
+
const [maxText, setMaxText] = useState(String(absMax));
|
|
76
|
+
const [lo, hi] = sliderValues;
|
|
77
|
+
// Keep input text in sync when slider moves
|
|
78
|
+
useEffect(()=>{
|
|
79
|
+
setMinText(String(lo));
|
|
80
|
+
}, [
|
|
81
|
+
lo
|
|
82
|
+
]);
|
|
83
|
+
useEffect(()=>{
|
|
84
|
+
setMaxText(String(hi));
|
|
85
|
+
}, [
|
|
86
|
+
hi
|
|
87
|
+
]);
|
|
88
|
+
// Hold dropdown open for any pointer interaction inside the panel
|
|
89
|
+
const panelRef = useRef(null);
|
|
90
|
+
useEffect(()=>{
|
|
91
|
+
const el = panelRef.current;
|
|
92
|
+
if (!el) return;
|
|
93
|
+
const onDown = ()=>{
|
|
94
|
+
isSelectingRef.current = true;
|
|
95
|
+
};
|
|
96
|
+
const onUp = ()=>{
|
|
97
|
+
// Small delay so the blur handler fires first and sees isSelectingRef=true
|
|
98
|
+
setTimeout(()=>{
|
|
99
|
+
isSelectingRef.current = false;
|
|
100
|
+
}, 150);
|
|
101
|
+
};
|
|
102
|
+
el.addEventListener('pointerdown', onDown);
|
|
103
|
+
window.addEventListener('pointerup', onUp);
|
|
104
|
+
return ()=>{
|
|
105
|
+
el.removeEventListener('pointerdown', onDown);
|
|
106
|
+
window.removeEventListener('pointerup', onUp);
|
|
107
|
+
};
|
|
108
|
+
}, [
|
|
109
|
+
isSelectingRef
|
|
110
|
+
]);
|
|
111
|
+
const addLabel = useMemo(()=>{
|
|
112
|
+
if (lo === absMin && hi === absMax) return `Add ">=${lo},<=${hi}"`;
|
|
113
|
+
if (lo === absMin) return `Add "<=${hi}"`;
|
|
114
|
+
if (hi === absMax) return `Add ">=${lo}"`;
|
|
115
|
+
return `Add ">=${lo},<=${hi}"`;
|
|
116
|
+
}, [
|
|
117
|
+
lo,
|
|
118
|
+
hi,
|
|
119
|
+
absMin,
|
|
120
|
+
absMax
|
|
121
|
+
]);
|
|
122
|
+
const buildValue = useCallback(()=>{
|
|
123
|
+
if (lo === absMin && hi === absMax) return `[${lo} TO ${hi}]`;
|
|
124
|
+
if (lo === absMin) return `<=${hi}`;
|
|
125
|
+
if (hi === absMax) return `>=${lo}`;
|
|
126
|
+
return `[${lo} TO ${hi}]`;
|
|
127
|
+
}, [
|
|
128
|
+
lo,
|
|
129
|
+
hi,
|
|
130
|
+
absMin,
|
|
131
|
+
absMax
|
|
132
|
+
]);
|
|
133
|
+
const handleApplyRange = useCallback(()=>{
|
|
134
|
+
const value = buildValue();
|
|
135
|
+
saveRecent(facetName, value);
|
|
136
|
+
setRecentValues(loadRecent(facetName));
|
|
137
|
+
onApply(value);
|
|
138
|
+
}, [
|
|
139
|
+
buildValue,
|
|
140
|
+
facetName,
|
|
141
|
+
onApply
|
|
142
|
+
]);
|
|
143
|
+
const handlePreset = useCallback((preset)=>{
|
|
144
|
+
saveRecent(facetName, preset);
|
|
145
|
+
setRecentValues(loadRecent(facetName));
|
|
146
|
+
onApply(preset);
|
|
147
|
+
}, [
|
|
148
|
+
facetName,
|
|
149
|
+
onApply
|
|
150
|
+
]);
|
|
151
|
+
const handleClearRecent = useCallback((e)=>{
|
|
152
|
+
e.preventDefault();
|
|
153
|
+
clearRecent(facetName);
|
|
154
|
+
setRecentValues([]);
|
|
155
|
+
}, [
|
|
156
|
+
facetName
|
|
157
|
+
]);
|
|
158
|
+
// Build flat list of navigable preset values (recent + common)
|
|
159
|
+
const navigableItems = useMemo(()=>{
|
|
160
|
+
const items = [];
|
|
161
|
+
for (const v of recentValues.slice(0, 3))items.push(v);
|
|
162
|
+
if (config.presets) {
|
|
163
|
+
for (const v of config.presets)items.push(v);
|
|
164
|
+
}
|
|
165
|
+
return items;
|
|
166
|
+
}, [
|
|
167
|
+
recentValues,
|
|
168
|
+
config.presets
|
|
169
|
+
]);
|
|
170
|
+
// Keyboard navigation for preset/recent items (capture phase to fire before Lexical)
|
|
171
|
+
useEffect(()=>{
|
|
172
|
+
if (navigableItems.length === 0) return;
|
|
173
|
+
const handleKeyDown = (e)=>{
|
|
174
|
+
// Don't intercept when user is editing min/max inputs
|
|
175
|
+
if (document.activeElement instanceof HTMLInputElement) return;
|
|
176
|
+
if (e.key === 'ArrowDown') {
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
e.stopPropagation();
|
|
179
|
+
setSelectedPresetIndex((prev)=>prev + 1 >= navigableItems.length ? 0 : prev + 1);
|
|
180
|
+
} else if (e.key === 'ArrowUp') {
|
|
181
|
+
e.preventDefault();
|
|
182
|
+
e.stopPropagation();
|
|
183
|
+
setSelectedPresetIndex((prev)=>prev - 1 < 0 ? navigableItems.length - 1 : prev - 1);
|
|
184
|
+
} else if (e.key === 'Enter') {
|
|
185
|
+
const idx = selectedPresetIndexRef.current;
|
|
186
|
+
if (idx < 0) return;
|
|
187
|
+
e.preventDefault();
|
|
188
|
+
e.stopPropagation();
|
|
189
|
+
const value = navigableItems[idx];
|
|
190
|
+
if (value) handlePreset(value);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
window.addEventListener('keydown', handleKeyDown, true);
|
|
194
|
+
return ()=>window.removeEventListener('keydown', handleKeyDown, true);
|
|
195
|
+
}, [
|
|
196
|
+
navigableItems,
|
|
197
|
+
handlePreset
|
|
198
|
+
]);
|
|
199
|
+
// Scroll selected preset into view
|
|
200
|
+
useEffect(()=>{
|
|
201
|
+
const el = presetRowRefs.current[selectedPresetIndex];
|
|
202
|
+
if (el) el.scrollIntoView({
|
|
203
|
+
behavior: 'smooth',
|
|
204
|
+
block: 'nearest'
|
|
205
|
+
});
|
|
206
|
+
}, [
|
|
207
|
+
selectedPresetIndex
|
|
208
|
+
]);
|
|
209
|
+
// Commit min input on blur or Enter
|
|
210
|
+
const commitMin = useCallback(()=>{
|
|
211
|
+
const v = Number(minText);
|
|
212
|
+
if (!Number.isNaN(v)) {
|
|
213
|
+
const clamped = Math.min(Math.max(v, absMin), hi);
|
|
214
|
+
setSliderValues([
|
|
215
|
+
clamped,
|
|
216
|
+
hi
|
|
217
|
+
]);
|
|
218
|
+
setMinText(String(clamped));
|
|
219
|
+
} else {
|
|
220
|
+
setMinText(String(lo));
|
|
221
|
+
}
|
|
222
|
+
}, [
|
|
223
|
+
minText,
|
|
224
|
+
absMin,
|
|
225
|
+
hi,
|
|
226
|
+
lo
|
|
227
|
+
]);
|
|
228
|
+
// Commit max input on blur or Enter
|
|
229
|
+
const commitMax = useCallback(()=>{
|
|
230
|
+
const v = Number(maxText);
|
|
231
|
+
if (!Number.isNaN(v)) {
|
|
232
|
+
const clamped = Math.max(Math.min(v, absMax), lo);
|
|
233
|
+
setSliderValues([
|
|
234
|
+
lo,
|
|
235
|
+
clamped
|
|
236
|
+
]);
|
|
237
|
+
setMaxText(String(clamped));
|
|
238
|
+
} else {
|
|
239
|
+
setMaxText(String(hi));
|
|
240
|
+
}
|
|
241
|
+
}, [
|
|
242
|
+
maxText,
|
|
243
|
+
absMax,
|
|
244
|
+
lo,
|
|
245
|
+
hi
|
|
246
|
+
]);
|
|
247
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
248
|
+
ref: panelRef,
|
|
249
|
+
className: "flex flex-col",
|
|
250
|
+
children: [
|
|
251
|
+
/*#__PURE__*/ _jsx("div", {
|
|
252
|
+
className: "flex w-full items-center px-8 h-30 shrink-0 border-b border-border-neutral-base-component",
|
|
253
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
254
|
+
className: "text-xs font-normal uppercase text-foreground-neutral-muted",
|
|
255
|
+
children: facetName
|
|
256
|
+
})
|
|
257
|
+
}),
|
|
258
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
259
|
+
className: "p-8 space-y-2",
|
|
260
|
+
children: [
|
|
261
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
262
|
+
className: "flex items-center gap-12",
|
|
263
|
+
children: [
|
|
264
|
+
/*#__PURE__*/ _jsx("input", {
|
|
265
|
+
type: "number",
|
|
266
|
+
value: minText,
|
|
267
|
+
onChange: (e)=>setMinText(e.target.value),
|
|
268
|
+
onBlur: commitMin,
|
|
269
|
+
onKeyDown: (e)=>{
|
|
270
|
+
if (e.key === 'Enter') commitMin();
|
|
271
|
+
},
|
|
272
|
+
className: INPUT_CLASSES
|
|
273
|
+
}),
|
|
274
|
+
/*#__PURE__*/ _jsx(Slider, {
|
|
275
|
+
className: "flex-1",
|
|
276
|
+
min: absMin,
|
|
277
|
+
max: absMax,
|
|
278
|
+
value: sliderValues,
|
|
279
|
+
onValueChange: (vals)=>{
|
|
280
|
+
const newLo = vals[0];
|
|
281
|
+
const newHi = vals[1];
|
|
282
|
+
if (newLo !== undefined && newHi !== undefined) {
|
|
283
|
+
setSliderValues([
|
|
284
|
+
newLo,
|
|
285
|
+
newHi
|
|
286
|
+
]);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}),
|
|
290
|
+
/*#__PURE__*/ _jsx("input", {
|
|
291
|
+
type: "number",
|
|
292
|
+
value: maxText,
|
|
293
|
+
onChange: (e)=>setMaxText(e.target.value),
|
|
294
|
+
onBlur: commitMax,
|
|
295
|
+
onKeyDown: (e)=>{
|
|
296
|
+
if (e.key === 'Enter') commitMax();
|
|
297
|
+
},
|
|
298
|
+
className: INPUT_CLASSES
|
|
299
|
+
})
|
|
300
|
+
]
|
|
301
|
+
}),
|
|
302
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
303
|
+
type: "button",
|
|
304
|
+
onMouseDown: (e)=>{
|
|
305
|
+
e.preventDefault();
|
|
306
|
+
handleApplyRange();
|
|
307
|
+
},
|
|
308
|
+
variant: "secondary",
|
|
309
|
+
size: "sm",
|
|
310
|
+
className: "mt-6 w-full justify-start shadow-none rounded-4!",
|
|
311
|
+
iconLeft: "addLine",
|
|
312
|
+
children: addLabel
|
|
313
|
+
})
|
|
314
|
+
]
|
|
315
|
+
}),
|
|
316
|
+
(recentValues.length > 0 || config.presets && config.presets.length > 0) && /*#__PURE__*/ _jsxs("div", {
|
|
317
|
+
className: "border-t border-border-neutral-base-component",
|
|
318
|
+
children: [
|
|
319
|
+
recentValues.length > 0 && /*#__PURE__*/ _jsxs(_Fragment, {
|
|
320
|
+
children: [
|
|
321
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
322
|
+
className: "flex w-full items-center px-8 h-30 shrink-0",
|
|
323
|
+
children: [
|
|
324
|
+
/*#__PURE__*/ _jsx("span", {
|
|
325
|
+
className: "flex-1 text-xs font-normal uppercase text-foreground-neutral-muted",
|
|
326
|
+
children: "Recent"
|
|
327
|
+
}),
|
|
328
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
329
|
+
type: "button",
|
|
330
|
+
variant: "transparentMuted",
|
|
331
|
+
size: "xs",
|
|
332
|
+
onMouseDown: handleClearRecent,
|
|
333
|
+
children: "Clear"
|
|
334
|
+
})
|
|
335
|
+
]
|
|
336
|
+
}),
|
|
337
|
+
recentValues.slice(0, 3).map((v, i)=>/*#__PURE__*/ _jsx(PresetRow, {
|
|
338
|
+
value: v,
|
|
339
|
+
onClick: handlePreset,
|
|
340
|
+
isRecent: true,
|
|
341
|
+
isHighlighted: selectedPresetIndex === i,
|
|
342
|
+
rowRef: (el)=>{
|
|
343
|
+
presetRowRefs.current[i] = el;
|
|
344
|
+
}
|
|
345
|
+
}, v))
|
|
346
|
+
]
|
|
347
|
+
}),
|
|
348
|
+
config.presets && config.presets.length > 0 && /*#__PURE__*/ _jsxs(_Fragment, {
|
|
349
|
+
children: [
|
|
350
|
+
/*#__PURE__*/ _jsx("div", {
|
|
351
|
+
className: "flex w-full items-center px-8 h-30 shrink-0",
|
|
352
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
353
|
+
className: "text-xs font-normal uppercase text-foreground-neutral-muted",
|
|
354
|
+
children: "Common"
|
|
355
|
+
})
|
|
356
|
+
}),
|
|
357
|
+
config.presets.map((v, i)=>{
|
|
358
|
+
const idx = recentValues.slice(0, 3).length + i;
|
|
359
|
+
return /*#__PURE__*/ _jsx(PresetRow, {
|
|
360
|
+
value: v,
|
|
361
|
+
onClick: handlePreset,
|
|
362
|
+
isHighlighted: selectedPresetIndex === idx,
|
|
363
|
+
rowRef: (el)=>{
|
|
364
|
+
presetRowRefs.current[idx] = el;
|
|
365
|
+
}
|
|
366
|
+
}, v);
|
|
367
|
+
})
|
|
368
|
+
]
|
|
369
|
+
})
|
|
370
|
+
]
|
|
371
|
+
})
|
|
372
|
+
]
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
//# sourceMappingURL=shipql-range-facet-panel.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SuggestionItem } from './types';
|
|
2
|
+
interface ShipQLSuggestionItemProps {
|
|
3
|
+
item: SuggestionItem;
|
|
4
|
+
isHighlighted: boolean;
|
|
5
|
+
isNegated?: boolean;
|
|
6
|
+
onMouseDown: (value: string) => void;
|
|
7
|
+
itemRef?: (el: HTMLButtonElement | null) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare function ShipQLSuggestionItem({ item, isHighlighted, isNegated, onMouseDown, itemRef, }: ShipQLSuggestionItemProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=shipql-suggestion-item.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../../../utils/cn.js';
|
|
3
|
+
export function ShipQLSuggestionItem({ item, isHighlighted, isNegated, onMouseDown, itemRef }) {
|
|
4
|
+
if (item.type === 'section-header') {
|
|
5
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
6
|
+
className: "flex w-full items-center px-8 h-30 shrink-0",
|
|
7
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
8
|
+
className: "text-xs font-normal uppercase text-foreground-neutral-muted",
|
|
9
|
+
children: item.label
|
|
10
|
+
})
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
return /*#__PURE__*/ _jsxs("button", {
|
|
14
|
+
ref: itemRef,
|
|
15
|
+
type: "button",
|
|
16
|
+
onMouseDown: (e)=>{
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
onMouseDown(item.value);
|
|
19
|
+
},
|
|
20
|
+
className: cn('flex w-full items-center gap-12 rounded-none px-8 py-6 h-24 text-left transition-colors duration-75 cursor-pointer', isHighlighted ? 'bg-background-button-transparent-hover' : 'hover:bg-background-button-transparent-hover'),
|
|
21
|
+
children: [
|
|
22
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
23
|
+
className: "flex min-w-0 flex-1 items-center gap-12",
|
|
24
|
+
children: [
|
|
25
|
+
item.icon,
|
|
26
|
+
/*#__PURE__*/ _jsx("span", {
|
|
27
|
+
className: cn('flex-1 truncate text-sm', isNegated ? 'text-foreground-highlights-interactive' : item.selected ? 'text-foreground-neutral-base' : 'text-foreground-neutral-subtle'),
|
|
28
|
+
children: isNegated ? `-${item.label}` : item.label
|
|
29
|
+
})
|
|
30
|
+
]
|
|
31
|
+
}),
|
|
32
|
+
isHighlighted && /*#__PURE__*/ _jsx("span", {
|
|
33
|
+
className: "shrink-0 text-xs text-foreground-neutral-muted font-medium select-none",
|
|
34
|
+
children: "↵"
|
|
35
|
+
})
|
|
36
|
+
]
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=shipql-suggestion-item.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type SyntaxHintMode } from './shipql-suggestions-footer';
|
|
2
|
+
import type { SuggestionItem } from './types';
|
|
3
|
+
interface ShipQLSuggestionsDropdownProps {
|
|
4
|
+
items: SuggestionItem[];
|
|
5
|
+
selectedIndex: number;
|
|
6
|
+
isSelectingRef: React.RefObject<boolean>;
|
|
7
|
+
onSelect: (value: string) => void;
|
|
8
|
+
isLoading?: boolean;
|
|
9
|
+
isNegated: boolean;
|
|
10
|
+
onToggleNegate: (negated: boolean) => void;
|
|
11
|
+
showValueActions: boolean;
|
|
12
|
+
showSyntaxHelp: boolean;
|
|
13
|
+
onToggleSyntaxHelp: () => void;
|
|
14
|
+
isError?: boolean;
|
|
15
|
+
syntaxHintMode: SyntaxHintMode;
|
|
16
|
+
}
|
|
17
|
+
export declare function ShipQLSuggestionsDropdown({ items, selectedIndex, isSelectingRef, onSelect, isLoading, isNegated, onToggleNegate, showValueActions, showSyntaxHelp, onToggleSyntaxHelp, isError, syntaxHintMode, }: ShipQLSuggestionsDropdownProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=shipql-suggestions-dropdown.d.ts.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PopoverContent } from '../../../components/popover/index.js';
|
|
3
|
+
import { ScrollArea } from '../../../components/scroll-area/index.js';
|
|
4
|
+
import { Skeleton } from '../../../components/skeleton/index.js';
|
|
5
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
6
|
+
import { ShipQLRangeFacetPanel } from './shipql-range-facet-panel.js';
|
|
7
|
+
import { ShipQLSuggestionItem } from './shipql-suggestion-item.js';
|
|
8
|
+
import { ShipQLSuggestionsFooter } from './shipql-suggestions-footer.js';
|
|
9
|
+
export function ShipQLSuggestionsDropdown({ items, selectedIndex, isSelectingRef, onSelect, isLoading, isNegated, onToggleNegate, showValueActions, showSyntaxHelp, onToggleSyntaxHelp, isError, syntaxHintMode }) {
|
|
10
|
+
const itemRefs = useRef([]);
|
|
11
|
+
useEffect(()=>{
|
|
12
|
+
const el = itemRefs.current[selectedIndex];
|
|
13
|
+
if (el) el.scrollIntoView({
|
|
14
|
+
behavior: 'smooth',
|
|
15
|
+
block: 'nearest'
|
|
16
|
+
});
|
|
17
|
+
}, [
|
|
18
|
+
selectedIndex
|
|
19
|
+
]);
|
|
20
|
+
// Shift key toggles negation while dropdown is visible
|
|
21
|
+
useEffect(()=>{
|
|
22
|
+
if (!showValueActions) return;
|
|
23
|
+
const handleKeyDown = (e)=>{
|
|
24
|
+
if (e.key === 'Shift') onToggleNegate(true);
|
|
25
|
+
};
|
|
26
|
+
const handleKeyUp = (e)=>{
|
|
27
|
+
if (e.key === 'Shift') onToggleNegate(false);
|
|
28
|
+
};
|
|
29
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
30
|
+
window.addEventListener('keyup', handleKeyUp);
|
|
31
|
+
return ()=>{
|
|
32
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
33
|
+
window.removeEventListener('keyup', handleKeyUp);
|
|
34
|
+
};
|
|
35
|
+
}, [
|
|
36
|
+
showValueActions,
|
|
37
|
+
onToggleNegate
|
|
38
|
+
]);
|
|
39
|
+
// Reset negation when showValueActions turns off
|
|
40
|
+
useEffect(()=>{
|
|
41
|
+
if (!showValueActions) onToggleNegate(false);
|
|
42
|
+
}, [
|
|
43
|
+
showValueActions,
|
|
44
|
+
onToggleNegate
|
|
45
|
+
]);
|
|
46
|
+
const handleMouseDown = useCallback((value)=>{
|
|
47
|
+
isSelectingRef.current = true;
|
|
48
|
+
onSelect(value);
|
|
49
|
+
setTimeout(()=>{
|
|
50
|
+
isSelectingRef.current = false;
|
|
51
|
+
}, 150);
|
|
52
|
+
}, [
|
|
53
|
+
isSelectingRef,
|
|
54
|
+
onSelect
|
|
55
|
+
]);
|
|
56
|
+
const firstItem = items.length === 1 ? items[0] : undefined;
|
|
57
|
+
const rangeItem = firstItem?.type === 'range-slider' && firstItem.facetName && firstItem.rangeFacetConfig ? firstItem : null;
|
|
58
|
+
const popoverContent = rangeItem ? /*#__PURE__*/ _jsxs("div", {
|
|
59
|
+
className: "flex flex-col overflow-hidden rounded-8 bg-background-neutral-base shadow-tooltip",
|
|
60
|
+
children: [
|
|
61
|
+
/*#__PURE__*/ _jsx(ScrollArea, {
|
|
62
|
+
className: "flex-1 min-h-0 overflow-y-auto scrollbar",
|
|
63
|
+
children: /*#__PURE__*/ _jsx(ShipQLRangeFacetPanel, {
|
|
64
|
+
facetName: rangeItem.facetName,
|
|
65
|
+
config: rangeItem.rangeFacetConfig,
|
|
66
|
+
isSelectingRef: isSelectingRef,
|
|
67
|
+
onApply: onSelect
|
|
68
|
+
})
|
|
69
|
+
}),
|
|
70
|
+
/*#__PURE__*/ _jsx(ShipQLSuggestionsFooter, {
|
|
71
|
+
showValueActions: false,
|
|
72
|
+
showSyntaxHelp: showSyntaxHelp,
|
|
73
|
+
onToggleSyntaxHelp: onToggleSyntaxHelp,
|
|
74
|
+
isError: isError,
|
|
75
|
+
syntaxHintMode: "range"
|
|
76
|
+
})
|
|
77
|
+
]
|
|
78
|
+
}) : /*#__PURE__*/ _jsxs("div", {
|
|
79
|
+
className: "flex flex-col overflow-hidden rounded-8 bg-background-neutral-base shadow-tooltip max-h-[min(70vh,320px)] min-h-0",
|
|
80
|
+
children: [
|
|
81
|
+
/*#__PURE__*/ _jsx(ScrollArea, {
|
|
82
|
+
className: "flex-1 min-h-0 overflow-y-auto scrollbar",
|
|
83
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
84
|
+
className: "flex flex-col",
|
|
85
|
+
children: isLoading && items.length === 0 ? /*#__PURE__*/ _jsx("div", {
|
|
86
|
+
className: "px-8 py-6 flex items-center",
|
|
87
|
+
children: /*#__PURE__*/ _jsx(Skeleton, {
|
|
88
|
+
className: "w-60 h-20"
|
|
89
|
+
})
|
|
90
|
+
}) : items.length === 0 ? /*#__PURE__*/ _jsx("div", {
|
|
91
|
+
className: "px-8 py-6 text-sm text-foreground-neutral-muted",
|
|
92
|
+
children: "No suggestions found"
|
|
93
|
+
}) : items.map((item, index)=>/*#__PURE__*/ _jsx(ShipQLSuggestionItem, {
|
|
94
|
+
item: item,
|
|
95
|
+
isHighlighted: selectedIndex === index,
|
|
96
|
+
isNegated: isNegated && showValueActions,
|
|
97
|
+
onMouseDown: handleMouseDown,
|
|
98
|
+
itemRef: (el)=>{
|
|
99
|
+
itemRefs.current[index] = el;
|
|
100
|
+
}
|
|
101
|
+
}, item.value))
|
|
102
|
+
})
|
|
103
|
+
}),
|
|
104
|
+
/*#__PURE__*/ _jsx(ShipQLSuggestionsFooter, {
|
|
105
|
+
showValueActions: showValueActions,
|
|
106
|
+
showSyntaxHelp: showSyntaxHelp,
|
|
107
|
+
onToggleSyntaxHelp: onToggleSyntaxHelp,
|
|
108
|
+
isError: isError,
|
|
109
|
+
syntaxHintMode: syntaxHintMode
|
|
110
|
+
})
|
|
111
|
+
]
|
|
112
|
+
});
|
|
113
|
+
return /*#__PURE__*/ _jsx(PopoverContent, {
|
|
114
|
+
align: "start",
|
|
115
|
+
sideOffset: 4,
|
|
116
|
+
className: "p-0 w-(--radix-popover-trigger-width) rounded-8",
|
|
117
|
+
onOpenAutoFocus: (e)=>e.preventDefault(),
|
|
118
|
+
onInteractOutside: (e)=>{
|
|
119
|
+
if (isSelectingRef.current) e.preventDefault();
|
|
120
|
+
},
|
|
121
|
+
onPointerDownOutside: (e)=>{
|
|
122
|
+
if (isSelectingRef.current) e.preventDefault();
|
|
123
|
+
},
|
|
124
|
+
children: popoverContent
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//# sourceMappingURL=shipql-suggestions-dropdown.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type SyntaxHintMode = 'value' | 'range';
|
|
2
|
+
interface ShipQLSuggestionsFooterProps {
|
|
3
|
+
showValueActions: boolean;
|
|
4
|
+
showSyntaxHelp: boolean;
|
|
5
|
+
onToggleSyntaxHelp: () => void;
|
|
6
|
+
isError?: boolean;
|
|
7
|
+
syntaxHintMode: SyntaxHintMode;
|
|
8
|
+
}
|
|
9
|
+
export declare function ShipQLSuggestionsFooter({ showValueActions, showSyntaxHelp, onToggleSyntaxHelp, isError, syntaxHintMode, }: ShipQLSuggestionsFooterProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=shipql-suggestions-footer.d.ts.map
|