Skip to content

Redesign with tailwindcss #10

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

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "next/core-web-vitals"
"extends": ["next/core-web-vitals", "prettier"]
}
49 changes: 49 additions & 0 deletions components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { cva, VariantProps } from "class-variance-authority";
import { HtmlAttributes } from "csstype";
import { ComponentPropsWithoutRef, ElementType, HTMLAttributes } from "react";

const buttonStyles = cva(
'select-none inline-flex justify-center items-center rounded-lg border border-transparent text-sm font-medium focus:outline-none focus:ring-0 focus-visible:ring-2 focus-visible:ring-offset-2',
{
variants: {
variant: {
primary: 'px-4 py-2 bg-yellow-900 dark:bg-yellow-100 text-yellow-100 dark:text-yellow-900 hover:enabled:bg-yellow-800 dark:hover:enabled:bg-yellow-200 focus-visible:ring-yellow-300 dark:focus-visible:ring-yellow-700',
secondary: 'px-4 py-2 bg-yellow-100 dark:bg-yellow-900 text-yellow-900 dark:text-yellow-100 hover:enabled:bg-yellow-200 dark:hover:enabled:bg-yellow-800 focus-visible:ring-yellow-700 dark:focus-visible:ring-yellow-300',
ghost: 'px-4 py-2 bg-white dark:bg-gray-900 hover:bg-gray-100 hover:dark:bg-gray-800 text-black dark:text-white',
link: 'py-1 text-black dark:text-white'
},
loading: {
true: 'cursor-wait'
},
disabled: {
true: 'cursor-not-allowed'
},
fullWidth: {
true: 'w-full'
}
},
defaultVariants: {
variant: "secondary",
}
}
)

type Props<T extends ElementType> = {
as?: T
withForwardArrow?: boolean
}

