-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a0166b7
commit 8b906e9
Showing
11 changed files
with
709 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { FOCUS_VISIBLE_OUTLINE, GRADIENT_LINK } from "@/lib/constants"; | ||
import { Portal, Transition } from "@headlessui/react"; | ||
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"; | ||
import cx from "clsx"; | ||
import Image from "next/image"; | ||
import { encode } from "qss"; | ||
import React from "react"; | ||
|
||
export const LinkPreview = ({ children, url }) => { | ||
const width = 200; | ||
const height = 125; | ||
const quality = 50; | ||
const layout = "fixed"; | ||
|
||
// Simplifies things by encoding our microlink params into a query string. | ||
const params = encode({ | ||
url, | ||
screenshot: true, | ||
meta: false, | ||
embed: "screenshot.url", | ||
colorScheme: "dark", | ||
"viewport.isMobile": true, | ||
"viewport.deviceScaleFactor": 1, | ||
|
||
// To capture useful content, the screenshot viewport needs to be bigger | ||
// than our images but maintain the same ratio | ||
"viewport.width": width * 3, | ||
"viewport.height": height * 3, | ||
}); | ||
|
||
const src = `https://api.microlink.io/?${params}`; | ||
|
||
const [isOpen, setOpen] = React.useState(false); | ||
// const [static, setStatic] = useState(false); | ||
|
||
// if (staticImage) setStatic(true); | ||
|
||
const [isMounted, setIsMounted] = React.useState(false); | ||
|
||
React.useEffect(() => { | ||
setIsMounted(true); | ||
}, []); | ||
|
||
return ( | ||
<> | ||
{/** | ||
* Microlink.io + next/image can take a few seconds to fetch and generate | ||
* a screenshot. The delay makes <LinkPreview> pointless. As a hacky | ||
* solution we create a second <Image> in a Portal after the component has | ||
* mounted. This <Image> triggers microlink.io + next/image so that the | ||
* image itself is ready by the time the user hovers on a <LinkPreview>. | ||
* Not concerned about the performance impact because <Image>'s are cached | ||
* after they are generated and the images themselves are tiny (< 10kb). | ||
*/} | ||
{isMounted ? ( | ||
<Portal> | ||
<div className="hidden"> | ||
<Image | ||
src={src} | ||
width={width} | ||
height={height} | ||
quality={quality} | ||
layout={layout} | ||
priority={true} | ||
/> | ||
</div> | ||
</Portal> | ||
) : null} | ||
|
||
<HoverCardPrimitive.Root | ||
openDelay={50} | ||
onOpenChange={(open) => { | ||
setOpen(open); | ||
}} | ||
> | ||
<HoverCardPrimitive.Trigger | ||
href={url} | ||
className={cx(GRADIENT_LINK, FOCUS_VISIBLE_OUTLINE)} | ||
> | ||
{children} | ||
</HoverCardPrimitive.Trigger> | ||
|
||
<HoverCardPrimitive.Content side="top" align="center" sideOffset={10}> | ||
<Transition | ||
show={isOpen} | ||
appear={true} | ||
enter="transform transition duration-300 origin-bottom ease-out" | ||
enterFrom="opacity-0 translate-y-2 scale-0" | ||
enterTo="opacity-100 translate-y-0 scale-100" | ||
className="shadow-xl rounded-xl" | ||
> | ||
<a | ||
href={url} | ||
className="block p-1 bg-white border border-transparent shadow rounded-xl hover:border-pink-500" | ||
// Unfortunate hack to remove the weird whitespace left by | ||
// next/image wrapper div | ||
// https://github.com/vercel/next.js/issues/18915 | ||
style={{ fontSize: 0 }} | ||
> | ||
<Image | ||
src={src} | ||
width={width} | ||
height={height} | ||
quality={quality} | ||
layout={layout} | ||
priority={true} | ||
className="rounded-lg" | ||
/> | ||
</a> | ||
</Transition> | ||
</HoverCardPrimitive.Content> | ||
</HoverCardPrimitive.Root> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import { FOCUS_VISIBLE_OUTLINE, GRADIENT_LINK } from "@/lib/constants"; | ||
import { Portal, Transition } from "@headlessui/react"; | ||
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"; | ||
import cx from "clsx"; | ||
import Image from "next/image"; | ||
import { encode } from "qss"; | ||
import React from "react"; | ||
|
||
export const StaticLinkPreview = ({ children, url }) => { | ||
const width = 200; | ||
const height = 125; | ||
const quality = 50; | ||
const layout = "fixed"; | ||
|
||
// Simplifies things by encoding our microlink params into a query string. | ||
const params = encode({ | ||
url, | ||
screenshot: true, | ||
meta: false, | ||
embed: "screenshot.url", | ||
colorScheme: "dark", | ||
"viewport.isMobile": true, | ||
"viewport.deviceScaleFactor": 1, | ||
|
||
// To capture useful content, the screenshot viewport needs to be bigger | ||
// than our images but maintain the same ratio | ||
"viewport.width": width * 3, | ||
"viewport.height": height * 3, | ||
}); | ||
|
||
const src = `https://api.microlink.io/?${params}`; | ||
|
||
const [isOpen, setOpen] = React.useState(false); | ||
// const [static, setStatic] = useState(false); | ||
|
||
// if (staticImage) setStatic(true); | ||
|
||
const [isMounted, setIsMounted] = React.useState(false); | ||
|
||
React.useEffect(() => { | ||
setIsMounted(true); | ||
}, []); | ||
|
||
return ( | ||
<> | ||
{/** | ||
* Microlink.io + next/image can take a few seconds to fetch and generate | ||
* a screenshot. The delay makes <LinkPreview> pointless. As a hacky | ||
* solution we create a second <Image> in a Portal after the component has | ||
* mounted. This <Image> triggers microlink.io + next/image so that the | ||
* image itself is ready by the time the user hovers on a <LinkPreview>. | ||
* Not concerned about the performance impact because <Image>'s are cached | ||
* after they are generated and the images themselves are tiny (< 10kb). | ||
*/} | ||
{isMounted ? ( | ||
<Portal> | ||
<div className="hidden"> | ||
<Image | ||
src={src} | ||
width={width} | ||
height={height} | ||
quality={quality} | ||
layout={layout} | ||
priority={true} | ||
/> | ||
</div> | ||
</Portal> | ||
) : null} | ||
|
||
<HoverCardPrimitive.Root | ||
openDelay={50} | ||
onOpenChange={(open) => { | ||
setOpen(open); | ||
}} | ||
> | ||
<HoverCardPrimitive.Trigger | ||
href={url} | ||
className={cx(GRADIENT_LINK, FOCUS_VISIBLE_OUTLINE)} | ||
> | ||
{children} | ||
</HoverCardPrimitive.Trigger> | ||
|
||
<HoverCardPrimitive.Content side="top" align="center" sideOffset={10}> | ||
<Transition | ||
show={isOpen} | ||
appear={true} | ||
enter="transform transition duration-300 origin-bottom ease-out" | ||
enterFrom="opacity-0 translate-y-2 scale-0" | ||
enterTo="opacity-100 translate-y-0 scale-100" | ||
className="shadow-xl rounded-xl" | ||
> | ||
<span | ||
className="block p-1 bg-white border border-transparent shadow rounded-xl hover:border-pink-500" | ||
// Unfortunate hack to remove the weird whitespace left by | ||
// next/image wrapper div | ||
// https://github.com/vercel/next.js/issues/18915 | ||
style={{ fontSize: 0 }} | ||
> | ||
<Image | ||
src={src} | ||
width={width} | ||
height={height} | ||
quality={quality} | ||
layout={layout} | ||
priority={true} | ||
className="rounded-lg" | ||
/> | ||
</span> | ||
</Transition> | ||
</HoverCardPrimitive.Content> | ||
</HoverCardPrimitive.Root> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
export const FOCUS_VISIBLE_OUTLINE = `focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-lightBlue-500 focus-visible:ring-opacity-50 focus-visible:outline-none focus:outline-none`; | ||
|
||
export const GRADIENT_LINK = `decoration-clone bg-clip-text font-medium text-transparent bg-gradient-to-br from-pink-500 via-red-500 to-yellow-500 hover:text-pink-600 hover:bg-none`; | ||
|
||
export const LIGHT_COLORS = ["#E9D5FF", "#FBCFE8", "#FECACA", "#FDE68A"]; | ||
|
||
export const DARK_COLORS = [ | ||
"#FB7185", | ||
"#FBBF24", | ||
"#34D399", | ||
"#E879F9", | ||
"#38BDF8", | ||
"#9CA3AF", | ||
"#FB923C", | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Fisher–Yates Shuffle Algorithm | ||
export function shuffleArray(array) { | ||
for (let i = array.length - 1; i > 0; i--) { | ||
let j = Math.floor(Math.random() * (i + 1)); | ||
let temp = array[i]; | ||
array[i] = array[j]; | ||
array[j] = temp; | ||
} | ||
|
||
return array; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import React from "react"; | ||
|
||
// a custom hook to detect when custom fonts have finished loading | ||
export function useIsFontReady() { | ||
const [isReady, setIsReady] = React.useState(false); | ||
|
||
React.useEffect(() => { | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Document/fonts | ||
document.fonts.ready.then(() => { | ||
setIsReady(true); | ||
}); | ||
}, []); | ||
|
||
return isReady; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.