Skip to content

Commit

Permalink
Merge pull request #189 from steven-tey/keyboard-shortcuts
Browse files Browse the repository at this point in the history
Added keyboard shortcuts
  • Loading branch information
steven-tey committed May 2, 2023
2 parents a1a4fee + 2f59c36 commit 0539ebb
Show file tree
Hide file tree
Showing 16 changed files with 261 additions and 75 deletions.
12 changes: 11 additions & 1 deletion app/(marketing)/changelog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { allChangelogPosts } from "contentlayer/generated";
import { MDX } from "app/(marketing)/components/mdx";
import Link from "next/link";
import { formatDate } from "@/lib/utils";
import { getBlurDataURL, getImages } from "@/lib/images";
import { getBlurDataURL } from "@/lib/images";
import BlurImage from "@/components/shared/blur-image";
import Author from "app/(marketing)/components/author";
import { Facebook, LinkedIn, Twitter } from "@/components/shared/icons";
Expand Down Expand Up @@ -139,6 +139,16 @@ export default async function ChangelogPost({
</div>
</div>
<MDX code={post.body.code} />
<div className="mt-10 flex justify-end border-t border-gray-200 pt-5">
<Link
href={`https://github.com/steven-tey/dub/blob/main/posts/changelog/${params.slug}.mdx`}
target="_blank"
rel="noopener noreferrer"
className="text-sm text-gray-500 transition-colors hover:text-gray-800"
>
<p>Found a typo? Edit this page →</p>
</Link>
</div>
</div>
</div>
);
Expand Down
10 changes: 9 additions & 1 deletion app/(marketing)/components/mdx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,15 @@ export function MDX({ code, images, tweets, repos }: MDXProps) {
};

return (
<article className="prose prose-gray mx-5 w-full transition-all prose-headings:font-display prose-h2:text-3xl prose-a:font-medium prose-a:text-gray-500 prose-a:underline-offset-4 hover:prose-a:text-black prose-thead:text-lg md:mx-0 md:prose-lg">
<article
className={`
prose prose-gray mx-5 transition-all md:prose-lg prose-headings:font-display prose-h2:text-3xl
prose-a:font-medium prose-a:text-gray-500
prose-a:underline-offset-4 hover:prose-a:text-black prose-code:rounded-md prose-code:bg-gray-200
prose-code:px-2 prose-code:py-1 prose-code:font-medium prose-code:text-rose-500 prose-code:before:hidden prose-code:after:hidden prose-thead:text-lg
md:mx-0 md:w-full
`}
>
<Component components={{ ...components, MDXImage, MDXTweet, MDXRepo }} />
</article>
);
Expand Down
4 changes: 2 additions & 2 deletions components/app/links/link-card-placeholder.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export default function LinkCardPlaceholder() {
return (
<li className="flex items-center rounded-lg bg-white px-4 py-[1.15rem] shadow transition-all hover:shadow-md">
<li className="flex items-center rounded-lg border-2 border-gray-50 bg-white p-3 shadow transition-all hover:shadow-md sm:p-4">
<div className="mr-2 h-10 w-10 animate-pulse rounded-full bg-gray-200" />
<div>
<div className="mb-2.5 flex items-center space-x-2">
<div className="mb-3 flex items-center space-x-2">
<div className="h-5 w-28 animate-pulse rounded-md bg-gray-200" />
<div className="h-5 w-5 animate-pulse rounded-full bg-gray-200" />
<div className="h-5 w-5 animate-pulse rounded-full bg-gray-200" />
Expand Down
105 changes: 85 additions & 20 deletions components/app/links/link-card.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Link from "next/link";
import { useRouter } from "next/router";
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import useSWR from "swr";
import { useAddEditLinkModal } from "@/components/app/modals/add-edit-link-modal";
import { useArchiveLinkModal } from "@/components/app/modals/archive-link-modal";
Expand Down Expand Up @@ -92,27 +92,62 @@ export default function LinkCard({ props }: { props: LinkProps }) {
props,
});
const [openPopover, setOpenPopover] = useState(false);
const [selected, setSelected] = useState(false);
// if clicked on linkRef, setSelected to true
// else setSelected to false
// do this via event listener

const expired = expiresAt && new Date() > new Date(expiresAt);
const onClick = (e: any) => {
if (linkRef.current && !linkRef.current.contains(e.target)) {
setSelected(false);
} else {
setSelected(!selected);
}
};

const shortcuts = ["e", "d", "a", "x"];
const onKeyDown = (e: any) => {
// only run shortcut logic if link is selected or the 3 dots menu is open
if ((selected || openPopover) && shortcuts.includes(e.key)) {
setOpenPopover(false);
switch (e.key) {
case "e":
setShowAddEditLinkModal(true);
break;
case "d":
setShowDuplicateLinkModal(true);
break;
case "a":
setShowArchiveLinkModal(true);
break;
case "x":
setShowDeleteLinkModal(true);
break;
}
}
};

useEffect(() => {
document.addEventListener("click", onClick);
document.addEventListener("keydown", onKeyDown);
return () => {
document.removeEventListener("click", onClick);
document.removeEventListener("keydown", onKeyDown);
};
}, [onClick]);

return (
<div
ref={linkRef}
className="relative rounded-lg border border-gray-100 bg-white p-3 pr-1 shadow transition-all hover:shadow-md sm:p-4 "
className={`${
selected ? "border-black" : "border-gray-50"
} relative rounded-lg border-2 bg-white p-3 pr-1 shadow transition-all hover:shadow-md sm:p-4`}
>
<LinkQRModal />
<AddEditLinkModal />
<DuplicateLinkModal />
<ArchiveLinkModal />
<DeleteLinkModal />
{/* <div className="absolute top-0 left-0 flex h-full w-1.5 flex-col overflow-hidden rounded-l-lg">
{archived && <div className="h-full w-full bg-gray-400" />}
{expired ? (
<div className="h-full w-full bg-amber-500" />
) : (
<div className="h-full w-full bg-green-500" />
)}
</div> */}
<li className="relative flex items-center justify-between">
<div className="relative flex shrink items-center space-x-2 sm:space-x-4">
<BlurImage
Expand Down Expand Up @@ -144,6 +179,9 @@ export default function LinkCard({ props }: { props: LinkProps }) {
</Tooltip>
) : (
<a
onClick={(e) => {
e.stopPropagation();
}}
className="w-24 truncate text-sm font-semibold text-blue-800 sm:w-full sm:text-base"
href={linkConstructor({ key, domain })}
target="_blank"
Expand All @@ -158,13 +196,19 @@ export default function LinkCard({ props }: { props: LinkProps }) {
)}
<CopyButton url={linkConstructor({ key, domain })} />
<button
onClick={() => setShowLinkQRModal(true)}
onClick={(e) => {
e.stopPropagation();
setShowLinkQRModal(true);
}}
className="group rounded-full bg-gray-100 p-1.5 transition-all duration-75 hover:scale-105 hover:bg-blue-100 active:scale-95"
>
<span className="sr-only">Download QR</span>
<QR className="text-gray-700 transition-all group-hover:text-blue-800" />
</button>
<Link
onClick={(e) => {
e.stopPropagation();
}}
href={`/${slug ? `${slug}/${domain}` : "links"}/${encodeURI(
key,
)}`}
Expand Down Expand Up @@ -203,11 +247,14 @@ export default function LinkCard({ props }: { props: LinkProps }) {
/>
}
>
<div className="w-full cursor-not-allowed p-2 text-left text-sm font-medium text-gray-300 transition-all duration-75">
<div className="flex w-full cursor-not-allowed items-center justify-between p-2 text-left text-sm font-medium text-gray-300 transition-all duration-75">
<IconMenu
text="Edit"
icon={<Edit3 className="h-4 w-4" />}
/>
<kbd className="hidden rounded bg-gray-100 px-2 py-0.5 text-xs font-light text-gray-300 transition-all duration-75 sm:inline-block">
E
</kbd>
</div>
</Tooltip>
) : (
Expand All @@ -216,29 +263,35 @@ export default function LinkCard({ props }: { props: LinkProps }) {
setOpenPopover(false);
setShowAddEditLinkModal(true);
}}
className="w-full rounded-md p-2 text-left text-sm font-medium text-gray-500 transition-all duration-75 hover:bg-gray-100"
className="group flex w-full items-center justify-between rounded-md p-2 text-left text-sm font-medium text-gray-500 transition-all duration-75 hover:bg-gray-100"
>
<IconMenu
text="Edit"
icon={<Edit3 className="h-4 w-4" />}
/>
<kbd className="hidden rounded bg-gray-100 px-2 py-0.5 text-xs font-light text-gray-500 transition-all duration-75 group-hover:bg-gray-200 sm:inline-block">
E
</kbd>
</button>
)}
{slug && exceededUsage ? (
<Tooltip
content={
<TooltipContent
title="Your project has exceeded its usage limit. We're still collecting data on your existing links, but you need to upgrade to edit them."
title="Your project has exceeded its usage limit. We're still collecting data on your existing links, but you need to upgrade to create a new link."
cta="Upgrade"
ctaLink={`/${slug}/settings/billing`}
/>
}
>
<div className="w-full cursor-not-allowed p-2 text-left text-sm font-medium text-gray-300 transition-all duration-75">
<div className="flex w-full cursor-not-allowed items-center justify-between p-2 text-left text-sm font-medium text-gray-300 transition-all duration-75">
<IconMenu
text="Duplicate"
icon={<CopyPlus className="h-4 w-4" />}
/>
<kbd className="hidden rounded bg-gray-100 px-2 py-0.5 text-xs font-light text-gray-300 transition-all duration-75 sm:inline-block">
D
</kbd>
</div>
</Tooltip>
) : (
Expand All @@ -247,12 +300,15 @@ export default function LinkCard({ props }: { props: LinkProps }) {
setOpenPopover(false);
setShowDuplicateLinkModal(true);
}}
className="w-full rounded-md p-2 text-left text-sm font-medium text-gray-500 transition-all duration-75 hover:bg-gray-100"
className="group flex w-full items-center justify-between rounded-md p-2 text-left text-sm font-medium text-gray-500 transition-all duration-75 hover:bg-gray-100"
>
<IconMenu
text="Duplicate"
icon={<CopyPlus className="h-4 w-4" />}
/>
<kbd className="hidden rounded bg-gray-100 px-2 py-0.5 text-xs font-light text-gray-500 transition-all duration-75 group-hover:bg-gray-200 sm:inline-block">
D
</kbd>
</button>
)}
<button
Expand All @@ -261,24 +317,30 @@ export default function LinkCard({ props }: { props: LinkProps }) {
setOpenPopover(false);
setShowArchiveLinkModal(true);
}}
className="w-full rounded-md p-2 text-left text-sm font-medium text-gray-500 transition-all duration-75 hover:bg-gray-100"
className="group flex w-full items-center justify-between rounded-md p-2 text-left text-sm font-medium text-gray-500 transition-all duration-75 hover:bg-gray-100"
>
<IconMenu
text={archived ? "Unarchive" : "Archive"}
icon={<Archive className="h-4 w-4" />}
/>
<kbd className="hidden rounded bg-gray-100 px-2 py-0.5 text-xs font-light text-gray-500 transition-all duration-75 group-hover:bg-gray-200 sm:inline-block">
A
</kbd>
</button>
<button
onClick={() => {
setOpenPopover(false);
setShowDeleteLinkModal(true);
}}
className="w-full rounded-md p-2 text-left text-sm font-medium text-red-600 transition-all duration-75 hover:bg-red-600 hover:text-white"
className="group flex w-full items-center justify-between rounded-md p-2 text-left text-sm font-medium text-red-600 transition-all duration-75 hover:bg-red-600 hover:text-white"
>
<IconMenu
text="Delete"
icon={<Delete className="h-4 w-4" />}
/>
<kbd className="hidden rounded bg-red-100 px-2 py-0.5 text-xs font-light text-red-600 transition-all duration-75 group-hover:bg-red-500 group-hover:text-white sm:inline-block">
X
</kbd>
</button>
</div>
}
Expand All @@ -288,7 +350,10 @@ export default function LinkCard({ props }: { props: LinkProps }) {
>
<button
type="button"
onClick={() => setOpenPopover(!openPopover)}
onClick={(e) => {
e.stopPropagation();
setOpenPopover(!openPopover);
}}
className="rounded-md px-1 py-2 transition-all duration-75 hover:bg-gray-100 active:bg-gray-200"
>
<ThreeDots className="h-5 w-5 text-gray-500" />
Expand Down
24 changes: 22 additions & 2 deletions components/app/links/link-filters.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRouter } from "next/router";
import { useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { nFormatter, setQueryString } from "@/lib/utils";
import { ChevronRight, XCircle, Search } from "lucide-react";
import useDomains from "@/lib/swr/use-domains";
Expand All @@ -23,7 +23,7 @@ export default function LinkFilters() {
<div className="grid w-full gap-6 rounded-md bg-white p-5 lg:divide-y lg:divide-gray-300">
<div className="grid gap-3">
<div className="flex items-center justify-between">
<h3 className="mt-2 ml-1 font-semibold">Filter Links</h3>
<h3 className="ml-1 mt-2 font-semibold">Filter Links</h3>
{(search || domain || status) && (
<ClearButton searchInputRef={searchInputRef} />
)}
Expand Down Expand Up @@ -60,6 +60,26 @@ const SearchBox = ({ searchInputRef }: { searchInputRef }) => {
}, 500);
const { isValidating } = useLinks();

const onKeyDown = useCallback((e: KeyboardEvent) => {
const target = e.target as HTMLElement;
// only focus on filter input when:
// - user is not typing in an input or textarea
// - there is no existing modal backdrop (i.e. no other modal is open)
if (
e.key === "/" &&
target.tagName !== "INPUT" &&
target.tagName !== "TEXTAREA"
) {
e.preventDefault();
searchInputRef.current?.focus();
}
}, []);

useEffect(() => {
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, [onKeyDown]);

return (
<div className="relative">
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
Expand Down
39 changes: 33 additions & 6 deletions components/app/modals/add-edit-link-modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ function AddEditLinkModal({
{!hideXButton && !homepageDemo && (
<button
onClick={() => setShowAddEditLinkModal(false)}
className="group absolute top-0 right-0 z-20 m-3 hidden rounded-full p-2 text-gray-500 transition-all duration-75 hover:bg-gray-100 focus:outline-none active:bg-gray-200 sm:block"
className="group absolute right-0 top-0 z-20 m-3 hidden rounded-full p-2 text-gray-500 transition-all duration-75 hover:bg-gray-100 focus:outline-none active:bg-gray-200 sm:block"
>
<X className="h-5 w-5" />
</button>
Expand All @@ -250,7 +250,7 @@ function AddEditLinkModal({
className="rounded-l-2xl sm:max-h-[min(906px,_90vh)] sm:overflow-scroll"
onScroll={handleScroll}
>
<div className="z-10 flex flex-col items-center justify-center space-y-3 border-b border-gray-200 bg-white px-4 pt-8 pb-8 transition-all sm:sticky sm:top-0 sm:px-16">
<div className="z-10 flex flex-col items-center justify-center space-y-3 border-b border-gray-200 bg-white px-4 pb-8 pt-8 transition-all sm:sticky sm:top-0 sm:px-16">
<BlurImage
src={logo}
alt="Logo"
Expand Down Expand Up @@ -559,6 +559,27 @@ function AddEditLinkButton({

const { exceededUsage } = useProject();

const onKeyDown = useCallback((e: KeyboardEvent) => {
const target = e.target as HTMLElement;
const existingModalBackdrop = document.getElementById("modal-backdrop");
// only open modal with keyboard shortcut if:
// - user is not typing in an input or textarea
// - there is no existing modal backdrop (i.e. no other modal is open)
if (
e.key === "c" &&
target.tagName !== "INPUT" &&
target.tagName !== "TEXTAREA" &&
!existingModalBackdrop
) {
setShowAddEditLinkModal(true);
}
}, []);

useEffect(() => {
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, [onKeyDown]);

return slug && exceededUsage ? ( // only show exceeded usage tooltip if user is on a project page
<Tooltip
content={
Expand All @@ -569,16 +590,22 @@ function AddEditLinkButton({
/>
}
>
<div className="cursor-not-allowed rounded-md border border-gray-200 px-5 py-2 text-sm font-medium text-gray-300 transition-all duration-75">
Create link
<div className="flex cursor-not-allowed items-center space-x-3 rounded-md border border-gray-200 px-3 py-2 text-sm font-medium text-gray-300">
<p>Create link</p>
<kbd className="hidden rounded bg-gray-100 px-2 py-0.5 text-xs font-light text-gray-300 sm:inline-block">
C
</kbd>
</div>
</Tooltip>
) : (
<button
onClick={() => setShowAddEditLinkModal(true)}
className="rounded-md border border-black bg-black px-5 py-2 text-sm font-medium text-white transition-all duration-75 hover:bg-white hover:text-black active:scale-95"
className="group flex items-center space-x-3 rounded-md border border-black bg-black px-3 py-2 text-sm font-medium text-white transition-all duration-75 hover:bg-white hover:text-black active:scale-95"
>
Create link
<p>Create link</p>
<kbd className="hidden rounded bg-zinc-700 px-2 py-0.5 text-xs font-light text-gray-400 transition-all duration-75 group-hover:bg-gray-100 group-hover:text-gray-500 sm:inline-block">
C
</kbd>
</button>
);
}
Expand Down

0 comments on commit 0539ebb

Please sign in to comment.