feat(ui): Improve login card design and theme customization

Refactor the `Button` component, add new `LoginCard` component, update `ThemePicker` component, edit global CSS styles to include card background and foreground and style the login page, including the logo and login form.
This commit is contained in:
Maximilian Liebmann 2025-05-21 16:42:48 +02:00 committed by Maximilian Liebmann
parent 9f9c2157f5
commit 4f4e73318a
11 changed files with 122 additions and 29 deletions

View file

@ -39,6 +39,8 @@
--active-secondary: oklch(0.4254 0.133 272.15);
--disabled-secondary: oklch(0.4937 0.1697 271.26 / 0.5);
--card: var(--neutral-800);
/* ------------------- */
--foreground: oklch(0.13 0.028 261.692);
@ -99,7 +101,7 @@
}
@theme inline {
--transparent: var(--transpatent);
--transparent: var(--transparent);
--color-neutral-000: var(--neutral-000);
--color-neutral-100: var(--neutral-100);
@ -114,12 +116,12 @@
--color-neutral-800: var(--neutral-800);
--color-neutral-900: var(--neutral-900);
--background: var(--neutral-750);
--base: var(--neutral-800);
--text: var(--neutral-000);
--text-alt: var(--neutral-900);
--background-disabled: var(--neutral-500);
--text-disabled: var(--neutral-700);
--color-background: var(--neutral-750);
--color-base: var(--neutral-800);
--color-text: var(--text);
--color-text-alt: var(--text-alt);
--color-background-disabled: var(--neutral-500);
--color-text-disabled: var(--neutral-700);
--radius: 0.688rem;
--color-primary: var(--primary);
@ -224,7 +226,7 @@
--neutral-900: oklch(0 0 0);
--background: var(--neutral-750);
--base: var(--neutral-800);
--base: var(--neutral-750);
--text: var(--neutral-000);
--text-alt: var(--neutral-900);
--background-disabled: var(--neutral-500);
@ -240,12 +242,12 @@
--active-secondary: oklch(0.4471 0.15 271.61);
--disabled-secondary: oklch(0.6065 0.213 271.11 / 0.4);
--card: var(--neutral-750);
/* ------------------- */
--foreground: oklch(0.985 0.002 247.839);
--card: oklch(0.21 0.034 264.665);
--card-foreground: oklch(0.985 0.002 247.839);
--popover: oklch(0.21 0.034 264.665);

View file

@ -2,13 +2,17 @@ import { auth, providerMap } from '@/auth';
import SSOLogin from '@/components/user/sso-login-button';
import LoginForm from '@/components/user/login-form';
import { redirect } from 'next/navigation';
import { Button } from '@/components/ui/button';
import { Button } from '@/components/custom-ui/button';
import Image from 'next/image';
import { Separator } from '@/components/custom-ui/separator';
import Logo from '@/components/logo';
import '@/app/globals.css';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import {
Card,
CardContent,
CardHeader,
} from '@/components/custom-ui/login-card';
import { ThemePicker } from '@/components/user/theme-picker';
import {
HoverCard,
@ -30,14 +34,9 @@ export default async function LoginPage() {
<ThemePicker />
</div>
<div>
<Card className='w-[350px] max-w-screen'>
<CardHeader>
<Logo
colorType='colored'
logoType='secondary'
width={200}
height={200}
></Logo>
<Card className='w-[350px] max-w-screen;'>
<CardHeader className='grid place-items-center'>
<Logo colorType='colored' logoType='secondary'></Logo>
</CardHeader>
<CardContent className='gap-6 flex flex-col items-center'>
<LoginForm />

View file

@ -1,5 +1,5 @@
import { signOut } from '@/auth';
import { Button } from '@/components/ui/button';
import { Button } from '@/components/custom-ui/button';
import {
Card,
CardContent,

View file

@ -1,4 +1,4 @@
import { Button } from '@/components/ui/button';
import { Button } from '@/components/custom-ui/button';
import {
Card,
CardContent,

View file

@ -15,8 +15,9 @@ const buttonVariants = cva(
'bg-secondary text-text-alt shadow-xs hover:bg-hover-secondary active:bg-active-secondary',
outline:
'border-2 border-primary bg-transparent text-text shadow-xs hover:bg-primary hover:border-neutral-000 hover:border-1.5 hover:text-neutral-000 active:bg-active-primary',
destructive:
/*destructive:
'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
*/
ghost:
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-text underline-offset-4 hover:underline',

View file

@ -0,0 +1,92 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
function Card({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot='card'
className={cn(
'bg-card flex flex-col gap-6 py-6 shadow-[4px_4px_9px_9px_rgba(0,0,0,0.25)] rounded-[11px]',
className,
)}
{...props}
/>
);
}
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot='card-header'
className={cn(
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
className,
)}
{...props}
/>
);
}
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot='card-title'
className={cn('leading-none font-semibold', className)}
{...props}
/>
);
}
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot='card-description'
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
);
}
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot='card-action'
className={cn(
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
className,
)}
{...props}
/>
);
}
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot='card-content'
className={cn('px-6', className)}
{...props}
/>
);
}
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot='card-footer'
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
{...props}
/>
);
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
};

View file

@ -1,4 +1,4 @@
import { Button } from '@/components/ui/button';
import { Button } from '@/components/custom-ui/button';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@ -12,7 +12,7 @@ export function IconButton({
children: React.ReactNode;
} & React.ComponentProps<typeof Button>) {
return (
<Button type='button' variant='default' {...props}>
<Button type='button' variant='secondary' {...props}>
<FontAwesomeIcon icon={icon} className='mr-2' />
{children}
</Button>

View file

@ -1,6 +1,6 @@
import { signIn } from '@/auth';
import LabeledInput from '@/components/labeled-input';
import { Button } from '@/components/ui/button';
import { Button } from '@/components/custom-ui/button';
import { AuthError } from 'next-auth';
import { redirect } from 'next/navigation';

View file

@ -1,4 +1,4 @@
import { Button } from '../ui/button';
import { Button } from '../custom-ui/button';
import Link from 'next/link';
export function RedirectButton({

View file

@ -22,7 +22,6 @@ export default function SSOLogin({
type='submit'
variant='secondary'
icon={faOpenid}
icon={faOpenid}
>
Login with {providerDisplayName}
</IconButton>

View file

@ -4,7 +4,7 @@ import * as React from 'react';
import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { Button } from '@/components/ui/button';
import { Button } from '@/components/custom-ui/button';
import {
DropdownMenu,
DropdownMenuContent,