1+ import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData" ;
2+ import { useLocale } from "@calcom/lib/hooks/useLocale" ;
3+ import { isMac } from "@calcom/lib/isMac" ;
4+ import { trpc } from "@calcom/trpc/react" ;
5+ import { Icon } from "@calcom/ui/components/icon" ;
6+ import { Tooltip } from "@calcom/ui/components/tooltip" ;
17import type { Action } from "kbar" ;
28import {
39 KBarAnimator ,
@@ -12,14 +18,7 @@ import {
1218} from "kbar" ;
1319import { useRouter } from "next/navigation" ;
1420import type { ReactNode } from "react" ;
15- import { useMemo } from "react" ;
16-
17- import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData" ;
18- import { useLocale } from "@calcom/lib/hooks/useLocale" ;
19- import { isMac } from "@calcom/lib/isMac" ;
20- import { trpc } from "@calcom/trpc/react" ;
21- import { Icon } from "@calcom/ui/components/icon" ;
22- import { Tooltip } from "@calcom/ui/components/tooltip" ;
21+ import { useEffect , useMemo } from "react" ;
2322
2423type ShortcutArrayType = {
2524 shortcuts ?: string [ ] ;
@@ -49,26 +48,165 @@ const getApps: AppAction[] = Object.values(appStoreMetadata).map(({ name, slug }
4948} ) ) ;
5049
5150const KBAR_ACTION_CONFIGS : ActionConfig [ ] = [
52- { id : "workflows" , name : "workflows" , section : "workflows" , shortcut : [ "w" , "f" ] , keywords : "workflows automation" , href : "/workflows" } ,
53- { id : "event-types" , name : "event_types_page_title" , section : "event_types_page_title" , shortcut : [ "e" , "t" ] , keywords : "event types" , href : "/event-types" } ,
54- { id : "app-store" , name : "app_store" , section : "apps" , shortcut : [ "a" , "s" ] , keywords : "app store" , href : "/apps" } ,
55- { id : "upcoming-bookings" , name : "upcoming" , section : "bookings" , shortcut : [ "u" , "b" ] , keywords : "upcoming bookings" , href : "/bookings/upcoming" } ,
56- { id : "recurring-bookings" , name : "recurring" , section : "bookings" , shortcut : [ "r" , "b" ] , keywords : "recurring bookings" , href : "/bookings/recurring" } ,
57- { id : "past-bookings" , name : "past" , section : "bookings" , shortcut : [ "p" , "b" ] , keywords : "past bookings" , href : "/bookings/past" } ,
58- { id : "cancelled-bookings" , name : "cancelled" , section : "bookings" , shortcut : [ "c" , "b" ] , keywords : "cancelled bookings" , href : "/bookings/cancelled" } ,
59- { id : "schedule" , name : "availability" , section : "availability" , shortcut : [ "s" , "a" ] , keywords : "schedule availability" , href : "/availability" } ,
60- { id : "profile" , name : "profile" , section : "profile" , shortcut : [ "p" , "s" ] , keywords : "setting profile" , href : "/settings/my-account/profile" } ,
61- { id : "avatar" , name : "change_avatar" , section : "profile" , shortcut : [ "c" , "a" ] , keywords : "remove change modify avatar" , href : "/settings/my-account/profile" } ,
62- { id : "timezone" , name : "timezone" , section : "profile" , shortcut : [ "c" , "t" ] , keywords : "change modify timezone" , href : "/settings/my-account/general" } ,
63- { id : "brand-color" , name : "brand_color" , section : "profile" , shortcut : [ "b" , "c" ] , keywords : "change modify brand color" , href : "/settings/my-account/appearance" } ,
64- { id : "teams" , name : "teams" , shortcut : [ "t" , "s" ] , keywords : "add manage modify team" , href : "/settings/teams" } ,
65- { id : "password" , name : "change_password" , section : "security" , shortcut : [ "c" , "p" ] , keywords : "change modify password" , href : "/settings/security/password" } ,
66- { id : "two-factor" , name : "two_factor_auth" , section : "security" , shortcut : [ "t" , "f" , "a" ] , keywords : "two factor authentication" , href : "/settings/security/two-factor-auth" } ,
67- { id : "impersonation" , name : "user_impersonation_heading" , section : "security" , shortcut : [ "u" , "i" ] , keywords : "user impersonation" , href : "/settings/security/impersonation" } ,
68- { id : "license" , name : "choose_a_license" , section : "admin" , shortcut : [ "u" , "l" ] , keywords : "license" , href : "/auth/setup?step=1" } ,
69- { id : "webhooks" , name : "Webhooks" , section : "developer" , shortcut : [ "w" , "h" ] , keywords : "webhook automation" , href : "/settings/developer/webhooks" } ,
70- { id : "api-keys" , name : "api_keys" , section : "developer" , shortcut : [ "a" , "p" , "i" ] , keywords : "api keys" , href : "/settings/developer/api-keys" } ,
71- { id : "billing" , name : "manage_billing" , section : "billing" , shortcut : [ "m" , "b" ] , keywords : "billing view manage" , href : "/settings/billing" } ,
51+ {
52+ id : "workflows" ,
53+ name : "workflows" ,
54+ section : "workflows" ,
55+ shortcut : [ "w" , "f" ] ,
56+ keywords : "workflows automation" ,
57+ href : "/workflows" ,
58+ } ,
59+ {
60+ id : "event-types" ,
61+ name : "event_types_page_title" ,
62+ section : "event_types_page_title" ,
63+ shortcut : [ "e" , "t" ] ,
64+ keywords : "event types" ,
65+ href : "/event-types" ,
66+ } ,
67+ {
68+ id : "app-store" ,
69+ name : "app_store" ,
70+ section : "apps" ,
71+ shortcut : [ "a" , "s" ] ,
72+ keywords : "app store" ,
73+ href : "/apps" ,
74+ } ,
75+ {
76+ id : "upcoming-bookings" ,
77+ name : "upcoming" ,
78+ section : "bookings" ,
79+ shortcut : [ "u" , "b" ] ,
80+ keywords : "upcoming bookings" ,
81+ href : "/bookings/upcoming" ,
82+ } ,
83+ {
84+ id : "recurring-bookings" ,
85+ name : "recurring" ,
86+ section : "bookings" ,
87+ shortcut : [ "r" , "b" ] ,
88+ keywords : "recurring bookings" ,
89+ href : "/bookings/recurring" ,
90+ } ,
91+ {
92+ id : "past-bookings" ,
93+ name : "past" ,
94+ section : "bookings" ,
95+ shortcut : [ "p" , "b" ] ,
96+ keywords : "past bookings" ,
97+ href : "/bookings/past" ,
98+ } ,
99+ {
100+ id : "cancelled-bookings" ,
101+ name : "cancelled" ,
102+ section : "bookings" ,
103+ shortcut : [ "c" , "b" ] ,
104+ keywords : "cancelled bookings" ,
105+ href : "/bookings/cancelled" ,
106+ } ,
107+ {
108+ id : "schedule" ,
109+ name : "availability" ,
110+ section : "availability" ,
111+ shortcut : [ "s" , "a" ] ,
112+ keywords : "schedule availability" ,
113+ href : "/availability" ,
114+ } ,
115+ {
116+ id : "profile" ,
117+ name : "profile" ,
118+ section : "profile" ,
119+ shortcut : [ "p" , "s" ] ,
120+ keywords : "setting profile" ,
121+ href : "/settings/my-account/profile" ,
122+ } ,
123+ {
124+ id : "avatar" ,
125+ name : "change_avatar" ,
126+ section : "profile" ,
127+ shortcut : [ "c" , "a" ] ,
128+ keywords : "remove change modify avatar" ,
129+ href : "/settings/my-account/profile" ,
130+ } ,
131+ {
132+ id : "timezone" ,
133+ name : "timezone" ,
134+ section : "profile" ,
135+ shortcut : [ "c" , "t" ] ,
136+ keywords : "change modify timezone" ,
137+ href : "/settings/my-account/general" ,
138+ } ,
139+ {
140+ id : "brand-color" ,
141+ name : "brand_color" ,
142+ section : "profile" ,
143+ shortcut : [ "b" , "c" ] ,
144+ keywords : "change modify brand color" ,
145+ href : "/settings/my-account/appearance" ,
146+ } ,
147+ {
148+ id : "teams" ,
149+ name : "teams" ,
150+ shortcut : [ "t" , "s" ] ,
151+ keywords : "add manage modify team" ,
152+ href : "/settings/teams" ,
153+ } ,
154+ {
155+ id : "password" ,
156+ name : "change_password" ,
157+ section : "security" ,
158+ shortcut : [ "c" , "p" ] ,
159+ keywords : "change modify password" ,
160+ href : "/settings/security/password" ,
161+ } ,
162+ {
163+ id : "two-factor" ,
164+ name : "two_factor_auth" ,
165+ section : "security" ,
166+ shortcut : [ "t" , "f" , "a" ] ,
167+ keywords : "two factor authentication" ,
168+ href : "/settings/security/two-factor-auth" ,
169+ } ,
170+ {
171+ id : "impersonation" ,
172+ name : "user_impersonation_heading" ,
173+ section : "security" ,
174+ shortcut : [ "u" , "i" ] ,
175+ keywords : "user impersonation" ,
176+ href : "/settings/security/impersonation" ,
177+ } ,
178+ {
179+ id : "license" ,
180+ name : "choose_a_license" ,
181+ section : "admin" ,
182+ shortcut : [ "u" , "l" ] ,
183+ keywords : "license" ,
184+ href : "/auth/setup?step=1" ,
185+ } ,
186+ {
187+ id : "webhooks" ,
188+ name : "Webhooks" ,
189+ section : "developer" ,
190+ shortcut : [ "w" , "h" ] ,
191+ keywords : "webhook automation" ,
192+ href : "/settings/developer/webhooks" ,
193+ } ,
194+ {
195+ id : "api-keys" ,
196+ name : "api_keys" ,
197+ section : "developer" ,
198+ shortcut : [ "a" , "p" , "i" ] ,
199+ keywords : "api keys" ,
200+ href : "/settings/developer/api-keys" ,
201+ } ,
202+ {
203+ id : "billing" ,
204+ name : "manage_billing" ,
205+ section : "billing" ,
206+ shortcut : [ "m" , "b" ] ,
207+ keywords : "billing view manage" ,
208+ href : "/settings/billing" ,
209+ } ,
72210] ;
73211
74212function buildKbarActions ( push : ( href : string ) => void ) : Action [ ] {
@@ -145,16 +283,16 @@ const KBarContent = (): JSX.Element => {
145283 return (
146284 < KBarPortal >
147285 < KBarPositioner className = "overflow-scroll" >
148- < KBarAnimator className = "bg-default max-w-(--breakpoint-sm) z-10 w-full overflow-hidden rounded-md shadow-lg" >
149- < div className = "border-subtle flex items-center justify-center border-b" >
150- < Icon name = "search" className = "text-default mx-3 h-4 w-4" />
286+ < KBarAnimator className = "z-10 w-full max-w-(--breakpoint-sm) overflow-hidden rounded-md bg-default shadow-lg" >
287+ < div className = "flex items-center justify-center border-subtle border-b" >
288+ < Icon name = "search" className = "mx-3 h-4 w-4 text-default " />
151289 < KBarSearch
152290 defaultPlaceholder = { t ( "kbar_search_placeholder" ) }
153- className = "bg-default placeholder:text-subtle text-default w-full rounded-sm border-0 py-2.5 px-0 focus:ring-0 focus-visible:outline-none"
291+ className = "w-full rounded-sm border-0 bg-default px-0 py-2.5 text-default placeholder:text-subtle focus:ring-0 focus-visible:outline-none"
154292 />
155293 </ div >
156294 < RenderResults />
157- < div className = "text-subtle border-subtle hidden items-center space-x-1 border-t px-2 py-1.5 text-xs sm:flex" >
295+ < div className = "hidden items-center space-x-1 border-subtle border- t px-2 py-1.5 text-subtle text-xs sm:flex" >
158296 < Icon name = "arrow-up" className = "h-4 w-4" />
159297 < Icon name = "arrow-down" className = "h-4 w-4" /> < span className = "pr-2" > { t ( "navigate" ) } </ span >
160298 < Icon name = "corner-down-left" className = "h-4 w-4" />
@@ -164,7 +302,7 @@ const KBarContent = (): JSX.Element => {
164302 < span className = "pr-2" > { t ( "close" ) } </ span >
165303 </ div >
166304 </ KBarAnimator >
167- < div className = "z-1 fixed inset-0 bg-neutral-800/70" />
305+ < div className = "fixed inset-0 z-1 bg-neutral-800/70" />
168306 </ KBarPositioner >
169307 </ KBarPortal >
170308 ) ;
@@ -189,7 +327,7 @@ const KBarTrigger = (): JSX.Element | null => {
189327 < button
190328 color = "minimal"
191329 onClick = { query . toggle }
192- className = "text-default hover:bg-subtle todesktop:hover:!bg-transparent lg:hover:bg-emphasis lg:hover:text-emphasis group flex rounded-md px-3 py-2 text-sm font-medium transition lg:px-2" >
330+ className = "todesktop:hover:!bg-transparent group flex rounded-md px-3 py-2 font-medium text-default text-sm transition hover:bg-subtle lg:px-2 lg:hover:bg-emphasis lg:hover:text-emphasis " >
193331 < Icon name = "search" className = "h-4 w-4 shrink-0 text-inherit" />
194332 </ button >
195333 </ Tooltip >
@@ -205,7 +343,7 @@ function DisplayShortcuts(item: ShortcutArrayType): JSX.Element {
205343 return (
206344 < kbd
207345 key = { shortcut }
208- className = "bg-default hover:bg-subtle text-emphasis rounded-sm border px-2 py-1 transition" >
346+ className = "rounded-sm border bg-default px-2 py-1 text-emphasis transition hover:bg-subtle " >
209347 { shortcut }
210348 </ kbd >
211349 ) ;
@@ -221,7 +359,7 @@ type RenderItemProps = {
221359
222360function renderResultItem ( item : string | Action , active : boolean , t : ( key : string ) => string ) : JSX . Element {
223361 if ( typeof item === "string" ) {
224- return < div className = "bg-default text-emphasis p-4 text-xs font-bold uppercase" > { t ( item ) } </ div > ;
362+ return < div className = "bg-default p-4 font-bold text-emphasis text-xs uppercase" > { t ( item ) } </ div > ;
225363 }
226364
227365 let background = "var(--cal-bg-default)" ;
@@ -241,10 +379,48 @@ function renderResultItem(item: string | Action, active: boolean, t: (key: strin
241379 ) ;
242380}
243381
382+ function NoResultsFound ( { searchQuery } : { searchQuery : string } ) : JSX . Element {
383+ const { t } = useLocale ( ) ;
384+ const helpUrl = `https://cal.com/help/welcome?search=${ encodeURIComponent ( searchQuery ) } ` ;
385+
386+ useEffect ( ( ) => {
387+ const handleKeyDown = ( event : KeyboardEvent ) : void => {
388+ if ( event . key === "Enter" ) {
389+ window . open ( helpUrl , "_blank" , "noopener,noreferrer" ) ;
390+ }
391+ } ;
392+
393+ document . addEventListener ( "keydown" , handleKeyDown ) ;
394+ return ( ) => {
395+ document . removeEventListener ( "keydown" , handleKeyDown ) ;
396+ } ;
397+ } , [ helpUrl ] ) ;
398+
399+ return (
400+ < div className = "px-4 py-6 text-center" >
401+ < p className = "mb-3 text-sm text-subtle" > { t ( "kbar_no_results_found" ) } </ p >
402+ < a
403+ href = { helpUrl }
404+ target = "_blank"
405+ rel = "noopener noreferrer"
406+ className = "flex items-center justify-center gap-2 text-emphasis text-sm transition hover:text-default" >
407+ < Icon name = "external-link" className = "h-4 w-4" />
408+ { t ( "kbar_search_help_desk_prefix" ) } < span className = "underline" > "{ searchQuery } "</ span > { " " }
409+ { t ( "kbar_search_help_desk_suffix" ) }
410+ </ a >
411+ </ div >
412+ ) ;
413+ }
414+
244415function RenderResults ( ) : JSX . Element {
245416 const { results } = useMatches ( ) ;
417+ const { searchQuery } = useKBar ( ( state ) => ( { searchQuery : state . searchQuery } ) ) ;
246418 const { t } = useLocale ( ) ;
247419
420+ if ( results . length === 0 && searchQuery . trim ( ) . length > 0 ) {
421+ return < NoResultsFound searchQuery = { searchQuery } /> ;
422+ }
423+
248424 return (
249425 < KBarResults
250426 items = { results }
0 commit comments