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:
parent
9f9c2157f5
commit
4f4e73318a
11 changed files with 122 additions and 29 deletions
|
@ -39,6 +39,8 @@
|
||||||
--active-secondary: oklch(0.4254 0.133 272.15);
|
--active-secondary: oklch(0.4254 0.133 272.15);
|
||||||
--disabled-secondary: oklch(0.4937 0.1697 271.26 / 0.5);
|
--disabled-secondary: oklch(0.4937 0.1697 271.26 / 0.5);
|
||||||
|
|
||||||
|
--card: var(--neutral-800);
|
||||||
|
|
||||||
/* ------------------- */
|
/* ------------------- */
|
||||||
|
|
||||||
--foreground: oklch(0.13 0.028 261.692);
|
--foreground: oklch(0.13 0.028 261.692);
|
||||||
|
@ -99,7 +101,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--transparent: var(--transpatent);
|
--transparent: var(--transparent);
|
||||||
|
|
||||||
--color-neutral-000: var(--neutral-000);
|
--color-neutral-000: var(--neutral-000);
|
||||||
--color-neutral-100: var(--neutral-100);
|
--color-neutral-100: var(--neutral-100);
|
||||||
|
@ -114,12 +116,12 @@
|
||||||
--color-neutral-800: var(--neutral-800);
|
--color-neutral-800: var(--neutral-800);
|
||||||
--color-neutral-900: var(--neutral-900);
|
--color-neutral-900: var(--neutral-900);
|
||||||
|
|
||||||
--background: var(--neutral-750);
|
--color-background: var(--neutral-750);
|
||||||
--base: var(--neutral-800);
|
--color-base: var(--neutral-800);
|
||||||
--text: var(--neutral-000);
|
--color-text: var(--text);
|
||||||
--text-alt: var(--neutral-900);
|
--color-text-alt: var(--text-alt);
|
||||||
--background-disabled: var(--neutral-500);
|
--color-background-disabled: var(--neutral-500);
|
||||||
--text-disabled: var(--neutral-700);
|
--color-text-disabled: var(--neutral-700);
|
||||||
--radius: 0.688rem;
|
--radius: 0.688rem;
|
||||||
|
|
||||||
--color-primary: var(--primary);
|
--color-primary: var(--primary);
|
||||||
|
@ -224,7 +226,7 @@
|
||||||
--neutral-900: oklch(0 0 0);
|
--neutral-900: oklch(0 0 0);
|
||||||
|
|
||||||
--background: var(--neutral-750);
|
--background: var(--neutral-750);
|
||||||
--base: var(--neutral-800);
|
--base: var(--neutral-750);
|
||||||
--text: var(--neutral-000);
|
--text: var(--neutral-000);
|
||||||
--text-alt: var(--neutral-900);
|
--text-alt: var(--neutral-900);
|
||||||
--background-disabled: var(--neutral-500);
|
--background-disabled: var(--neutral-500);
|
||||||
|
@ -240,12 +242,12 @@
|
||||||
--active-secondary: oklch(0.4471 0.15 271.61);
|
--active-secondary: oklch(0.4471 0.15 271.61);
|
||||||
--disabled-secondary: oklch(0.6065 0.213 271.11 / 0.4);
|
--disabled-secondary: oklch(0.6065 0.213 271.11 / 0.4);
|
||||||
|
|
||||||
|
--card: var(--neutral-750);
|
||||||
|
|
||||||
/* ------------------- */
|
/* ------------------- */
|
||||||
|
|
||||||
--foreground: oklch(0.985 0.002 247.839);
|
--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);
|
--card-foreground: oklch(0.985 0.002 247.839);
|
||||||
|
|
||||||
--popover: oklch(0.21 0.034 264.665);
|
--popover: oklch(0.21 0.034 264.665);
|
||||||
|
|
|
@ -2,13 +2,17 @@ import { auth, providerMap } from '@/auth';
|
||||||
import SSOLogin from '@/components/user/sso-login-button';
|
import SSOLogin from '@/components/user/sso-login-button';
|
||||||
import LoginForm from '@/components/user/login-form';
|
import LoginForm from '@/components/user/login-form';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/custom-ui/button';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { Separator } from '@/components/custom-ui/separator';
|
import { Separator } from '@/components/custom-ui/separator';
|
||||||
import Logo from '@/components/logo';
|
import Logo from '@/components/logo';
|
||||||
|
|
||||||
import '@/app/globals.css';
|
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 { ThemePicker } from '@/components/user/theme-picker';
|
||||||
import {
|
import {
|
||||||
HoverCard,
|
HoverCard,
|
||||||
|
@ -30,14 +34,9 @@ export default async function LoginPage() {
|
||||||
<ThemePicker />
|
<ThemePicker />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Card className='w-[350px] max-w-screen'>
|
<Card className='w-[350px] max-w-screen;'>
|
||||||
<CardHeader>
|
<CardHeader className='grid place-items-center'>
|
||||||
<Logo
|
<Logo colorType='colored' logoType='secondary'></Logo>
|
||||||
colorType='colored'
|
|
||||||
logoType='secondary'
|
|
||||||
width={200}
|
|
||||||
height={200}
|
|
||||||
></Logo>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className='gap-6 flex flex-col items-center'>
|
<CardContent className='gap-6 flex flex-col items-center'>
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { signOut } from '@/auth';
|
import { signOut } from '@/auth';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/custom-ui/button';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/custom-ui/button';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
|
|
@ -15,8 +15,9 @@ const buttonVariants = cva(
|
||||||
'bg-secondary text-text-alt shadow-xs hover:bg-hover-secondary active:bg-active-secondary',
|
'bg-secondary text-text-alt shadow-xs hover:bg-hover-secondary active:bg-active-secondary',
|
||||||
outline:
|
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',
|
'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',
|
'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:
|
ghost:
|
||||||
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||||
link: 'text-text underline-offset-4 hover:underline',
|
link: 'text-text underline-offset-4 hover:underline',
|
92
src/components/custom-ui/login-card.tsx
Normal file
92
src/components/custom-ui/login-card.tsx
Normal 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,
|
||||||
|
};
|
|
@ -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 { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
@ -12,7 +12,7 @@ export function IconButton({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
} & React.ComponentProps<typeof Button>) {
|
} & React.ComponentProps<typeof Button>) {
|
||||||
return (
|
return (
|
||||||
<Button type='button' variant='default' {...props}>
|
<Button type='button' variant='secondary' {...props}>
|
||||||
<FontAwesomeIcon icon={icon} className='mr-2' />
|
<FontAwesomeIcon icon={icon} className='mr-2' />
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { signIn } from '@/auth';
|
import { signIn } from '@/auth';
|
||||||
import LabeledInput from '@/components/labeled-input';
|
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 { AuthError } from 'next-auth';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../custom-ui/button';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
export function RedirectButton({
|
export function RedirectButton({
|
||||||
|
|
|
@ -22,7 +22,6 @@ export default function SSOLogin({
|
||||||
type='submit'
|
type='submit'
|
||||||
variant='secondary'
|
variant='secondary'
|
||||||
icon={faOpenid}
|
icon={faOpenid}
|
||||||
icon={faOpenid}
|
|
||||||
>
|
>
|
||||||
Login with {providerDisplayName}
|
Login with {providerDisplayName}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as React from 'react';
|
||||||
import { Moon, Sun } from 'lucide-react';
|
import { Moon, Sun } from 'lucide-react';
|
||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/custom-ui/button';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue