Tailwind CSS 4 Migration (#100)

* Updated to TailwindCSS v4
* Moved CSS module to its own CSS file because of lightingcss strict validation
* Respect next parameter in middleware
* Updated all packages. 
* Split CSSs for better organization.
* Redesigned theme and auth pages
* Improved pill and header design
* Formatted files using Prettier
* Better footer layout
* Better auth layout
* Bump version of the repository to 2.0.0
This commit is contained in:
Giancarlo Buomprisco
2025-01-28 13:19:52 +07:00
committed by GitHub
parent d799f54ede
commit 4e91f267e0
109 changed files with 1347 additions and 1178 deletions

View File

@@ -39,7 +39,7 @@ export function BorderedNavigationMenuItem(props: {
<Button
asChild
variant={'ghost'}
className={cn('relative active:shadow-sm', props.buttonClassName)}
className={cn('relative active:shadow-xs', props.buttonClassName)}
>
<Link
href={props.path}
@@ -58,7 +58,7 @@ export function BorderedNavigationMenuItem(props: {
{active ? (
<span
className={cn(
'absolute -bottom-2.5 left-0 h-0.5 w-full bg-primary animate-in fade-in zoom-in-90',
'bg-primary animate-in fade-in zoom-in-90 absolute -bottom-2.5 left-0 h-0.5 w-full',
)}
/>
) : null}

View File

@@ -17,7 +17,7 @@ export const CardButton: React.FC<
return (
<Comp
className={cn(
'group relative flex h-36 flex-col rounded-lg border transition-all hover:bg-secondary/20 hover:shadow active:bg-secondary active:bg-secondary/50 active:shadow-lg dark:shadow-primary/20',
'group hover:bg-secondary/20 active:bg-secondary active:bg-secondary/50 dark:shadow-primary/20 relative flex h-36 flex-col rounded-lg border transition-all hover:shadow-xs active:shadow-lg',
className,
)}
{...props}
@@ -39,7 +39,7 @@ export const CardButtonTitle: React.FC<
<Comp
className={cn(
className,
'align-super text-sm font-medium text-muted-foreground transition-colors group-hover:text-secondary-foreground',
'text-muted-foreground group-hover:text-secondary-foreground align-super text-sm font-medium transition-colors',
)}
{...props}
>
@@ -69,7 +69,7 @@ export const CardButtonHeader: React.FC<
<ChevronRight
className={cn(
'absolute right-2 top-4 h-4 text-muted-foreground transition-colors group-hover:text-secondary-foreground',
'text-muted-foreground group-hover:text-secondary-foreground absolute top-4 right-2 h-4 transition-colors',
{
hidden: !displayArrow,
},

View File

@@ -188,7 +188,7 @@ function Pagination<T>({
table: ReactTable<T>;
}>) {
return (
<div className="flex items-center justify-end space-x-4">
<div className="flex items-center justify-end gap-x-4">
<span className="flex items-center text-sm">
<Trans
i18nKey={'common:pageOfPages'}
@@ -199,7 +199,7 @@ function Pagination<T>({
/>
</span>
<div className="flex items-center justify-end space-x-1">
<div className="flex items-center justify-end gap-x-2">
<Button
size={'icon'}
variant={'ghost'}

View File

@@ -18,7 +18,7 @@ const EmptyStateText: React.FC<React.HTMLAttributes<HTMLParagraphElement>> = ({
className,
...props
}) => (
<p className={cn('text-sm text-muted-foreground', className)} {...props} />
<p className={cn('text-muted-foreground text-sm', className)} {...props} />
);
EmptyStateText.displayName = 'EmptyStateText';
@@ -60,7 +60,7 @@ const EmptyState: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({
return (
<div
className={cn(
'flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm',
'flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-xs',
className,
)}
{...props}

View File

@@ -138,14 +138,14 @@ export const ImageUploadInput: React.FC<Props> =
return (
<label
id={'image-upload-input'}
className={`relative flex h-10 w-full cursor-pointer rounded-md border border-dashed border-input bg-background px-3 py-2 text-sm outline-none ring-primary ring-offset-2 ring-offset-background transition-all file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus:ring-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50`}
className={`border-input bg-background ring-primary ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring relative flex h-10 w-full cursor-pointer rounded-md border border-dashed px-3 py-2 text-sm ring-offset-2 outline-hidden transition-all file:border-0 file:bg-transparent file:text-sm file:font-medium focus:ring-2 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50`}
>
<Input />
<div className={'flex items-center space-x-4'}>
<div className={'flex'}>
<If condition={!state.image}>
<UploadCloud className={'h-5 text-muted-foreground'} />
<UploadCloud className={'text-muted-foreground h-5'} />
</If>
<If condition={state.image}>
@@ -188,7 +188,7 @@ export const ImageUploadInput: React.FC<Props> =
<If condition={state.image}>
<Button
size={'icon'}
className={'!h-5 !w-5'}
className={'h-5! w-5!'}
onClick={imageRemoved}
>
<X className="h-4" />

View File

@@ -20,14 +20,14 @@ export function LoadingOverlay({
'flex flex-col items-center justify-center space-y-4',
className,
{
[`fixed left-0 top-0 z-[100] h-screen w-screen bg-background`]:
[`bg-background fixed top-0 left-0 z-100 h-screen w-screen`]:
fullPage,
},
)}
>
<Spinner className={spinnerClassName} />
<div className={'text-sm text-muted-foreground'}>{children}</div>
<div className={'text-muted-foreground text-sm'}>{children}</div>
</div>
);
}

View File

@@ -1,45 +1,28 @@
import React from 'react';
import { cn } from '../../lib/utils';
import {
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '../../shadcn/card';
import { CardDescription, CardHeader, CardTitle } from '../../shadcn/card';
interface FeatureCardProps extends React.HTMLAttributes<HTMLDivElement> {
label: string;
description: string;
image?: React.ReactNode;
}
export const FeatureCard: React.FC<FeatureCardProps> = ({
className,
label,
description,
image,
children,
...props
}) => {
return (
<div
className={cn(
'rounded-3xl p-2 ring-2 ring-gray-100 dark:ring-primary/10',
className,
)}
{...props}
>
<div className={cn('rounded-xl border p-4', className)} {...props}>
<CardHeader>
<CardTitle className="text-xl font-semibold">{label}</CardTitle>
<CardDescription className="max-w-xs text-sm font-semibold tracking-tight text-muted-foreground">
<CardTitle className="text-xl font-medium">{label}</CardTitle>
<CardDescription className="text-muted-foreground max-w-xs text-sm font-normal">
{description}
</CardDescription>
</CardHeader>
<CardContent>
{image}
{children}
</CardContent>
</div>
);
};

View File

@@ -20,7 +20,7 @@ export const FeatureShowcase: React.FC<FeatureShowcaseProps> =
className={cn('flex flex-col justify-between space-y-8', className)}
{...props}
>
<div className="flex w-full max-w-5xl flex-col space-y-4">
<div className="flex w-full max-w-5xl flex-col gap-y-4">
{icon && <div className="flex">{icon}</div>}
<h3 className="text-3xl font-normal tracking-tight xl:text-5xl">
{heading}
@@ -40,7 +40,7 @@ export function FeatureShowcaseIconContainer(
<div className={'flex'}>
<div
className={cn(
'flex items-center justify-center space-x-4 rounded-lg p-3 font-semibold',
'flex items-center justify-center space-x-4 rounded-lg p-3 font-medium',
props.className,
)}
>

View File

@@ -26,31 +26,35 @@ export const Footer: React.FC<FooterProps> = ({
return (
<footer
className={cn(
'site-footer relative mt-auto w-full py-8 2xl:py-16',
'site-footer relative mt-auto w-full py-8 2xl:py-20',
className,
)}
{...props}
>
<div className="container">
<div className="flex flex-col space-y-8 lg:flex-row lg:space-y-0">
<div className="flex w-full space-x-2 lg:w-4/12 xl:w-4/12 xl:space-x-6 2xl:space-x-8">
<div className="flex flex-col space-y-4">
<div className="flex w-full gap-x-3 lg:w-4/12 xl:w-4/12 xl:space-x-6 2xl:space-x-8">
<div className="flex flex-col gap-y-4">
<div>{logo}</div>
<div className="flex flex-col space-y-4">
<div className="flex flex-col gap-y-4">
<div>
<p className="text-sm text-muted-foreground">{description}</p>
<p className="text-muted-foreground text-sm tracking-tight">
{description}
</p>
</div>
<div className="flex text-xs text-muted-foreground">
<div className="text-muted-foreground flex text-xs">
<p>{copyright}</p>
</div>
</div>
</div>
</div>
<div className="flex w-full flex-col space-y-8 lg:flex-row lg:justify-end lg:space-x-6 lg:space-y-0 xl:space-x-16">
<div className="flex w-full flex-col gap-y-4 lg:flex-row lg:justify-end lg:gap-x-6 lg:gap-y-0 xl:gap-x-12">
{sections.map((section, index) => (
<div key={index}>
<div className="flex flex-col space-y-2.5">
<div className="flex flex-col gap-y-2.5">
<FooterSectionHeading>{section.heading}</FooterSectionHeading>
<FooterSectionList>
@@ -71,11 +75,15 @@ export const Footer: React.FC<FooterProps> = ({
};
function FooterSectionHeading(props: React.PropsWithChildren) {
return <span className="font-heading">{props.children}</span>;
return (
<span className="font-heading text-sm font-semibold tracking-tight">
{props.children}
</span>
);
}
function FooterSectionList(props: React.PropsWithChildren) {
return <ul className="flex flex-col space-y-2.5">{props.children}</ul>;
return <ul className="flex flex-col gap-y-2">{props.children}</ul>;
}
function FooterLink({
@@ -83,7 +91,7 @@ function FooterLink({
children,
}: React.PropsWithChildren<{ href: string }>) {
return (
<li className="text-sm text-muted-foreground hover:underline [&>a]:transition-colors">
<li className="text-muted-foreground text-sm tracking-tight hover:underline [&>a]:transition-colors">
<a href={href}>{children}</a>
</li>
);

View File

@@ -12,7 +12,7 @@ export const GradientSecondaryText: React.FC<
return (
<Comp
className={cn(
'bg-gradient-to-r from-foreground/50 to-foreground bg-clip-text text-transparent',
'dark:from-foreground/60 dark:to-foreground text-secondary-foreground dark:bg-linear-to-r dark:bg-clip-text dark:text-transparent',
className,
)}
{...props}

View File

@@ -7,7 +7,7 @@ export const GradientText: React.FC<React.HTMLAttributes<HTMLSpanElement>> =
return (
<span
className={cn(
'bg-gradient-to-r bg-clip-text text-transparent',
'bg-linear-to-r bg-clip-text text-transparent',
className,
)}
{...props}

View File

@@ -16,7 +16,7 @@ export const Header: React.FC<HeaderProps> = function ({
return (
<div
className={cn(
'site-header sticky top-0 z-10 w-full bg-background/80 py-2 backdrop-blur-md dark:bg-background/50',
'site-header bg-background/80 dark:bg-background/50 sticky top-0 z-10 w-full py-1 backdrop-blur-md',
className,
)}
{...props}
@@ -25,9 +25,7 @@ export const Header: React.FC<HeaderProps> = function ({
<div className="grid h-14 grid-cols-3 items-center">
<div className={'mx-auto lg:mx-0'}>{logo}</div>
<div className="order-first md:order-none">{navigation}</div>
<div className="flex items-center justify-end space-x-1">
{actions}
</div>
<div className="flex items-center justify-end gap-x-2">{actions}</div>
</div>
</div>
</div>

View File

@@ -12,7 +12,7 @@ export const HeroTitle: React.FC<
return (
<Comp
className={cn(
'hero-title flex flex-col text-center font-sans text-4xl font-semibold tracking-tighter dark:text-white sm:text-6xl lg:max-w-5xl lg:text-7xl xl:text-[4.85rem]',
'hero-title flex flex-col text-center font-sans text-4xl font-semibold tracking-tighter sm:text-6xl lg:max-w-5xl lg:text-7xl xl:text-[4.5rem] dark:text-white',
className,
)}
{...props}

View File

@@ -29,17 +29,17 @@ export function Hero({
MozAnimationDuration: '100ms',
}}
className={cn(
'mx-auto flex flex-1 flex-col items-center justify-center duration-1000 md:flex-row',
'mx-auto flex flex-1 flex-col items-center justify-center duration-800 md:flex-row',
{
['animate-in fade-in zoom-in-90 slide-in-from-top-36']: animate,
['animate-in fade-in zoom-in-90 slide-in-from-top-24']: animate,
},
)}
>
<div className="flex w-full flex-1 flex-col items-center space-y-6 xl:space-y-8 2xl:space-y-10">
<div className="flex w-full flex-1 flex-col items-center gap-y-6 xl:gap-y-8 2xl:gap-y-12">
{pill && (
<div
className={cn({
['delay-300 duration-700 animate-in fade-in fill-mode-both']:
['animate-in fade-in fill-mode-both delay-300 duration-700']:
animate,
})}
>
@@ -47,12 +47,12 @@ export function Hero({
</div>
)}
<div className="flex flex-col items-center space-y-8">
<div className="flex flex-col items-center gap-y-6">
<HeroTitle>{title}</HeroTitle>
{subtitle && (
<div className="flex max-w-2xl flex-col space-y-1">
<h3 className="p-0 text-center font-sans text-xl font-normal tracking-tight text-muted-foreground">
<div className="flex max-w-lg">
<h3 className="text-muted-foreground p-0 text-center font-sans text-2xl font-normal tracking-tight">
{subtitle}
</h3>
</div>
@@ -62,7 +62,7 @@ export function Hero({
{cta && (
<div
className={cn({
['delay-500 duration-1000 animate-in fade-in fill-mode-both']:
['animate-in fade-in fill-mode-both delay-500 duration-1000']:
animate,
})}
>
@@ -78,7 +78,7 @@ export function Hero({
MozAnimationDuration: '100ms',
}}
className={cn('container mx-auto flex justify-center py-8', {
['delay-1000 duration-1000 animate-in fade-in zoom-in-95 slide-in-from-top-32 fill-mode-both']:
['animate-in fade-in zoom-in-90 slide-in-from-top-32 fill-mode-both delay-600 duration-1000']:
animate,
})}
>

View File

@@ -46,7 +46,7 @@ export function NewsletterSignup({
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSignup)}
className="flex flex-col space-y-2"
className="flex flex-col gap-y-3"
>
<FormField
control={form.control}

View File

@@ -1,6 +1,7 @@
import { Slot, Slottable } from '@radix-ui/react-slot';
import { cn } from '../../lib/utils';
import { GradientSecondaryText } from './gradient-secondary-text';
export const Pill: React.FC<
React.HTMLAttributes<HTMLHeadingElement> & {
@@ -13,7 +14,7 @@ export const Pill: React.FC<
return (
<Comp
className={cn(
'space-x-2.5 rounded-full border border-gray-100 px-2 py-2.5 text-center text-sm font-medium text-transparent dark:border-primary/10',
'bg-muted/50 flex items-center gap-x-1.5 rounded-full border px-2 py-1.5 pr-2 text-center text-sm font-medium text-transparent',
className,
)}
{...props}
@@ -21,15 +22,38 @@ export const Pill: React.FC<
{props.label && (
<span
className={
'rounded-2xl bg-primary px-2.5 py-1.5 text-sm font-semibold text-primary-foreground'
'text-primary-foreground bg-primary rounded-2xl border px-1.5 py-0.5 text-xs font-bold tracking-tight'
}
>
{props.label}
</span>
)}
<Slottable>
<span className={'text-secondary-foreground'}>{props.children}</span>
<GradientSecondaryText
className={'flex items-center gap-x-2 font-semibold tracking-tight'}
>
{props.children}
</GradientSecondaryText>
</Slottable>
</Comp>
);
};
export const PillActionButton: React.FC<
React.HTMLAttributes<HTMLButtonElement> & {
asChild?: boolean;
}
> = ({ asChild, ...props }) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
{...props}
className={
'text-secondary-foreground bg-input active:bg-primary active:text-primary-foreground hover:ring-muted-foreground/50 rounded-full px-1.5 py-1.5 text-center text-sm font-medium ring ring-transparent transition-colors'
}
>
{props.children}
</Comp>
);
};

View File

@@ -53,8 +53,8 @@ export function ModeToggle(props: { className?: string }) {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className={props.className}>
<Sun className="h-[0.9rem] w-[0.9rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[0.9rem] w-[0.9rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<Sun className="h-[0.9rem] w-[0.9rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
<Moon className="absolute h-[0.9rem] w-[0.9rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
@@ -99,7 +99,7 @@ export function SubMenuModeToggle() {
<DropdownMenuSub>
<DropdownMenuSubTrigger
className={
'hidden w-full items-center justify-between space-x-2 lg:flex'
'hidden w-full items-center justify-between gap-x-3 lg:flex'
}
>
<span className={'flex space-x-2'}>

View File

@@ -42,7 +42,7 @@ function PageWithSidebar(props: PageProps) {
<div
className={
'flex flex-1 flex-col overflow-y-auto bg-background px-4 lg:px-0'
'bg-background flex flex-1 flex-col overflow-y-auto px-4 lg:px-0'
}
>
{Children}
@@ -81,7 +81,7 @@ function PageWithHeader(props: PageProps) {
>
<div
className={cn(
'flex h-14 items-center justify-between bg-muted/40 px-4 dark:border-border dark:shadow-primary/10 lg:justify-start lg:shadow-sm',
'bg-muted/40 dark:border-border dark:shadow-primary/10 flex h-14 items-center justify-between px-4 lg:justify-start lg:shadow-xs',
{
'sticky top-0 z-10 backdrop-blur-md': props.sticky ?? true,
},
@@ -119,7 +119,7 @@ export function PageNavigation(props: React.PropsWithChildren) {
export function PageDescription(props: React.PropsWithChildren) {
return (
<div className={'h-6'}>
<div className={'text-xs font-normal leading-none text-muted-foreground'}>
<div className={'text-muted-foreground text-xs leading-none font-normal'}>
{props.children}
</div>
</div>
@@ -130,7 +130,7 @@ export function PageTitle(props: React.PropsWithChildren) {
return (
<h1
className={
'h-6 font-heading font-bold leading-none tracking-tight dark:text-white'
'font-heading text-xl leading-none font-bold tracking-tight dark:text-white'
}
>
{props.children}
@@ -155,7 +155,7 @@ export function PageHeader({
return (
<div
className={cn(
'flex items-center justify-between py-4 lg:px-4',
'flex items-center justify-between py-5 lg:px-4',
className,
)}
>

View File

@@ -144,7 +144,7 @@ export function SidebarGroup({
}
return (
<span className={'text-xs font-semibold uppercase text-muted-foreground'}>
<span className={'text-muted-foreground text-xs font-semibold uppercase'}>
{props.children}
</span>
);
@@ -189,7 +189,7 @@ export function SidebarGroup({
return (
<div
className={cn('flex flex-col', {
'space-y-1 py-1': !collapsed,
'gap-y-2 py-1': !collapsed,
})}
>
<Wrapper />
@@ -232,7 +232,7 @@ export function SidebarItem({
<Button
asChild
className={cn(
'flex w-full text-sm shadow-none active:bg-secondary/60',
'active:bg-secondary/60 flex w-full text-sm shadow-none',
{
'justify-start space-x-2.5': !collapsed,
'hover:bg-initial': active,
@@ -267,7 +267,7 @@ export function SidebarItem({
function getClassNameBuilder(className: string) {
return cva([
cn(
'group/sidebar transition-width fixed box-content flex h-screen w-2/12 flex-col bg-inherit backdrop-blur-sm duration-200',
'group/sidebar transition-width fixed box-content flex h-screen w-2/12 flex-col bg-inherit backdrop-blur-xs duration-200',
className,
),
]);

View File

@@ -75,7 +75,7 @@ export function Stepper(props: {
const containerClassName = cn('w-full', {
['flex justify-between']: variant === 'numbers',
['flex space-x-0.5']: variant === 'default',
['flex space-x-2.5 self-center']: variant === 'dots',
['flex gap-x-4 self-center']: variant === 'dots',
});
return (
@@ -92,7 +92,7 @@ function getClassNameBuilder() {
default: `flex h-[2.5px] w-full flex-col transition-all duration-500`,
numbers:
'flex h-9 w-9 items-center justify-center rounded-full border text-sm font-bold',
dots: 'h-2.5 w-2.5 rounded-full bg-muted transition-colors',
dots: 'bg-muted h-2.5 w-2.5 rounded-full transition-colors',
},
selected: {
true: '',
@@ -183,7 +183,7 @@ function StepDivider({
complete: boolean;
}>) {
const spanClassName = cn('min-w-max text-sm font-medium', {
['hidden text-muted-foreground sm:flex']: !selected,
['text-muted-foreground hidden sm:flex']: !selected,
['text-secondary-foreground']: selected || complete,
['font-medium']: selected,
});
@@ -200,7 +200,7 @@ function StepDivider({
<div
className={
'divider h-[1px] w-full bg-gray-200 transition-colors' +
' hidden group-last:hidden dark:bg-border sm:flex'
' dark:bg-border hidden group-last:hidden sm:flex'
}
/>
</div>