mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
🔐 style: update auth and loading screen (#3875)
* style: improve auth UI * style(SocialButton): fix hover style * remove testing files * fix: package-lock * feat: loading screen color based on theme * fix: handle `system` style on loading screen * fix(ThemeSelector): Correct icon and text color handling for `system` theme * remove test file
This commit is contained in:
parent
020995514e
commit
35a89bfa99
8 changed files with 144 additions and 110 deletions
|
|
@ -2,47 +2,57 @@
|
||||||
<html lang="en-US">
|
<html lang="en-US">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="theme-color" content="#171717">
|
<meta name="theme-color" content="#171717" />
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<title>LibreChat</title>
|
<title>LibreChat</title>
|
||||||
<link
|
<link rel="shortcut icon" href="#" />
|
||||||
rel="shortcut icon"
|
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png" />
|
||||||
href="#"
|
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png" />
|
||||||
/>
|
<link rel="apple-touch-icon" href="/assets/apple-touch-icon-180x180.png" />
|
||||||
<link
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
rel="icon"
|
<style>
|
||||||
type="image/png"
|
html,
|
||||||
sizes="32x32"
|
body {
|
||||||
href="/assets/favicon-32x32.png"
|
margin: 0;
|
||||||
/>
|
padding: 0;
|
||||||
<link
|
height: 100%;
|
||||||
rel="icon"
|
}
|
||||||
type="image/png"
|
</style>
|
||||||
sizes="16x16"
|
<script>
|
||||||
href="/assets/favicon-16x16.png"
|
const theme = localStorage.getItem('color-theme');
|
||||||
/>
|
const loadingContainerStyle = document.createElement('style');
|
||||||
<link
|
let backgroundColor;
|
||||||
rel="apple-touch-icon"
|
|
||||||
href="/assets/apple-touch-icon-180x180.png"
|
if (theme === 'dark') {
|
||||||
/>
|
backgroundColor = '#0d0d0d';
|
||||||
<meta
|
} else if (theme === 'light') {
|
||||||
name="viewport"
|
backgroundColor = '#ffffff';
|
||||||
content="width=device-width, initial-scale=1"
|
} else if (theme === 'system') {
|
||||||
/>
|
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
<script
|
backgroundColor = prefersDarkScheme ? '#0d0d0d' : '#ffffff';
|
||||||
defer
|
} else {
|
||||||
type="module"
|
backgroundColor = '#ffffff';
|
||||||
src="/src/main.jsx"
|
}
|
||||||
></script>
|
|
||||||
|
loadingContainerStyle.innerHTML = `
|
||||||
|
#loading-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: ${backgroundColor};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(loadingContainerStyle);
|
||||||
|
</script>
|
||||||
|
<script defer type="module" src="/src/main.jsx"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root">
|
||||||
|
<div id="loading-container"></div>
|
||||||
<script
|
</div>
|
||||||
type="module"
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
src="/src/main.jsx"
|
|
||||||
></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ function AuthLayout({
|
||||||
return (
|
return (
|
||||||
<div className="relative flex min-h-screen flex-col bg-white dark:bg-gray-900">
|
<div className="relative flex min-h-screen flex-col bg-white dark:bg-gray-900">
|
||||||
<BlinkAnimation active={isFetching}>
|
<BlinkAnimation active={isFetching}>
|
||||||
<div className="mt-12 h-24 w-full bg-cover">
|
<div className="mt-6 h-10 w-full bg-cover">
|
||||||
<img src="/assets/logo.svg" className="h-full w-full object-contain" alt="Logo" />
|
<img src="/assets/logo.svg" className="h-full w-full object-contain" alt="Logo" />
|
||||||
</div>
|
</div>
|
||||||
</BlinkAnimation>
|
</BlinkAnimation>
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
||||||
method="POST"
|
method="POST"
|
||||||
onSubmit={handleSubmit((data) => onSubmit(data))}
|
onSubmit={handleSubmit((data) => onSubmit(data))}
|
||||||
>
|
>
|
||||||
<div className="mb-2">
|
<div className="mb-4">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -97,12 +97,20 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
aria-invalid={!!errors.email}
|
aria-invalid={!!errors.email}
|
||||||
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500"
|
className="
|
||||||
|
webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light
|
||||||
|
bg-surface-primary px-3.5 pb-2.5 pt-3 duration-200 focus:border-green-500 focus:outline-none
|
||||||
|
"
|
||||||
placeholder=" "
|
placeholder=" "
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor="email"
|
htmlFor="email"
|
||||||
className="absolute start-1 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-white px-3 text-sm text-gray-500 duration-100 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-3 peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
className="
|
||||||
|
absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200
|
||||||
|
peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100
|
||||||
|
peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-600 dark:peer-focus:text-green-500
|
||||||
|
rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{useUsernameLogin
|
{useUsernameLogin
|
||||||
? localize('com_auth_username').replace(/ \(.*$/, '')
|
? localize('com_auth_username').replace(/ \(.*$/, '')
|
||||||
|
|
@ -124,12 +132,20 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
||||||
maxLength: { value: 128, message: localize('com_auth_password_max_length') },
|
maxLength: { value: 128, message: localize('com_auth_password_max_length') },
|
||||||
})}
|
})}
|
||||||
aria-invalid={!!errors.password}
|
aria-invalid={!!errors.password}
|
||||||
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500"
|
className="
|
||||||
|
webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light
|
||||||
|
bg-surface-primary px-3.5 pb-2.5 pt-3 duration-200 focus:border-green-500 focus:outline-none
|
||||||
|
"
|
||||||
placeholder=" "
|
placeholder=" "
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor="password"
|
htmlFor="password"
|
||||||
className="absolute start-1 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-white px-3 text-sm text-gray-500 duration-100 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-3 peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
className="
|
||||||
|
absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200
|
||||||
|
peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100
|
||||||
|
peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-600 dark:peer-focus:text-green-500
|
||||||
|
rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{localize('com_auth_password')}
|
{localize('com_auth_password')}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -146,7 +162,7 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
||||||
aria-label="Sign in"
|
aria-label="Sign in"
|
||||||
data-testid="login-button"
|
data-testid="login-button"
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full transform rounded-md bg-green-500 px-4 py-3 tracking-wide text-white transition-colors duration-200 hover:bg-green-550 focus:bg-green-550 focus:outline-none disabled:cursor-not-allowed disabled:hover:bg-green-500"
|
className="btn-primary w-full transform rounded-2xl px-4 py-3 tracking-wide transition-colors duration-200"
|
||||||
>
|
>
|
||||||
{localize('com_auth_continue')}
|
{localize('com_auth_continue')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ const Registration: React.FC = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderInput = (id: string, label: string, type: string, validation: object) => (
|
const renderInput = (id: string, label: string, type: string, validation: object) => (
|
||||||
<div className="mb-2">
|
<div className="mb-4">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
id={id}
|
id={id}
|
||||||
|
|
@ -69,19 +69,27 @@ const Registration: React.FC = () => {
|
||||||
validation,
|
validation,
|
||||||
)}
|
)}
|
||||||
aria-invalid={!!errors[id]}
|
aria-invalid={!!errors[id]}
|
||||||
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500"
|
className="
|
||||||
|
webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light
|
||||||
|
bg-surface-primary px-3.5 pb-2.5 pt-3 duration-200 focus:border-green-500 focus:outline-none
|
||||||
|
"
|
||||||
placeholder=" "
|
placeholder=" "
|
||||||
data-testid={id}
|
data-testid={id}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
className="absolute start-1 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-white px-3 text-sm text-gray-500 duration-100 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-3 peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
className="
|
||||||
|
absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200
|
||||||
|
peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100
|
||||||
|
peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-500
|
||||||
|
rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{localize(label)}
|
{localize(label)}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{errors[id] && (
|
{errors[id] && (
|
||||||
<span role="alert" className="mt-1 text-sm text-red-500 dark:text-red-900">
|
<span role="alert" className="mt-1 text-sm text-red-500">
|
||||||
{String(errors[id]?.message) ?? ''}
|
{String(errors[id]?.message) ?? ''}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -175,7 +183,7 @@ const Registration: React.FC = () => {
|
||||||
disabled={Object.keys(errors).length > 0}
|
disabled={Object.keys(errors).length > 0}
|
||||||
type="submit"
|
type="submit"
|
||||||
aria-label="Submit registration"
|
aria-label="Submit registration"
|
||||||
className="w-full transform rounded-md bg-green-500 px-4 py-3 tracking-wide text-white transition-colors duration-200 hover:bg-green-550 focus:bg-green-550 focus:outline-none disabled:cursor-not-allowed disabled:hover:bg-green-500"
|
className="btn-primary w-full transform rounded-2xl px-4 py-3 tracking-wide transition-colors duration-200"
|
||||||
>
|
>
|
||||||
{isSubmitting ? <Spinner /> : localize('com_auth_continue')}
|
{isSubmitting ? <Spinner /> : localize('com_auth_continue')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -104,12 +104,20 @@ function RequestPasswordReset() {
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
aria-invalid={!!errors.email}
|
aria-invalid={!!errors.email}
|
||||||
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500"
|
className="
|
||||||
|
webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light
|
||||||
|
bg-surface-primary px-3.5 pb-2.5 pt-3 duration-200 focus:border-green-500 focus:outline-none
|
||||||
|
"
|
||||||
placeholder=" "
|
placeholder=" "
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor="email"
|
htmlFor="email"
|
||||||
className="absolute start-1 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-white px-3 text-sm text-gray-500 duration-100 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-3 peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
className="
|
||||||
|
absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200
|
||||||
|
peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100
|
||||||
|
peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-500
|
||||||
|
rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{localize('com_auth_email_address')}
|
{localize('com_auth_email_address')}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -124,7 +132,7 @@ function RequestPasswordReset() {
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={!!errors.email}
|
disabled={!!errors.email}
|
||||||
className="w-full transform rounded-md bg-green-500 px-4 py-3 tracking-wide text-white transition-colors duration-200 hover:bg-green-550 focus:bg-green-550 focus:outline-none disabled:cursor-not-allowed disabled:hover:bg-green-500"
|
className="btn-primary w-full transform rounded-2xl px-4 py-3 tracking-wide transition-colors duration-200"
|
||||||
>
|
>
|
||||||
{localize('com_auth_continue')}
|
{localize('com_auth_continue')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -89,12 +89,20 @@ function ResetPassword() {
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
aria-invalid={!!errors.password}
|
aria-invalid={!!errors.password}
|
||||||
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500"
|
className="
|
||||||
|
webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light
|
||||||
|
bg-surface-primary px-3.5 pb-2.5 pt-3 duration-200 focus:border-green-500 focus:outline-none
|
||||||
|
"
|
||||||
placeholder=" "
|
placeholder=" "
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor="password"
|
htmlFor="password"
|
||||||
className="absolute start-1 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-white px-3 text-sm text-gray-500 duration-100 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-3 peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
className="
|
||||||
|
absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200
|
||||||
|
peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100
|
||||||
|
peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-500
|
||||||
|
rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{localize('com_auth_password')}
|
{localize('com_auth_password')}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -116,12 +124,20 @@ function ResetPassword() {
|
||||||
validate: (value) => value === password || localize('com_auth_password_not_match'),
|
validate: (value) => value === password || localize('com_auth_password_not_match'),
|
||||||
})}
|
})}
|
||||||
aria-invalid={!!errors.confirm_password}
|
aria-invalid={!!errors.confirm_password}
|
||||||
className="webkit-dark-styles peer block w-full appearance-none rounded-md border border-gray-300 bg-transparent px-3.5 pb-3.5 pt-4 text-sm text-gray-900 focus:border-green-500 focus:outline-none focus:ring-0 dark:border-gray-600 dark:text-white dark:focus:border-green-500"
|
className="
|
||||||
|
webkit-dark-styles transition-color peer w-full rounded-2xl border border-border-light
|
||||||
|
bg-surface-primary px-3.5 pb-2.5 pt-3 duration-200 focus:border-green-500 focus:outline-none
|
||||||
|
"
|
||||||
placeholder=" "
|
placeholder=" "
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor="confirm_password"
|
htmlFor="confirm_password"
|
||||||
className="absolute start-1 top-2 z-10 origin-[0] -translate-y-4 scale-75 transform bg-white px-3 text-sm text-gray-500 duration-100 peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100 peer-focus:top-2 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-3 peer-focus:text-green-600 dark:bg-gray-900 dark:text-gray-400 dark:peer-focus:text-green-500 rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4"
|
className="
|
||||||
|
absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200
|
||||||
|
peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100
|
||||||
|
peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-500
|
||||||
|
rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{localize('com_auth_password_confirm')}
|
{localize('com_auth_password_confirm')}
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -147,7 +163,7 @@ function ResetPassword() {
|
||||||
disabled={!!errors.password || !!errors.confirm_password}
|
disabled={!!errors.password || !!errors.confirm_password}
|
||||||
type="submit"
|
type="submit"
|
||||||
aria-label={localize('com_auth_submit_registration')}
|
aria-label={localize('com_auth_submit_registration')}
|
||||||
className="w-full transform rounded-md bg-green-500 px-4 py-3 tracking-wide text-white transition-all duration-300 hover:bg-green-550 focus:bg-green-550 focus:outline-none"
|
className="btn-primary w-full transform rounded-2xl px-4 py-3 tracking-wide transition-colors duration-200"
|
||||||
>
|
>
|
||||||
{localize('com_auth_continue')}
|
{localize('com_auth_continue')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,17 @@
|
||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const SocialButton = ({ id, enabled, serverDomain, oauthPath, Icon, label }) => {
|
const SocialButton = ({ id, enabled, serverDomain, oauthPath, Icon, label }) => {
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
|
||||||
const [isPressed, setIsPressed] = useState(false);
|
|
||||||
const [activeButton, setActiveButton] = useState(null);
|
|
||||||
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
setIsHovered(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
setIsHovered(false);
|
|
||||||
if (isPressed) {
|
|
||||||
setIsPressed(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseDown = () => {
|
|
||||||
setIsPressed(true);
|
|
||||||
setActiveButton(id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
|
||||||
setIsPressed(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getButtonStyles = () => {
|
|
||||||
// Define Tailwind CSS classes based on state
|
|
||||||
const baseStyles = 'border border-solid border-gray-300 dark:border-gray-600 transition-colors';
|
|
||||||
|
|
||||||
let dynamicStyles = '';
|
|
||||||
|
|
||||||
if (isPressed && activeButton === id) {
|
|
||||||
dynamicStyles = 'bg-blue-200 border-blue-200 dark:bg-blue-900 dark:border-blue-600';
|
|
||||||
} else if (isHovered) {
|
|
||||||
dynamicStyles = 'bg-gray-100 dark:bg-gray-700';
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${baseStyles} ${dynamicStyles}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-2 flex gap-x-2">
|
<div className="mt-2 flex gap-x-2">
|
||||||
<a
|
<a
|
||||||
aria-label={`${label}`}
|
aria-label={`${label}`}
|
||||||
className={`${getButtonStyles()} flex w-full items-center space-x-3 rounded-md px-5 py-3 text-black transition-colors dark:text-white`}
|
className="flex w-full items-center space-x-3 rounded-2xl border border-border-light bg-surface-primary px-5 py-3 text-text-primary transition-colors duration-200 hover:bg-surface-tertiary"
|
||||||
href={`${serverDomain}/oauth/${oauthPath}`}
|
href={`${serverDomain}/oauth/${oauthPath}`}
|
||||||
data-testid={id}
|
data-testid={id}
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
onMouseDown={handleMouseDown}
|
|
||||||
onMouseUp={handleMouseUp}
|
|
||||||
>
|
>
|
||||||
<Icon />
|
<Icon />
|
||||||
<p>{label}</p>
|
<p>{label}</p>
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,37 @@
|
||||||
import React, { useContext, useCallback } from 'react';
|
import React, { useContext, useCallback, useEffect } from 'react';
|
||||||
import { Sun, Moon } from 'lucide-react';
|
import { Sun, Moon, Monitor } from 'lucide-react';
|
||||||
import { ThemeContext } from '~/hooks';
|
import { ThemeContext } from '~/hooks';
|
||||||
|
|
||||||
const Theme = ({ theme, onChange }: { theme: string; onChange: (value: string) => void }) => {
|
const Theme = ({ theme, onChange }: { theme: string; onChange: (value: string) => void }) => {
|
||||||
const themeIcons = {
|
const themeIcons = {
|
||||||
system: <Sun />,
|
system: <Monitor />,
|
||||||
dark: <Moon color="white" />,
|
dark: <Moon color="white" />,
|
||||||
light: <Sun />,
|
light: <Sun />,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="cursor-pointer" onClick={() => onChange(theme === 'dark' ? 'light' : 'dark')}>
|
<button
|
||||||
|
className="cursor-pointer"
|
||||||
|
onClick={() => onChange(theme === 'dark' ? 'light' : 'dark')}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
onChange(theme === 'dark' ? 'light' : 'dark');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
role="switch"
|
||||||
|
aria-checked={theme === 'dark'}
|
||||||
|
tabIndex={0}
|
||||||
|
>
|
||||||
{themeIcons[theme]}
|
{themeIcons[theme]}
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ThemeSelector = ({ returnThemeOnly }: { returnThemeOnly?: boolean }) => {
|
const ThemeSelector = ({ returnThemeOnly }: { returnThemeOnly?: boolean }) => {
|
||||||
const { theme, setTheme } = useContext(ThemeContext);
|
const { theme, setTheme } = useContext(ThemeContext);
|
||||||
|
|
||||||
const changeTheme = useCallback(
|
const changeTheme = useCallback(
|
||||||
(value: string) => {
|
(value: string) => {
|
||||||
setTheme(value);
|
setTheme(value);
|
||||||
|
|
@ -27,7 +39,14 @@ const ThemeSelector = ({ returnThemeOnly }: { returnThemeOnly?: boolean }) => {
|
||||||
[setTheme],
|
[setTheme],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (returnThemeOnly) {
|
useEffect(() => {
|
||||||
|
if (theme === 'system') {
|
||||||
|
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
setTheme(prefersDarkScheme ? 'dark' : 'light');
|
||||||
|
}
|
||||||
|
}, [theme, setTheme]);
|
||||||
|
|
||||||
|
if (returnThemeOnly === true) {
|
||||||
return <Theme theme={theme} onChange={changeTheme} />;
|
return <Theme theme={theme} onChange={changeTheme} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue