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 stringspull/3363/head
parent
6f073762fa
commit
7ec5f01647
|
@ -1,3 +1,4 @@
|
|||
import { SwitchVerticalIcon } from "@heroicons/react/outline";
|
||||
import { SearchIcon } from "@heroicons/react/solid";
|
||||
import {
|
||||
KBarProvider,
|
||||
|
@ -10,6 +11,7 @@ import {
|
|||
useKBar,
|
||||
} from "kbar";
|
||||
import { useRouter } from "next/router";
|
||||
import { Command, CornerDownLeft } from "react-feather";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { isMac } from "@calcom/lib/isMac";
|
||||
|
@ -33,13 +35,14 @@ export const KBarRoot = ({ children }: { children: React.ReactNode }) => {
|
|||
// keywords: "set yourself away bookings",
|
||||
// perform: () => alert("Hello World"),
|
||||
// },
|
||||
|
||||
{
|
||||
id: "upcoming-bookings",
|
||||
name: "Upcoming Bookings",
|
||||
section: "Booking",
|
||||
shortcut: ["u", "b"],
|
||||
keywords: "upcoming bookings",
|
||||
perform: () => router.push("/bookings/upcoming"),
|
||||
id: "workflows",
|
||||
name: "Workflows",
|
||||
section: "Workflows",
|
||||
shortcut: ["w", "f"],
|
||||
keywords: "workflows automation",
|
||||
perform: () => router.push("/workflows"),
|
||||
},
|
||||
{
|
||||
id: "event-types",
|
||||
|
@ -57,10 +60,18 @@ export const KBarRoot = ({ children }: { children: React.ReactNode }) => {
|
|||
keywords: "app store",
|
||||
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",
|
||||
name: "Recurring Bookings",
|
||||
section: "Booking",
|
||||
section: "Bookings",
|
||||
shortcut: ["r", "b"],
|
||||
keywords: "recurring bookings",
|
||||
perform: () => router.push("/bookings/recurring"),
|
||||
|
@ -68,7 +79,7 @@ export const KBarRoot = ({ children }: { children: React.ReactNode }) => {
|
|||
{
|
||||
id: "past-bookings",
|
||||
name: "Past Bookings",
|
||||
section: "Booking",
|
||||
section: "Bookings",
|
||||
shortcut: ["p", "b"],
|
||||
keywords: "past bookings",
|
||||
perform: () => router.push("/bookings/past"),
|
||||
|
@ -76,7 +87,7 @@ export const KBarRoot = ({ children }: { children: React.ReactNode }) => {
|
|||
{
|
||||
id: "cancelled-bookings",
|
||||
name: "Cancelled Bookings",
|
||||
section: "Booking",
|
||||
section: "Bookings",
|
||||
shortcut: ["c", "b"],
|
||||
keywords: "cancelled bookings",
|
||||
perform: () => router.push("/bookings/cancelled"),
|
||||
|
@ -182,13 +193,27 @@ export const KBarRoot = ({ children }: { children: React.ReactNode }) => {
|
|||
};
|
||||
|
||||
export const KBarContent = () => {
|
||||
const { t } = useLocale();
|
||||
|
||||
return (
|
||||
<KBarPortal>
|
||||
<KBarPositioner>
|
||||
<KBarAnimator className="bg-white shadow-lg">
|
||||
<KBarSearch className="min-w-96 rounded-sm px-4 py-2.5 focus-visible:outline-none" />
|
||||
<KBarAnimator className="z-10 w-full max-w-screen-sm overflow-hidden rounded-sm bg-white shadow-lg">
|
||||
<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 />
|
||||
<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>
|
||||
<div className="z-1 fixed inset-0 bg-gray-600 bg-opacity-75" />
|
||||
</KBarPositioner>
|
||||
</KBarPortal>
|
||||
);
|
||||
|
@ -196,22 +221,17 @@ export const KBarContent = () => {
|
|||
|
||||
export const KBarTrigger = () => {
|
||||
const { query } = useKBar();
|
||||
const { t } = useLocale();
|
||||
|
||||
return (
|
||||
<div className="flex">
|
||||
<>
|
||||
<Tooltip side="right" content={isMac ? "⌘ + K" : "CTRL + K"}>
|
||||
<button
|
||||
color="minimal"
|
||||
onClick={query.toggle}
|
||||
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">
|
||||
<span className="h-5 w-5 flex-shrink-0 text-neutral-400 group-hover:text-neutral-500 ltr:mr-3 rtl:ml-3">
|
||||
<SearchIcon />
|
||||
</span>
|
||||
<Tooltip content={isMac ? "⌘ + K" : "CTRL + K"}>
|
||||
<span className="hidden lg:inline">{t("commandbar")}</span>
|
||||
</Tooltip>
|
||||
className="group flex text-sm font-medium text-neutral-500 hover:text-neutral-900">
|
||||
<SearchIcon className="h-5 w-5 flex-shrink-0 text-neutral-400 group-hover:text-neutral-500" />
|
||||
</button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -222,7 +242,7 @@ const DisplayShortcuts = (item: shortcutArrayType) => {
|
|||
<span className="space-x-1">
|
||||
{shortcuts?.map((shortcut) => {
|
||||
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}
|
||||
</kbd>
|
||||
);
|
||||
|
|
|
@ -202,11 +202,17 @@ const Layout = ({
|
|||
<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 flex-1 flex-col overflow-y-auto pt-3 pb-4 lg:pt-5">
|
||||
<div className="justify-between md:hidden lg:flex">
|
||||
<Link href="/event-types">
|
||||
<a className="px-4 md:hidden lg:inline">
|
||||
<a className="px-4">
|
||||
<Logo small />
|
||||
</a>
|
||||
</Link>
|
||||
<div className="px-2">
|
||||
<KBarTrigger />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* logo icon for tablet */}
|
||||
<Link href="/event-types">
|
||||
<a className="text-center md:inline lg:hidden">
|
||||
|
@ -261,7 +267,9 @@ const Layout = ({
|
|||
})}
|
||||
</Fragment>
|
||||
))}
|
||||
<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>
|
||||
</div>
|
||||
<TrialBanner />
|
||||
|
@ -304,9 +312,13 @@ const Layout = ({
|
|||
<Logo />
|
||||
</a>
|
||||
</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">
|
||||
<span className="sr-only">{t("view_notifications")}</span>
|
||||
<span className="sr-only">{t("settings")}</span>
|
||||
<Link href="/settings/profile">
|
||||
<a>
|
||||
<CogIcon className="h-6 w-6" aria-hidden="true" />
|
||||
|
|
|
@ -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">
|
||||
{/* Tooltip doesn't show... WHY????? */}
|
||||
{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")} />
|
||||
</Tooltip>
|
||||
)}
|
||||
|
@ -100,7 +100,7 @@ export default function MemberListItem(props: Props) {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Tooltip content={t("team_view_user_availability")}>
|
||||
<Tooltip side="top" content={t("team_view_user_availability")}>
|
||||
<Button
|
||||
// Disabled buttons don't trigger Tooltips
|
||||
title={
|
||||
|
|
|
@ -143,7 +143,7 @@ export default function TeamListItem(props: Props) {
|
|||
<div className="flex rtl:space-x-reverse">
|
||||
<TeamRole role={team.role} />
|
||||
|
||||
<Tooltip content={t("copy_link_team")}>
|
||||
<Tooltip side="top" content={t("copy_link_team")}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(process.env.NEXT_PUBLIC_WEBSITE_URL + "/team/" + team.slug);
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Tooltip } from "@calcom/ui/Tooltip";
|
|||
export default function InfoBadge({ content }: { content: string }) {
|
||||
return (
|
||||
<>
|
||||
<Tooltip content={content}>
|
||||
<Tooltip side="top" content={content}>
|
||||
<span title={content}>
|
||||
<InformationCircleIcon className="relative top-px left-1 right-1 mt-px h-4 w-4 text-gray-500" />
|
||||
</span>
|
||||
|
|
|
@ -51,7 +51,7 @@ export default function WebhookListItem(props: { webhook: TWebhook; onEditWebhoo
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Tooltip content={t("edit_webhook")}>
|
||||
<Tooltip side="top" content={t("edit_webhook")}>
|
||||
<Button
|
||||
onClick={() => props.onEditWebhook()}
|
||||
color="minimal"
|
||||
|
@ -61,7 +61,7 @@ export default function WebhookListItem(props: { webhook: TWebhook; onEditWebhoo
|
|||
/>
|
||||
</Tooltip>
|
||||
<Dialog>
|
||||
<Tooltip content={t("delete_webhook")}>
|
||||
<Tooltip side="top" content={t("delete_webhook")}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
|
|
|
@ -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">
|
||||
{apiKey}
|
||||
</code>
|
||||
<Tooltip content={t("copy_to_clipboard")}>
|
||||
<Tooltip side="top" content={t("copy_to_clipboard")}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(apiKey);
|
||||
|
|
|
@ -61,7 +61,7 @@ export default function ApiKeyListItem(props: { apiKey: TApiKeys; onEditApiKey:
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Tooltip content={t("edit_api_key")}>
|
||||
<Tooltip side="top" content={t("edit_api_key")}>
|
||||
<Button
|
||||
onClick={() => props.onEditApiKey()}
|
||||
color="minimal"
|
||||
|
@ -71,7 +71,7 @@ export default function ApiKeyListItem(props: { apiKey: TApiKeys; onEditApiKey:
|
|||
/>
|
||||
</Tooltip>
|
||||
<Dialog>
|
||||
<Tooltip content={t("delete_api_key")}>
|
||||
<Tooltip side="top" content={t("delete_api_key")}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
"react-digit-input": "^2.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-easy-crop": "^3.5.2",
|
||||
"react-feather": "^2.0.10",
|
||||
"react-hook-form": "^7.31.1",
|
||||
"react-hot-toast": "^2.1.0",
|
||||
"react-intl": "^5.25.1",
|
||||
|
|
|
@ -259,7 +259,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||
"flex justify-between space-x-2 rtl:space-x-reverse ",
|
||||
type.$disabled && "pointer-events-none cursor-not-allowed"
|
||||
)}>
|
||||
<Tooltip content={t("preview") as string}>
|
||||
<Tooltip side="top" content={t("preview") as string}>
|
||||
<a
|
||||
href={`${CAL_URL}/${group.profile.slug}/${type.slug}`}
|
||||
target="_blank"
|
||||
|
@ -271,7 +271,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||
</a>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content={t("copy_link") as string}>
|
||||
<Tooltip side="top" content={t("copy_link") as string}>
|
||||
<button
|
||||
onClick={() => {
|
||||
showToast(t("link_copied"), "success");
|
||||
|
|
|
@ -986,5 +986,7 @@
|
|||
"new_seat_subject": "New Attendee {{name}} on {{eventType}} at {{date}}",
|
||||
"new_seat_title": "Someone has added themselves to an event",
|
||||
"invalid_number": "Invalid phone number",
|
||||
"commandbar": "Command Bar"
|
||||
"navigate": "Navigate",
|
||||
"open": "Open",
|
||||
"close": "Close"
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ export default function ZapierSetup(props: IZapierSetupProps) {
|
|||
<div className="mt-1 text-xl">{t("your_unique_api_key")}</div>
|
||||
<div className="my-2 mt-3 flex">
|
||||
<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
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(newApiKey);
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
||||
import React from "react";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
|
||||
export function Tooltip({
|
||||
children,
|
||||
side,
|
||||
content,
|
||||
open,
|
||||
defaultOpen,
|
||||
|
@ -12,6 +15,7 @@ export function Tooltip({
|
|||
children: React.ReactNode;
|
||||
content: React.ReactNode;
|
||||
open?: boolean;
|
||||
side?: "top" | "right" | "bottom" | "left" | undefined;
|
||||
defaultOpen?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}) {
|
||||
|
@ -23,8 +27,12 @@ export function Tooltip({
|
|||
onOpenChange={onOpenChange}>
|
||||
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
|
||||
<TooltipPrimitive.Content
|
||||
className="-mt-2 rounded-sm bg-black px-1 py-0.5 text-xs text-white shadow-lg"
|
||||
side="top"
|
||||
className={classNames(
|
||||
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"
|
||||
{...props}>
|
||||
{content}
|
||||
|
|
Loading…
Reference in New Issue