diff --git a/surfsense_web/app/dashboard/[search_space_id]/settings/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/settings/page.tsx index d999aacfb..e385e3983 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/settings/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/settings/page.tsx @@ -1,14 +1,6 @@ "use client"; -import { - Bot, - Brain, - FileText, - Globe, - ImageIcon, - MessageSquare, - Shield, -} from "lucide-react"; +import { Bot, Brain, FileText, Globe, ImageIcon, MessageSquare, Shield } from "lucide-react"; import { useParams, useRouter, useSearchParams } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useEffect } from "react"; @@ -19,12 +11,7 @@ import { LLMRoleManager } from "@/components/settings/llm-role-manager"; import { ModelConfigManager } from "@/components/settings/model-config-manager"; import { PromptConfigManager } from "@/components/settings/prompt-config-manager"; import { RolesManager } from "@/components/settings/roles-manager"; -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@/components/ui/animated-tabs"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/animated-tabs"; import { trackSettingsViewed } from "@/lib/posthog/events"; const VALID_TABS = [ diff --git a/surfsense_web/app/dashboard/[search_space_id]/team/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/team/page.tsx index cd413f57e..831a48ed2 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/team/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/team/page.tsx @@ -292,10 +292,7 @@ export default function TeamManagementPage() { {SKELETON_KEYS.map((id) => ( - +
@@ -546,9 +543,9 @@ function MemberRow({
- - {member.user_last_login ? formatRelativeDate(member.user_last_login) : "Never"} - + + {member.user_last_login ? formatRelativeDate(member.user_last_login) : "Never"} + {showActions ? ( @@ -608,12 +605,10 @@ function MemberRow({ )} - - - router.push(`/dashboard/${searchSpaceId}/settings?tab=team-roles`) - } - > + + router.push(`/dashboard/${searchSpaceId}/settings?tab=team-roles`)} + > Manage Roles @@ -833,21 +828,21 @@ function CreateInviteDialog({ - - - - + + + + )} @@ -877,8 +872,8 @@ function AllInvitesDialog({ return ( - + + {copied ? t("copied") : t("copy")} + + + + ) : ( +

{t("no_api_key")}

+ )} + + +
+

{t("usage_title")}

+

{t("usage_description")}

-

- {apiKey} -

+
+								Authorization: Bearer {apiKey || "YOUR_API_KEY"}
+							
@@ -59,47 +95,21 @@ export function ApiKeyContent() { - {copied ? t("copied") : t("copy")} + {copiedUsage ? t("copied") : t("copy")}
- ) : ( -

{t("no_api_key")}

- )} -
- -
-

{t("usage_title")}

-

{t("usage_description")}

-
-
-
-						Authorization: Bearer {apiKey || "YOUR_API_KEY"}
-					
- - - - - - {copiedUsage ? t("copied") : t("copy")} - - -
-
); diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ProfileContent.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ProfileContent.tsx index c2736f208..49f23e85a 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ProfileContent.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/components/ProfileContent.tsx @@ -105,9 +105,7 @@ export function ProfileContent() { value={displayName} onChange={(e) => setDisplayName(e.target.value)} /> -

- {t("profile_display_name_hint")} -

+

{t("profile_display_name_hint")}

diff --git a/surfsense_web/app/dashboard/[search_space_id]/user-settings/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/user-settings/page.tsx index 53f1182eb..8cdfdc178 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/user-settings/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/user-settings/page.tsx @@ -1,15 +1,10 @@ "use client"; -import { UserKey, User } from "lucide-react"; -import { useTranslations } from "next-intl"; +import { User, UserKey } from "lucide-react"; import { useRouter, useSearchParams } from "next/navigation"; +import { useTranslations } from "next-intl"; import { useCallback } from "react"; -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@/components/ui/animated-tabs"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/animated-tabs"; import { ApiKeyContent } from "./components/ApiKeyContent"; import { ProfileContent } from "./components/ProfileContent"; diff --git a/surfsense_web/components/homepage/github-stars-badge.tsx b/surfsense_web/components/homepage/github-stars-badge.tsx index 8638a9293..56abdc464 100644 --- a/surfsense_web/components/homepage/github-stars-badge.tsx +++ b/surfsense_web/components/homepage/github-stars-badge.tsx @@ -1,17 +1,17 @@ "use client"; -import * as React from "react"; +import { IconBrandGithub } from "@tabler/icons-react"; +import { StarIcon } from "lucide-react"; +import type { HTMLMotionProps, UseInViewOptions } from "motion/react"; import { - motion, AnimatePresence, + motion, useInView, useMotionValue, useSpring, useTransform, } from "motion/react"; -import type { HTMLMotionProps, UseInViewOptions } from "motion/react"; -import { StarIcon } from "lucide-react"; -import { IconBrandGithub } from "@tabler/icons-react"; +import * as React from "react"; import { cn } from "@/lib/utils"; // --------------------------------------------------------------------------- diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx index 17856c6fb..0c7181ee0 100644 --- a/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx +++ b/surfsense_web/components/layout/ui/sidebar/SidebarUserProfile.tsx @@ -1,6 +1,17 @@ "use client"; -import { Check, ChevronUp, ExternalLink, Info, Languages, Laptop, LogOut, Moon, Settings, Sun } from "lucide-react"; +import { + Check, + ChevronUp, + ExternalLink, + Info, + Languages, + Laptop, + LogOut, + Moon, + Settings, + Sun, +} from "lucide-react"; import Image from "next/image"; import { useTranslations } from "next-intl"; import { useState } from "react"; @@ -165,21 +176,21 @@ export function SidebarUserProfile({ if (isCollapsed) { return (
- - - - + + + + @@ -276,7 +287,9 @@ export function SidebarUserProfile({ ))} -

v{APP_VERSION}

+

+ v{APP_VERSION} +

@@ -419,7 +432,9 @@ export function SidebarUserProfile({ ))} -

v{APP_VERSION}

+

+ v{APP_VERSION} +

diff --git a/surfsense_web/components/sources/DocumentUploadTab.tsx b/surfsense_web/components/sources/DocumentUploadTab.tsx index 7d8f44c71..f1c81bbfb 100644 --- a/surfsense_web/components/sources/DocumentUploadTab.tsx +++ b/surfsense_web/components/sources/DocumentUploadTab.tsx @@ -223,7 +223,11 @@ export function DocumentUploadTab({ const rawFiles = files.map((entry) => entry.file); uploadDocuments( - { files: rawFiles, search_space_id: Number(searchSpaceId), should_summarize: shouldSummarize }, + { + files: rawFiles, + search_space_id: Number(searchSpaceId), + should_summarize: shouldSummarize, + }, { onSuccess: () => { clearInterval(progressInterval); diff --git a/surfsense_web/components/tool-ui/linear/update-linear-issue.tsx b/surfsense_web/components/tool-ui/linear/update-linear-issue.tsx index 076896549..8a68a1176 100644 --- a/surfsense_web/components/tool-ui/linear/update-linear-issue.tsx +++ b/surfsense_web/components/tool-ui/linear/update-linear-issue.tsx @@ -1,14 +1,7 @@ "use client"; import { makeAssistantToolUI } from "@assistant-ui/react"; -import { - AlertTriangleIcon, - CheckIcon, - InfoIcon, - Loader2Icon, - Pen, - XIcon, -} from "lucide-react"; +import { AlertTriangleIcon, CheckIcon, InfoIcon, Loader2Icon, Pen, XIcon } from "lucide-react"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; diff --git a/surfsense_web/components/ui/animated-tabs.tsx b/surfsense_web/components/ui/animated-tabs.tsx index ea45759c4..7d39ce1dd 100644 --- a/surfsense_web/components/ui/animated-tabs.tsx +++ b/surfsense_web/components/ui/animated-tabs.tsx @@ -1,37 +1,35 @@ -"use client" +"use client"; import React, { - createContext, - forwardRef, - useCallback, - useContext, - useEffect, - useRef, - useState, - type ReactNode, -} from "react" + createContext, + forwardRef, + type ReactNode, + useCallback, + useContext, + useEffect, + useRef, + useState, +} from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; /* ─────────────────────────── Context (replaces cloneElement) ─────────────────────────── */ interface TabsContextValue { - activeValue: string - onValueChange: (value: string) => void + activeValue: string; + onValueChange: (value: string) => void; } -const TabsContext = createContext(null) +const TabsContext = createContext(null); function useTabsContext() { - const ctx = useContext(TabsContext) - if (!ctx) { - throw new Error( - "AnimatedTabs compound components must be rendered inside " - ) - } - return ctx + const ctx = useContext(TabsContext); + if (!ctx) { + throw new Error("AnimatedTabs compound components must be rendered inside "); + } + return ctx; } /* ─────────────────────────── @@ -39,538 +37,515 @@ function useTabsContext() { ─────────────────────────── */ const SIZE_CLASSES = { - sm: "h-[32px] text-sm", - md: "h-[40px] text-base", - lg: "h-[48px] text-lg", -} as const + sm: "h-[32px] text-sm", + md: "h-[40px] text-base", + lg: "h-[48px] text-lg", +} as const; const VARIANT_CLASSES = { - default: "", - pills: "rounded-full", - underlined: "", -} as const + default: "", + pills: "rounded-full", + underlined: "", +} as const; const ACTIVE_INDICATOR_CLASSES = { - default: "h-[4px] bg-primary dark:bg-primary", - pills: "hidden", - underlined: "h-[4px] bg-primary dark:bg-primary", -} as const + default: "h-[4px] bg-primary dark:bg-primary", + pills: "hidden", + underlined: "h-[4px] bg-primary dark:bg-primary", +} as const; const HOVER_INDICATOR_CLASSES = { - default: "bg-muted dark:bg-muted rounded-[6px]", - pills: "bg-muted dark:bg-muted rounded-full", - underlined: "bg-muted dark:bg-muted rounded-[6px]", -} as const + default: "bg-muted dark:bg-muted rounded-[6px]", + pills: "bg-muted dark:bg-muted rounded-full", + underlined: "bg-muted dark:bg-muted rounded-[6px]", +} as const; /* ─────────────────────────── XScrollable (internal) ─────────────────────────── */ const XScrollable = forwardRef< - HTMLDivElement, - { - className?: string - children?: ReactNode - showScrollbar?: boolean - contentClassName?: string - } & React.HTMLAttributes + HTMLDivElement, + { + className?: string; + children?: ReactNode; + showScrollbar?: boolean; + contentClassName?: string; + } & React.HTMLAttributes >(({ className, children, showScrollbar = true, contentClassName, ...props }, ref) => { - const scrollRef = useRef(null) - const dragging = useRef(false) - const startX = useRef(0) - const startScrollLeft = useRef(0) - const [scrollPos, setScrollPos] = useState<"start" | "middle" | "end">("start") + const scrollRef = useRef(null); + const dragging = useRef(false); + const startX = useRef(0); + const startScrollLeft = useRef(0); + const [scrollPos, setScrollPos] = useState<"start" | "middle" | "end">("start"); - const updateScrollPos = useCallback(() => { - const el = scrollRef.current - if (!el) return - const canScroll = el.scrollWidth > el.clientWidth + 1 - if (!canScroll) { - setScrollPos("start") - return - } - const atStart = el.scrollLeft <= 2 - const atEnd = el.scrollWidth - el.scrollLeft - el.clientWidth <= 2 - setScrollPos(atStart ? "start" : atEnd ? "end" : "middle") - }, []) + const updateScrollPos = useCallback(() => { + const el = scrollRef.current; + if (!el) return; + const canScroll = el.scrollWidth > el.clientWidth + 1; + if (!canScroll) { + setScrollPos("start"); + return; + } + const atStart = el.scrollLeft <= 2; + const atEnd = el.scrollWidth - el.scrollLeft - el.clientWidth <= 2; + setScrollPos(atStart ? "start" : atEnd ? "end" : "middle"); + }, []); - useEffect(() => { - updateScrollPos() - const el = scrollRef.current - if (!el) return - const ro = new ResizeObserver(updateScrollPos) - ro.observe(el) - return () => ro.disconnect() - }, [updateScrollPos]) + useEffect(() => { + updateScrollPos(); + const el = scrollRef.current; + if (!el) return; + const ro = new ResizeObserver(updateScrollPos); + ro.observe(el); + return () => ro.disconnect(); + }, [updateScrollPos]); - const onMouseDown = (e: React.MouseEvent) => { - if (!scrollRef.current) return - dragging.current = true - startX.current = e.clientX - startScrollLeft.current = scrollRef.current.scrollLeft - } - const endDrag = () => { - dragging.current = false - } - const onMouseMove = (e: React.MouseEvent) => { - if (!dragging.current || !scrollRef.current) return - e.preventDefault() - const dx = e.clientX - startX.current - scrollRef.current.scrollLeft = startScrollLeft.current - dx - } + const onMouseDown = (e: React.MouseEvent) => { + if (!scrollRef.current) return; + dragging.current = true; + startX.current = e.clientX; + startScrollLeft.current = scrollRef.current.scrollLeft; + }; + const endDrag = () => { + dragging.current = false; + }; + const onMouseMove = (e: React.MouseEvent) => { + if (!dragging.current || !scrollRef.current) return; + e.preventDefault(); + const dx = e.clientX - startX.current; + scrollRef.current.scrollLeft = startScrollLeft.current - dx; + }; - const onWheel = (e: React.WheelEvent) => { - if (!scrollRef.current) return - const delta = - Math.abs(e.deltaY) > Math.abs(e.deltaX) ? e.deltaY : e.deltaX - if (delta !== 0) { - e.preventDefault() - scrollRef.current.scrollLeft += delta - } - } + const onWheel = (e: React.WheelEvent) => { + if (!scrollRef.current) return; + const delta = Math.abs(e.deltaY) > Math.abs(e.deltaX) ? e.deltaY : e.deltaX; + if (delta !== 0) { + e.preventDefault(); + scrollRef.current.scrollLeft += delta; + } + }; - const handleScroll = useCallback(() => { - updateScrollPos() - }, [updateScrollPos]) + const handleScroll = useCallback(() => { + updateScrollPos(); + }, [updateScrollPos]); - const maskStart = scrollPos === "start" ? "black" : "transparent" - const maskEnd = scrollPos === "end" ? "black" : "transparent" - const maskImage = `linear-gradient(to right, ${maskStart}, black 24px, black calc(100% - 24px), ${maskEnd})` + const maskStart = scrollPos === "start" ? "black" : "transparent"; + const maskEnd = scrollPos === "end" ? "black" : "transparent"; + const maskImage = `linear-gradient(to right, ${maskStart}, black 24px, black calc(100% - 24px), ${maskEnd})`; - return ( - // biome-ignore lint/a11y/noStaticElementInteractions: drag-scroll container needs mouse events -
- {/* biome-ignore lint/a11y/noStaticElementInteractions: drag-scroll requires onMouseDown */} -
- {children} -
-
- ) -}) -XScrollable.displayName = "XScrollable" + return ( + // biome-ignore lint/a11y/noStaticElementInteractions: drag-scroll container needs mouse events +
+ {/* biome-ignore lint/a11y/noStaticElementInteractions: drag-scroll requires onMouseDown */} +
+ {children} +
+
+ ); +}); +XScrollable.displayName = "XScrollable"; /* ─────────────────────────── Tabs (root) ─────────────────────────── */ const Tabs = forwardRef< - HTMLDivElement, - { - defaultValue?: string - value?: string - onValueChange?: (value: string) => void - className?: string - children?: ReactNode - } ->( - ( - { defaultValue, value, onValueChange, className, children, ...props }, - ref - ) => { - const [activeValue, setActiveValue] = useState(value || defaultValue || "") + HTMLDivElement, + { + defaultValue?: string; + value?: string; + onValueChange?: (value: string) => void; + className?: string; + children?: ReactNode; + } +>(({ defaultValue, value, onValueChange, className, children, ...props }, ref) => { + const [activeValue, setActiveValue] = useState(value || defaultValue || ""); - useEffect(() => { - if (value !== undefined) { - setActiveValue(value) - } - }, [value]) + useEffect(() => { + if (value !== undefined) { + setActiveValue(value); + } + }, [value]); - const handleValueChange = useCallback( - (newValue: string) => { - if (value === undefined) { - setActiveValue(newValue) - } - onValueChange?.(newValue) - }, - [onValueChange, value] - ) + const handleValueChange = useCallback( + (newValue: string) => { + if (value === undefined) { + setActiveValue(newValue); + } + onValueChange?.(newValue); + }, + [onValueChange, value] + ); - return ( - -
- {children} -
-
- ) - } -) -Tabs.displayName = "Tabs" + return ( + +
+ {children} +
+
+ ); +}); +Tabs.displayName = "Tabs"; /* ─────────────────────────── TabsList ─────────────────────────── */ -type TabsListVariant = "default" | "pills" | "underlined" -type TabsListSize = "sm" | "md" | "lg" +type TabsListVariant = "default" | "pills" | "underlined"; +type TabsListSize = "sm" | "md" | "lg"; const TabsList = forwardRef< - HTMLDivElement, - { - className?: string - children?: ReactNode - showHoverEffect?: boolean - showActiveIndicator?: boolean - activeIndicatorPosition?: "top" | "bottom" - activeIndicatorOffset?: number - size?: TabsListSize - variant?: TabsListVariant - stretch?: boolean - ariaLabel?: string - showBottomBorder?: boolean - bottomBorderClassName?: string - activeIndicatorClassName?: string - hoverIndicatorClassName?: string - } + HTMLDivElement, + { + className?: string; + children?: ReactNode; + showHoverEffect?: boolean; + showActiveIndicator?: boolean; + activeIndicatorPosition?: "top" | "bottom"; + activeIndicatorOffset?: number; + size?: TabsListSize; + variant?: TabsListVariant; + stretch?: boolean; + ariaLabel?: string; + showBottomBorder?: boolean; + bottomBorderClassName?: string; + activeIndicatorClassName?: string; + hoverIndicatorClassName?: string; + } >( - ( - { - className, - children, - showHoverEffect = true, - showActiveIndicator = true, - activeIndicatorPosition = "bottom", - activeIndicatorOffset = 0, - size = "sm", - variant = "default", - stretch = false, - ariaLabel = "Tabs", - showBottomBorder = false, - bottomBorderClassName, - activeIndicatorClassName, - hoverIndicatorClassName, - ...props - }, - ref - ) => { - const { activeValue, onValueChange } = useTabsContext() + ( + { + className, + children, + showHoverEffect = true, + showActiveIndicator = true, + activeIndicatorPosition = "bottom", + activeIndicatorOffset = 0, + size = "sm", + variant = "default", + stretch = false, + ariaLabel = "Tabs", + showBottomBorder = false, + bottomBorderClassName, + activeIndicatorClassName, + hoverIndicatorClassName, + ...props + }, + ref + ) => { + const { activeValue, onValueChange } = useTabsContext(); - const [hoveredIndex, setHoveredIndex] = useState(null) - const [hoverStyle, setHoverStyle] = useState({}) - const [activeStyle, setActiveStyle] = useState({ - left: "0px", - width: "0px", - }) - const tabRefs = useRef<(HTMLDivElement | null)[]>([]) - const scrollContainerRef = useRef(null) + const [hoveredIndex, setHoveredIndex] = useState(null); + const [hoverStyle, setHoverStyle] = useState({}); + const [activeStyle, setActiveStyle] = useState({ + left: "0px", + width: "0px", + }); + const tabRefs = useRef<(HTMLDivElement | null)[]>([]); + const scrollContainerRef = useRef(null); - const activeIndex = React.Children.toArray(children).findIndex( - (child) => - React.isValidElement(child) && - (child as React.ReactElement<{ value: string }>).props.value === - activeValue - ) + const activeIndex = React.Children.toArray(children).findIndex( + (child) => + React.isValidElement(child) && + (child as React.ReactElement<{ value: string }>).props.value === activeValue + ); - useEffect(() => { - if (hoveredIndex !== null && showHoverEffect) { - const hoveredElement = tabRefs.current[hoveredIndex] - if (hoveredElement) { - const { offsetLeft, offsetWidth } = hoveredElement - setHoverStyle({ - left: `${offsetLeft}px`, - width: `${offsetWidth}px`, - }) - } - } - }, [hoveredIndex, showHoverEffect]) + useEffect(() => { + if (hoveredIndex !== null && showHoverEffect) { + const hoveredElement = tabRefs.current[hoveredIndex]; + if (hoveredElement) { + const { offsetLeft, offsetWidth } = hoveredElement; + setHoverStyle({ + left: `${offsetLeft}px`, + width: `${offsetWidth}px`, + }); + } + } + }, [hoveredIndex, showHoverEffect]); - const updateActiveIndicator = useCallback(() => { - if (showActiveIndicator && activeIndex >= 0) { - const activeElement = tabRefs.current[activeIndex] - if (activeElement) { - const { offsetLeft, offsetWidth } = activeElement - setActiveStyle({ - left: `${offsetLeft}px`, - width: `${offsetWidth}px`, - }) - } - } - }, [showActiveIndicator, activeIndex]) + const updateActiveIndicator = useCallback(() => { + if (showActiveIndicator && activeIndex >= 0) { + const activeElement = tabRefs.current[activeIndex]; + if (activeElement) { + const { offsetLeft, offsetWidth } = activeElement; + setActiveStyle({ + left: `${offsetLeft}px`, + width: `${offsetWidth}px`, + }); + } + } + }, [showActiveIndicator, activeIndex]); - useEffect(() => { - updateActiveIndicator() - }, [updateActiveIndicator]) + useEffect(() => { + updateActiveIndicator(); + }, [updateActiveIndicator]); - useEffect(() => { - requestAnimationFrame(updateActiveIndicator) - }, [updateActiveIndicator]) + useEffect(() => { + requestAnimationFrame(updateActiveIndicator); + }, [updateActiveIndicator]); - const scrollTabToCenter = useCallback((index: number) => { - const tabElement = tabRefs.current[index] - const scrollContainer = scrollContainerRef.current + const scrollTabToCenter = useCallback((index: number) => { + const tabElement = tabRefs.current[index]; + const scrollContainer = scrollContainerRef.current; - if (tabElement && scrollContainer) { - const containerWidth = scrollContainer.offsetWidth - const tabWidth = tabElement.offsetWidth - const tabLeft = tabElement.offsetLeft - const scrollTarget = tabLeft - containerWidth / 2 + tabWidth / 2 - scrollContainer.scrollTo({ left: scrollTarget, behavior: "smooth" }) - } - }, []) + if (tabElement && scrollContainer) { + const containerWidth = scrollContainer.offsetWidth; + const tabWidth = tabElement.offsetWidth; + const tabLeft = tabElement.offsetLeft; + const scrollTarget = tabLeft - containerWidth / 2 + tabWidth / 2; + scrollContainer.scrollTo({ left: scrollTarget, behavior: "smooth" }); + } + }, []); - const setTabRef = useCallback( - (el: HTMLDivElement | null, index: number) => { - tabRefs.current[index] = el - }, - [] - ) + const setTabRef = useCallback((el: HTMLDivElement | null, index: number) => { + tabRefs.current[index] = el; + }, []); - const handleScrollableRef = useCallback((node: HTMLDivElement | null) => { - if (node) { - const scrollableDiv = node.querySelector( - 'div[class*="overflow-x-auto"]' - ) - if (scrollableDiv) { - scrollContainerRef.current = scrollableDiv as HTMLDivElement - } - } - }, []) + const handleScrollableRef = useCallback((node: HTMLDivElement | null) => { + if (node) { + const scrollableDiv = node.querySelector('div[class*="overflow-x-auto"]'); + if (scrollableDiv) { + scrollContainerRef.current = scrollableDiv as HTMLDivElement; + } + } + }, []); - useEffect(() => { - if (activeIndex >= 0) { - const timer = setTimeout(() => { - scrollTabToCenter(activeIndex) - }, 100) - return () => clearTimeout(timer) - } - }, [activeIndex, scrollTabToCenter]) + useEffect(() => { + if (activeIndex >= 0) { + const timer = setTimeout(() => { + scrollTabToCenter(activeIndex); + }, 100); + return () => clearTimeout(timer); + } + }, [activeIndex, scrollTabToCenter]); - return ( -
- {showBottomBorder && ( -
- )} - -
+ return ( +
+ {showBottomBorder && ( +
+ )} + +
+ {showHoverEffect && ( + + ); + } +); +TabsList.displayName = "TabsList"; /* ─────────────────────────── TabsTrigger ─────────────────────────── */ const TabsTrigger = forwardRef< - HTMLDivElement, - { - value: string - disabled?: boolean - label?: string - className?: string - activeClassName?: string - inactiveClassName?: string - disabledClassName?: string - children?: ReactNode - } + HTMLDivElement, + { + value: string; + disabled?: boolean; + label?: string; + className?: string; + activeClassName?: string; + inactiveClassName?: string; + disabledClassName?: string; + children?: ReactNode; + } >( - ( - { - value, - disabled = false, - label, - className, - activeClassName, - inactiveClassName, - disabledClassName, - children, - ...props - }, - ref - ) => { - return ( -
- {label || children} -
- ) - } -) -TabsTrigger.displayName = "TabsTrigger" + ( + { + value, + disabled = false, + label, + className, + activeClassName, + inactiveClassName, + disabledClassName, + children, + ...props + }, + ref + ) => { + return ( +
+ {label || children} +
+ ); + } +); +TabsTrigger.displayName = "TabsTrigger"; /* ─────────────────────────── TabsContent ─────────────────────────── */ const TabsContent = forwardRef< - HTMLDivElement, - { - value: string - className?: string - children: ReactNode - } ->( - ( - { value, className, children, ...props }, - ref - ) => { - const { activeValue } = useTabsContext() + HTMLDivElement, + { + value: string; + className?: string; + children: ReactNode; + } +>(({ value, className, children, ...props }, ref) => { + const { activeValue } = useTabsContext(); - if (value !== activeValue) return null - return ( -
- {children} -
- ) - } -) -TabsContent.displayName = "TabsContent" + if (value !== activeValue) return null; + return ( +
+ {children} +
+ ); +}); +TabsContent.displayName = "TabsContent"; -export { Tabs, TabsList, TabsTrigger, TabsContent } +export { Tabs, TabsList, TabsTrigger, TabsContent };