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

@@ -111,7 +111,12 @@ export function AccountSelector({
<If
condition={selected}
fallback={
<span className={'flex max-w-full items-center space-x-4'}>
<span
className={cn('flex max-w-full items-center', {
'justify-center gap-x-0': collapsed,
'gap-x-4': !collapsed,
})}
>
<PersonalAccountAvatar />
<span
@@ -125,12 +130,17 @@ export function AccountSelector({
}
>
{(account) => (
<span className={'flex max-w-full items-center space-x-4'}>
<Avatar className={'h-6 w-6 rounded-sm'}>
<span
className={cn('flex max-w-full items-center', {
'justify-center gap-x-0': collapsed,
'gap-x-4': !collapsed,
})}
>
<Avatar className={'rounded-xs h-6 w-6'}>
<AvatarImage src={account.image ?? undefined} />
<AvatarFallback
className={'group-hover:bg-background rounded-sm'}
className={'group-hover:bg-background rounded-xs'}
>
{account.label ? account.label[0] : ''}
</AvatarFallback>
@@ -212,11 +222,11 @@ export function AccountSelector({
}}
>
<div className={'flex items-center'}>
<Avatar className={'mr-2 h-6 w-6 rounded-sm'}>
<Avatar className={'rounded-xs mr-2 h-6 w-6'}>
<AvatarImage src={account.image ?? undefined} />
<AvatarFallback
className={cn('rounded-sm', {
className={cn('rounded-xs', {
['bg-background']: value === account.value,
['group-hover:bg-background']:
value !== account.value,
@@ -276,7 +286,7 @@ export function AccountSelector({
function UserAvatar(props: { pictureUrl?: string }) {
return (
<Avatar className={'h-6 w-6 rounded-sm'}>
<Avatar className={'rounded-xs h-6 w-6'}>
<AvatarImage src={props.pictureUrl} />
</Avatar>
);

View File

@@ -88,7 +88,7 @@ export function PersonalAccountDropdown({
'animate-in fade-in focus:outline-primary flex cursor-pointer items-center duration-500 group-data-[minimized=true]:px-0',
className ?? '',
{
['active:bg-secondary/50 items-center space-x-4 rounded-md' +
['active:bg-secondary/50 items-center gap-4 rounded-md' +
' hover:bg-secondary p-2 transition-colors']: showProfileName,
},
)}
@@ -129,8 +129,8 @@ export function PersonalAccountDropdown({
</If>
</DropdownMenuTrigger>
<DropdownMenuContent className={'xl:!min-w-[15rem]'}>
<DropdownMenuItem className={'!h-10 rounded-none'}>
<DropdownMenuContent className={'xl:min-w-[15rem]!'}>
<DropdownMenuItem className={'h-10! rounded-none'}>
<div
className={'flex flex-col justify-start truncate text-left text-xs'}
>

View File

@@ -70,8 +70,8 @@ async function PersonalAccountPage(props: { account: Account }) {
/>
<div className={'flex items-center justify-between'}>
<div className={'flex items-center space-x-4'}>
<div className={'flex items-center space-x-2.5'}>
<div className={'flex items-center gap-x-4'}>
<div className={'flex items-center gap-x-2.5'}>
<ProfileAvatar
pictureUrl={props.account.picture_url}
displayName={props.account.name}
@@ -89,7 +89,7 @@ async function PersonalAccountPage(props: { account: Account }) {
</If>
</div>
<div className={'flex space-x-1'}>
<div className={'flex gap-x-1'}>
<If condition={isBanned}>
<AdminReactivateUserDialog userId={props.account.id}>
<Button size={'sm'} variant={'ghost'}>
@@ -124,10 +124,10 @@ async function PersonalAccountPage(props: { account: Account }) {
</div>
</div>
<div className={'flex flex-col space-y-8'}>
<div className={'flex flex-col gap-y-8'}>
<SubscriptionsTable accountId={props.account.id} />
<div className={'divider-divider-x flex flex-col space-y-2.5'}>
<div className={'divider-divider-x flex flex-col gap-y-2.5'}>
<Heading level={6}>Teams</Heading>
<div>
@@ -145,7 +145,7 @@ async function TeamAccountPage(props: {
const members = await getMembers(props.account.slug ?? '');
return (
<div className={'flex flex-col space-y-4'}>
<div className={'flex flex-col gap-y-4'}>
<AppBreadcrumbs
values={{
[props.account.id]:
@@ -154,8 +154,8 @@ async function TeamAccountPage(props: {
/>
<div className={'flex justify-between'}>
<div className={'flex items-center space-x-4'}>
<div className={'flex items-center space-x-2.5'}>
<div className={'flex items-center gap-x-4'}>
<div className={'flex items-center gap-x-2.5'}>
<ProfileAvatar
pictureUrl={props.account.picture_url}
displayName={props.account.name}
@@ -178,10 +178,10 @@ async function TeamAccountPage(props: {
</div>
<div>
<div className={'flex flex-col space-y-8'}>
<div className={'flex flex-col gap-y-8'}>
<SubscriptionsTable accountId={props.account.id} />
<div className={'flex flex-col space-y-2.5'}>
<div className={'flex flex-col gap-y-2.5'}>
<Heading level={6}>Team Members</Heading>
<AdminMembersTable members={members} />

View File

@@ -2,20 +2,20 @@ export function AuthLayoutShell({
children,
Logo,
}: React.PropsWithChildren<{
Logo: React.ComponentType;
Logo?: React.ComponentType;
}>) {
return (
<div
className={
'flex h-screen flex-col items-center justify-center' +
' dark:lg:bg-background space-y-10 lg:space-y-12 lg:bg-gray-50' +
' animate-in fade-in slide-in-from-top-8 zoom-in-95 duration-1000'
' bg-background lg:bg-muted/30 gap-y-10 lg:gap-y-8' +
' animate-in fade-in slide-in-from-top-16 zoom-in-95 duration-1000'
}
>
{Logo && <Logo />}
{Logo ? <Logo /> : null}
<div
className={`bg-background dark:border-border flex w-full max-w-sm flex-col items-center space-y-5 rounded-lg border-transparent px-6 md:w-8/12 md:border md:px-8 md:py-6 md:shadow lg:w-5/12 lg:px-6 xl:w-4/12 xl:py-8 2xl:w-3/12 dark:shadow`}
className={`bg-background flex w-full max-w-[23rem] flex-col gap-y-6 rounded-lg px-6 md:w-8/12 md:px-8 md:py-6 lg:w-5/12 lg:px-8 xl:w-4/12 xl:gap-y-8 xl:py-8`}
>
{children}
</div>

View File

@@ -12,7 +12,7 @@ export function AuthProviderButton({
}>) {
return (
<Button
className={'flex w-full space-x-2 text-center'}
className={'flex w-full gap-x-3 text-center'}
data-provider={providerId}
data-test={'auth-provider-button'}
variant={'outline'}

View File

@@ -75,13 +75,7 @@ export function PasswordResetRequestContainer(params: {
})}
className={'w-full'}
>
<div className={'flex flex-col space-y-4'}>
<div>
<p className={'text-muted-foreground text-sm'}>
<Trans i18nKey={'auth:passwordResetSubheading'} />
</p>
</div>
<div className={'flex flex-col gap-y-4'}>
<AuthErrorAlert error={error} />
<FormField

View File

@@ -43,7 +43,7 @@ export function PasswordSignInForm({
return (
<Form {...form}>
<form
className={'w-full space-y-2.5'}
className={'flex w-full flex-col gap-y-4'}
onSubmit={form.handleSubmit(onSubmit)}
>
<FormField
@@ -91,17 +91,19 @@ export function PasswordSignInForm({
<FormMessage />
<Button
asChild
type={'button'}
size={'sm'}
variant={'link'}
className={'text-xs'}
>
<Link href={'/auth/password-reset'}>
<Trans i18nKey={'auth:passwordForgottenQuestion'} />
</Link>
</Button>
<div>
<Button
asChild
type={'button'}
size={'sm'}
variant={'link'}
className={'text-xs'}
>
<Link href={'/auth/password-reset'}>
<Trans i18nKey={'auth:passwordForgottenQuestion'} />
</Link>
</Button>
</div>
</FormItem>
)}
/>

View File

@@ -55,7 +55,7 @@ export function PasswordSignUpForm({
return (
<Form {...form}>
<form
className={'w-full space-y-2.5'}
className={'flex w-full flex-col gap-y-4'}
onSubmit={form.handleSubmit(onSubmit)}
>
<FormField

View File

@@ -7,6 +7,7 @@ import type { Provider } from '@supabase/supabase-js';
import { isBrowser } from '@kit/shared/utils';
import { If } from '@kit/ui/if';
import { Separator } from '@kit/ui/separator';
import { Trans } from '@kit/ui/trans';
import { MagicLinkAuthContainer } from './magic-link-auth-container';
import { OauthProviders } from './oauth-providers';
@@ -63,7 +64,17 @@ export function SignInMethodsContainer(props: {
</If>
<If condition={props.providers.oAuth.length}>
<Separator />
<div className="relative">
<div className="absolute inset-0 flex items-center">
<Separator />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background text-muted-foreground px-2">
<Trans i18nKey="auth:orContinueWith" />
</span>
</div>
</div>
<OauthProviders
enabledProviders={props.providers.oAuth}

View File

@@ -55,7 +55,17 @@ export function SignUpMethodsContainer(props: {
</If>
<If condition={props.providers.oAuth.length}>
<Separator />
<div className="relative">
<div className="absolute inset-0 flex items-center">
<Separator />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background text-muted-foreground px-2">
<Trans i18nKey="auth:orContinueWith" />
</span>
</div>
</div>
<OauthProviders
enabledProviders={props.providers.oAuth}

View File

@@ -16,7 +16,7 @@ export function TermsAndConditionsFormField(
return (
<FormItem>
<FormControl>
<label className={'flex items-start space-x-2 py-2'}>
<label className={'flex items-start gap-x-3 py-2'}>
<Checkbox required name={field.name} />
<div className={'text-xs'}>

View File

@@ -186,7 +186,7 @@ export function NotificationsPopover(params: {
<div
key={notification.id.toString()}
className={cn(
'min-h-18 flex flex-col items-start justify-center space-y-0.5 px-3 py-2',
'min-h-18 flex flex-col items-start justify-center gap-y-1 px-3 py-2',
)}
onClick={() => {
if (params.onClick) {
@@ -196,7 +196,7 @@ export function NotificationsPopover(params: {
>
<div className={'flex w-full items-start justify-between'}>
<div
className={'flex items-start justify-start space-x-2 py-2'}
className={'flex items-start justify-start gap-x-3 py-2'}
>
<div className={'py-0.5'}>
<Icon />

View File

@@ -138,7 +138,7 @@ function InviteMembersForm({
data-test={'invite-members-form'}
onSubmit={form.handleSubmit(onSubmit)}
>
<div className="flex flex-col space-y-4">
<div className="flex flex-col gap-y-4">
{fieldArray.fields.map((field, index) => {
const isFirst = index === 0;
@@ -147,7 +147,7 @@ function InviteMembersForm({
return (
<div data-test={'invite-member-form-item'} key={field.id}>
<div className={'flex items-end space-x-0.5 md:space-x-2'}>
<div className={'flex items-end gap-x-1 md:space-x-2'}>
<div className={'w-7/12'}>
<FormField
name={emailInputName}
@@ -189,6 +189,7 @@ function InviteMembersForm({
<FormControl>
<MembershipRoleSelector
triggerClassName={'m-0'}
roles={roles}
value={field.value}
onChange={(role) => {
@@ -204,7 +205,7 @@ function InviteMembersForm({
/>
</div>
<div className={'flex w-[40px] justify-end'}>
<div className={'flex w-[40px] items-end justify-end'}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>

View File

@@ -14,15 +14,20 @@ export function MembershipRoleSelector({
value,
currentUserRole,
onChange,
triggerClassName,
}: {
roles: Role[];
value: Role;
currentUserRole?: Role;
onChange: (role: Role) => unknown;
triggerClassName?: string;
}) {
return (
<Select value={value} onValueChange={onChange}>
<SelectTrigger data-test={'role-selector-trigger'}>
<SelectTrigger
className={triggerClassName}
data-test={'role-selector-trigger'}
>
<SelectValue />
</SelectTrigger>