Skip to content
This repository has been archived by the owner on Jun 27, 2024. It is now read-only.

Commit

Permalink
newsletter form
Browse files Browse the repository at this point in the history
  • Loading branch information
front-bender committed Mar 17, 2024
1 parent 45075fe commit 1ad1d8f
Show file tree
Hide file tree
Showing 16 changed files with 579 additions and 131 deletions.
5 changes: 4 additions & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"plugins": ["prettier-plugin-tailwindcss"],
"plugins": [
"prettier-plugin-organize-imports",
"prettier-plugin-tailwindcss"
],
"tailwindConfig": "./tailwind.config.js",
"tailwindFunctions": ["tv"]
}
63 changes: 63 additions & 0 deletions components/pages/home/HomePage.newsletter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client'

import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { z } from 'zod'

import {
Button,
Form,
FormControl,
FormField,
FormMessage,
Input,
} from '@/components/ui'

import { newsletterStyles } from './HomePage.styles'

const formSchema = z.object({
email: z.string().email(),
})

export const HomePageNewsletter = () => {
const { wrapper, input, button } = newsletterStyles()

const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
email: '',
},
})

const onSubmit = (values: z.infer<typeof formSchema>) => {
console.log(values)
}

return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className={wrapper()}>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<>
<FormControl>
<Input
type="email"
placeholder="Your email"
variant="outline"
className={input()}
{...field}
/>
</FormControl>
<FormMessage />
</>
)}
/>
<Button type="submit" variant="white" className={button()}>
Stay in the loop
</Button>
</form>
</Form>
)
}
28 changes: 19 additions & 9 deletions components/pages/home/HomePage.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,27 @@ import { tv, type VariantProps } from 'tailwind-variants'

const homeStyles = tv({
slots: {
base: 'font-mono text-white h-screen flex',
left: 'w-1/2 bg-blackberry-800 flex justify-center py-16',
right: 'w-1/2 bg-white flex',
leftInner: 'max-w-2xl flex flex-col justify-between px-6',
logo: '',
content: '',
footer: '',
heading: 'text-4xl leading-snug',
base: 'font-mono text-white h-screen overflow-hidden flex flex-col-reverse md:flex-row',
left: 'w-full bg-blackberry-800 flex justify-center py-10 md:py-16 md:w-1/2',
right: 'w-full bg-white flex flex-1 relative md:w-1/2',
leftInner:
'relative gap-9 md:gap-0 md:max-w-xl flex flex-col justify-between px-6',
rightImage: 'object-cover',
heading: 'text-2xl leading-snug md:text-4xl',
footer: 'hidden md:block',
githubMobile: 'z-auto block absolute top-0 right-8 md:hidden',
},
})

const newsletterStyles = tv({
slots: {
wrapper: 'relative mt-14 flex gap-5',
input: 'w-full h-12 md:h-auto',
button: 'absolute top-2 h-8 right-2 text-xs md:relative md:text-sm',
},
})

export type HomeVariants = VariantProps<typeof homeStyles>
export type NewsletterVariants = VariantProps<typeof newsletterStyles>

export { homeStyles }
export { homeStyles, newsletterStyles }
45 changes: 37 additions & 8 deletions components/pages/home/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,72 @@ import Link from 'next/link'

import { Logo } from '@/components/global/Logo'
import { Button } from '@/components/ui'
import { GITHUB_URL } from '@/constants'

import { HomePageAnimation } from './HomePage.animation'
import { HomePageNewsletter } from './HomePage.newsletter'
import { homeStyles } from './HomePage.styles'

export function HomePage() {
const { base, left, right, leftInner, logo, content, heading, footer } =
homeStyles()
const {
base,
left,
right,
rightImage,
leftInner,
heading,
footer,
githubMobile,
} = homeStyles()

return (
<main className={base()}>
<div className={left()}>
<div className={leftInner()}>
<div className={logo()}>
<div>
<Logo />
</div>
<div className={content()}>
<div>
<h1 className={heading()}>
Every foundational tool that software companies need for
hyper-scale, backed by open source.
</h1>
<HomePageNewsletter />
</div>
<div className={footer()}>
<Button variant="secondary" asChild>
<Link href="https://github.com/datumforge" target="_blank">
<Link href={GITHUB_URL} target="_blank">
Get involved on GitHub{' '}
<Image
src="/icons/github.svg"
width={13}
height={13}
alt="Datum on GitHub"
alt="Get involved on GitHub"
/>
</Link>
</Button>
</div>
<div className={githubMobile()}>
<Button variant="secondary" size="icon" asChild>
<Link href={GITHUB_URL} target="_blank">
<Image
src="/icons/github.svg"
width={13}
height={13}
alt="Get involved on GitHub"
/>
</Link>
</Button>
</div>
</div>
</div>
<div className={right()}>{/* <HomePageAnimation /> */}</div>
<div className={right()}>
<Image
src="/images/landing-page-bg.png"
fill
alt=""
className={rightImage()}
/>
</div>
</main>
)
}
3 changes: 2 additions & 1 deletion components/ui/Button/Button.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ export const button = tv({
ghost:
'hover:bg-slate-100 hover:text-slate-900 dark:hover:bg-slate-800 dark:hover:text-slate-50',
link: 'text-slate-900 underline-offset-4 hover:underline dark:text-slate-50',
white: 'bg-white text-blackberry-800',
},
size: {
default: 'h-14 px-7 text-lg',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
icon: 'h-10 w-10 px-0 py-0',
},
},
defaultVariants: {
Expand Down
181 changes: 181 additions & 0 deletions components/ui/Form/Form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
'use client'

import * as LabelPrimitive from '@radix-ui/react-label'
import { Slot } from '@radix-ui/react-slot'
import { createContext, forwardRef, useContext, useId } from 'react'
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from 'react-hook-form'

import { Label } from '@/components/ui/Label/Label'
import { cn } from '@/lib/utils'

const Form = FormProvider

type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName
}

const FormFieldContext = createContext<FormFieldContextValue>(
{} as FormFieldContextValue,
)

const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}

const useFormField = () => {
const fieldContext = useContext(FormFieldContext)
const itemContext = useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()

const fieldState = getFieldState(fieldContext.name, formState)

if (!fieldContext) {
throw new Error('useFormField should be used within <FormField>')
}

const { id } = itemContext

return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}

type FormItemContextValue = {
id: string
}

const FormItemContext = createContext<FormItemContextValue>(
{} as FormItemContextValue,
)

const FormItem = forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = useId()

return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn('space-y-2', className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = 'FormItem'

const FormLabel = forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()

return (
<Label
ref={ref}
className={cn(error && 'text-red-500 dark:text-red-900', className)}
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = 'FormLabel'

const FormControl = forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()

return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = 'FormControl'

const FormDescription = forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()

return (
<p
ref={ref}
id={formDescriptionId}
className={cn('text-sm text-slate-500 dark:text-slate-400', className)}
{...props}
/>
)
})
FormDescription.displayName = 'FormDescription'

const FormMessage = forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children

if (!body) {
return null
}

return (
<p
ref={ref}
id={formMessageId}
className={cn(
'text-sm font-medium text-red-500 dark:text-red-900',
className,
)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = 'FormMessage'

export {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
useFormField,
}
Loading

0 comments on commit 1ad1d8f

Please sign in to comment.