Skip to content

Commit d52848d

Browse files
committed
feat: improve homepage and links
1 parent bf9464f commit d52848d

File tree

12 files changed

+224
-83
lines changed

12 files changed

+224
-83
lines changed

app/[lang]/[[...mdxPath]]/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@ export default async function Page(props: PageProps) {
3434
`/${params.lang}/${params.mdxPath?.join('/') || ''}`
3535
)
3636

37+
const isHomepage = !params.mdxPath || params.mdxPath.length === 0
38+
3739
return (
3840
<>
3941
<JsonLd data={schemaData} />
4042
<Wrapper toc={toc} metadata={metadata}>
4143
<MDXContent {...props} params={params} />
42-
<SuggestPattern />
44+
{!isHomepage && <SuggestPattern />}
4345
</Wrapper>
4446
</>
4547
)

app/_components/featured.tsx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'use client'
2+
3+
import { Star } from "lucide-react";
4+
import { usePlausible } from 'next-plausible';
5+
import { getRandomPattern, Pattern } from "../_constants/patterns";
6+
import { LinkCustom } from "./link-custom";
7+
8+
type FeaturedPatternProps = {
9+
pattern: Pattern;
10+
};
11+
12+
const FeaturedPatternSection = ({ pattern }: FeaturedPatternProps) => {
13+
const plausible = usePlausible()
14+
15+
return (
16+
<div className="featured-pattern animate-fade-up flex flex-col mt-10 py-10 px-4 border border-neutral-400 dark:border-neutral-600 rounded-xl">
17+
<div className="absolute inset-0 bg-gradient-radial from-neutral-900/10 via-transparent to-transparent dark:bg-gradient-radial dark:from-neutral-500/10 dark:via-transparent dark:to-transparent
18+
" />
19+
<div className="relative">
20+
<div className="flex items-center gap-2 text-neutral-800 dark:text-neutral-400">
21+
<Star className="h-5 w-5" />
22+
<h2 className="text-sm font-medium text-neutral-800 dark:text-neutral-100">Featured Pattern</h2>
23+
</div>
24+
<h3 className="mt-4 text-2xl font-bold text-neutral-800 dark:text-neutral-100">{pattern.title}</h3>
25+
<p className="mt-2 text-muted-foreground text-neutral-800 dark:text-neutral-100">
26+
{pattern.description}
27+
</p>
28+
<div className="mt-6 flex gap-4 text-neutral-800 dark:text-neutral-100">
29+
<LinkCustom
30+
href={`${pattern.href}`}
31+
variant="outline"
32+
size="xs"
33+
onClick={() => plausible('view-pattern')}
34+
>
35+
View Pattern
36+
</LinkCustom>
37+
</div>
38+
</div>
39+
</div>
40+
);
41+
};
42+
43+
const pattern = getRandomPattern();
44+
45+
export const FeaturedPattern = () => {
46+
return pattern ? <FeaturedPatternSection pattern={pattern} /> : null;
47+
};

app/_components/link-custom.tsx

Lines changed: 92 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,108 @@
1-
import clsx from 'clsx'
1+
import { cn } from "@/app/_utils/cn"
2+
import { Slot } from '@radix-ui/react-slot'
3+
import { type VariantProps, cva } from 'class-variance-authority'
24
import Link, { LinkProps } from 'next/link'
3-
import React from 'react'
5+
import * as React from 'react'
46

