@lastbrain/ai-ui-react 1.0.67 → 1.0.69
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/AiChipLabel.d.ts +8 -3
- package/dist/components/AiChipLabel.d.ts.map +1 -1
- package/dist/components/AiChipLabel.js +21 -70
- package/dist/components/AiContextButton.d.ts +5 -1
- package/dist/components/AiContextButton.d.ts.map +1 -1
- package/dist/components/AiContextButton.js +67 -291
- package/dist/components/AiImageButton.d.ts +5 -1
- package/dist/components/AiImageButton.d.ts.map +1 -1
- package/dist/components/AiImageButton.js +6 -142
- package/dist/components/AiInput.d.ts +5 -3
- package/dist/components/AiInput.d.ts.map +1 -1
- package/dist/components/AiInput.js +13 -25
- package/dist/components/AiPromptPanel.d.ts.map +1 -1
- package/dist/components/AiPromptPanel.js +58 -212
- package/dist/components/AiSelect.d.ts +5 -3
- package/dist/components/AiSelect.d.ts.map +1 -1
- package/dist/components/AiSelect.js +21 -30
- package/dist/components/AiStatusButton.d.ts +4 -1
- package/dist/components/AiStatusButton.d.ts.map +1 -1
- package/dist/components/AiStatusButton.js +198 -626
- package/dist/components/AiTextarea.d.ts +4 -2
- package/dist/components/AiTextarea.d.ts.map +1 -1
- package/dist/components/AiTextarea.js +14 -26
- package/dist/components/LBApiKeySelector.d.ts.map +1 -1
- package/dist/components/LBApiKeySelector.js +5 -166
- package/dist/components/LBConnectButton.d.ts +4 -7
- package/dist/components/LBConnectButton.d.ts.map +1 -1
- package/dist/components/LBConnectButton.js +17 -86
- package/dist/components/LBSigninModal.d.ts +1 -1
- package/dist/components/LBSigninModal.d.ts.map +1 -1
- package/dist/components/LBSigninModal.js +42 -320
- package/dist/examples/AiUiPremiumShowcase.d.ts +2 -0
- package/dist/examples/AiUiPremiumShowcase.d.ts.map +1 -0
- package/dist/examples/AiUiPremiumShowcase.js +15 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/styles/inline.d.ts +1 -0
- package/dist/styles/inline.d.ts.map +1 -1
- package/dist/styles/inline.js +25 -129
- package/dist/styles.css +1268 -369
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/AiChipLabel.tsx +64 -101
- package/src/components/AiContextButton.tsx +138 -430
- package/src/components/AiImageButton.tsx +29 -190
- package/src/components/AiInput.tsx +49 -74
- package/src/components/AiPromptPanel.tsx +71 -254
- package/src/components/AiSelect.tsx +61 -69
- package/src/components/AiStatusButton.tsx +477 -1219
- package/src/components/AiTextarea.tsx +49 -64
- package/src/components/LBApiKeySelector.tsx +86 -274
- package/src/components/LBConnectButton.tsx +46 -334
- package/src/components/LBSigninModal.tsx +140 -481
- package/src/examples/AiUiPremiumShowcase.tsx +91 -0
- package/src/index.ts +3 -0
- package/src/styles/inline.ts +27 -148
- package/src/styles.css +1268 -369
- package/src/types.ts +3 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Sparkles } from "lucide-react";
|
|
4
|
+
import { AiProvider } from "../context/AiProvider";
|
|
5
|
+
import { AiInput } from "../components/AiInput";
|
|
6
|
+
import { AiTextarea } from "../components/AiTextarea";
|
|
7
|
+
import { AiChipLabel } from "../components/AiChipLabel";
|
|
8
|
+
|
|
9
|
+
function ShowcasePanel({
|
|
10
|
+
title,
|
|
11
|
+
theme,
|
|
12
|
+
}: {
|
|
13
|
+
title: string;
|
|
14
|
+
theme: "light" | "dark";
|
|
15
|
+
}) {
|
|
16
|
+
return (
|
|
17
|
+
<section
|
|
18
|
+
data-theme={theme}
|
|
19
|
+
className={`rounded-2xl border p-6 ${
|
|
20
|
+
theme === "dark"
|
|
21
|
+
? "bg-slate-950 border-slate-800 text-slate-100"
|
|
22
|
+
: "bg-white border-slate-200 text-slate-900"
|
|
23
|
+
}`}
|
|
24
|
+
>
|
|
25
|
+
<div className="mb-4 flex items-center justify-between">
|
|
26
|
+
<h3 className="text-base font-semibold">{title}</h3>
|
|
27
|
+
<span className="text-xs opacity-70">{theme}</span>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<AiProvider baseUrl="/api/ai" apiKeyId="demo-key">
|
|
31
|
+
<div className="space-y-4">
|
|
32
|
+
<AiInput
|
|
33
|
+
value=""
|
|
34
|
+
onChange={() => {}}
|
|
35
|
+
placeholder="Ask anything..."
|
|
36
|
+
data-testid={`showcase-input-${theme}`}
|
|
37
|
+
/>
|
|
38
|
+
|
|
39
|
+
<AiTextarea
|
|
40
|
+
value=""
|
|
41
|
+
onChange={() => {}}
|
|
42
|
+
placeholder="Generate a concise product description..."
|
|
43
|
+
data-testid={`showcase-textarea-${theme}`}
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
<div className="flex flex-wrap gap-2">
|
|
47
|
+
<AiChipLabel>Default</AiChipLabel>
|
|
48
|
+
<AiChipLabel variant="selected">Selected</AiChipLabel>
|
|
49
|
+
<AiChipLabel variant="success">Success</AiChipLabel>
|
|
50
|
+
<AiChipLabel variant="danger">Danger</AiChipLabel>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div className="ai-surface p-3">
|
|
54
|
+
<div className="flex items-center justify-between">
|
|
55
|
+
<span className="text-sm font-medium">Static control states</span>
|
|
56
|
+
<Sparkles size={14} className="text-[var(--ai-primary)]" />
|
|
57
|
+
</div>
|
|
58
|
+
<div className="mt-3 space-y-2">
|
|
59
|
+
<input className="ai-control ai-control-input" placeholder="Default state" />
|
|
60
|
+
<input
|
|
61
|
+
className="ai-control ai-control-input ai-control--focused"
|
|
62
|
+
placeholder="Focused style"
|
|
63
|
+
readOnly
|
|
64
|
+
/>
|
|
65
|
+
<input
|
|
66
|
+
className="ai-control ai-control-input ai-control--error"
|
|
67
|
+
aria-invalid="true"
|
|
68
|
+
placeholder="Error style"
|
|
69
|
+
readOnly
|
|
70
|
+
/>
|
|
71
|
+
<input
|
|
72
|
+
className="ai-control ai-control-input ai-control--disabled"
|
|
73
|
+
placeholder="Disabled style"
|
|
74
|
+
disabled
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</AiProvider>
|
|
80
|
+
</section>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function AiUiPremiumShowcase() {
|
|
85
|
+
return (
|
|
86
|
+
<div className="grid gap-6 md:grid-cols-2">
|
|
87
|
+
<ShowcasePanel title="Premium UI Showcase" theme="dark" />
|
|
88
|
+
<ShowcasePanel title="Premium UI Showcase" theme="light" />
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import "./styles.css";
|
|
2
|
+
|
|
1
3
|
// Types
|
|
2
4
|
export * from "./types";
|
|
3
5
|
|
|
@@ -45,3 +47,4 @@ export * from "./examples/AiPromptPanelAdvanced";
|
|
|
45
47
|
export * from "./examples/AiChipInputExample";
|
|
46
48
|
export * from "./examples/AiImageButtonExample";
|
|
47
49
|
export * from "./examples/AiContextButtonExample";
|
|
50
|
+
export * from "./examples/AiUiPremiumShowcase";
|
package/src/styles/inline.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Theme: Purple/Violet primary color with light/dark mode support
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import type React from "react";
|
|
7
|
+
|
|
6
8
|
// Theme colors
|
|
7
9
|
const colors = {
|
|
8
10
|
// Purple/Violet theme
|
|
@@ -18,114 +20,23 @@ const colors = {
|
|
|
18
20
|
warningLight: "#fbbf24",
|
|
19
21
|
danger: "#ef4444",
|
|
20
22
|
dangerLight: "#f87171",
|
|
21
|
-
|
|
22
|
-
// Neutral colors - Light mode
|
|
23
|
-
light: {
|
|
24
|
-
bg: "#ffffff",
|
|
25
|
-
bgSecondary: "#f9fafb",
|
|
26
|
-
bgTertiary: "#f3f4f6",
|
|
27
|
-
border: "#e2e8f0",
|
|
28
|
-
borderLight: "#f1f5f9",
|
|
29
|
-
text: "#111827",
|
|
30
|
-
textSecondary: "#6b7280",
|
|
31
|
-
textTertiary: "#9ca3af",
|
|
32
|
-
shadow: "rgba(0, 0, 0, 0.1)",
|
|
33
|
-
shadowMd:
|
|
34
|
-
"0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
|
|
35
|
-
shadowLg:
|
|
36
|
-
"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)",
|
|
37
|
-
shadowXl:
|
|
38
|
-
"0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)",
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
// Neutral colors - Dark mode
|
|
42
|
-
dark: {
|
|
43
|
-
bg: "#1f2937",
|
|
44
|
-
bgSecondary: "#111827",
|
|
45
|
-
bgTertiary: "#0f172a",
|
|
46
|
-
border: "#374151",
|
|
47
|
-
borderLight: "#4b5563",
|
|
48
|
-
text: "#f9fafb",
|
|
49
|
-
textSecondary: "#d1d5db",
|
|
50
|
-
textTertiary: "#9ca3af",
|
|
51
|
-
shadow: "rgba(0, 0, 0, 0.3)",
|
|
52
|
-
shadowMd:
|
|
53
|
-
"0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -2px rgba(0, 0, 0, 0.3)",
|
|
54
|
-
shadowLg:
|
|
55
|
-
"0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -4px rgba(0, 0, 0, 0.3)",
|
|
56
|
-
shadowXl:
|
|
57
|
-
"0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 8px 10px -6px rgba(0, 0, 0, 0.3)",
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// Use light mode by default (can be made dynamic later)
|
|
62
|
-
const getIsDark = () => {
|
|
63
|
-
if (typeof document !== "undefined") {
|
|
64
|
-
const root = document.documentElement;
|
|
65
|
-
if (root.classList.contains("dark")) {
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
if (root.classList.contains("light")) {
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
typeof window !== "undefined" &&
|
|
75
|
-
window.matchMedia?.("(prefers-color-scheme: dark)").matches
|
|
76
|
-
);
|
|
77
23
|
};
|
|
78
24
|
|
|
79
25
|
const themeVars = {
|
|
80
26
|
bg: "var(--ai-bg)",
|
|
81
|
-
bgSecondary: "var(--ai-
|
|
82
|
-
bgTertiary: "var(--ai-
|
|
27
|
+
bgSecondary: "var(--ai-bg2)",
|
|
28
|
+
bgTertiary: "var(--ai-bg3)",
|
|
83
29
|
border: "var(--ai-border)",
|
|
84
|
-
borderLight: "var(--ai-border
|
|
85
|
-
text: "var(--ai-
|
|
86
|
-
textSecondary: "var(--ai-
|
|
87
|
-
textTertiary: "var(--ai-
|
|
88
|
-
shadow: "var(--ai-shadow)",
|
|
89
|
-
shadowMd: "var(--ai-shadow-
|
|
30
|
+
borderLight: "color-mix(in srgb, var(--ai-border) 70%, transparent)",
|
|
31
|
+
text: "var(--ai-foreground)",
|
|
32
|
+
textSecondary: "var(--ai-secondary)",
|
|
33
|
+
textTertiary: "var(--ai-tertiary)",
|
|
34
|
+
shadow: "var(--ai-shadow-sm)",
|
|
35
|
+
shadowMd: "var(--ai-shadow-sm)",
|
|
90
36
|
shadowLg: "var(--ai-shadow-lg)",
|
|
91
37
|
shadowXl: "var(--ai-shadow-xl)",
|
|
92
38
|
} as const;
|
|
93
39
|
|
|
94
|
-
const applyThemeVariables = () => {
|
|
95
|
-
if (typeof document === "undefined") {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const nextTheme = getIsDark() ? colors.dark : colors.light;
|
|
100
|
-
const root = document.documentElement;
|
|
101
|
-
|
|
102
|
-
root.style.setProperty("--ai-bg", nextTheme.bg);
|
|
103
|
-
root.style.setProperty("--ai-bg-secondary", nextTheme.bgSecondary);
|
|
104
|
-
root.style.setProperty("--ai-bg-tertiary", nextTheme.bgTertiary);
|
|
105
|
-
root.style.setProperty("--ai-border", nextTheme.border);
|
|
106
|
-
root.style.setProperty("--ai-border-light", nextTheme.borderLight);
|
|
107
|
-
root.style.setProperty("--ai-text", nextTheme.text);
|
|
108
|
-
root.style.setProperty("--ai-text-secondary", nextTheme.textSecondary);
|
|
109
|
-
root.style.setProperty("--ai-text-tertiary", nextTheme.textTertiary);
|
|
110
|
-
root.style.setProperty("--ai-shadow", nextTheme.shadow);
|
|
111
|
-
root.style.setProperty("--ai-shadow-md", nextTheme.shadowMd);
|
|
112
|
-
root.style.setProperty("--ai-shadow-lg", nextTheme.shadowLg);
|
|
113
|
-
root.style.setProperty("--ai-shadow-xl", nextTheme.shadowXl);
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
if (typeof window !== "undefined") {
|
|
117
|
-
applyThemeVariables();
|
|
118
|
-
|
|
119
|
-
const media = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
120
|
-
media?.addEventListener?.("change", applyThemeVariables);
|
|
121
|
-
|
|
122
|
-
const observer = new MutationObserver(() => applyThemeVariables());
|
|
123
|
-
observer.observe(document.documentElement, {
|
|
124
|
-
attributes: true,
|
|
125
|
-
attributeFilter: ["class"],
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
40
|
export const aiStyles = {
|
|
130
41
|
// Input field
|
|
131
42
|
input: {
|
|
@@ -134,7 +45,7 @@ export const aiStyles = {
|
|
|
134
45
|
fontSize: "14px",
|
|
135
46
|
lineHeight: "1.5",
|
|
136
47
|
color: themeVars.text,
|
|
137
|
-
background:
|
|
48
|
+
background: "var(--ai-glass-strong)",
|
|
138
49
|
border: `1px solid ${themeVars.border}`,
|
|
139
50
|
borderColor: themeVars.border,
|
|
140
51
|
borderRadius: "8px",
|
|
@@ -192,7 +103,7 @@ export const aiStyles = {
|
|
|
192
103
|
fontSize: "14px",
|
|
193
104
|
lineHeight: "1.5",
|
|
194
105
|
color: themeVars.text,
|
|
195
|
-
background:
|
|
106
|
+
background: "var(--ai-glass-strong)",
|
|
196
107
|
border: `1px solid ${themeVars.border}`,
|
|
197
108
|
borderColor: themeVars.border,
|
|
198
109
|
borderRadius: "8px",
|
|
@@ -277,7 +188,7 @@ export const aiStyles = {
|
|
|
277
188
|
fontSize: "14px",
|
|
278
189
|
lineHeight: "1.5",
|
|
279
190
|
color: themeVars.text,
|
|
280
|
-
background:
|
|
191
|
+
background: "var(--ai-glass-strong)",
|
|
281
192
|
borderWidth: "1px",
|
|
282
193
|
borderStyle: "solid",
|
|
283
194
|
borderColor: themeVars.border,
|
|
@@ -311,7 +222,7 @@ export const aiStyles = {
|
|
|
311
222
|
height: "40px",
|
|
312
223
|
border: `1px solid ${themeVars.border}`,
|
|
313
224
|
borderRadius: "8px",
|
|
314
|
-
background:
|
|
225
|
+
background: "var(--ai-glass, rgba(255,255,255,0.7))",
|
|
315
226
|
color: themeVars.text,
|
|
316
227
|
cursor: "pointer" as const,
|
|
317
228
|
transition: "all 0.2s",
|
|
@@ -319,7 +230,7 @@ export const aiStyles = {
|
|
|
319
230
|
} as React.CSSProperties,
|
|
320
231
|
|
|
321
232
|
statusButtonHover: {
|
|
322
|
-
background:
|
|
233
|
+
background: "var(--ai-glass-strong, rgba(255,255,255,0.82))",
|
|
323
234
|
boxShadow: themeVars.shadowLg,
|
|
324
235
|
transform: "scale(1.05)",
|
|
325
236
|
} as React.CSSProperties,
|
|
@@ -336,7 +247,7 @@ export const aiStyles = {
|
|
|
336
247
|
minWidth: "320px",
|
|
337
248
|
maxWidth: "400px",
|
|
338
249
|
padding: "16px",
|
|
339
|
-
background:
|
|
250
|
+
background: "var(--ai-bg2)",
|
|
340
251
|
border: `1px solid ${themeVars.border}`,
|
|
341
252
|
borderRadius: "12px",
|
|
342
253
|
boxShadow: themeVars.shadowXl,
|
|
@@ -428,13 +339,13 @@ export const aiStyles = {
|
|
|
428
339
|
transition: "all 0.2s",
|
|
429
340
|
fontSize: "11px",
|
|
430
341
|
fontWeight: 500,
|
|
431
|
-
background:
|
|
432
|
-
border:
|
|
342
|
+
background: "color-mix(in srgb, var(--ai-primary) 10%, transparent)",
|
|
343
|
+
border: "1px solid color-mix(in srgb, var(--ai-primary) 30%, transparent)",
|
|
433
344
|
} as React.CSSProperties,
|
|
434
345
|
|
|
435
346
|
tooltipLinkHover: {
|
|
436
|
-
background:
|
|
437
|
-
borderColor:
|
|
347
|
+
background: "color-mix(in srgb, var(--ai-primary) 20%, transparent)",
|
|
348
|
+
borderColor: "var(--ai-primary)",
|
|
438
349
|
} as React.CSSProperties,
|
|
439
350
|
|
|
440
351
|
// Chip/Label
|
|
@@ -446,9 +357,9 @@ export const aiStyles = {
|
|
|
446
357
|
fontSize: "12px",
|
|
447
358
|
fontWeight: 500,
|
|
448
359
|
borderRadius: "6px",
|
|
449
|
-
background:
|
|
450
|
-
color:
|
|
451
|
-
border:
|
|
360
|
+
background: "color-mix(in srgb, var(--ai-primary) 10%, transparent)",
|
|
361
|
+
color: "var(--ai-primary)",
|
|
362
|
+
border: "1px solid color-mix(in srgb, var(--ai-primary) 30%, transparent)",
|
|
452
363
|
} as React.CSSProperties,
|
|
453
364
|
|
|
454
365
|
// Modal (basic styles)
|
|
@@ -458,7 +369,7 @@ export const aiStyles = {
|
|
|
458
369
|
left: 0,
|
|
459
370
|
right: 0,
|
|
460
371
|
bottom: 0,
|
|
461
|
-
zIndex:
|
|
372
|
+
zIndex: 2147483645,
|
|
462
373
|
display: "flex" as const,
|
|
463
374
|
alignItems: "center" as const,
|
|
464
375
|
justifyContent: "center" as const,
|
|
@@ -472,7 +383,7 @@ export const aiStyles = {
|
|
|
472
383
|
left: 0,
|
|
473
384
|
right: 0,
|
|
474
385
|
bottom: 0,
|
|
475
|
-
background: "rgba(0, 0, 0, 0.5)",
|
|
386
|
+
background: "var(--ai-overlay, rgba(0, 0, 0, 0.5))",
|
|
476
387
|
backdropFilter: "blur(4px)",
|
|
477
388
|
WebkitBackdropFilter: "blur(4px)",
|
|
478
389
|
} as React.CSSProperties,
|
|
@@ -483,7 +394,8 @@ export const aiStyles = {
|
|
|
483
394
|
maxWidth: "600px",
|
|
484
395
|
maxHeight: "90vh",
|
|
485
396
|
overflow: "auto" as const,
|
|
486
|
-
background:
|
|
397
|
+
background: "var(--ai-bg2)",
|
|
398
|
+
border: "1px solid var(--ai-border)",
|
|
487
399
|
borderRadius: "12px",
|
|
488
400
|
boxShadow: themeVars.shadowXl,
|
|
489
401
|
padding: "0",
|
|
@@ -578,39 +490,6 @@ export const aiStyles = {
|
|
|
578
490
|
} as React.CSSProperties,
|
|
579
491
|
};
|
|
580
492
|
|
|
581
|
-
// Inject keyframes animation for spinner
|
|
582
|
-
if (typeof document !== "undefined") {
|
|
583
|
-
const styleSheet = document.createElement("style");
|
|
584
|
-
styleSheet.textContent = `
|
|
585
|
-
@keyframes ai-spin {
|
|
586
|
-
from { transform: rotate(0deg); }
|
|
587
|
-
to { transform: rotate(360deg); }
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
@keyframes ai-fadeIn {
|
|
591
|
-
from { opacity: 0; }
|
|
592
|
-
to { opacity: 1; }
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
@keyframes ai-slideUp {
|
|
596
|
-
from {
|
|
597
|
-
opacity: 0;
|
|
598
|
-
transform: translateY(20px);
|
|
599
|
-
}
|
|
600
|
-
to {
|
|
601
|
-
opacity: 1;
|
|
602
|
-
transform: translateY(0);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
@keyframes pulse {
|
|
607
|
-
0%, 100% { opacity: 1; }
|
|
608
|
-
50% { opacity: 0.6; }
|
|
609
|
-
}
|
|
610
|
-
`;
|
|
611
|
-
document.head.appendChild(styleSheet);
|
|
612
|
-
}
|
|
613
|
-
|
|
614
493
|
// Helper function to calculate tooltip position
|
|
615
494
|
export function calculateTooltipPosition(
|
|
616
495
|
buttonRect: DOMRect,
|