Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: rework landing page #49

Merged
merged 12 commits into from
Nov 11, 2024
Binary file modified docs/bun.lockb
Binary file not shown.
15 changes: 8 additions & 7 deletions docs/components/clipboard-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import {useState} from 'react'
import {Copy, Check} from 'lucide-react'
import {useCopyToClipboard} from 'usehooks-ts'
import {Button} from '@/components/ui/button'
import {Button, ButtonProps} from '@/components/ui/button'

interface ClipboardButtonProps {
interface ClipboardButtonProps extends ButtonProps {
text: string
}

export function ClipboardButton(
{text}: ClipboardButtonProps = {text: 'Copy me!'}
{text, ...rest}: ClipboardButtonProps = {text: 'Copy me!'}
) {
const [isCopied, setIsCopied] = useState(false)
const [_, copy] = useCopyToClipboard()
Expand All @@ -24,14 +24,15 @@ export function ClipboardButton(
return (
<Button
variant="outline"
size="icon"
className="font-mono text-sm"
onClick={handleCopy}
aria-label={isCopied ? 'Copied!' : 'Copy to clipboard'}>
{text}
aria-label={isCopied ? 'Copied!' : 'Copy to clipboard'}
{...rest}>
{isCopied ? (
<Check className="ml-2 h-4 w-4 text-green-500" />
<Check className="size-4 text-green-500" />
) : (
<Copy className="ml-2 h-4 w-4" />
<Copy className="size-4" />
)}
</Button>
)
Expand Down
464 changes: 438 additions & 26 deletions docs/components/landing.tsx

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions docs/components/ui/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ import {cn} from '@/lib/utils'

const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({className, ...props}, ref) => (
React.HTMLAttributes<HTMLDivElement> & {showGradient?: boolean}
>(({className, showGradient = true, ...props}, ref) => (
<div
ref={ref}
className={cn(
'rounded-xl bg-card text-card-foreground shadow-sm group relative border border-slate-200 dark:border-slate-800',
className
)}
{...props}>
<div className="absolute -inset-px rounded-xl border-2 border-transparent opacity-0 [background:linear-gradient(var(--quick-links-hover-bg,theme(colors.sky.50)),var(--quick-links-hover-bg,theme(colors.sky.50)))_padding-box,linear-gradient(to_top,theme(colors.indigo.400),theme(colors.cyan.400),theme(colors.sky.500))_border-box] group-hover:opacity-100 dark:[--quick-links-hover-bg:theme(colors.slate.800)] -z-10" />
{showGradient && (
<div className="absolute -inset-px rounded-xl border-2 border-transparent opacity-0 [background:linear-gradient(var(--quick-links-hover-bg,theme(colors.sky.50)),var(--quick-links-hover-bg,theme(colors.sky.50)))_padding-box,linear-gradient(to_top,theme(colors.indigo.400),theme(colors.cyan.400),theme(colors.sky.500))_border-box] group-hover:opacity-100 dark:[--quick-links-hover-bg:theme(colors.slate.800)] -z-10" />
)}
{props.children}
</div>
))
Expand Down
169 changes: 169 additions & 0 deletions docs/components/ui/glowing-stars-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
'use client'

import React, {useEffect, useRef, useState} from 'react'
import {AnimatePresence, motion} from 'framer-motion'
import {cn} from '@/lib/utils'
import {useTheme} from 'nextra-theme-docs'

export const GlowingStarsCard = ({
className,
children,
hoverEfect = true
}: {
className?: string
children?: React.ReactNode
hoverEfect?: boolean
}) => {
const [mouseEnter, setMouseEnter] = useState(false)

return (
<div
onMouseEnter={() => {
if (!hoverEfect) return
setMouseEnter(true)
}}
onMouseLeave={() => {
if (!hoverEfect) return
setMouseEnter(false)
}}
className={cn(
'p-6 max-w-md h-full w-full rounded-xl border border-[#eaeaea] dark:border-gray-800',
className
)}>
<div className="flex justify-center items-center">
<Illustration mouseEnter={mouseEnter} />
</div>
<div className="px-2 pb-6">{children}</div>
</div>
)
}

export const GlowingStarsDescription = ({
className,
children
}: {
className?: string
children?: React.ReactNode
}) => {
return (
<p className={cn('text-base text-white max-w-[16rem]', className)}>
{children}
</p>
)
}

export const GlowingStarsTitle = ({
className,
children
}: {
className?: string
children?: React.ReactNode
}) => {
return (
<h2 className={cn('font-bold text-2xl text-[#eaeaea]', className)}>
{children}
</h2>
)
}

export const Illustration = ({mouseEnter}: {mouseEnter: boolean}) => {
const stars = 108
const columns = 18

const [glowingStars, setGlowingStars] = useState<number[]>([])

const highlightedStars = useRef<number[]>([])

useEffect(() => {
const interval = setInterval(() => {
highlightedStars.current = Array.from({length: 5}, () =>
Math.floor(Math.random() * stars)
)
setGlowingStars([...highlightedStars.current])
}, 3000)

return () => clearInterval(interval)
}, [])

return (
<div
className="h-[180px] p-1 w-full"
style={{
display: 'grid',
gridTemplateColumns: `repeat(${columns}, 1fr)`,
gap: `1px`
}}>
{[...Array(stars)].map((_, starIdx) => {
const isGlowing = glowingStars.includes(starIdx)
const delay = (starIdx % 10) * 0.1
const staticDelay = starIdx * 0.01
return (
<div
key={`matrix-col-${starIdx}}`}
className="relative flex items-center justify-center">
<Star
isGlowing={mouseEnter ? true : isGlowing}
delay={mouseEnter ? staticDelay : delay}
/>
{mouseEnter && <Glow delay={staticDelay} />}
<AnimatePresence mode="wait">
{isGlowing && <Glow delay={delay} />}
</AnimatePresence>
</div>
)
})}
</div>
)
}

const Star = ({isGlowing, delay}: {isGlowing: boolean; delay: number}) => {
const {resolvedTheme} = useTheme()
const isDarkTheme = resolvedTheme === 'dark'
return (
<motion.div
key={delay}
initial={{
scale: 1
}}
animate={{
scale: isGlowing ? [1, 1.2, 2.5, 2.2, 1.5] : 1,
background: isGlowing
? isDarkTheme
? '#fff'
: '#f3f3f3'
: isDarkTheme
? '#666'
: '#4f4f4f'
}}
transition={{
duration: 2,
ease: 'easeInOut',
delay: delay
}}
className={cn(
'bg-[#4f4f4f] dark:bg-[#666] size-[2px] dark:size-[1px] rounded-full relative z-20'
)}></motion.div>
)
}

const Glow = ({delay}: {delay: number}) => {
return (
<motion.div
initial={{
opacity: 0
}}
animate={{
opacity: 1
}}
transition={{
duration: 2,
ease: 'easeInOut',
delay: delay
}}
exit={{
opacity: 0
}}
className="absolute left-1/2 -translate-x-1/2 z-10 h-[4px] w-[4px] rounded-full bg-blue-500 blur-[1px] shadow-2xl shadow-blue-400"
/>
)
}
118 changes: 118 additions & 0 deletions docs/components/ui/hover-border-gradient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
'use client'
import React, {useState, useEffect} from 'react'
import {motion} from 'framer-motion'
import {useTheme} from 'nextra-theme-docs'

import {cn} from '@/lib/utils'

type Direction = 'TOP' | 'LEFT' | 'BOTTOM' | 'RIGHT'

export function HoverBorderGradient({
children,
containerClassName,
className,
as: Tag = 'button',
duration = 1,
clockwise = true,
...props
}: React.PropsWithChildren<
{
as?: React.ElementType
containerClassName?: string
className?: string
duration?: number
clockwise?: boolean
} & React.HTMLAttributes<HTMLElement>
>) {
const [hovered, setHovered] = useState<boolean>(false)
const [direction, setDirection] = useState<Direction>('TOP')

const {resolvedTheme} = useTheme()

// Usually, we wouldnt need this, but the useTheme hook from nextra-theme-docs seem to have (a) bug(s)
const theme =
resolvedTheme && resolvedTheme === 'system'
? 'dark'
: resolvedTheme ?? 'dark'

const rotateDirection = (currentDirection: Direction): Direction => {
const directions: Direction[] = ['TOP', 'LEFT', 'BOTTOM', 'RIGHT']
const currentIndex = directions.indexOf(currentDirection)
const nextIndex = clockwise
? (currentIndex - 1 + directions.length) % directions.length
: (currentIndex + 1) % directions.length
return directions[nextIndex]
}

const movingMap: Record<'dark' | 'light', Record<Direction, string>> = {
dark: {
TOP: 'radial-gradient(20.7% 50% at 50% 0%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)',
LEFT: 'radial-gradient(16.6% 43.1% at 0% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)',
BOTTOM:
'radial-gradient(20.7% 50% at 50% 100%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)',
RIGHT:
'radial-gradient(16.2% 41.199999999999996% at 100% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)'
},
light: {
TOP: 'radial-gradient(20.7% 50% at 50% 0%, #3275F8 0%, rgba(255, 255, 255, 1) 100%)',
LEFT: 'radial-gradient(16.6% 43.1% at 0% 50%, #3275F8 0%, rgba(255, 255, 255, 1) 100%)',
BOTTOM:
'radial-gradient(20.7% 50% at 50% 100%, #3275F8 0%, rgba(255, 255, 255, 1) 100%)',
RIGHT:
'radial-gradient(16.2% 41.199999999999996% at 100% 50%, #3275F8 0%, rgba(255, 255, 255, 1) 100%)'
}
}

const highlight =
theme === 'dark'
? 'radial-gradient(75% 181.15942028985506% at 50% 50%, #3275F8 0%, rgba(255, 255, 255, 0) 100%)'
: 'radial-gradient(75% 181.15942028985506% at 50% 50%, #3275F8 0%, rgba(255, 255, 255, 1) 100%)'

useEffect(() => {
if (!hovered) {
const interval = setInterval(() => {
setDirection(prevState => rotateDirection(prevState))
}, duration * 1000)
return () => clearInterval(interval)
}
}, [hovered])
return (
<Tag
onMouseEnter={(event: React.MouseEvent<HTMLDivElement>) => {
setHovered(true)
}}
onMouseLeave={() => setHovered(false)}
className={cn(
'relative flex rounded-full border content-center bg-border dark:hover:bg-white/10 transition duration-500 dark:bg-white/20 items-center flex-col flex-nowrap gap-10 h-min justify-center overflow-visible p-px decoration-clone w-fit',
containerClassName
)}
{...props}>
<div
className={cn(
'w-auto text-primary z-10 bg-white dark:bg-[#0a0a0a] px-4 py-2 rounded-[inherit]',
className
)}>
{children}
</div>
<motion.div
className={cn(
'flex-none inset-0 overflow-hidden absolute z-0 rounded-[inherit]'
)}
style={{
filter: 'blur(2px)',
position: 'absolute',
width: '100%',
height: '100%'
}}
initial={{background: movingMap[direction]}}
animate={{
background: hovered
? [movingMap[theme][direction], highlight]
: movingMap[theme][direction]
}}
transition={{ease: 'linear', duration: duration ?? 1}}
/>
<div className="bg-black absolute z-1 flex-none inset-[2px] rounded-[100px]" />
</Tag>
)
}
10 changes: 9 additions & 1 deletion docs/next.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
const withNextra = require('nextra')({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.tsx'
themeConfig: './theme.config.tsx',
cleanDistDir: true
})

module.exports = withNextra({
images: {
domains: [
'raw.githubusercontent.com',
'avatars.githubusercontent.com',
'upload.wikimedia.org'
]
},
async redirects() {
return [
{
Expand Down
2 changes: 2 additions & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"@vercel/analytics": "^1.3.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"framer-motion": "^11.11.11",
"lottie-react": "^2.4.0",
"lucide-react": "^0.440.0",
"next": "^13.0.6",
"nextra": "latest",
Expand Down
2 changes: 1 addition & 1 deletion docs/pages/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"display": "hidden",
"theme": {
"timestamp": false,
"layout": "full"
"layout": "raw"
}
},
"docs": {
Expand Down
Loading