refactor handlers
This commit is contained in:
parent
740836a593
commit
a6c5ba0d44
1 changed files with 2391 additions and 2396 deletions
|
|
@ -20,7 +20,11 @@ import { MarkdownRenderer } from "@/components/markdown-renderer";
|
||||||
import { ProtectedRoute } from "@/components/protected-route";
|
import { ProtectedRoute } from "@/components/protected-route";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Popover, PopoverAnchor, PopoverContent } from "@/components/ui/popover";
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverAnchor,
|
||||||
|
PopoverContent,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
import { useAuth } from "@/contexts/auth-context";
|
import { useAuth } from "@/contexts/auth-context";
|
||||||
import { type EndpointType, useChat } from "@/contexts/chat-context";
|
import { type EndpointType, useChat } from "@/contexts/chat-context";
|
||||||
import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context";
|
import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context";
|
||||||
|
|
@ -132,7 +136,10 @@ function ChatPage() {
|
||||||
const [dropdownDismissed, setDropdownDismissed] = useState(false);
|
const [dropdownDismissed, setDropdownDismissed] = useState(false);
|
||||||
const [isUserInteracting, setIsUserInteracting] = useState(false);
|
const [isUserInteracting, setIsUserInteracting] = useState(false);
|
||||||
const [isForkingInProgress, setIsForkingInProgress] = useState(false);
|
const [isForkingInProgress, setIsForkingInProgress] = useState(false);
|
||||||
const [anchorPosition, setAnchorPosition] = useState<{ x: number; y: number } | null>(null);
|
const [anchorPosition, setAnchorPosition] = useState<{
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
} | null>(null);
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
@ -149,7 +156,7 @@ function ChatPage() {
|
||||||
|
|
||||||
const getCursorPosition = (textarea: HTMLTextAreaElement) => {
|
const getCursorPosition = (textarea: HTMLTextAreaElement) => {
|
||||||
// Create a hidden div with the same styles as the textarea
|
// Create a hidden div with the same styles as the textarea
|
||||||
const div = document.createElement('div');
|
const div = document.createElement("div");
|
||||||
const computedStyle = getComputedStyle(textarea);
|
const computedStyle = getComputedStyle(textarea);
|
||||||
|
|
||||||
// Copy all computed styles to the hidden div
|
// Copy all computed styles to the hidden div
|
||||||
|
|
@ -158,12 +165,12 @@ function ChatPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the div to be hidden but not un-rendered
|
// Set the div to be hidden but not un-rendered
|
||||||
div.style.position = 'absolute';
|
div.style.position = "absolute";
|
||||||
div.style.visibility = 'hidden';
|
div.style.visibility = "hidden";
|
||||||
div.style.whiteSpace = 'pre-wrap';
|
div.style.whiteSpace = "pre-wrap";
|
||||||
div.style.wordWrap = 'break-word';
|
div.style.wordWrap = "break-word";
|
||||||
div.style.overflow = 'hidden';
|
div.style.overflow = "hidden";
|
||||||
div.style.height = 'auto';
|
div.style.height = "auto";
|
||||||
div.style.width = `${textarea.getBoundingClientRect().width}px`;
|
div.style.width = `${textarea.getBoundingClientRect().width}px`;
|
||||||
|
|
||||||
// Get the text up to the cursor position
|
// Get the text up to the cursor position
|
||||||
|
|
@ -174,8 +181,8 @@ function ChatPage() {
|
||||||
div.textContent = textBeforeCursor;
|
div.textContent = textBeforeCursor;
|
||||||
|
|
||||||
// Create a span to mark the end position
|
// Create a span to mark the end position
|
||||||
const span = document.createElement('span');
|
const span = document.createElement("span");
|
||||||
span.textContent = '|'; // Cursor marker
|
span.textContent = "|"; // Cursor marker
|
||||||
div.appendChild(span);
|
div.appendChild(span);
|
||||||
|
|
||||||
// Add the text after cursor to handle word wrapping
|
// Add the text after cursor to handle word wrapping
|
||||||
|
|
@ -742,7 +749,6 @@ function ChatPage() {
|
||||||
};
|
};
|
||||||
}, [endpoint, setPreviousResponseIds]);
|
}, [endpoint, setPreviousResponseIds]);
|
||||||
|
|
||||||
|
|
||||||
const { data: nudges = [], cancel: cancelNudges } = useGetNudgesQuery(
|
const { data: nudges = [], cancel: cancelNudges } = useGetNudgesQuery(
|
||||||
previousResponseIds[endpoint],
|
previousResponseIds[endpoint],
|
||||||
);
|
);
|
||||||
|
|
@ -1830,7 +1836,8 @@ function ChatPage() {
|
||||||
));
|
));
|
||||||
})()}
|
})()}
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
Found {(() => {
|
Found{" "}
|
||||||
|
{(() => {
|
||||||
let resultsToCount = fc.result;
|
let resultsToCount = fc.result;
|
||||||
if (
|
if (
|
||||||
fc.result.length > 0 &&
|
fc.result.length > 0 &&
|
||||||
|
|
@ -1841,7 +1848,8 @@ function ChatPage() {
|
||||||
resultsToCount = fc.result[0].results;
|
resultsToCount = fc.result[0].results;
|
||||||
}
|
}
|
||||||
return resultsToCount.length;
|
return resultsToCount.length;
|
||||||
})()} result
|
})()}{" "}
|
||||||
|
result
|
||||||
{(() => {
|
{(() => {
|
||||||
let resultsToCount = fc.result;
|
let resultsToCount = fc.result;
|
||||||
if (
|
if (
|
||||||
|
|
@ -1876,6 +1884,172 @@ function ChatPage() {
|
||||||
handleSendMessage(suggestion);
|
handleSendMessage(suggestion);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
|
// Handle backspace for filter clearing
|
||||||
|
if (e.key === "Backspace" && selectedFilter && input.trim() === "") {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (isFilterHighlighted) {
|
||||||
|
// Second backspace - remove the filter
|
||||||
|
setSelectedFilter(null);
|
||||||
|
setIsFilterHighlighted(false);
|
||||||
|
} else {
|
||||||
|
// First backspace - highlight the filter
|
||||||
|
setIsFilterHighlighted(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFilterDropdownOpen) {
|
||||||
|
const filteredFilters = availableFilters.filter((filter) =>
|
||||||
|
filter.name.toLowerCase().includes(filterSearchTerm.toLowerCase()),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsFilterDropdownOpen(false);
|
||||||
|
setFilterSearchTerm("");
|
||||||
|
setSelectedFilterIndex(0);
|
||||||
|
setDropdownDismissed(true);
|
||||||
|
|
||||||
|
// Keep focus on the textarea so user can continue typing normally
|
||||||
|
inputRef.current?.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === "ArrowDown") {
|
||||||
|
e.preventDefault();
|
||||||
|
setSelectedFilterIndex((prev) =>
|
||||||
|
prev < filteredFilters.length - 1 ? prev + 1 : 0,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === "ArrowUp") {
|
||||||
|
e.preventDefault();
|
||||||
|
setSelectedFilterIndex((prev) =>
|
||||||
|
prev > 0 ? prev - 1 : filteredFilters.length - 1,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
// Check if we're at the end of an @ mention (space before cursor or end of input)
|
||||||
|
const cursorPos = e.currentTarget.selectionStart || 0;
|
||||||
|
const textBeforeCursor = input.slice(0, cursorPos);
|
||||||
|
const words = textBeforeCursor.split(" ");
|
||||||
|
const lastWord = words[words.length - 1];
|
||||||
|
|
||||||
|
if (lastWord.startsWith("@") && filteredFilters[selectedFilterIndex]) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleFilterSelect(filteredFilters[selectedFilterIndex]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === " ") {
|
||||||
|
// Select filter on space if we're typing an @ mention
|
||||||
|
const cursorPos = e.currentTarget.selectionStart || 0;
|
||||||
|
const textBeforeCursor = input.slice(0, cursorPos);
|
||||||
|
const words = textBeforeCursor.split(" ");
|
||||||
|
const lastWord = words[words.length - 1];
|
||||||
|
|
||||||
|
if (lastWord.startsWith("@") && filteredFilters[selectedFilterIndex]) {
|
||||||
|
e.preventDefault();
|
||||||
|
handleFilterSelect(filteredFilters[selectedFilterIndex]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === "Enter" && !e.shiftKey && !isFilterDropdownOpen) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (input.trim() && !loading) {
|
||||||
|
// Trigger form submission by finding the form and calling submit
|
||||||
|
const form = e.currentTarget.closest("form");
|
||||||
|
if (form) {
|
||||||
|
form.requestSubmit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
const newValue = e.target.value;
|
||||||
|
setInput(newValue);
|
||||||
|
|
||||||
|
// Clear filter highlight when user starts typing
|
||||||
|
if (isFilterHighlighted) {
|
||||||
|
setIsFilterHighlighted(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find if there's an @ at the start of the last word
|
||||||
|
const words = newValue.split(" ");
|
||||||
|
const lastWord = words[words.length - 1];
|
||||||
|
|
||||||
|
if (lastWord.startsWith("@") && !dropdownDismissed) {
|
||||||
|
const searchTerm = lastWord.slice(1); // Remove the @
|
||||||
|
console.log("Setting search term:", searchTerm);
|
||||||
|
setFilterSearchTerm(searchTerm);
|
||||||
|
setSelectedFilterIndex(0);
|
||||||
|
|
||||||
|
// Only set anchor position when @ is first detected (search term is empty)
|
||||||
|
if (searchTerm === "") {
|
||||||
|
const pos = getCursorPosition(e.target);
|
||||||
|
setAnchorPosition(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isFilterDropdownOpen) {
|
||||||
|
loadAvailableFilters();
|
||||||
|
setIsFilterDropdownOpen(true);
|
||||||
|
}
|
||||||
|
} else if (isFilterDropdownOpen) {
|
||||||
|
// Close dropdown if @ is no longer present
|
||||||
|
console.log("Closing dropdown - no @ found");
|
||||||
|
setIsFilterDropdownOpen(false);
|
||||||
|
setFilterSearchTerm("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset dismissed flag when user moves to a different word
|
||||||
|
if (dropdownDismissed && !lastWord.startsWith("@")) {
|
||||||
|
setDropdownDismissed(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAtClick = () => {
|
||||||
|
if (inputRef.current) {
|
||||||
|
// Insert @ at current cursor position
|
||||||
|
const textarea = inputRef.current;
|
||||||
|
const start = textarea.selectionStart || 0;
|
||||||
|
const end = textarea.selectionEnd || 0;
|
||||||
|
const currentValue = textarea.value;
|
||||||
|
|
||||||
|
// Insert @ at cursor position
|
||||||
|
const newValue =
|
||||||
|
currentValue.substring(0, start) + "@" + currentValue.substring(end);
|
||||||
|
setInput(newValue);
|
||||||
|
|
||||||
|
// Set cursor position after the @
|
||||||
|
const newCursorPos = start + 1;
|
||||||
|
setTimeout(() => {
|
||||||
|
textarea.setSelectionRange(newCursorPos, newCursorPos);
|
||||||
|
textarea.focus();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
// Open popover and set anchor position
|
||||||
|
loadAvailableFilters();
|
||||||
|
setIsFilterDropdownOpen(true);
|
||||||
|
setFilterSearchTerm("");
|
||||||
|
setSelectedFilterIndex(0);
|
||||||
|
|
||||||
|
// Get cursor position for popover anchoring
|
||||||
|
setTimeout(() => {
|
||||||
|
const cursorPos = getCursorPosition(textarea);
|
||||||
|
setAnchorPosition(cursorPos);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`fixed inset-0 md:left-72 top-[53px] flex flex-col transition-all duration-300 ${
|
className={`fixed inset-0 md:left-72 top-[53px] flex flex-col transition-all duration-300 ${
|
||||||
|
|
@ -2095,156 +2269,8 @@ function ChatPage() {
|
||||||
<textarea
|
<textarea
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
value={input}
|
value={input}
|
||||||
onChange={(e) => {
|
onChange={onChange}
|
||||||
const newValue = e.target.value;
|
onKeyDown={handleKeyDown}
|
||||||
setInput(newValue);
|
|
||||||
|
|
||||||
// Clear filter highlight when user starts typing
|
|
||||||
if (isFilterHighlighted) {
|
|
||||||
setIsFilterHighlighted(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find if there's an @ at the start of the last word
|
|
||||||
const words = newValue.split(" ");
|
|
||||||
const lastWord = words[words.length - 1];
|
|
||||||
|
|
||||||
if (lastWord.startsWith("@") && !dropdownDismissed) {
|
|
||||||
const searchTerm = lastWord.slice(1); // Remove the @
|
|
||||||
console.log("Setting search term:", searchTerm);
|
|
||||||
setFilterSearchTerm(searchTerm);
|
|
||||||
setSelectedFilterIndex(0);
|
|
||||||
|
|
||||||
// Only set anchor position when @ is first detected (search term is empty)
|
|
||||||
if (searchTerm === "") {
|
|
||||||
const pos = getCursorPosition(e.target);
|
|
||||||
setAnchorPosition(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isFilterDropdownOpen) {
|
|
||||||
loadAvailableFilters();
|
|
||||||
setIsFilterDropdownOpen(true);
|
|
||||||
}
|
|
||||||
} else if (isFilterDropdownOpen) {
|
|
||||||
// Close dropdown if @ is no longer present
|
|
||||||
console.log("Closing dropdown - no @ found");
|
|
||||||
setIsFilterDropdownOpen(false);
|
|
||||||
setFilterSearchTerm("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset dismissed flag when user moves to a different word
|
|
||||||
if (dropdownDismissed && !lastWord.startsWith("@")) {
|
|
||||||
setDropdownDismissed(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
// Handle backspace for filter clearing
|
|
||||||
if (
|
|
||||||
e.key === "Backspace" &&
|
|
||||||
selectedFilter &&
|
|
||||||
input.trim() === ""
|
|
||||||
) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (isFilterHighlighted) {
|
|
||||||
// Second backspace - remove the filter
|
|
||||||
setSelectedFilter(null);
|
|
||||||
setIsFilterHighlighted(false);
|
|
||||||
} else {
|
|
||||||
// First backspace - highlight the filter
|
|
||||||
setIsFilterHighlighted(true);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFilterDropdownOpen) {
|
|
||||||
const filteredFilters = availableFilters.filter((filter) =>
|
|
||||||
filter.name
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(filterSearchTerm.toLowerCase()),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (e.key === "Escape") {
|
|
||||||
e.preventDefault();
|
|
||||||
setIsFilterDropdownOpen(false);
|
|
||||||
setFilterSearchTerm("");
|
|
||||||
setSelectedFilterIndex(0);
|
|
||||||
setDropdownDismissed(true);
|
|
||||||
|
|
||||||
// Keep focus on the textarea so user can continue typing normally
|
|
||||||
inputRef.current?.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === "ArrowDown") {
|
|
||||||
e.preventDefault();
|
|
||||||
setSelectedFilterIndex((prev) =>
|
|
||||||
prev < filteredFilters.length - 1 ? prev + 1 : 0,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === "ArrowUp") {
|
|
||||||
e.preventDefault();
|
|
||||||
setSelectedFilterIndex((prev) =>
|
|
||||||
prev > 0 ? prev - 1 : filteredFilters.length - 1,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === "Enter") {
|
|
||||||
// Check if we're at the end of an @ mention (space before cursor or end of input)
|
|
||||||
const cursorPos = e.currentTarget.selectionStart || 0;
|
|
||||||
const textBeforeCursor = input.slice(0, cursorPos);
|
|
||||||
const words = textBeforeCursor.split(" ");
|
|
||||||
const lastWord = words[words.length - 1];
|
|
||||||
|
|
||||||
if (
|
|
||||||
lastWord.startsWith("@") &&
|
|
||||||
filteredFilters[selectedFilterIndex]
|
|
||||||
) {
|
|
||||||
e.preventDefault();
|
|
||||||
handleFilterSelect(
|
|
||||||
filteredFilters[selectedFilterIndex],
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === " ") {
|
|
||||||
// Select filter on space if we're typing an @ mention
|
|
||||||
const cursorPos = e.currentTarget.selectionStart || 0;
|
|
||||||
const textBeforeCursor = input.slice(0, cursorPos);
|
|
||||||
const words = textBeforeCursor.split(" ");
|
|
||||||
const lastWord = words[words.length - 1];
|
|
||||||
|
|
||||||
if (
|
|
||||||
lastWord.startsWith("@") &&
|
|
||||||
filteredFilters[selectedFilterIndex]
|
|
||||||
) {
|
|
||||||
e.preventDefault();
|
|
||||||
handleFilterSelect(
|
|
||||||
filteredFilters[selectedFilterIndex],
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
e.key === "Enter" &&
|
|
||||||
!e.shiftKey &&
|
|
||||||
!isFilterDropdownOpen
|
|
||||||
) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (input.trim() && !loading) {
|
|
||||||
// Trigger form submission by finding the form and calling submit
|
|
||||||
const form = e.currentTarget.closest("form");
|
|
||||||
if (form) {
|
|
||||||
form.requestSubmit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
placeholder="Type to ask a question..."
|
placeholder="Type to ask a question..."
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className={`w-full bg-transparent px-4 ${
|
className={`w-full bg-transparent px-4 ${
|
||||||
|
|
@ -2268,38 +2294,7 @@ function ChatPage() {
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={onAtClick}
|
||||||
if (inputRef.current) {
|
|
||||||
// Insert @ at current cursor position
|
|
||||||
const textarea = inputRef.current;
|
|
||||||
const start = textarea.selectionStart || 0;
|
|
||||||
const end = textarea.selectionEnd || 0;
|
|
||||||
const currentValue = textarea.value;
|
|
||||||
|
|
||||||
// Insert @ at cursor position
|
|
||||||
const newValue = currentValue.substring(0, start) + '@' + currentValue.substring(end);
|
|
||||||
setInput(newValue);
|
|
||||||
|
|
||||||
// Set cursor position after the @
|
|
||||||
const newCursorPos = start + 1;
|
|
||||||
setTimeout(() => {
|
|
||||||
textarea.setSelectionRange(newCursorPos, newCursorPos);
|
|
||||||
textarea.focus();
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
// Open popover and set anchor position
|
|
||||||
loadAvailableFilters();
|
|
||||||
setIsFilterDropdownOpen(true);
|
|
||||||
setFilterSearchTerm("");
|
|
||||||
setSelectedFilterIndex(0);
|
|
||||||
|
|
||||||
// Get cursor position for popover anchoring
|
|
||||||
setTimeout(() => {
|
|
||||||
const cursorPos = getCursorPosition(textarea);
|
|
||||||
setAnchorPosition(cursorPos);
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<AtSign className="h-4 w-4" />
|
<AtSign className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -2313,12 +2308,12 @@ function ChatPage() {
|
||||||
<PopoverAnchor
|
<PopoverAnchor
|
||||||
asChild
|
asChild
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: "fixed",
|
||||||
left: anchorPosition.x,
|
left: anchorPosition.x,
|
||||||
top: anchorPosition.y,
|
top: anchorPosition.y,
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 1,
|
height: 1,
|
||||||
pointerEvents: 'none',
|
pointerEvents: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div />
|
<div />
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue