feat(select): enhance dropdown positioning and filtering functionality
- Introduced a new measureDropdownRect function to accurately position the dropdown menu based on viewport dimensions, improving usability. - Updated the Select component to utilize the new dropdown measurement logic, ensuring consistent placement above or below the trigger. - Refactored panelRect state management to use a more structured DropdownRect type, enhancing type safety and clarity. - Improved event handling for dropdown visibility and positioning during scroll and resize events, ensuring a responsive user experience. - Adjusted styling in related components for better alignment and visual consistency.
This commit is contained in:
@@ -43,16 +43,19 @@ export function TenderDetailsHeader({
|
||||
<div className="flex w-full min-w-0 shrink-0 flex-col">
|
||||
<div
|
||||
className={cn(
|
||||
"relative overflow-hidden rounded-[1.35rem] border border-stroke/55 bg-white/75 p-4 shadow-[0_12px_42px_-14px_rgba(15,23,42,0.14),0_0_0_1px_rgba(87,80,241,0.05)] backdrop-blur-md dark:border-white/[0.09] dark:bg-gradient-to-br dark:from-dark-2/95 dark:via-dark-3/55 dark:to-violet-600/[0.12] dark:shadow-[0_18px_50px_-18px_rgba(0,0,0,0.5),inset_0_1px_0_0_rgba(255,255,255,0.04)] sm:p-5",
|
||||
"relative overflow-hidden rounded-[1.35rem] border border-stroke/55 p-4 sm:p-5",
|
||||
"bg-white/75 shadow-[0_12px_42px_-14px_rgba(15,23,42,0.14),0_0_0_1px_rgba(87,80,241,0.05)] backdrop-blur-md",
|
||||
"dark:border-white/[0.08] dark:bg-transparent dark:bg-gradient-to-br dark:from-dark-2 dark:via-dark-3 dark:to-violet-950/35",
|
||||
"dark:shadow-none dark:backdrop-blur-none",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="pointer-events-none absolute -right-6 -top-10 h-28 w-28 rounded-full bg-gradient-to-br from-primary/25 to-violet-500/10 blur-2xl dark:from-violet-500/30 dark:to-primary/5"
|
||||
className="pointer-events-none absolute -right-6 -top-10 h-28 w-28 rounded-full bg-gradient-to-br from-primary/25 to-violet-500/10 blur-2xl dark:from-violet-500/15 dark:to-transparent dark:opacity-50"
|
||||
aria-hidden
|
||||
/>
|
||||
<div className="relative space-y-4">
|
||||
<div className="flex gap-3">
|
||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl bg-gradient-to-br from-primary/20 to-violet-600/10 text-primary shadow-inner ring-1 ring-primary/15 dark:from-primary/30 dark:to-violet-500/15 dark:ring-white/10">
|
||||
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl bg-gradient-to-br from-primary/20 to-violet-600/10 text-primary shadow-inner ring-1 ring-primary/15 dark:from-primary/30 dark:to-violet-500/15 dark:shadow-none dark:ring-white/10">
|
||||
<GlobeIcon className="h-5 w-5 opacity-95" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 pt-0.5">
|
||||
@@ -79,7 +82,7 @@ export function TenderDetailsHeader({
|
||||
className={cn(
|
||||
"w-full shrink-0 space-y-0",
|
||||
"[&_label]:sr-only",
|
||||
"[&_button]:h-11 [&_button]:min-h-[2.75rem] [&_button]:rounded-full [&_button]:border-stroke/60 [&_button]:bg-white/95 [&_button]:py-0 [&_button]:text-sm [&_button]:shadow-sm dark:[&_button]:border-white/10 dark:[&_button]:bg-dark-2/92",
|
||||
"[&_button]:h-11 [&_button]:min-h-[2.75rem] [&_button]:rounded-full [&_button]:border-stroke/60 [&_button]:bg-white/95 [&_button]:py-0 [&_button]:text-sm [&_button]:shadow-sm dark:[&_button]:border-white/10 dark:[&_button]:bg-dark-2 dark:[&_button]:shadow-none",
|
||||
)}
|
||||
value={selectedLanguage}
|
||||
onChange={onLanguageChange as LanguageSelectOnChange}
|
||||
|
||||
@@ -28,6 +28,50 @@ import {
|
||||
|
||||
export type { SelectSearchMode } from "./use-select-server-search";
|
||||
|
||||
const DROPDOWN_GAP_PX = 8;
|
||||
const DROPDOWN_MIN_WIDTH_PX = 200;
|
||||
const DROPDOWN_MAX_HEIGHT_PX = 352;
|
||||
|
||||
type DropdownPlacement = "above" | "below";
|
||||
|
||||
type DropdownRect = {
|
||||
top: number;
|
||||
left: number;
|
||||
width: number;
|
||||
placement: DropdownPlacement;
|
||||
};
|
||||
|
||||
/** Anchor a fixed portal menu to the trigger; avoids native `<select>` popover bugs inside transformed ancestors. */
|
||||
function measureDropdownRect(trigger: HTMLElement): DropdownRect {
|
||||
const r = trigger.getBoundingClientRect();
|
||||
const width = Math.max(r.width, DROPDOWN_MIN_WIDTH_PX);
|
||||
|
||||
const vv = window.visualViewport;
|
||||
const viewportLeft = vv?.offsetLeft ?? 0;
|
||||
const viewportTop = vv?.offsetTop ?? 0;
|
||||
const viewportWidth = vv?.width ?? window.innerWidth;
|
||||
const viewportHeight = vv?.height ?? window.innerHeight;
|
||||
|
||||
const left = Math.min(
|
||||
Math.max(viewportLeft + 8, r.left),
|
||||
viewportLeft + viewportWidth - width - 8,
|
||||
);
|
||||
|
||||
const spaceBelow = viewportTop + viewportHeight - r.bottom - DROPDOWN_GAP_PX;
|
||||
const spaceAbove = r.top - viewportTop - DROPDOWN_GAP_PX;
|
||||
const placement: DropdownPlacement =
|
||||
spaceBelow >= Math.min(DROPDOWN_MAX_HEIGHT_PX, 160) || spaceBelow >= spaceAbove
|
||||
? "below"
|
||||
: "above";
|
||||
|
||||
const top =
|
||||
placement === "below"
|
||||
? r.bottom + DROPDOWN_GAP_PX
|
||||
: r.top - DROPDOWN_GAP_PX;
|
||||
|
||||
return { top, left, width, placement };
|
||||
}
|
||||
|
||||
/** Match options by label or value; exact / prefix matches rank higher. */
|
||||
export function filterSelectItems(
|
||||
items: readonly ILabelValue[],
|
||||
@@ -148,11 +192,7 @@ export function Select<T extends FieldValues>({
|
||||
const [open, setOpen] = useState(false);
|
||||
const [query, setQuery] = useState("");
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [panelRect, setPanelRect] = useState<{
|
||||
top: number;
|
||||
left: number;
|
||||
width: number;
|
||||
} | null>(null);
|
||||
const [panelRect, setPanelRect] = useState<DropdownRect | null>(null);
|
||||
|
||||
const error = errors && errors[name];
|
||||
|
||||
@@ -196,13 +236,7 @@ export function Select<T extends FieldValues>({
|
||||
const syncPanelPosition = useCallback(() => {
|
||||
const el = triggerRef.current;
|
||||
if (!el) return;
|
||||
const r = el.getBoundingClientRect();
|
||||
const gap = 8;
|
||||
setPanelRect({
|
||||
top: r.bottom + gap,
|
||||
left: r.left,
|
||||
width: r.width,
|
||||
});
|
||||
setPanelRect(measureDropdownRect(el));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -210,23 +244,32 @@ export function Select<T extends FieldValues>({
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!searchable || !open) return;
|
||||
if (!open) return;
|
||||
syncPanelPosition();
|
||||
}, [open, searchable, syncPanelPosition]);
|
||||
const id = window.requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => syncPanelPosition());
|
||||
});
|
||||
return () => window.cancelAnimationFrame(id);
|
||||
}, [open, syncPanelPosition]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchable || !open) return;
|
||||
if (!open) return;
|
||||
const onScrollOrResize = () => syncPanelPosition();
|
||||
window.addEventListener("scroll", onScrollOrResize, true);
|
||||
window.addEventListener("resize", onScrollOrResize);
|
||||
const vv = window.visualViewport;
|
||||
vv?.addEventListener("resize", onScrollOrResize);
|
||||
vv?.addEventListener("scroll", onScrollOrResize);
|
||||
return () => {
|
||||
window.removeEventListener("scroll", onScrollOrResize, true);
|
||||
window.removeEventListener("resize", onScrollOrResize);
|
||||
vv?.removeEventListener("resize", onScrollOrResize);
|
||||
vv?.removeEventListener("scroll", onScrollOrResize);
|
||||
};
|
||||
}, [open, searchable, syncPanelPosition]);
|
||||
}, [open, syncPanelPosition]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchable || !open) return;
|
||||
if (!open) return;
|
||||
const onPointerDown = (e: MouseEvent | TouchEvent) => {
|
||||
const t = e.target as Node;
|
||||
if (rootRef.current?.contains(t)) return;
|
||||
@@ -241,7 +284,7 @@ export function Select<T extends FieldValues>({
|
||||
document.removeEventListener("mousedown", onPointerDown);
|
||||
document.removeEventListener("touchstart", onPointerDown);
|
||||
};
|
||||
}, [open, searchable, listboxId]);
|
||||
}, [open, listboxId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchable || !open) return;
|
||||
@@ -280,19 +323,9 @@ export function Select<T extends FieldValues>({
|
||||
triggerRef.current?.focus();
|
||||
};
|
||||
|
||||
const selectSharedClasses = cn(
|
||||
"w-full appearance-none rounded-xl border border-stroke/70 bg-white/90 px-5.5 py-3 shadow-[0_1px_2px_rgba(16,24,40,0.04),0_8px_20px_rgba(16,24,40,0.04)] outline-none backdrop-blur-[1px] transition-all duration-200 focus:border-primary/70 focus:bg-white focus:shadow-[0_0_0_2px_rgba(59,130,246,0.14),0_10px_24px_rgba(59,130,246,0.08)] active:border-primary/70 dark:border-dark-3/80 dark:bg-dark-2/85 dark:focus:border-primary/80 dark:focus:shadow-[0_0_0_2px_rgba(59,130,246,0.2),0_10px_24px_rgba(15,23,42,0.45)] [&>option]:text-dark-5 dark:[&>option]:text-dark-6",
|
||||
isOptionSelected && "text-dark dark:text-white",
|
||||
disabled &&
|
||||
"cursor-not-allowed opacity-60 hover:border-stroke/70 dark:hover:border-dark-3/80",
|
||||
error &&
|
||||
"border-red/80 focus:border-red focus:shadow-[0_0_0_2px_rgba(239,68,68,0.16),0_10px_24px_rgba(239,68,68,0.1)] dark:border-red/80 dark:focus:shadow-[0_0_0_2px_rgba(239,68,68,0.24),0_10px_24px_rgba(127,29,29,0.45)]",
|
||||
prefixIcon && "pl-11.5",
|
||||
clearable && value && "pr-20",
|
||||
);
|
||||
const dropdownItems = searchable ? listItems : items;
|
||||
|
||||
const dropdown =
|
||||
searchable &&
|
||||
mounted &&
|
||||
open &&
|
||||
panelRect &&
|
||||
@@ -306,17 +339,13 @@ export function Select<T extends FieldValues>({
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: panelRect.top,
|
||||
left: (() => {
|
||||
const w = Math.max(panelRect.width, 200);
|
||||
if (typeof window === "undefined") return panelRect.left;
|
||||
return Math.min(
|
||||
Math.max(8, panelRect.left),
|
||||
window.innerWidth - w - 8,
|
||||
);
|
||||
})(),
|
||||
width: Math.max(panelRect.width, 200),
|
||||
left: panelRect.left,
|
||||
width: panelRect.width,
|
||||
transform:
|
||||
panelRect.placement === "above" ? "translateY(-100%)" : undefined,
|
||||
}}
|
||||
>
|
||||
{searchable ? (
|
||||
<div className="border-b border-stroke/50 p-2 dark:border-white/[0.08]">
|
||||
<label htmlFor={`${listboxId}-search`} className="sr-only">
|
||||
{searchPlaceholder}
|
||||
@@ -342,22 +371,23 @@ export function Select<T extends FieldValues>({
|
||||
className="w-full rounded-lg border border-stroke/60 bg-white/95 px-3 py-2 text-sm text-dark outline-none ring-primary/20 placeholder:text-dark-5 focus:border-primary/60 focus:ring-2 dark:border-white/10 dark:bg-dark-3/90 dark:text-white dark:placeholder:text-dark-6"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
<ul className="max-h-[min(18rem,calc(100vh-10rem))] overflow-y-auto overscroll-contain p-1.5">
|
||||
{isSearchLoading ? (
|
||||
{searchable && isSearchLoading ? (
|
||||
<li className="flex items-center justify-center gap-2 px-3 py-6 text-sm text-dark-5 dark:text-dark-6">
|
||||
<span className="inline-flex h-4 w-4 animate-spin rounded-full border-2 border-primary/25 border-t-primary" />
|
||||
Searching…
|
||||
</li>
|
||||
) : isSearchError ? (
|
||||
) : searchable && isSearchError ? (
|
||||
<li className="px-3 py-6 text-center text-sm text-error">
|
||||
Could not load options. Try again.
|
||||
</li>
|
||||
) : listItems.length === 0 ? (
|
||||
) : dropdownItems.length === 0 ? (
|
||||
<li className="px-3 py-6 text-center text-sm text-dark-5 dark:text-dark-6">
|
||||
No matches.
|
||||
{searchable ? "No matches." : "No options."}
|
||||
</li>
|
||||
) : (
|
||||
listItems.map((item) => {
|
||||
dropdownItems.map((item) => {
|
||||
const v = String(item.value);
|
||||
const isActive = v === String(value);
|
||||
return (
|
||||
@@ -370,16 +400,19 @@ export function Select<T extends FieldValues>({
|
||||
data-value={v}
|
||||
onClick={() => pickItem(v)}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-2 rounded-lg px-3 py-2 text-start text-sm transition",
|
||||
"flex w-full items-center gap-2 rounded-lg px-3 py-2 text-start text-sm transition",
|
||||
searchable ? "justify-between" : "justify-start",
|
||||
isActive
|
||||
? "bg-primary/12 font-medium text-primary dark:bg-primary/20 dark:text-white"
|
||||
: "text-dark hover:bg-stroke/30 dark:text-white dark:hover:bg-white/[0.06]",
|
||||
)}
|
||||
>
|
||||
<span className="min-w-0 truncate">{item.label}</span>
|
||||
{searchable ? (
|
||||
<span className="shrink-0 font-mono text-xs text-dark-5 dark:text-dark-6">
|
||||
{v}
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
</li>
|
||||
);
|
||||
@@ -390,7 +423,6 @@ export function Select<T extends FieldValues>({
|
||||
document.body,
|
||||
);
|
||||
|
||||
if (searchable) {
|
||||
return (
|
||||
<div ref={rootRef} className={cn("space-y-3", className)}>
|
||||
<label
|
||||
@@ -410,6 +442,7 @@ export function Select<T extends FieldValues>({
|
||||
disabled={disabled}
|
||||
tabIndex={-1}
|
||||
aria-hidden
|
||||
data-cy={dataCy}
|
||||
className="sr-only"
|
||||
onChange={(e) => {
|
||||
if (controlledValue === undefined) {
|
||||
@@ -425,7 +458,7 @@ export function Select<T extends FieldValues>({
|
||||
{placeholder}
|
||||
</option>
|
||||
)}
|
||||
{listItems.map((item) => (
|
||||
{items.map((item) => (
|
||||
<option key={String(item.value)} value={String(item.value)}>
|
||||
{item.label}
|
||||
</option>
|
||||
@@ -446,7 +479,6 @@ export function Select<T extends FieldValues>({
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={open}
|
||||
aria-controls={open ? listboxId : undefined}
|
||||
data-cy={dataCy}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
setOpen((o) => {
|
||||
@@ -476,6 +508,7 @@ export function Select<T extends FieldValues>({
|
||||
>
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
{value ? (
|
||||
searchable ? (
|
||||
<>
|
||||
<span className="text-dark dark:text-white">
|
||||
{selectedLabel}
|
||||
@@ -484,6 +517,11 @@ export function Select<T extends FieldValues>({
|
||||
{value}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-dark dark:text-white">
|
||||
{selectedLabel}
|
||||
</span>
|
||||
)
|
||||
) : (
|
||||
placeholder || "Select…"
|
||||
)}
|
||||
@@ -528,88 +566,3 @@ export function Select<T extends FieldValues>({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("space-y-3", className)}>
|
||||
<label
|
||||
htmlFor={id}
|
||||
className="block text-body-sm font-semibold capitalize text-dark dark:text-white"
|
||||
>
|
||||
{label}
|
||||
{required && <span className="ml-1 select-none text-error">*</span>}
|
||||
</label>
|
||||
|
||||
<div className="relative">
|
||||
{prefixIcon && (
|
||||
<div className="absolute left-4 top-1/2 -translate-y-1/2">
|
||||
{prefixIcon}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<select
|
||||
{...props}
|
||||
id={id}
|
||||
name={name}
|
||||
ref={ref}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
if (controlledValue === undefined) {
|
||||
setValue(e.target.value);
|
||||
}
|
||||
setIsOptionSelected(!!e.target.value);
|
||||
onChange?.(e);
|
||||
}}
|
||||
onBlur={onBlur}
|
||||
data-cy={dataCy}
|
||||
className={selectSharedClasses}
|
||||
>
|
||||
{placeholder && (
|
||||
<option value="" disabled>
|
||||
{placeholder}
|
||||
</option>
|
||||
)}
|
||||
|
||||
{items.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
{clearable && value && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClear}
|
||||
data-cy={dataCy ? `${dataCy}-clear` : undefined}
|
||||
className="absolute right-10 top-1/2 -translate-y-1/2 text-dark-5 transition hover:scale-110 hover:text-dark dark:text-dark-6 dark:hover:text-white"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 4L4 12M4 4L12 12"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<ChevronUpIcon className="pointer-events-none absolute right-4 top-1/2 -translate-y-1/2 rotate-180" />
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<p className="text-red mt-1.5 text-sm font-medium">
|
||||
{error.message as string}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Select } from "@/components/FormElements/select";
|
||||
import { Button } from "@/components/ui-elements/button";
|
||||
import InlineListFiltersPanel from "@/components/ui/InlineListFiltersPanel";
|
||||
import { Currencies } from "@/constants/enums";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
FieldValues,
|
||||
UseFormHandleSubmit,
|
||||
@@ -12,7 +13,6 @@ import {
|
||||
UseFormSetValue,
|
||||
UseFormWatch,
|
||||
} from "react-hook-form";
|
||||
import { useMemo } from "react";
|
||||
|
||||
interface CompanyListFiltersProps {
|
||||
filterRegister: UseFormRegister<FieldValues>;
|
||||
@@ -60,7 +60,6 @@ const CompanyListFilters = ({
|
||||
return (
|
||||
<InlineListFiltersPanel
|
||||
title="Filter companies"
|
||||
description="Filter by identity, currency, or headcount — inline, no popup."
|
||||
activeFilterCount={activeCount}
|
||||
expandAriaLabel="Expand company filters"
|
||||
collapseAriaLabel="Collapse company filters"
|
||||
|
||||
@@ -175,7 +175,7 @@ export default function InlineListFiltersPanel({
|
||||
aria-label={panelOpen ? collapseAriaLabel : expandAriaLabel}
|
||||
data-cy={panelToggleDataCy}
|
||||
className={cn(
|
||||
"flex w-full flex-wrap items-start gap-x-3 gap-y-3 px-4 py-4 text-start transition-colors sm:items-center sm:justify-between sm:gap-4 sm:px-6 sm:py-5",
|
||||
"flex w-full flex-col gap-3 px-4 py-4 text-start transition-colors sm:flex-row sm:items-center sm:justify-between sm:gap-4 sm:px-6 sm:py-5",
|
||||
"rounded-[1.65rem] border border-transparent",
|
||||
"hover:border-stroke/50 hover:bg-white/70 dark:hover:border-white/[0.08] dark:hover:bg-dark-3/50",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-dark-2",
|
||||
@@ -187,8 +187,8 @@ export default function InlineListFiltersPanel({
|
||||
<span className="mt-0.5 flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-gradient-to-br from-primary to-violet-600 text-white sm:mt-0">
|
||||
<SearchIcon className="h-5 w-5 opacity-95" />
|
||||
</span>
|
||||
<div className="min-w-0">
|
||||
<span className="block text-lg font-bold tracking-tight text-dark dark:text-white">
|
||||
<div className="min-w-0 flex-1">
|
||||
<span className="block text-balance text-lg font-bold leading-snug tracking-tight text-dark dark:text-white">
|
||||
{title}
|
||||
</span>
|
||||
<IsVisible condition={!!description}>
|
||||
@@ -198,7 +198,7 @@ export default function InlineListFiltersPanel({
|
||||
</IsVisible>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-auto flex shrink-0 items-center gap-2">
|
||||
<div className="flex w-full min-w-0 items-center justify-between gap-3 pl-14 sm:ml-auto sm:w-auto sm:shrink-0 sm:justify-end sm:pl-0">
|
||||
{activeFilterCount > 0 ? (
|
||||
<div className="flex items-center gap-2 rounded-full border border-primary/25 bg-primary/10 px-3 py-1.5 dark:border-primary/35 dark:bg-primary/15">
|
||||
<span className="text-xs font-semibold uppercase tracking-wide text-primary">
|
||||
@@ -206,7 +206,7 @@ export default function InlineListFiltersPanel({
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="inline-flex items-center rounded-full border border-stroke/50 bg-white/60 px-3 py-1.5 text-xs font-medium text-dark-5 dark:border-white/10 dark:bg-dark-3/80 dark:text-dark-6">
|
||||
<span className="inline-flex min-w-0 max-w-[calc(100%-2rem)] items-center truncate rounded-full border border-stroke/50 bg-white/60 px-3 py-1.5 text-xs font-medium text-dark-5 dark:border-white/10 dark:bg-dark-3/80 dark:text-dark-6">
|
||||
No filters applied
|
||||
</span>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user