mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-31 15:06:42 +00:00
support search in tags input
This commit is contained in:
@@ -1,10 +1,23 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
// import { Command, CommandList, CommandItem, CommandGroup, CommandEmpty } from '../ui/command';
|
|
||||||
import { TagInputStyleClassesProps, type Tag as TagType } from "./tag-input";
|
import { TagInputStyleClassesProps, type Tag as TagType } from "./tag-input";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList
|
||||||
|
} from "../ui/command";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverAnchor,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger
|
||||||
|
} from "../ui/popover";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { cn } from "@app/lib/cn";
|
import { cn } from "@app/lib/cn";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { Check } from "lucide-react";
|
||||||
|
|
||||||
type AutocompleteProps = {
|
type AutocompleteProps = {
|
||||||
tags: TagType[];
|
tags: TagType[];
|
||||||
@@ -20,6 +33,8 @@ type AutocompleteProps = {
|
|||||||
inlineTags?: boolean;
|
inlineTags?: boolean;
|
||||||
classStyleProps: TagInputStyleClassesProps["autoComplete"];
|
classStyleProps: TagInputStyleClassesProps["autoComplete"];
|
||||||
usePortal?: boolean;
|
usePortal?: boolean;
|
||||||
|
/** Narrows the dropdown list from the main field (cmdk search filters further). */
|
||||||
|
filterQuery?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Autocomplete: React.FC<AutocompleteProps> = ({
|
export const Autocomplete: React.FC<AutocompleteProps> = ({
|
||||||
@@ -35,10 +50,10 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
|
|||||||
inlineTags,
|
inlineTags,
|
||||||
children,
|
children,
|
||||||
classStyleProps,
|
classStyleProps,
|
||||||
usePortal
|
usePortal,
|
||||||
|
filterQuery = ""
|
||||||
}) => {
|
}) => {
|
||||||
const triggerContainerRef = useRef<HTMLDivElement | null>(null);
|
const triggerContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
const popoverContentRef = useRef<HTMLDivElement | null>(null);
|
const popoverContentRef = useRef<HTMLDivElement | null>(null);
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
@@ -46,17 +61,21 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
|
|||||||
const [popoverWidth, setPopoverWidth] = useState<number>(0);
|
const [popoverWidth, setPopoverWidth] = useState<number>(0);
|
||||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||||
const [inputFocused, setInputFocused] = useState(false);
|
const [inputFocused, setInputFocused] = useState(false);
|
||||||
const [popooverContentTop, setPopoverContentTop] = useState<number>(0);
|
const [commandResetKey, setCommandResetKey] = useState(0);
|
||||||
const [selectedIndex, setSelectedIndex] = useState<number>(-1);
|
|
||||||
|
|
||||||
// Dynamically calculate the top position for the popover content
|
const visibleOptions = useMemo(() => {
|
||||||
useEffect(() => {
|
const q = filterQuery.trim().toLowerCase();
|
||||||
if (!triggerContainerRef.current || !triggerRef.current) return;
|
if (!q) return autocompleteOptions;
|
||||||
setPopoverContentTop(
|
return autocompleteOptions.filter((option) =>
|
||||||
triggerContainerRef.current?.getBoundingClientRect().bottom -
|
option.text.toLowerCase().includes(q)
|
||||||
triggerRef.current?.getBoundingClientRect().bottom
|
|
||||||
);
|
);
|
||||||
}, [tags]);
|
}, [autocompleteOptions, filterQuery]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isPopoverOpen) {
|
||||||
|
setCommandResetKey((k) => k + 1);
|
||||||
|
}
|
||||||
|
}, [isPopoverOpen]);
|
||||||
|
|
||||||
// Close the popover when clicking outside of it
|
// Close the popover when clicking outside of it
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -135,36 +154,6 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
|
|||||||
if (userOnBlur) userOnBlur(event);
|
if (userOnBlur) userOnBlur(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
||||||
if (!isPopoverOpen) return;
|
|
||||||
|
|
||||||
switch (event.key) {
|
|
||||||
case "ArrowUp":
|
|
||||||
event.preventDefault();
|
|
||||||
setSelectedIndex((prevIndex) =>
|
|
||||||
prevIndex <= 0
|
|
||||||
? autocompleteOptions.length - 1
|
|
||||||
: prevIndex - 1
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "ArrowDown":
|
|
||||||
event.preventDefault();
|
|
||||||
setSelectedIndex((prevIndex) =>
|
|
||||||
prevIndex === autocompleteOptions.length - 1
|
|
||||||
? 0
|
|
||||||
: prevIndex + 1
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "Enter":
|
|
||||||
event.preventDefault();
|
|
||||||
if (selectedIndex !== -1) {
|
|
||||||
toggleTag(autocompleteOptions[selectedIndex]);
|
|
||||||
setSelectedIndex(-1);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleTag = (option: TagType) => {
|
const toggleTag = (option: TagType) => {
|
||||||
// Check if the tag already exists in the array
|
// Check if the tag already exists in the array
|
||||||
const index = tags.findIndex((tag) => tag.text === option.text);
|
const index = tags.findIndex((tag) => tag.text === option.text);
|
||||||
@@ -197,18 +186,25 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSelectedIndex(-1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const childrenWithProps = React.cloneElement(
|
const child = children as React.ReactElement<
|
||||||
children as React.ReactElement<any>,
|
React.InputHTMLAttributes<HTMLInputElement> & {
|
||||||
{
|
ref?: React.Ref<HTMLInputElement>;
|
||||||
onKeyDown: handleKeyDown,
|
|
||||||
onFocus: handleInputFocus,
|
|
||||||
onBlur: handleInputBlur,
|
|
||||||
ref: inputRef
|
|
||||||
}
|
}
|
||||||
);
|
>;
|
||||||
|
const userOnKeyDown = child.props.onKeyDown;
|
||||||
|
|
||||||
|
const childrenWithProps = React.cloneElement(child, {
|
||||||
|
onKeyDown: userOnKeyDown,
|
||||||
|
onFocus: handleInputFocus,
|
||||||
|
onBlur: handleInputBlur,
|
||||||
|
ref: inputRef
|
||||||
|
} as Partial<
|
||||||
|
React.InputHTMLAttributes<HTMLInputElement> & {
|
||||||
|
ref?: React.Ref<HTMLInputElement>;
|
||||||
|
}
|
||||||
|
>);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -222,132 +218,105 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
|
|||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
modal={usePortal}
|
modal={usePortal}
|
||||||
>
|
>
|
||||||
<div
|
<PopoverAnchor asChild>
|
||||||
className="relative h-full flex items-center rounded-md border border-input bg-transparent pr-3"
|
<div
|
||||||
ref={triggerContainerRef}
|
className="relative h-full flex items-center rounded-md border border-input bg-transparent pr-3"
|
||||||
>
|
ref={triggerContainerRef}
|
||||||
{childrenWithProps}
|
>
|
||||||
<PopoverTrigger asChild ref={triggerRef}>
|
{childrenWithProps}
|
||||||
<Button
|
<PopoverTrigger asChild>
|
||||||
variant="ghost"
|
<Button
|
||||||
size="icon"
|
variant="ghost"
|
||||||
role="combobox"
|
size="icon"
|
||||||
className={cn(
|
role="combobox"
|
||||||
`hover:bg-transparent ${!inlineTags ? "ml-auto" : ""}`,
|
className={cn(
|
||||||
classStyleProps?.popoverTrigger
|
`hover:bg-transparent ${!inlineTags ? "ml-auto" : ""}`,
|
||||||
)}
|
classStyleProps?.popoverTrigger
|
||||||
onClick={() => {
|
)}
|
||||||
setIsPopoverOpen(!isPopoverOpen);
|
onClick={() => {
|
||||||
}}
|
setIsPopoverOpen(!isPopoverOpen);
|
||||||
>
|
}}
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
className={`lucide lucide-chevron-down h-4 w-4 shrink-0 opacity-50 ${isPopoverOpen ? "rotate-180" : "rotate-0"}`}
|
|
||||||
>
|
>
|
||||||
<path d="m6 9 6 6 6-6"></path>
|
<svg
|
||||||
</svg>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</Button>
|
width="24"
|
||||||
</PopoverTrigger>
|
height="24"
|
||||||
</div>
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
className={`lucide lucide-chevron-down h-4 w-4 shrink-0 opacity-50 ${isPopoverOpen ? "rotate-180" : "rotate-0"}`}
|
||||||
|
>
|
||||||
|
<path d="m6 9 6 6 6-6"></path>
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
</div>
|
||||||
|
</PopoverAnchor>
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
ref={popoverContentRef}
|
ref={popoverContentRef}
|
||||||
side="bottom"
|
side="bottom"
|
||||||
align="start"
|
align="start"
|
||||||
forceMount
|
forceMount
|
||||||
className={cn(
|
className={cn(
|
||||||
`p-0 relative`,
|
"p-0",
|
||||||
classStyleProps?.popoverContent
|
classStyleProps?.popoverContent
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
top: `${popooverContentTop}px`,
|
|
||||||
marginLeft: `calc(-${popoverWidth}px + 36px)`,
|
|
||||||
width: `${popoverWidth}px`,
|
width: `${popoverWidth}px`,
|
||||||
minWidth: `${popoverWidth}px`,
|
minWidth: `${popoverWidth}px`,
|
||||||
zIndex: 9999
|
zIndex: 9999
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<Command
|
||||||
|
key={commandResetKey}
|
||||||
className={cn(
|
className={cn(
|
||||||
"max-h-[300px] overflow-y-auto overflow-x-hidden",
|
"rounded-lg border-0 shadow-none",
|
||||||
classStyleProps?.commandList
|
classStyleProps?.command
|
||||||
)}
|
)}
|
||||||
style={{
|
|
||||||
minHeight: "68px"
|
|
||||||
}}
|
|
||||||
key={autocompleteOptions.length}
|
|
||||||
>
|
>
|
||||||
{autocompleteOptions.length > 0 ? (
|
<CommandInput
|
||||||
<div
|
placeholder={t("searchPlaceholder")}
|
||||||
key={autocompleteOptions.length}
|
className="h-9"
|
||||||
role="group"
|
/>
|
||||||
className={cn(
|
<CommandList
|
||||||
"overflow-y-auto overflow-hidden p-1 text-foreground",
|
className={cn(
|
||||||
classStyleProps?.commandGroup
|
"max-h-[300px]",
|
||||||
)}
|
classStyleProps?.commandList
|
||||||
style={{
|
)}
|
||||||
minHeight: "68px"
|
>
|
||||||
}}
|
<CommandEmpty>{t("noResults")}</CommandEmpty>
|
||||||
|
<CommandGroup
|
||||||
|
className={classStyleProps?.commandGroup}
|
||||||
>
|
>
|
||||||
<span className="text-muted-foreground font-medium text-sm py-1.5 px-2 pb-2">
|
{visibleOptions.map((option) => {
|
||||||
Suggestions
|
const isChosen = tags.some(
|
||||||
</span>
|
(tag) => tag.text === option.text
|
||||||
<div role="separator" className="py-0.5" />
|
);
|
||||||
{autocompleteOptions.map((option, index) => {
|
|
||||||
const isSelected = index === selectedIndex;
|
|
||||||
return (
|
return (
|
||||||
<div
|
<CommandItem
|
||||||
key={option.id}
|
key={option.id}
|
||||||
role="option"
|
value={`${option.text} ${option.id}`}
|
||||||
aria-selected={isSelected}
|
onSelect={() => toggleTag(option)}
|
||||||
className={cn(
|
className={classStyleProps?.commandItem}
|
||||||
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 hover:bg-accent",
|
|
||||||
isSelected &&
|
|
||||||
"bg-accent text-accent-foreground",
|
|
||||||
classStyleProps?.commandItem
|
|
||||||
)}
|
|
||||||
data-value={option.text}
|
|
||||||
onClick={() => toggleTag(option)}
|
|
||||||
>
|
>
|
||||||
<div className="w-full flex items-center gap-2">
|
<Check
|
||||||
{option.text}
|
className={cn(
|
||||||
{tags.some(
|
"mr-2 h-4 w-4 shrink-0",
|
||||||
(tag) =>
|
isChosen
|
||||||
tag.text === option.text
|
? "opacity-100"
|
||||||
) && (
|
: "opacity-0"
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="14"
|
|
||||||
height="14"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
className="lucide lucide-check"
|
|
||||||
>
|
|
||||||
<path d="M20 6 9 17l-5-5"></path>
|
|
||||||
</svg>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
/>
|
||||||
</div>
|
{option.text}
|
||||||
|
</CommandItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</CommandGroup>
|
||||||
) : (
|
</CommandList>
|
||||||
<div className="py-6 text-center text-sm">
|
</Command>
|
||||||
{t("noResults")}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useMemo } from "react";
|
import React from "react";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { type VariantProps } from "class-variance-authority";
|
import { type VariantProps } from "class-variance-authority";
|
||||||
@@ -434,14 +434,6 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(
|
|||||||
// const filteredAutocompleteOptions = autocompleteFilter
|
// const filteredAutocompleteOptions = autocompleteFilter
|
||||||
// ? autocompleteOptions?.filter((option) => autocompleteFilter(option.text))
|
// ? autocompleteOptions?.filter((option) => autocompleteFilter(option.text))
|
||||||
// : autocompleteOptions;
|
// : autocompleteOptions;
|
||||||
const filteredAutocompleteOptions = useMemo(() => {
|
|
||||||
return (autocompleteOptions || []).filter((option) =>
|
|
||||||
option.text
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(inputValue ? inputValue.toLowerCase() : "")
|
|
||||||
);
|
|
||||||
}, [inputValue, autocompleteOptions]);
|
|
||||||
|
|
||||||
const displayedTags = sortTags ? [...tags].sort() : tags;
|
const displayedTags = sortTags ? [...tags].sort() : tags;
|
||||||
|
|
||||||
const truncatedTags = truncate
|
const truncatedTags = truncate
|
||||||
@@ -571,9 +563,9 @@ const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(
|
|||||||
tags={tags}
|
tags={tags}
|
||||||
setTags={setTags}
|
setTags={setTags}
|
||||||
setInputValue={setInputValue}
|
setInputValue={setInputValue}
|
||||||
autocompleteOptions={
|
autocompleteOptions={(autocompleteOptions ||
|
||||||
filteredAutocompleteOptions as Tag[]
|
[]) as Tag[]}
|
||||||
}
|
filterQuery={inputValue}
|
||||||
setTagCount={setTagCount}
|
setTagCount={setTagCount}
|
||||||
maxTags={maxTags}
|
maxTags={maxTags}
|
||||||
onTagAdd={onTagAdd}
|
onTagAdd={onTagAdd}
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverAnchor,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger
|
||||||
|
} from "../ui/popover";
|
||||||
import { TagInputStyleClassesProps, type Tag as TagType } from "./tag-input";
|
import { TagInputStyleClassesProps, type Tag as TagType } from "./tag-input";
|
||||||
import { TagList, TagListProps } from "./tag-list";
|
import { TagList, TagListProps } from "./tag-list";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
@@ -33,33 +38,27 @@ export const TagPopover: React.FC<TagPopoverProps> = ({
|
|||||||
...tagProps
|
...tagProps
|
||||||
}) => {
|
}) => {
|
||||||
const triggerContainerRef = useRef<HTMLDivElement | null>(null);
|
const triggerContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
||||||
const popoverContentRef = useRef<HTMLDivElement | null>(null);
|
const popoverContentRef = useRef<HTMLDivElement | null>(null);
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
const [popoverWidth, setPopoverWidth] = useState<number>(0);
|
const [popoverWidth, setPopoverWidth] = useState<number>(0);
|
||||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||||
const [inputFocused, setInputFocused] = useState(false);
|
const [inputFocused, setInputFocused] = useState(false);
|
||||||
const [sideOffset, setSideOffset] = useState<number>(0);
|
|
||||||
|
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
if (triggerContainerRef.current && triggerRef.current) {
|
if (triggerContainerRef.current) {
|
||||||
setPopoverWidth(triggerContainerRef.current.offsetWidth);
|
setPopoverWidth(triggerContainerRef.current.offsetWidth);
|
||||||
setSideOffset(
|
|
||||||
triggerContainerRef.current.offsetWidth -
|
|
||||||
triggerRef?.current?.offsetWidth
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleResize(); // Call on mount and layout changes
|
handleResize();
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize); // Adjust on window resize
|
window.addEventListener("resize", handleResize);
|
||||||
return () => window.removeEventListener("resize", handleResize);
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
}, [triggerContainerRef, triggerRef]);
|
}, []);
|
||||||
|
|
||||||
// Close the popover when clicking outside of it
|
// Close the popover when clicking outside of it
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -135,52 +134,54 @@ export const TagPopover: React.FC<TagPopoverProps> = ({
|
|||||||
onOpenChange={handleOpenChange}
|
onOpenChange={handleOpenChange}
|
||||||
modal={usePortal}
|
modal={usePortal}
|
||||||
>
|
>
|
||||||
<div
|
<PopoverAnchor asChild>
|
||||||
className="relative flex items-center rounded-md border border-input bg-transparent pr-3"
|
<div
|
||||||
ref={triggerContainerRef}
|
className="relative flex items-center rounded-md border border-input bg-transparent pr-3"
|
||||||
>
|
ref={triggerContainerRef}
|
||||||
{React.cloneElement(children as React.ReactElement<any>, {
|
>
|
||||||
onFocus: handleInputFocus,
|
{React.cloneElement(children as React.ReactElement<any>, {
|
||||||
onBlur: handleInputBlur,
|
onFocus: handleInputFocus,
|
||||||
ref: inputRef
|
onBlur: handleInputBlur,
|
||||||
})}
|
ref: inputRef
|
||||||
<PopoverTrigger asChild>
|
})}
|
||||||
<Button
|
<PopoverTrigger asChild>
|
||||||
ref={triggerRef}
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
className={cn(
|
className={cn(
|
||||||
`hover:bg-transparent`,
|
`hover:bg-transparent`,
|
||||||
classStyleProps?.popoverClasses?.popoverTrigger
|
classStyleProps?.popoverClasses?.popoverTrigger
|
||||||
)}
|
)}
|
||||||
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
|
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
className={`lucide lucide-chevron-down h-4 w-4 shrink-0 opacity-50 ${isPopoverOpen ? "rotate-180" : "rotate-0"}`}
|
|
||||||
>
|
>
|
||||||
<path d="m6 9 6 6 6-6"></path>
|
<svg
|
||||||
</svg>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</Button>
|
width="24"
|
||||||
</PopoverTrigger>
|
height="24"
|
||||||
</div>
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
className={`lucide lucide-chevron-down h-4 w-4 shrink-0 opacity-50 ${isPopoverOpen ? "rotate-180" : "rotate-0"}`}
|
||||||
|
>
|
||||||
|
<path d="m6 9 6 6 6-6"></path>
|
||||||
|
</svg>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
</div>
|
||||||
|
</PopoverAnchor>
|
||||||
<PopoverContent
|
<PopoverContent
|
||||||
ref={popoverContentRef}
|
ref={popoverContentRef}
|
||||||
|
align="start"
|
||||||
|
side="bottom"
|
||||||
className={cn(
|
className={cn(
|
||||||
`w-full space-y-3`,
|
`w-full space-y-3`,
|
||||||
classStyleProps?.popoverClasses?.popoverContent
|
classStyleProps?.popoverClasses?.popoverContent
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
marginLeft: `-${sideOffset}px`,
|
|
||||||
width: `${popoverWidth}px`
|
width: `${popoverWidth}px`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user