"use client"; import type { CommandBarNavSection, SidebarNavSection } from "@app/app/navigation"; import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from "@app/components/ui/command"; import { cn } from "@app/lib/cn"; import { ListUserOrgsResponse } from "@server/routers/org"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { useCommandPaletteActions } from "./useCommandPaletteActions"; import { useCommandPaletteNavigation } from "./useCommandPaletteNavigation"; import { useCommandPaletteSearch } from "./useCommandPaletteSearch"; import { search } from "jmespath"; type CommandPaletteProps = { orgId?: string; orgs?: ListUserOrgsResponse["orgs"]; navItems: CommandBarNavSection[]; }; /** * Plan for command bar: * - the nav items should be custom items instead of all of the ones in the sidebar * - actions should be triggered by using `>` (like in Github) * -> if search starts with `>`, the filter should exclude that char in the filter string */ export function CommandPalette({ orgId, orgs, navItems }: CommandPaletteProps) { const t = useTranslations(); const router = useRouter(); const { open, setOpen } = useCommandPalette(); const [search, setSearch] = useState(""); const isActionMode = search.startsWith(">"); const navigationGroups = useCommandPaletteNavigation(navItems); // const organizations = useCommandPaletteOrganizations(orgs); const actions = useCommandPaletteActions(orgId, orgs); const { shouldSearch, sites, resources, users, machineClients, isLoading } = useCommandPaletteSearch({ orgId, query: search.substring(1), enabled: open && isActionMode }); const handleOpenChange = useCallback( (nextOpen: boolean) => { setOpen(nextOpen); if (!nextOpen) { setSearch(""); } }, [setOpen] ); const runCommand = useCallback( (command: () => void) => { setOpen(false); setSearch(""); command(); }, [setOpen] ); // const hasEntityResults = // sites.length > 0 || // resources.length > 0 || // users.length > 0 || // machineClients.length > 0; return ( ")) { search = query.substring(1); console.log({ search, value }); } if ( value .toLowerCase() .includes(search.trim().toLowerCase()) ) { return 1; } return 0; } }} > {t("commandPaletteNoResults")} {!isActionMode && navigationGroups.map((group, groupIndex) => { console.log({ groupIndex, groupHeading: group.heading }); return ( {groupIndex > 0 && } 0 && "[&_[cmdk-group-heading]]:pt-3" )} > {group.items.map((item) => ( runCommand(() => router.push(item.href) ) } className="h-9" > {item.icon} {item.title} ))} ); })} {/* {organizations.length > 1 && ( <> {organizations.map((org) => ( runCommand(() => router.push(org.href)) } > {org.name} {org.orgId} {org.isPrimaryOrg && ( {t("primary")} )} ))} )} */} {/* {shouldSearch && orgId && ( <> {isLoading && !hasEntityResults ? (
{t("commandPaletteSearching")}
) : ( <> {sites.length > 0 && ( {sites.map((site) => ( runCommand(() => router.push(site.href) ) } > {site.name} ))} )} {resources.length > 0 && ( {resources.map((resource) => ( runCommand(() => router.push( resource.href ) ) } > {resource.name} ))} )} {users.length > 0 && ( {users.map((user) => ( runCommand(() => router.push(user.href) ) } >
{user.name} {user.email}
))}
)} {machineClients.length > 0 && ( {machineClients.map((client) => ( runCommand(() => router.push(client.href) ) } > {client.name} ))} )} )} )} */} {isActionMode && actions.length > 0 && ( <> {actions.map((action) => ( runCommand(() => { if (action.onSelect) { action.onSelect(); } else if (action.href) { router.push(action.href); } }) } > {action.icon} {action.label} ))} )}
); } /*******************************/ /* COMMAND PALETTE CONTEXT */ /*******************************/ export type CommandPaletteContextValue = { open: boolean; setOpen: (open: boolean) => void; toggle: () => void; }; const CommandPaletteContext = createContext( null ); export function useCommandPalette() { const context = useContext(CommandPaletteContext); if (!context) { throw new Error( "useCommandPalette must be used within CommandPaletteProvider" ); } return context; } //*******************************/ /* COMMAND PALETTE PROVIDER */ /*******************************/ type CommandPaletteProviderProps = { children: React.ReactNode; orgId?: string; orgs?: ListUserOrgsResponse["orgs"]; navItems: SidebarNavSection[]; }; function isEditableTarget(target: EventTarget | null) { if (!(target instanceof HTMLElement)) return false; if (target.isContentEditable) return true; const tagName = target.tagName; return ( tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT" ); } export function CommandPaletteProvider({ children, orgId, orgs, navItems }: CommandPaletteProviderProps) { const [open, setOpen] = useState(false); const toggle = useCallback(() => { setOpen((current) => !current); }, []); const contextValue = useMemo( () => ({ open, setOpen, toggle }), [open, toggle] ); useEffect(() => { function onKeyDown(event: KeyboardEvent) { if ( event.key.toLowerCase() !== "k" || !(event.metaKey || event.ctrlKey) ) { return; } if (!open && isEditableTarget(event.target)) { return; } event.preventDefault(); toggle(); } document.addEventListener("keydown", onKeyDown); return () => document.removeEventListener("keydown", onKeyDown); }, [open, toggle]); return ( {children} ); }