export default function Button<T extends ElementType = "button">({ variant, fullWidth, children, type, as, loading, disabled, withForwardArrow, ...props }: Props<T> & VariantProps<typeof buttonStyles> & Omit<ComponentPropsWithoutRef<T>, keyof Props<T>>) {
let Component = as || "button"

return (
<Component type={type} className={buttonStyles({ variant, fullWidth, loading, disabled })} disabled={loading || disabled} {...props}>
{loading && <svg aria-hidden="true" role="status" className="inline mr-3 w-4 h-4 text-yellow-900 animate-spin" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="#fef08a" />
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentColor" />
</svg>}
{!loading ? children : "Loading..."}
{withForwardArrow && !loading && <svg aria-hidden="true" className="pl-2 -mr-1 w-5 h-4" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z" clipRule="evenodd"></path></svg>}
</Component>
)
}
176 changes: 176 additions & 0 deletions components/Hero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { useState } from 'react'
import { Dialog } from '@headlessui/react'
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
import MarketingButton from './MarketingButton'
import Link from "next/link"
import Image from "next/image"
import Logo from "public/logo.png"
import { kv } from '@vercel/kv'
import { GetServerSideProps } from 'next'

const navigation = [
{ name: 'Product', href: '#' },
{ name: 'Support', href: '#' },
{ name: 'Source code', href: '#' },
]

export default function Hero({ stars }) {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)

return (
<div className="isolate bg-white">
<div className="absolute hidden inset-x-0 top-[-10rem] -z-10 transform-gpu overflow-hidden blur-3xl sm:top-[-20rem]">
<svg
className="relative left-[calc(50%-11rem)] -z-10 h-[21.1875rem] max-w-none -translate-x-1/2 rotate-[30deg] sm:left-[calc(50%-30rem)] sm:h-[42.375rem]"
viewBox="0 0 1155 678"
>
<path
fill="url(#45de2b6b-92d5-4d68-a6a0-9b9b2abad533)"
fillOpacity=".3"
d="M317.219 518.975L203.852 678 0 438.341l317.219 80.634 204.172-286.402c1.307 132.337 45.083 346.658 209.733 145.248C936.936 126.058 882.053-94.234 1031.02 41.331c119.18 108.451 130.68 295.337 121.53 375.223L855 299l21.173 362.054-558.954-142.079z"
/>
<defs>
<linearGradient
id="45de2b6b-92d5-4d68-a6a0-9b9b2abad533"
x1="1155.49"
x2="-78.208"
y1=".177"
y2="474.645"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#9089FC" />
<stop offset={1} stopColor="#FF80B5" />
</linearGradient>
</defs>
</svg>
</div>
<div className="px-6 pt-6 lg:px-8">
<nav className="flex items-center justify-between" aria-label="Global">
<div className="flex lg:flex-1">
<a href="#" className="-m-1.5 p-1.5">
<Image src={Logo} alt="Startrack logo" className='h-8 w-8' />
</a>
</div>
<div className="flex lg:hidden">
<button
type="button"
className="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-700"
onClick={() => setMobileMenuOpen(true)}
>
<span className="sr-only">Open main menu</span>
<Bars3Icon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="hidden lg:flex lg:gap-x-12">
{navigation.map((item) => (
<a key={item.name} href={item.href} className="text-sm font-semibold leading-6 text-gray-900">
{item.name}
</a>
))}
</div>
<div className="hidden lg:flex lg:flex-1 lg:justify-end">
<MarketingButton variant='subtle'>Add your app</MarketingButton>
</div>
</nav>
<Dialog as="div" open={mobileMenuOpen} onClose={setMobileMenuOpen}>
<Dialog.Panel className="fixed inset-0 z-10 overflow-y-auto bg-white px-6 py-6 lg:hidden">
<div className="flex items-center justify-between">
<a href="#" className="-m-1.5 p-1.5">
<Image src={Logo} alt="Startrack logo" className='h-8 w-8' />
</a>
<button
type="button"
className="-m-2.5 rounded-md p-2.5 text-gray-700"
onClick={() => setMobileMenuOpen(false)}
>
<span className="sr-only">Close menu</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="mt-6 flow-root">
<div className="-my-6 divide-y divide-gray-500/10">
<div className="space-y-2 py-6">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className="-mx-3 block rounded-lg py-2 px-3 text-base font-semibold leading-7 text-gray-900 hover:bg-gray-400/10"
>
{item.name}
</a>
))}
</div>
<div className="py-6">
<MarketingButton variant='subtle'>Add your app</MarketingButton>
</div>
</div>
</div>
</Dialog.Panel>
</Dialog>
</div>
<main>
<div className="relative px-6 lg:px-8">
<div className="mx-auto max-w-2xl py-32 sm:py-48 lg:py-56">
<div className="hidden sm:mb-8 sm:flex sm:justify-center">
<div className="relative rounded-full py-1 px-3 text-sm leading-6 text-gray-600 ring-1 ring-gray-900/10 hover:ring-gray-900/20">
Introducing the redesigned Startrack.{' '}
<a href="#" className="font-semibold text-yellow-600">
<span className="absolute inset-0" aria-hidden="true" />
Read more <span aria-hidden="true">&rarr;</span>
</a>
</div>
</div>
<div className="text-center">
<h1 className="text-4xl font-bold tracking-tight text-gray-900 sm:text-6xl">
Embed GitHub stars into your next app
</h1>
<p className="mt-6 text-lg leading-8 text-gray-600">
Easily build a dynamic starring experience into your app, and provide feedback to the user depending on wether your GitHub repo is starred.
</p>
<div className="mt-10 flex items-center justify-center gap-x-2">
<MarketingButton as={Link} href="/star/jacobhq/startrack">Try it</MarketingButton>
<MarketingButton as={Link} href="/add-app" variant='ghost'>Add your app</MarketingButton>
</div>
</div>
</div>
<div className="absolute hidden inset-x-0 top-[calc(100%-13rem)] -z-10 transform-gpu overflow-hidden blur-3xl sm:top-[calc(100%-30rem)]">
<svg
className="relative left-[calc(50%+3rem)] h-[21.1875rem] max-w-none -translate-x-1/2 sm:left-[calc(50%+36rem)] sm:h-[42.375rem]"
viewBox="0 0 1155 678"
>
<path
fill="url(#ecb5b0c9-546c-4772-8c71-4d3f06d544bc)"
fillOpacity=".3"
d="M317.219 518.975L203.852 678 0 438.341l317.219 80.634 204.172-286.402c1.307 132.337 45.083 346.658 209.733 145.248C936.936 126.058 882.053-94.234 1031.02 41.331c119.18 108.451 130.68 295.337 121.53 375.223L855 299l21.173 362.054-558.954-142.079z"
/>
<defs>
<linearGradient
id="ecb5b0c9-546c-4772-8c71-4d3f06d544bc"
x1="1155.49"
x2="-78.208"
y1=".177"
y2="474.645"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#9089FC" />
<stop offset={1} stopColor="#FF80B5" />
</linearGradient>
</defs>
</svg>
</div>
</div>
</main>

<div className="w-full flex justify-center">
<div className="flex flex-col items-center pt-6">
<svg aria-hidden="true" className="w-5 h-5 text-yellow-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><title>Rating star</title><path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path></svg>
<div className="flex mt-2 mb-8">
<p className="mr-1 text-sm font-bold text-gray-900 dark:text-white">{Intl.NumberFormat('en-us').format(stars)}</p>
<p className="text-sm font-medium text-gray-800 dark:text-white">{" "}repos starred with Startrack</p>
</div>
</div>
</div>

</div>
)
}
78 changes: 78 additions & 0 deletions components/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { cva, VariantProps } from "class-variance-authority";
import { ElementType, HTMLAttributes } from "react";

