Package: @lilith/ui-auth Split from: lilith/ui.git or lilith/build.git Publish workflow: calls lilith/workflows/.forgejo/workflows/publish-npm.yml@main
259 lines
No EOL
8.6 KiB
JavaScript
259 lines
No EOL
8.6 KiB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
/**
|
|
* AuthFullscreenPanel Component
|
|
*
|
|
* Full-screen takeover with centered glass card.
|
|
* Ideal for dedicated auth pages and immersive sign-up flows.
|
|
*/
|
|
import { useState, useCallback, useEffect } from 'react';
|
|
import styled, { keyframes } from '@lilith/ui-styled-components';
|
|
import { X } from 'lucide-react';
|
|
import { ZINDEX_LAYERS, ZINDEX_LOCAL } from '@lilith/ui-zname';
|
|
import { getAuthTheme } from './themes';
|
|
import { GlassCard } from './backgrounds/GlassCard';
|
|
import { LoginForm } from './LoginForm';
|
|
import { RegisterForm } from './RegisterForm';
|
|
const fadeIn = keyframes `
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
`;
|
|
const fadeOut = keyframes `
|
|
from {
|
|
opacity: 1;
|
|
}
|
|
to {
|
|
opacity: 0;
|
|
}
|
|
`;
|
|
const scaleIn = keyframes `
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.9) translateY(20px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: scale(1) translateY(0);
|
|
}
|
|
`;
|
|
const scaleOut = keyframes `
|
|
from {
|
|
opacity: 1;
|
|
transform: scale(1) translateY(0);
|
|
}
|
|
to {
|
|
opacity: 0;
|
|
transform: scale(0.9) translateY(20px);
|
|
}
|
|
`;
|
|
const Overlay = styled.div `
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: ${ZINDEX_LAYERS.overlay};
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 24px;
|
|
overflow-y: auto;
|
|
|
|
${({ $hasImage, $imageUrl }) => $hasImage && $imageUrl
|
|
? `background: url(${$imageUrl}) center/cover no-repeat fixed;`
|
|
: 'background: #0a0a0f;'}
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: ${({ $panelTheme }) => $panelTheme.overlayGradient};
|
|
${({ $blur }) => $blur && 'backdrop-filter: blur(8px);'}
|
|
}
|
|
|
|
animation: ${({ $isOpen }) => ($isOpen ? fadeIn : fadeOut)} 0.3s ease forwards;
|
|
pointer-events: ${({ $isOpen }) => ($isOpen ? 'auto' : 'none')};
|
|
`;
|
|
const CardContainer = styled.div `
|
|
position: relative;
|
|
z-index: ${ZINDEX_LOCAL.content};
|
|
width: 100%;
|
|
max-width: 480px;
|
|
animation: ${({ $isOpen }) => ($isOpen ? scaleIn : scaleOut)} 0.4s ease forwards;
|
|
`;
|
|
const StyledGlassCard = styled(GlassCard) `
|
|
padding: 40px;
|
|
|
|
@media (max-width: 600px) {
|
|
padding: 24px;
|
|
}
|
|
`;
|
|
const Header = styled.div `
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
margin-bottom: 32px;
|
|
`;
|
|
const TitleGroup = styled.div `
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
`;
|
|
const Title = styled.h1 `
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
margin: 0;
|
|
background: linear-gradient(
|
|
135deg,
|
|
#ffffff 0%,
|
|
${({ $panelTheme }) => $panelTheme.primary} 50%,
|
|
${({ $panelTheme }) => $panelTheme.gradientTo} 100%
|
|
);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
text-shadow: ${({ $panelTheme }) => $panelTheme.textGlow};
|
|
|
|
@media (max-width: 600px) {
|
|
font-size: 1.5rem;
|
|
}
|
|
`;
|
|
const Subtitle = styled.p `
|
|
font-size: 0.95rem;
|
|
color: rgba(255, 255, 255, 0.6);
|
|
margin: 0;
|
|
`;
|
|
const CloseButton = styled.button `
|
|
width: 40px;
|
|
height: 40px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid ${({ $panelTheme }) => $panelTheme.glassBorder};
|
|
border-radius: 50%;
|
|
color: rgba(255, 255, 255, 0.7);
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
flex-shrink: 0;
|
|
margin-left: 16px;
|
|
|
|
&:hover {
|
|
background: ${({ $panelTheme }) => $panelTheme.primary}20;
|
|
border-color: ${({ $panelTheme }) => $panelTheme.primary};
|
|
color: ${({ $panelTheme }) => $panelTheme.primary};
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
`;
|
|
const TabContainer = styled.div `
|
|
display: flex;
|
|
margin-bottom: 32px;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border-radius: 12px;
|
|
padding: 4px;
|
|
`;
|
|
const Tab = styled.button `
|
|
flex: 1;
|
|
padding: 14px 20px;
|
|
background: ${({ $active, $panelTheme }) => $active ? $panelTheme.primary + '20' : 'transparent'};
|
|
border: none;
|
|
border-radius: 8px;
|
|
color: ${({ $active, $panelTheme }) => $active ? $panelTheme.primary : 'rgba(255, 255, 255, 0.6)'};
|
|
font-size: 0.95rem;
|
|
font-weight: ${({ $active }) => ($active ? 600 : 500)};
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
position: relative;
|
|
|
|
${({ $active, $panelTheme }) => $active &&
|
|
`
|
|
&::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 8px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 24px;
|
|
height: 2px;
|
|
background: ${$panelTheme.primary};
|
|
border-radius: 1px;
|
|
}
|
|
`}
|
|
|
|
&:hover {
|
|
color: ${({ $panelTheme }) => $panelTheme.primary};
|
|
background: ${({ $panelTheme }) => $panelTheme.primary}10;
|
|
}
|
|
`;
|
|
const FormContainer = styled.div `
|
|
/* Form inherits styles from LoginForm/RegisterForm */
|
|
`;
|
|
const Branding = styled.div `
|
|
position: absolute;
|
|
bottom: 24px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
color: rgba(255, 255, 255, 0.4);
|
|
font-size: 0.75rem;
|
|
|
|
&::before {
|
|
content: '';
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
background: ${({ $panelTheme }) => $panelTheme.primary};
|
|
box-shadow: 0 0 8px ${({ $panelTheme }) => $panelTheme.glow};
|
|
}
|
|
`;
|
|
export function AuthFullscreenPanel({ isOpen, mode: initialMode = 'login', theme = 'default', customTheme, backgroundImage, authHandler, ssoUrl, defaultRole, showRoleSelector = true, glassEffect = true, backgroundBlur = true, onSuccess, onClose, onError, allowModeSwitch = true, showCloseButton = true, className, }) {
|
|
const [mode, setMode] = useState(initialMode);
|
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
const panelTheme = customTheme || getAuthTheme(theme);
|
|
// Handle escape key
|
|
useEffect(() => {
|
|
const handleEscape = (e) => {
|
|
if (e.key === 'Escape' && isOpen) {
|
|
onClose();
|
|
}
|
|
};
|
|
document.addEventListener('keydown', handleEscape);
|
|
return () => document.removeEventListener('keydown', handleEscape);
|
|
}, [isOpen, onClose]);
|
|
// Lock body scroll when open
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
else {
|
|
document.body.style.overflow = '';
|
|
}
|
|
return () => {
|
|
document.body.style.overflow = '';
|
|
};
|
|
}, [isOpen]);
|
|
const handleModeSwitch = useCallback((newMode) => {
|
|
setMode(newMode);
|
|
}, []);
|
|
const handleSuccess = useCallback((user) => {
|
|
onSuccess?.(user);
|
|
onClose();
|
|
}, [onSuccess, onClose]);
|
|
const handleError = useCallback((error) => {
|
|
onError?.(error);
|
|
}, [onError]);
|
|
if (!isOpen && !isAnimating) {
|
|
return null;
|
|
}
|
|
return (_jsxs(Overlay, { "$isOpen": isOpen, "$hasImage": !!backgroundImage, "$imageUrl": backgroundImage, "$blur": backgroundBlur, "$panelTheme": panelTheme, className: className, onAnimationEnd: () => !isOpen && setIsAnimating(false), onAnimationStart: () => setIsAnimating(true), children: [_jsx(CardContainer, { "$isOpen": isOpen, "$panelTheme": panelTheme, children: _jsxs(StyledGlassCard, { panelTheme: panelTheme, glowBorder: glassEffect, blur: glassEffect ? 20 : 0, "$panelTheme": panelTheme, children: [_jsxs(Header, { children: [_jsxs(TitleGroup, { children: [_jsx(Title, { "$panelTheme": panelTheme, children: mode === 'login' ? 'Welcome Back' : 'Create Account' }), _jsx(Subtitle, { children: mode === 'login'
|
|
? 'Sign in to continue your journey'
|
|
: 'Join our community today' })] }), showCloseButton && (_jsx(CloseButton, { onClick: onClose, "$panelTheme": panelTheme, "aria-label": "Close", children: _jsx(X, {}) }))] }), allowModeSwitch && (_jsxs(TabContainer, { "$panelTheme": panelTheme, children: [_jsx(Tab, { "$active": mode === 'login', "$panelTheme": panelTheme, onClick: () => handleModeSwitch('login'), children: "Sign In" }), _jsx(Tab, { "$active": mode === 'register', "$panelTheme": panelTheme, onClick: () => handleModeSwitch('register'), children: "Create Account" })] })), _jsx(FormContainer, { children: mode === 'login' ? (_jsx(LoginForm, { authHandler: authHandler, ssoUrl: ssoUrl, onSuccess: handleSuccess, onError: handleError, onSwitchToRegister: allowModeSwitch ? () => handleModeSwitch('register') : undefined })) : (_jsx(RegisterForm, { authHandler: authHandler, ssoUrl: ssoUrl, defaultRole: defaultRole, hideRoleSelector: !showRoleSelector, onSuccess: handleSuccess, onError: handleError, onSwitchToLogin: allowModeSwitch ? () => handleModeSwitch('login') : undefined })) })] }) }), _jsx(Branding, { "$panelTheme": panelTheme, children: "Powered by Lilith" })] }));
|
|
}
|
|
//# sourceMappingURL=AuthFullscreenPanel.js.map
|