From 487a9d56e1a87bb4a51f79bda8e5ab0277895bb7 Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Mon, 27 Apr 2026 12:20:51 +0330 Subject: [PATCH] feat(theme-toggle): implement triple tap gesture for theme switching and enhance sidebar visibility logic --- .../Layouts/header/theme-toggle/index.tsx | 40 +++++++++++++++++++ src/components/Layouts/sidebar/data/index.ts | 10 ++++- src/components/Layouts/sidebar/index.tsx | 35 +++++++++++----- 3 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/components/Layouts/header/theme-toggle/index.tsx b/src/components/Layouts/header/theme-toggle/index.tsx index 49274f3..9386ed0 100644 --- a/src/components/Layouts/header/theme-toggle/index.tsx +++ b/src/components/Layouts/header/theme-toggle/index.tsx @@ -19,6 +19,7 @@ export function ThemeToggleSwitch() { const [mounted, setMounted] = useState(false); const [isTransitioning, setIsTransitioning] = useState(false); const isDarkTheme = theme === "dark"; + const tripleTapMaxDelay = 500; useEffect(() => { setMounted(true); @@ -34,6 +35,45 @@ export function ThemeToggleSwitch() { return () => window.clearTimeout(transitionTimer); }, [isTransitioning]); + useEffect(() => { + if (!mounted) return; + + let tapCount = 0; + let lastTapAt = 0; + + const isTypingInInput = (target: EventTarget | null) => { + if (!(target instanceof HTMLElement)) return false; + + const tagName = target.tagName.toLowerCase(); + if (tagName === "input" || tagName === "textarea" || tagName === "select") { + return true; + } + + return target.isContentEditable; + }; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.repeat) return; + if (event.key.toLowerCase() !== "t") return; + if (event.ctrlKey || event.metaKey || event.altKey) return; + if (isTypingInInput(event.target)) return; + + const now = Date.now(); + tapCount = now - lastTapAt <= tripleTapMaxDelay ? tapCount + 1 : 1; + lastTapAt = now; + + if (tapCount === 3) { + setIsTransitioning(true); + setTheme(theme === "dark" ? "light" : "dark"); + tapCount = 0; + lastTapAt = 0; + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [mounted, setTheme, theme]); + if (!mounted) { return null; } diff --git a/src/components/Layouts/sidebar/data/index.ts b/src/components/Layouts/sidebar/data/index.ts index 0122416..1da057a 100644 --- a/src/components/Layouts/sidebar/data/index.ts +++ b/src/components/Layouts/sidebar/data/index.ts @@ -29,12 +29,14 @@ export const NAV_DATA: NavData = [ url: "/", icon: Icons.HomeIcon, items: [], + visible: true, }, { title: "Admins", url: "/admins", icon: Icons.User, items: [], + visible: true, }, { title: "Manage Companies", @@ -46,12 +48,14 @@ export const NAV_DATA: NavData = [ url: "/companies", title: "Companies", items: [], + visible: true, }, { icon: Icons.Building, url: "/company-categories", title: "Company Categories", items: [], + visible: true, }, ], }, @@ -60,18 +64,21 @@ export const NAV_DATA: NavData = [ url: "/customers", icon: Icons.User, items: [], + visible: true, }, { title: "Tenders", url: "/tenders", icon: Icons.Table, items: [], + visible: true, }, { title: "Notifications", url: "/notification-history", icon: Icons.NotificationIcon, items: [], + visible: true, }, { title: "Contact Us", @@ -85,13 +92,14 @@ export const NAV_DATA: NavData = [ url: "/marketing", icon: GlobeSearch, items: [], - visible:false + visible: true }, { title: "Inquiries", url: "/inquiries", icon: Icons.BriefcaseTimer, items: [], + visible: true, }, ], }, diff --git a/src/components/Layouts/sidebar/index.tsx b/src/components/Layouts/sidebar/index.tsx index 9139e3a..4d7389e 100644 --- a/src/components/Layouts/sidebar/index.tsx +++ b/src/components/Layouts/sidebar/index.tsx @@ -15,6 +15,7 @@ export function Sidebar() { const pathname = usePathname(); const { setIsOpen, isOpen, isMobile, toggleSidebar } = useSidebarContext(); const [expandedItems, setExpandedItems] = useState([]); + const isVisible = (item: { visible?: boolean }) => item.visible !== false; const toggleExpanded = (title: string) => { setExpandedItems((prev) => @@ -26,9 +27,9 @@ export function Sidebar() { const newExpandedItems: string[] = []; NAV_DATA.forEach((section) => { - section.items.forEach((item) => { + section.items.filter(isVisible).forEach((item) => { if (item.items.length > 0) { - const hasActiveSubItem = item.items.some( + const hasActiveSubItem = item.items.filter(isVisible).some( (subItem: NavSubItem) => subItem.url === pathname || (pathname.startsWith(subItem.url) && subItem.url !== "/"), @@ -115,7 +116,14 @@ export function Sidebar() { isCollapsed ? "w-full px-1" : "pr-3", )} > - {NAV_DATA.map((section) => ( + {NAV_DATA.map((section) => { + const visibleItems = section.items.filter(isVisible); + + if (!visibleItems.length) { + return null; + } + + return (
{!isCollapsed && (

@@ -125,18 +133,21 @@ export function Sidebar() {

- ))} + ); + })} {/* Toggle button at bottom for collapsed state */}