diff --git a/apps/admin-panel/app/layout.tsx b/apps/admin-panel/app/layout.tsx
index ee87a7bba..7ec85b9c3 100644
--- a/apps/admin-panel/app/layout.tsx
+++ b/apps/admin-panel/app/layout.tsx
@@ -9,6 +9,8 @@ import { AuthSessionProvider } from "./session-provider"
import { AppLayout } from "./app-layout"
+import { CommandMenu } from "./command-menu"
+
import ApolloServerWrapper from "@/lib/apollo-client/server-wrapper"
import { Toast } from "@/components/toast"
import { SidebarProvider, SidebarInset } from "@/ui/sidebar"
@@ -56,7 +58,10 @@ export default async function RootLayout({
- {children}
+
+
+ {children}
+
)}
diff --git a/apps/admin-panel/components/app-sidebar/index.tsx b/apps/admin-panel/components/app-sidebar/index.tsx
index 32e266540..a29051951 100644
--- a/apps/admin-panel/components/app-sidebar/index.tsx
+++ b/apps/admin-panel/components/app-sidebar/index.tsx
@@ -46,7 +46,7 @@ export function AppSidebar({ appVersion, ...props }: AppSidebarProps) {
-
+
diff --git a/apps/admin-panel/components/app-sidebar/nav-section.tsx b/apps/admin-panel/components/app-sidebar/nav-section.tsx
index 2a562c157..fcfdb0ccf 100644
--- a/apps/admin-panel/components/app-sidebar/nav-section.tsx
+++ b/apps/admin-panel/components/app-sidebar/nav-section.tsx
@@ -45,7 +45,7 @@ export function NavSection({ items, label }: NavSectionProps) {
return (
-
+
{item.title}
diff --git a/apps/admin-panel/components/app-sidebar/user-block.tsx b/apps/admin-panel/components/app-sidebar/user-block.tsx
index 8a32a7955..ff1379d8f 100644
--- a/apps/admin-panel/components/app-sidebar/user-block.tsx
+++ b/apps/admin-panel/components/app-sidebar/user-block.tsx
@@ -59,7 +59,7 @@ export function UserBlock() {
-
+
{link.isCurrentPage ? (
-
+
{index === 0 && }
{link.title}
@@ -46,6 +50,8 @@ const BreadCrumbWrapper = ({ links }: FlexibleBreadcrumbProps) => {
{index === 0 && }
{link.title}
diff --git a/apps/admin-panel/components/data-table/index.tsx b/apps/admin-panel/components/data-table/index.tsx
index deac15d1a..c227ef352 100644
--- a/apps/admin-panel/components/data-table/index.tsx
+++ b/apps/admin-panel/components/data-table/index.tsx
@@ -1,9 +1,11 @@
"use client"
-import React from "react"
+import React, { useState, useEffect, useRef } from "react"
import Link from "next/link"
import { ArrowRight } from "lucide-react"
+import { useRouter } from "next/navigation"
+
import { useBreakpointDown } from "@/hooks/use-media-query"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/ui/table"
import { Button } from "@/ui/button"
@@ -47,6 +49,9 @@ const DataTable = ({
navigateTo,
}: DataTableProps) => {
const isMobile = useBreakpointDown("md")
+ const [focusedRowIndex, setFocusedRowIndex] = useState(-1)
+ const tableRef = useRef(null)
+ const router = useRouter()
const getNavigationUrl = (item: T): string | null => {
return navigateTo ? navigateTo(item) : null
@@ -58,6 +63,70 @@ const DataTable = ({
return url !== null && url !== ""
}
+ const focusRow = (index: number) => {
+ if (index < 0 || !data.length) return
+ const validIndex = Math.min(Math.max(0, index), data.length - 1)
+ const row = document.querySelector(
+ `[data-testid="table-row-${validIndex}"]`,
+ ) as HTMLElement
+ if (row) {
+ row.focus({ preventScroll: true })
+ row.scrollIntoView({ behavior: "smooth", block: "nearest" })
+ setFocusedRowIndex(validIndex)
+ }
+ }
+
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (
+ document.activeElement?.tagName === "INPUT" ||
+ document.activeElement?.tagName === "TEXTAREA" ||
+ document.activeElement?.tagName === "SELECT" ||
+ document.activeElement?.tagName === "BUTTON"
+ ) {
+ return
+ }
+
+ if (!data.length) return
+
+ switch (e.key) {
+ case "ArrowUp":
+ e.preventDefault()
+ focusRow(focusedRowIndex - 1)
+ break
+ case "ArrowDown":
+ e.preventDefault()
+ focusRow(focusedRowIndex + 1)
+ break
+ case "Enter":
+ e.preventDefault()
+ if (focusedRowIndex >= 0) {
+ const item = data[focusedRowIndex]
+ if (onRowClick) {
+ onRowClick(item)
+ } else if (navigateTo) {
+ const url = getNavigationUrl(item)
+ if (url) {
+ router.push(url)
+ }
+ }
+ }
+ break
+ }
+ }
+
+ window.addEventListener("keydown", handleKeyDown)
+ return () => window.removeEventListener("keydown", handleKeyDown)
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [data, focusedRowIndex, onRowClick, navigateTo])
+
+ useEffect(() => {
+ if (data.length && focusedRowIndex === -1) {
+ focusRow(0)
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [data.length])
+
if (loading) {
return isMobile ? (
{initials}
diff --git a/apps/admin-panel/components/breadcrumb-wrapper.tsx b/apps/admin-panel/components/breadcrumb-wrapper.tsx
index fe51fe787..3e3e5c692 100644
--- a/apps/admin-panel/components/breadcrumb-wrapper.tsx
+++ b/apps/admin-panel/components/breadcrumb-wrapper.tsx
@@ -38,7 +38,11 @@ const BreadCrumbWrapper = ({ links }: FlexibleBreadcrumbProps) => {
@@ -186,7 +255,12 @@ const DataTable = ({
}
return (
-
+
+
+
+
+))
+
+CommandInput.displayName = CommandPrimitive.Input.displayName
+
+const CommandList = React.forwardRef<
+ React.ElementRef