5-
type CustomLinkProps = LinkProps & {
7+
const linkVariants = cva(
8+
"inline-flex items-center gap-2 cursor-pointer transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 !no-underline relative",
9+
{
10+
variants: {
11+
variant: {
12+
default: 'text-foreground hover:text-foreground/50',
13+
primary: 'text-primary hover:text-primary/80 hover:translate-x-0.5',
14+
destructive: 'text-destructive hover:text-destructive/80 hover:scale-105',
15+
muted: 'text-muted-foreground hover:text-muted-foreground/80',
16+
neutral: 'flex items-center gap-2 dark:text-neutral-800 border border-neutral-400 bg-neutral-100 rounded-md px-4 py-2 !no-underline',
17+
outline: 'border border-current rounded-md px-3 py-1.5 hover:bg-accent/10 dark:hover:bg-neutral-100 dark:hover:text-neutral-900',
18+
gradient: `bg-gradient-to-r from-primary to-primary bg-[length:0%_2px] bg-no-repeat bg-left-bottom
19+
hover:bg-[length:100%_2px] transition-all duration-300`,
20+
},
21+
size: {
22+
default: 'text-base',
23+
sm: 'text-sm',
24+
xs: 'text-xs',
25+
lg: 'text-lg',
26+
},
27+
},
28+
defaultVariants: {
29+
variant: 'default',
30+
size: 'default',
31+
},
32+
}
33+
)
34+
35+
export interface CustomLinkProps
36+
extends LinkProps,
37+
VariantProps<typeof linkVariants> {
638
href: string
739
className?: string
840
children: React.ReactNode
941
rel?: string
1042
icon?: boolean
43+
asChild?: boolean
1144
}
1245

13-
export const LinkCustom: React.FC<CustomLinkProps> = ({
14-
href,
15-
className,
16-
children,
17-
icon = true,
18-
...rest
19-
}) => {
20-
const isInternalLink = href && (href.startsWith('/') || href.startsWith('#'))
46+
export const LinkCustom = React.forwardRef<HTMLAnchorElement, CustomLinkProps>(
47+
({ href, className, children, variant, size, icon = true, asChild = false, ...props }, ref) => {
48+
const isInternalLink = href && (href.startsWith('/') || href.startsWith('#'))
49+
50+
const ExternalIcon = () => (
51+
<span className="inline-flex items-center">
52+
<svg
53+
stroke="currentColor"
54+
fill="none"
55+
strokeWidth="2"
56+
viewBox="0 0 24 24"
57+
strokeLinecap="round"
58+
strokeLinejoin="round"
59+
focusable="false"
60+
aria-hidden="true"
61+
height="1em"
62+
width="1em"
63+
xmlns="http://www.w3.org/2000/svg"
64+
>
65+
<line x1="7" y1="17" x2="17" y2="7"></line>
66+
<polyline points="7 7 17 7 17 17"></polyline>
67+
</svg>
68+
</span>
69+
)
70+
71+
if (!isInternalLink) {
72+
const Comp = asChild ? Slot : 'a'
73+
return (
74+
<Comp
75+
ref={ref}
76+
href={href}
77+
rel="noopener noreferrer"
78+
target="_blank"
79+
className={cn(linkVariants({ variant, size, className }))}
80+
{...props}
81+
>
82+
{children}
83+
{icon && <ExternalIcon />}
84+
</Comp>
85+
)
86+
}
87+
88+
const Comp = asChild ? Slot : Link
89+
const linkProps = asChild ? { className: cn(linkVariants({ variant, size, className })) } : {
90+
href,
91+
className: cn(linkVariants({ variant, size, className }))
92+
}
2193

22-
if (!isInternalLink) {
2394
return (
24-
<a
25-
href={href}
26-
rel="noopener noreferrer"
27-
target="_blank"
28-
className={clsx(
29-
'cursor-pointer inline-flex',
30-
className
31-
)}
32-
{...rest}
95+
<Comp
96+
ref={ref}
97+
{...linkProps}
98+
{...props}
3399
>
34100
{children}
35-
{icon && (
36-
<span className="inline-flex items-center">
37-
<svg
38-
stroke="currentColor"
39-
fill="none"
40-
strokeWidth="2"
41-
viewBox="0 0 24 24"
42-
strokeLinecap="round"
43-
strokeLinejoin="round"
44-
focusable="false"
45-
aria-hidden="true"
46-
height="1em"
47-
width="1em"
48-
xmlns="http://www.w3.org/2000/svg"
49-
>
50-
<line x1="7" y1="17" x2="17" y2="7"></line>
51-
<polyline points="7 7 17 7 17 17"></polyline>
52-
</svg>
53-
</span>
54-
)}
55-
</a>
101+
</Comp>
56102
)
57103
}
104+
)
58105

59-
return (
60-
<Link
61-
href={href}
62-
className={clsx('cursor-pointer', className)}
63-
{...rest}
64-
>
65-
{children}
66-
</Link>
67-
)
68-
}
106+
LinkCustom.displayName = 'LinkCustom'
107+
108+
export { linkVariants }

app/_components/sections/home-cta.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use client'
2+
3+
import { ArrowRight } from "lucide-react";
4+
import { usePlausible } from "next-plausible";
5+
import { LinkCustom } from "../link-custom";
6+
7+
export const HomeCTA = () => {
8+
const plausible = usePlausible()
9+
10+
return (
11+
<div className="my-8 flex justify-center gap-4">
12+
<LinkCustom
13+
href="/patterns/getting-started"
14+
onClick={() => plausible('get-started')}
15+
variant="neutral"
16+
>
17+
Get Started
18+
<ArrowRight className="ml-2 h-4 w-4" />
19+
</LinkCustom>
20+
<LinkCustom
21+
href="https://github.com/thedaviddias/ux-patterns-for-developers"
22+
onClick={() => plausible('view-github')}
23+
variant="outline"
24+
>
25+
View on GitHub
26+
</LinkCustom>
27+
</div >
28+
);
29+
};

app/_components/stars.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use client'
22

3-
import { PROJECT_URL } from '@/app/_constants/project';
43
import * as Sentry from "@sentry/nextjs";
54
import { StarIcon } from 'lucide-react';
65
import { GitHubIcon } from 'nextra/icons';
@@ -26,10 +25,7 @@ export const Stars = ({ variant = 'default' }: StarsProps) => {
2625

2726
return (
2827
<div className="flex items-center justify-center gap-2">
29-
<a
30-
href={PROJECT_URL}
31-
target="_blank"
32-
rel="noopener noreferrer"
28+
<div
3329
className="!no-underline plausible-event-name=Star+Github inline-flex items-center gap-2 px-4 py-2 bg-white dark:bg-neutral-900 hover:bg-neutral-100 dark:hover:bg-neutral-800 text-sm font-medium text-neutral-900 dark:text-neutral-100 rounded-lg border border-neutral-400 dark:border-neutral-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-200 dark:focus:ring-neutral-700"
3430
aria-label="Star on GitHub"
3531
>
@@ -39,7 +35,7 @@ export const Stars = ({ variant = 'default' }: StarsProps) => {
3935
<StarIcon className="w-4 h-4 mr-1" aria-hidden="true" />
4036
{stars}
4137
</span>
42-
</a>
38+
</div>
4339
</div>
4440
)
4541
}

app/_components/subscribe.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { Button } from "@/app/_components/ui/button";
44
import { Input } from "@/app/_components/ui/input";
55
import { cn } from "@/app/_utils/cn";
66
import { LoaderCircle } from "lucide-react";
7+
import { usePlausible } from "next-plausible";
78
import { useState } from "react";
89
import { ROUTES } from "../_constants/routes";
910

1011
type FormStatus = "idle" | "loading" | "success" | "error";
1112

1213
export const SubscribeForm = () => {
14+
const plausible = usePlausible()
1315
const [formState, setFormState] = useState({
1416
email: "",
1517
status: "idle" as FormStatus,
@@ -82,6 +84,7 @@ export const SubscribeForm = () => {
8284
disabled={isLoading}
8385
data-loading={isLoading}
8486
variant="outline"
87+
onClick={() => plausible('subscribe')}
8588
>
8689
<span className="group-data-[loading=true]:text-transparent">
8790
Notify me

app/_components/suggest-pattern.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,25 @@
1+
'use client'
2+
13
import { PROJECT_URL } from '@/app/_constants/project'
4+
import { usePlausible } from 'next-plausible'
25
import { LinkCustom } from './link-custom'
3-
import { Button } from './ui/button'
46

57
export const SuggestPattern = () => {
8+
const plausible = usePlausible()
9+
610
return (
711
<section className="flex flex-col mt-10 items-center justify-center py-10 px-4 text-center border border-neutral-400 dark:border-neutral-600 rounded-xl" aria-labelledby="suggest-pattern-title">
812
<h2 id="suggest-pattern-title" className="text-2xl font-bold mb-5 text-foreground">Got a pattern request?</h2>
913
<p className="text-lg text-muted-foreground mb-6">
1014
Let us know, and we&apos;ll add it!
1115
</p>
12-
<Button
16+
<LinkCustom
17+
href={`${PROJECT_URL}/discussions/new?category=suggestions`}
18+
onClick={() => plausible('suggest-pattern')}
1319
variant="outline"
14-
className="font-medium"
15-
asChild
1620
>
17-
<LinkCustom
18-
href={`${PROJECT_URL}/discussions/new?category=suggestions`}
19-
className="plausible-event-name=Suggest+Pattern"
20-
>
21-
Send Suggestion
22-
</LinkCustom>
23-
</Button>
21+
Send Suggestion
22+
</LinkCustom>
2423
</section>
2524
)
2625
}

app/_components/ui/button.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import * as React from 'react'
1+
import * as React from 'react';
22

3-
import { Slot } from '@radix-ui/react-slot'
4-
import { type VariantProps, cva } from 'class-variance-authority'
3+
import { Slot } from '@radix-ui/react-slot';
4+
import { type VariantProps, cva } from 'class-variance-authority';
55

66
import { cn } from "@/app/_utils/cn";
77

@@ -12,7 +12,7 @@ const buttonVariants = cva(
1212
variant: {
1313
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
1414
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
15-
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
15+
outline: 'border border-input bg-background hover:border-neutral-200 dark:hover:bg-neutral-900',
1616
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
1717
ghost: 'hover:bg-accent hover:text-accent-foreground',
1818
link: 'text-primary underline-offset-4 hover:underline',
@@ -33,7 +33,7 @@ const buttonVariants = cva(
3333

3434
export interface ButtonProps
3535
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
36-
VariantProps<typeof buttonVariants> {
36+
VariantProps<typeof buttonVariants> {
3737
asChild?: boolean
3838
}
3939

@@ -47,4 +47,5 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
4747
)
4848
Button.displayName = 'Button'
4949

50-
export { Button, buttonVariants }
50+
export { Button, buttonVariants };
51+

app/_constants/footer.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ export const FOOTER_MENU_LINKS = (lang: string) => [
44
label: 'Home'
55
},
66
{
7-
path: '/patterns',
7+
path: `/${lang}/patterns`,
88
label: 'Patterns'
99
},
1010
{
11-
path: '/blog',
11+
path: `/${lang}/blog`,
1212
label: 'Blog'
1313
},
1414
{
15-
path: `/about`,
15+
path: `/${lang}/about`,
1616
label: 'About'
1717
},
1818
]

0 commit comments

Comments
 (0)