const inputStyles = cva(
'block px-2.5 pb-2.5 pt-4 w-full text-sm text-zinc-900 bg-transparent rounded-lg border-1 appearance-none dark:text-white focus:outline-none focus:ring-1 peer',
{
variants: {
state: {
default: 'border-zinc-300 dark:border-zinc-600 dark:focus:border-zinc-500 focus:ring-zinc-600 dark:focus:ring-zinc-500 focus:border-zinc-600',
success: 'dark:border-green-500 border-green-600 dark:focus:border-green-500 focus:border-green-600 focus:ring-green-600 dark:focus:ring-green-500',
error: 'dark:border-red-500 border-red-600 dark:focus:border-red-500 focus:border-red-600 focus:ring-red-600 dark:focus:ring-red-500'
},
handleInvalid: {
true: 'invalid:dark:border-red-500 invalid:border-red-600 invalid:dark:focus:border-red-500 invalid:focus:border-red-600 invalid:focus:ring-red-600 invalid:dark:focus:ring-red-500'
},
disabled: {
true: 'border-zinc-300 dark:border-zinc-600'
}
},
defaultVariants: {
state: 'default',
handleInvalid: true
}
}
)

const labelStyles = cva(
'bg-white pointer-events-none cursor-text absolute text-sm transition-all duration-300 transform -translate-y-4 scale-75 top-2 z-10 origin-[0] px-2 peer-placeholder-shown:px-3 peer-focus:px-2 peer-valid:peer-focus:text-zinc-600 peer-focus:dark:text-zinc-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:top-1/2 peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4 left-1',
{
variants: {
state: {
default: 'text-zinc-500 dark:text-zinc-400',
success: 'text-green-500 dark:text-green-400',
error: 'text-red-500 dark:text-red-400'
},
handleInvalid: {
true: 'peer-invalid:text-red-500 peer-invalid:dark:text-red-400'
},
disabled: {
true: 'text-zinc-400 dark:text-zinc-500'
},
darkBg: {
900: 'dark:bg-zinc-900',
800: 'dark:bg-zinc-800'
}
},
defaultVariants: {
state: 'default',
handleInvalid: true,
darkBg: 900
}
}
)

type Props = {
as?: ElementType
value?: string
type?: string
required?: boolean
autoFocus?: boolean
darkBg?: 800 | 900
}

interface InputProps extends HTMLAttributes<HTMLInputElement>, Props, VariantProps<typeof inputStyles> { }

export default function Input({ state, children, placeholder, type, handleInvalid, as, disabled, darkBg, ...props }: InputProps) {
as ??= "input"
type ??= "text"
darkBg ??= 900
let Component = as

return (
<div className="relative">
<Component name="input" className={inputStyles({ state, handleInvalid, disabled })} placeholder=" " type={type} disabled={disabled} {...props} />
<label htmlFor="input" className={labelStyles({ state, handleInvalid, disabled, darkBg })}>{placeholder}</label>
</div>
)
}
37 changes: 37 additions & 0 deletions components/MarketingButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { cva, type VariantProps } from "class-variance-authority";
import { ComponentPropsWithoutRef, ElementType } from "react";

const buttonStyles = cva("flex items-center gap-1 h-10 py-2.5 px-5 text-sm select-none font-semibold text-gray-900 focus:outline-none bg-white rounded-full border focus:z-10 ring-0 focus:ring-4 transition-shadow duration-75 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white", {
variants: {
variant: {
primary: "border-yellow-500 focus:ring-yellow-200",
subtle: "border-gray-700 hover:border-yellow-500 focus:border-yellow-500 focus:ring-yellow-200",
ghost: "border-none focus:ring-yellow-200"
},
hasArrow: {
true: "group"
}
},
defaultVariants: {
variant: "primary",
hasArrow: true
},
});

type Props<T extends ElementType> = {
as?: T
}

export default function MarketingButton<T extends ElementType = "button">({ variant, as, children, hasArrow, ...props }: Props<T> & VariantProps<typeof buttonStyles> & Omit<ComponentPropsWithoutRef<T>, keyof Props<T>>) {
let Component = as || "button"

hasArrow ??= true

return <Component type="button" className={buttonStyles({ variant })} {...props}>
{children}
{hasArrow && <svg width="16px" height="16px" viewBox="0 0 16 16" fill="none" strokeWidth="1.5" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" className="flex-shrink-0 text-yellow-500" aria-hidden="true" role="img">
<path className="origin-center scale-x-0 transition-transform group-hover:scale-x-100" d="M12 8H4"></path>
<path className="transition-transform group-hover:translate-x-[2px]" d="M6.5 11.5L9.64645 8.35355C9.84171 8.15829 9.84171 7.84171 9.64645 7.64645L6.5 4.5"></path>
</svg>}
</Component>;
};
Loading