mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30: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">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="theme-color" content="#171717">
|
||||
<meta name="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="theme-color" content="#171717" />
|
||||
<meta name="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" />
|
||||
<title>LibreChat</title>
|
||||
<link
|
||||
rel="shortcut icon"
|
||||
href="#"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/assets/favicon-32x32.png"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1"
|
||||
/>
|
||||
<script
|
||||
defer
|
||||
type="module"
|
||||
src="/src/main.jsx"
|
||||
></script>
|
||||
<link rel="shortcut icon" href="#" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png" />
|
||||
<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" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
const theme = localStorage.getItem('color-theme');
|
||||
const loadingContainerStyle = document.createElement('style');
|
||||
let backgroundColor;
|
||||
|
||||
if (theme === 'dark') {
|
||||
backgroundColor = '#0d0d0d';
|
||||
} else if (theme === 'light') {
|
||||
backgroundColor = '#ffffff';
|
||||
} else if (theme === 'system') {
|
||||
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
backgroundColor = prefersDarkScheme ? '#0d0d0d' : '#ffffff';
|
||||
} else {
|
||||
backgroundColor = '#ffffff';
|
||||
}
|
||||
|
||||
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>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<script
|
||||
type="module"
|
||||
src="/src/main.jsx"
|
||||
></script>
|
||||
<div id="root">
|
||||
<div id="loading-container"></div>
|
||||
</div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ function AuthLayout({
|
|||
return (
|
||||
<div className="relative flex min-h-screen flex-col bg-white dark:bg-gray-900">
|
||||
<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" />
|
||||
</div>
|
||||
</BlinkAnimation>
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
|||
method="POST"
|
||||
onSubmit={handleSubmit((data) => onSubmit(data))}
|
||||
>
|
||||
<div className="mb-2">
|
||||
<div className="mb-4">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="text"
|
||||
|
|
@ -97,12 +97,20 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
|||
},
|
||||
})}
|
||||
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=" "
|
||||
/>
|
||||
<label
|
||||
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
|
||||
? 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') },
|
||||
})}
|
||||
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=" "
|
||||
/>
|
||||
<label
|
||||
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')}
|
||||
</label>
|
||||
|
|
@ -146,7 +162,7 @@ const LoginForm: React.FC<TLoginFormProps> = ({ onSubmit, startupConfig, error,
|
|||
aria-label="Sign in"
|
||||
data-testid="login-button"
|
||||
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')}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ const Registration: React.FC = () => {
|
|||
});
|
||||
|
||||
const renderInput = (id: string, label: string, type: string, validation: object) => (
|
||||
<div className="mb-2">
|
||||
<div className="mb-4">
|
||||
<div className="relative">
|
||||
<input
|
||||
id={id}
|
||||
|
|
@ -69,19 +69,27 @@ const Registration: React.FC = () => {
|
|||
validation,
|
||||
)}
|
||||
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=" "
|
||||
data-testid={id}
|
||||
/>
|
||||
<label
|
||||
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)}
|
||||
</label>
|
||||
</div>
|
||||
{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) ?? ''}
|
||||
</span>
|
||||
)}
|
||||
|
|
@ -175,7 +183,7 @@ const Registration: React.FC = () => {
|
|||
disabled={Object.keys(errors).length > 0}
|
||||
type="submit"
|
||||
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')}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -104,12 +104,20 @@ function RequestPasswordReset() {
|
|||
},
|
||||
})}
|
||||
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=" "
|
||||
/>
|
||||
<label
|
||||
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')}
|
||||
</label>
|
||||
|
|
@ -124,7 +132,7 @@ function RequestPasswordReset() {
|
|||
<button
|
||||
type="submit"
|
||||
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')}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -89,12 +89,20 @@ function ResetPassword() {
|
|||
},
|
||||
})}
|
||||
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=" "
|
||||
/>
|
||||
<label
|
||||
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')}
|
||||
</label>
|
||||
|
|
@ -116,12 +124,20 @@ function ResetPassword() {
|
|||
validate: (value) => value === password || localize('com_auth_password_not_match'),
|
||||
})}
|
||||
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=" "
|
||||
/>
|
||||
<label
|
||||
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')}
|
||||
</label>
|
||||
|
|
@ -147,7 +163,7 @@ function ResetPassword() {
|
|||
disabled={!!errors.password || !!errors.confirm_password}
|
||||
type="submit"
|
||||
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')}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,60 +1,17 @@
|
|||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
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) {
|
||||
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 (
|
||||
<div className="mt-2 flex gap-x-2">
|
||||
<a
|
||||
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}`}
|
||||
data-testid={id}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
>
|
||||
<Icon />
|
||||
<p>{label}</p>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,37 @@
|
|||
import React, { useContext, useCallback } from 'react';
|
||||
import { Sun, Moon } from 'lucide-react';
|
||||
import React, { useContext, useCallback, useEffect } from 'react';
|
||||
import { Sun, Moon, Monitor } from 'lucide-react';
|
||||
import { ThemeContext } from '~/hooks';
|
||||
|
||||
const Theme = ({ theme, onChange }: { theme: string; onChange: (value: string) => void }) => {
|
||||
const themeIcons = {
|
||||
system: <Sun />,
|
||||
system: <Monitor />,
|
||||
dark: <Moon color="white" />,
|
||||
light: <Sun />,
|
||||
};
|
||||
|
||||
return (
|
||||
<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]}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ThemeSelector = ({ returnThemeOnly }: { returnThemeOnly?: boolean }) => {
|
||||
const { theme, setTheme } = useContext(ThemeContext);
|
||||
|
||||
const changeTheme = useCallback(
|
||||
(value: string) => {
|
||||
setTheme(value);
|
||||
|
|
@ -27,7 +39,14 @@ const ThemeSelector = ({ returnThemeOnly }: { returnThemeOnly?: boolean }) => {
|
|||
[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} />;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue