kbar follow up (#3361)

* added more routes to kbar

* added right direction for tooltip, moved search icon next to logo, added keyboard shortcuts to command bar

* added right direction for tooltip, moved search icon next to logo, added keyboard shortcuts to command bar

* fixed search icon for tablet

* fixed search icon for mobile

* hide keyboard shortcut legend on mobile

* extracted strings
This commit is contained in:
Peer Richelsen 2022-07-14 13:32:28 +02:00 committed by GitHub
parent 6f073762fa
commit 7ec5f01647
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 93 additions and 50 deletions

View File

@ -1,3 +1,4 @@
import { SwitchVerticalIcon } from "@heroicons/react/outline";
import { SearchIcon } from "@heroicons/react/solid"; import { SearchIcon } from "@heroicons/react/solid";
import { import {
KBarProvider, KBarProvider,
@ -10,6 +11,7 @@ import {
useKBar, useKBar,
} from "kbar"; } from "kbar";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Command, CornerDownLeft } from "react-feather";
import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useLocale } from "@calcom/lib/hooks/useLocale";
import { isMac } from "@calcom/lib/isMac"; import { isMac } from "@calcom/lib/isMac";
@ -33,13 +35,14 @@ export const KBarRoot = ({ children }: { children: React.ReactNode }) => {
// keywords: "set yourself away bookings", // keywords: "set yourself away bookings",
// perform: () => alert("Hello World"), // perform: () => alert("Hello World"),
// }, // },
{ {
id: "upcoming-bookings", id: "workflows",
name: "Upcoming Bookings", name: "Workflows",
section: "Booking", section: "Workflows",
shortcut: ["u", "b"], shortcut: ["w", "f"],
keywords: "upcoming bookings", keywords: "workflows automation",
perform: () => router.push("/bookings/upcoming"), perform: () => router.push("/workflows"),
}, },
{ {
id: "event-types", id: "event-types",
@ -57,10 +60,18 @@ export const KBarRoot = ({ children }: { children: React.ReactNode }) => {
keywords: "app store", keywords: "app store",
perform: () => router.push("/apps"), perform: () => router.push("/apps"),
}, },
{
id: "upcoming-bookings",
name: "Upcoming Bookings",
section: "Bookings",
shortcut: ["u", "b"],
keywords: "upcoming bookings",
perform: () => router.push("/bookings/upcoming"),
},
{ {
id: "recurring-bookings", id: "recurring-bookings",
name: "Recurring Bookings", name: "Recurring Bookings",
section: "Booking", section: "Bookings",
shortcut: ["r", "b"], shortcut: ["r", "b"],
keywords: "recurring bookings", keywords: "recurring bookings",
perform: () => router.push("/bookings/recurring"), perform: () => router.push("/bookings/recurring"),
@ -68,7 +79,7 @@ export const KBarRoot = ({ children }: { children: React.ReactNode }) => {
{ {
id: "past-bookings", id: "past-bookings",
name: "Past Bookings", name: "Past Bookings",
section: "Booking", section: "Bookings",
shortcut: ["p", "b"], shortcut: ["p", "b"],
keywords: "past bookings", keywords: "past bookings",
perform: () => router.push("/bookings/past"), perform: () => router.push("/bookings/past"),
@ -76,7 +87,7 @@ export const KBarRoot = ({ children }: { children: React.ReactNode }) => {
{ {
id: "cancelled-bookings", id: "cancelled-bookings",
name: "Cancelled Bookings", name: "Cancelled Bookings",
section: "Booking", section: "Bookings",
shortcut: ["c", "b"], shortcut: ["c", "b"],
keywords: "cancelled bookings", keywords: "cancelled bookings",
perform: () => router.push("/bookings/cancelled"), perform: () => router.push("/bookings/cancelled"),
@ -182,13 +193,27 @@ export const KBarRoot = ({ children }: { children: React.ReactNode }) => {
}; };
export const KBarContent = () => { export const KBarContent = () => {
const { t } = useLocale();
return ( return (
<KBarPortal> <KBarPortal>
<KBarPositioner> <KBarPositioner>
<KBarAnimator className="bg-white shadow-lg"> <KBarAnimator className="z-10 w-full max-w-screen-sm overflow-hidden rounded-sm bg-white shadow-lg">
<KBarSearch className="min-w-96 rounded-sm px-4 py-2.5 focus-visible:outline-none" /> <div className="flex items-center justify-center border-b">
<SearchIcon className="mx-3 h-4 w-4 text-gray-500" />
<KBarSearch className="w-full rounded-sm py-2.5 focus-visible:outline-none" />
</div>
<RenderResults /> <RenderResults />
<div className="hidden items-center space-x-1 border-t px-2 py-1.5 text-xs text-gray-500 sm:flex">
<SwitchVerticalIcon className="h-4 w-4" /> <span className="pr-2">{t("navigate")}</span>
<CornerDownLeft className="h-4 w-4" />
<span className="pr-2">{t("open")}</span>
{isMac ? <Command className="h-3 w-3" /> : "CTRL"}
<span className="pr-1">+ K </span>
<span className="pr-2">{t("close")}</span>
</div>
</KBarAnimator> </KBarAnimator>
<div className="z-1 fixed inset-0 bg-gray-600 bg-opacity-75" />
</KBarPositioner> </KBarPositioner>
</KBarPortal> </KBarPortal>
); );
@ -196,22 +221,17 @@ export const KBarContent = () => {
export const KBarTrigger = () => { export const KBarTrigger = () => {
const { query } = useKBar(); const { query } = useKBar();
const { t } = useLocale();
return ( return (
<div className="flex"> <>
<button <Tooltip side="right" content={isMac ? "⌘ + K" : "CTRL + K"}>
color="minimal" <button
onClick={query.toggle} color="minimal"
className="group flex w-full items-center rounded-sm px-2 py-2 text-sm font-medium text-neutral-500 hover:bg-gray-50 hover:text-neutral-900"> onClick={query.toggle}
<span className="h-5 w-5 flex-shrink-0 text-neutral-400 group-hover:text-neutral-500 ltr:mr-3 rtl:ml-3"> className="group flex text-sm font-medium text-neutral-500 hover:text-neutral-900">
<SearchIcon /> <SearchIcon className="h-5 w-5 flex-shrink-0 text-neutral-400 group-hover:text-neutral-500" />
</span> </button>
<Tooltip content={isMac ? "⌘ + K" : "CTRL + K"}> </Tooltip>
<span className="hidden lg:inline">{t("commandbar")}</span> </>
</Tooltip>
</button>
</div>
); );
}; };
@ -222,7 +242,7 @@ const DisplayShortcuts = (item: shortcutArrayType) => {
<span className="space-x-1"> <span className="space-x-1">
{shortcuts?.map((shortcut) => { {shortcuts?.map((shortcut) => {
return ( return (
<kbd key={shortcut} className="rounded-sm bg-gray-700 px-2 py-1 text-white"> <kbd key={shortcut} className="rounded-sm border bg-white px-2 py-1 text-black hover:bg-gray-100">
{shortcut} {shortcut}
</kbd> </kbd>
); );

View File

@ -202,11 +202,17 @@ const Layout = ({
<div className="flex w-14 flex-col lg:w-56"> <div className="flex w-14 flex-col lg:w-56">
<div className="flex h-0 flex-1 flex-col border-r border-gray-200 bg-white"> <div className="flex h-0 flex-1 flex-col border-r border-gray-200 bg-white">
<div className="flex flex-1 flex-col overflow-y-auto pt-3 pb-4 lg:pt-5"> <div className="flex flex-1 flex-col overflow-y-auto pt-3 pb-4 lg:pt-5">
<Link href="/event-types"> <div className="justify-between md:hidden lg:flex">
<a className="px-4 md:hidden lg:inline"> <Link href="/event-types">
<Logo small /> <a className="px-4">
</a> <Logo small />
</Link> </a>
</Link>
<div className="px-2">
<KBarTrigger />
</div>
</div>
{/* logo icon for tablet */} {/* logo icon for tablet */}
<Link href="/event-types"> <Link href="/event-types">
<a className="text-center md:inline lg:hidden"> <a className="text-center md:inline lg:hidden">
@ -261,7 +267,9 @@ const Layout = ({
})} })}
</Fragment> </Fragment>
))} ))}
<KBarTrigger /> <span className="group flex items-center rounded-sm px-2 py-2 text-sm font-medium text-neutral-500 hover:bg-gray-50 hover:text-neutral-900 lg:hidden">
<KBarTrigger />
</span>
</nav> </nav>
</div> </div>
<TrialBanner /> <TrialBanner />
@ -304,9 +312,13 @@ const Layout = ({
<Logo /> <Logo />
</a> </a>
</Link> </Link>
<div className="flex items-center gap-3 self-center"> <div className="flex items-center gap-2 self-center">
<span className="group flex items-center rounded-full p-2.5 text-sm font-medium text-neutral-500 hover:bg-gray-50 hover:text-neutral-900 lg:hidden">
<KBarTrigger />
</span>
<button className="rounded-full bg-white p-2 text-gray-400 hover:bg-gray-50 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2"> <button className="rounded-full bg-white p-2 text-gray-400 hover:bg-gray-50 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2">
<span className="sr-only">{t("view_notifications")}</span> <span className="sr-only">{t("settings")}</span>
<Link href="/settings/profile"> <Link href="/settings/profile">
<a> <a>
<CogIcon className="h-6 w-6" aria-hidden="true" /> <CogIcon className="h-6 w-6" aria-hidden="true" />

View File

@ -91,7 +91,7 @@ export default function MemberListItem(props: Props) {
<div className="mt-2 flex ltr:mr-2 rtl:ml-2 sm:mt-0 sm:justify-center"> <div className="mt-2 flex ltr:mr-2 rtl:ml-2 sm:mt-0 sm:justify-center">
{/* Tooltip doesn't show... WHY????? */} {/* Tooltip doesn't show... WHY????? */}
{props.member.isMissingSeat && ( {props.member.isMissingSeat && (
<Tooltip content={t("hidden_team_member_message")}> <Tooltip side="top" content={t("hidden_team_member_message")}>
<TeamPill color="red" text={t("hidden")} /> <TeamPill color="red" text={t("hidden")} />
</Tooltip> </Tooltip>
)} )}
@ -100,7 +100,7 @@ export default function MemberListItem(props: Props) {
</div> </div>
</div> </div>
<div className="flex"> <div className="flex">
<Tooltip content={t("team_view_user_availability")}> <Tooltip side="top" content={t("team_view_user_availability")}>
<Button <Button
// Disabled buttons don't trigger Tooltips // Disabled buttons don't trigger Tooltips
title={ title={

View File

@ -143,7 +143,7 @@ export default function TeamListItem(props: Props) {
<div className="flex rtl:space-x-reverse"> <div className="flex rtl:space-x-reverse">
<TeamRole role={team.role} /> <TeamRole role={team.role} />
<Tooltip content={t("copy_link_team")}> <Tooltip side="top" content={t("copy_link_team")}>
<Button <Button
onClick={() => { onClick={() => {
navigator.clipboard.writeText(process.env.NEXT_PUBLIC_WEBSITE_URL + "/team/" + team.slug); navigator.clipboard.writeText(process.env.NEXT_PUBLIC_WEBSITE_URL + "/team/" + team.slug);

View File

@ -5,7 +5,7 @@ import { Tooltip } from "@calcom/ui/Tooltip";
export default function InfoBadge({ content }: { content: string }) { export default function InfoBadge({ content }: { content: string }) {
return ( return (
<> <>
<Tooltip content={content}> <Tooltip side="top" content={content}>
<span title={content}> <span title={content}>
<InformationCircleIcon className="relative top-px left-1 right-1 mt-px h-4 w-4 text-gray-500" /> <InformationCircleIcon className="relative top-px left-1 right-1 mt-px h-4 w-4 text-gray-500" />
</span> </span>

View File

@ -51,7 +51,7 @@ export default function WebhookListItem(props: { webhook: TWebhook; onEditWebhoo
</div> </div>
</div> </div>
<div className="flex"> <div className="flex">
<Tooltip content={t("edit_webhook")}> <Tooltip side="top" content={t("edit_webhook")}>
<Button <Button
onClick={() => props.onEditWebhook()} onClick={() => props.onEditWebhook()}
color="minimal" color="minimal"
@ -61,7 +61,7 @@ export default function WebhookListItem(props: { webhook: TWebhook; onEditWebhoo
/> />
</Tooltip> </Tooltip>
<Dialog> <Dialog>
<Tooltip content={t("delete_webhook")}> <Tooltip side="top" content={t("delete_webhook")}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button <Button
onClick={(e) => { onClick={(e) => {

View File

@ -67,7 +67,7 @@ export default function ApiKeyDialogForm(props: {
<code className="my-2 mr-1 w-full truncate rounded-sm bg-gray-100 py-2 px-3 align-middle font-mono text-gray-800"> <code className="my-2 mr-1 w-full truncate rounded-sm bg-gray-100 py-2 px-3 align-middle font-mono text-gray-800">
{apiKey} {apiKey}
</code> </code>
<Tooltip content={t("copy_to_clipboard")}> <Tooltip side="top" content={t("copy_to_clipboard")}>
<Button <Button
onClick={() => { onClick={() => {
navigator.clipboard.writeText(apiKey); navigator.clipboard.writeText(apiKey);

View File

@ -61,7 +61,7 @@ export default function ApiKeyListItem(props: { apiKey: TApiKeys; onEditApiKey:
</div> </div>
</div> </div>
<div className="flex"> <div className="flex">
<Tooltip content={t("edit_api_key")}> <Tooltip side="top" content={t("edit_api_key")}>
<Button <Button
onClick={() => props.onEditApiKey()} onClick={() => props.onEditApiKey()}
color="minimal" color="minimal"
@ -71,7 +71,7 @@ export default function ApiKeyListItem(props: { apiKey: TApiKeys; onEditApiKey:
/> />
</Tooltip> </Tooltip>
<Dialog> <Dialog>
<Tooltip content={t("delete_api_key")}> <Tooltip side="top" content={t("delete_api_key")}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button <Button
onClick={(e) => { onClick={(e) => {

View File

@ -97,6 +97,7 @@
"react-digit-input": "^2.1.0", "react-digit-input": "^2.1.0",
"react-dom": "^18.1.0", "react-dom": "^18.1.0",
"react-easy-crop": "^3.5.2", "react-easy-crop": "^3.5.2",
"react-feather": "^2.0.10",
"react-hook-form": "^7.31.1", "react-hook-form": "^7.31.1",
"react-hot-toast": "^2.1.0", "react-hot-toast": "^2.1.0",
"react-intl": "^5.25.1", "react-intl": "^5.25.1",

View File

@ -259,7 +259,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
"flex justify-between space-x-2 rtl:space-x-reverse ", "flex justify-between space-x-2 rtl:space-x-reverse ",
type.$disabled && "pointer-events-none cursor-not-allowed" type.$disabled && "pointer-events-none cursor-not-allowed"
)}> )}>
<Tooltip content={t("preview") as string}> <Tooltip side="top" content={t("preview") as string}>
<a <a
href={`${CAL_URL}/${group.profile.slug}/${type.slug}`} href={`${CAL_URL}/${group.profile.slug}/${type.slug}`}
target="_blank" target="_blank"
@ -271,7 +271,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
</a> </a>
</Tooltip> </Tooltip>
<Tooltip content={t("copy_link") as string}> <Tooltip side="top" content={t("copy_link") as string}>
<button <button
onClick={() => { onClick={() => {
showToast(t("link_copied"), "success"); showToast(t("link_copied"), "success");

View File

@ -986,5 +986,7 @@
"new_seat_subject": "New Attendee {{name}} on {{eventType}} at {{date}}", "new_seat_subject": "New Attendee {{name}} on {{eventType}} at {{date}}",
"new_seat_title": "Someone has added themselves to an event", "new_seat_title": "Someone has added themselves to an event",
"invalid_number": "Invalid phone number", "invalid_number": "Invalid phone number",
"commandbar": "Command Bar" "navigate": "Navigate",
"open": "Open",
"close": "Close"
} }

View File

@ -79,7 +79,7 @@ export default function ZapierSetup(props: IZapierSetupProps) {
<div className="mt-1 text-xl">{t("your_unique_api_key")}</div> <div className="mt-1 text-xl">{t("your_unique_api_key")}</div>
<div className="my-2 mt-3 flex"> <div className="my-2 mt-3 flex">
<div className="mr-1 w-full rounded bg-gray-100 p-3 pr-5">{newApiKey}</div> <div className="mr-1 w-full rounded bg-gray-100 p-3 pr-5">{newApiKey}</div>
<Tooltip content="copy to clipboard"> <Tooltip side="top" content="copy to clipboard">
<Button <Button
onClick={() => { onClick={() => {
navigator.clipboard.writeText(newApiKey); navigator.clipboard.writeText(newApiKey);

View File

@ -1,8 +1,11 @@
import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import React from "react"; import React from "react";
import classNames from "@calcom/lib/classNames";
export function Tooltip({ export function Tooltip({
children, children,
side,
content, content,
open, open,
defaultOpen, defaultOpen,
@ -12,6 +15,7 @@ export function Tooltip({
children: React.ReactNode; children: React.ReactNode;
content: React.ReactNode; content: React.ReactNode;
open?: boolean; open?: boolean;
side?: "top" | "right" | "bottom" | "left" | undefined;
defaultOpen?: boolean; defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void; onOpenChange?: (open: boolean) => void;
}) { }) {
@ -23,8 +27,12 @@ export function Tooltip({
onOpenChange={onOpenChange}> onOpenChange={onOpenChange}>
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger> <TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
<TooltipPrimitive.Content <TooltipPrimitive.Content
className="-mt-2 rounded-sm bg-black px-1 py-0.5 text-xs text-white shadow-lg" className={classNames(
side="top" side === "top" && "-mt-2",
side === "right" && "ml-2",
"rounded-sm bg-black px-1 py-0.5 text-xs text-white shadow-lg"
)}
side={side}
align="center" align="center"
{...props}> {...props}>
{content} {content}