Package: @lilith/ui-auth Split from: lilith/ui.git or lilith/build.git Publish workflow: calls lilith/workflows/.forgejo/workflows/publish-npm.yml@main
201 lines
No EOL
7.5 KiB
JavaScript
201 lines
No EOL
7.5 KiB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
/**
|
|
* AuthSlidePanel Component
|
|
*
|
|
* Slide-out panel from edge of screen.
|
|
* Ideal for header login buttons and quick auth access.
|
|
*/
|
|
import { useState, useCallback, useEffect } from 'react';
|
|
import styled, { keyframes, css } from '@lilith/ui-styled-components';
|
|
import { X } from 'lucide-react';
|
|
import { ZINDEX_LAYERS } from '@lilith/ui-zname';
|
|
import { getAuthTheme } from './themes';
|
|
// GlassCard available for future use if needed
|
|
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 Overlay = styled.div `
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: ${ZINDEX_LAYERS.overlay};
|
|
background: rgba(0, 0, 0, 0.7);
|
|
${({ $blur }) => $blur && 'backdrop-filter: blur(8px);'}
|
|
animation: ${({ $isOpen }) => ($isOpen ? fadeIn : fadeOut)} 0.3s ease forwards;
|
|
pointer-events: ${({ $isOpen }) => ($isOpen ? 'auto' : 'none')};
|
|
`;
|
|
const slideInRight = keyframes `
|
|
from { transform: translateX(100%); }
|
|
to { transform: translateX(0); }
|
|
`;
|
|
const slideOutRight = keyframes `
|
|
from { transform: translateX(0); }
|
|
to { transform: translateX(100%); }
|
|
`;
|
|
const slideInLeft = keyframes `
|
|
from { transform: translateX(-100%); }
|
|
to { transform: translateX(0); }
|
|
`;
|
|
const slideOutLeft = keyframes `
|
|
from { transform: translateX(0); }
|
|
to { transform: translateX(-100%); }
|
|
`;
|
|
const PanelContainer = styled.div `
|
|
position: fixed;
|
|
top: 0;
|
|
${({ $direction }) => ($direction === 'right' ? 'right: 0;' : 'left: 0;')}
|
|
z-index: ${ZINDEX_LAYERS.modal};
|
|
width: ${({ $width }) => $width}px;
|
|
max-width: 100vw;
|
|
height: 100vh;
|
|
background: ${({ $panelTheme }) => $panelTheme.overlayGradient};
|
|
box-shadow: ${({ $direction }) => $direction === 'right'
|
|
? '-20px 0 60px rgba(0, 0, 0, 0.5)'
|
|
: '20px 0 60px rgba(0, 0, 0, 0.5)'};
|
|
overflow-y: auto;
|
|
|
|
${({ $isOpen, $direction }) => {
|
|
const isRight = $direction === 'right';
|
|
return css `
|
|
animation: ${$isOpen
|
|
? isRight
|
|
? slideInRight
|
|
: slideInLeft
|
|
: isRight
|
|
? slideOutRight
|
|
: slideOutLeft}
|
|
0.3s ease forwards;
|
|
`;
|
|
}}
|
|
`;
|
|
const PanelInner = styled.div `
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 100%;
|
|
padding: 24px;
|
|
`;
|
|
const Header = styled.div `
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 32px;
|
|
padding-bottom: 16px;
|
|
border-bottom: 1px solid ${({ $panelTheme }) => $panelTheme.glassBorder};
|
|
`;
|
|
const Title = styled.h2 `
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
margin: 0;
|
|
background: linear-gradient(
|
|
135deg,
|
|
#ffffff 0%,
|
|
${({ $panelTheme }) => $panelTheme.primary} 100%
|
|
);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
`;
|
|
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;
|
|
|
|
&: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: 24px;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border-radius: 12px;
|
|
padding: 4px;
|
|
`;
|
|
const Tab = styled.button `
|
|
flex: 1;
|
|
padding: 12px 16px;
|
|
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.9rem;
|
|
font-weight: ${({ $active }) => ($active ? 600 : 500)};
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
|
|
&:hover {
|
|
color: ${({ $panelTheme }) => $panelTheme.primary};
|
|
background: ${({ $panelTheme }) => $panelTheme.primary}10;
|
|
}
|
|
`;
|
|
const FormContainer = styled.div `
|
|
flex: 1;
|
|
`;
|
|
export function AuthSlidePanel({ isOpen, mode: initialMode = 'login', direction = 'right', theme = 'default', customTheme, authHandler, ssoUrl, defaultRole, showRoleSelector = true, overlayBlur = true, width = 420, onSuccess, onClose, onError, allowModeSwitch = true, className, }) {
|
|
const [mode, setMode] = useState(initialMode);
|
|
const [isAnimating, setIsAnimating] = useState(false);
|
|
const panelTheme = customTheme || getAuthTheme(theme);
|
|
// Sync internal mode with initialMode prop when it changes
|
|
useEffect(() => {
|
|
setMode(initialMode);
|
|
}, [initialMode]);
|
|
// 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(_Fragment, { children: [_jsx(Overlay, { "$isOpen": isOpen, "$blur": overlayBlur, onClick: onClose, onAnimationEnd: () => !isOpen && setIsAnimating(false) }), _jsx(PanelContainer, { "$isOpen": isOpen, "$direction": direction, "$width": width, "$panelTheme": panelTheme, className: className, onAnimationStart: () => setIsAnimating(true), onAnimationEnd: () => !isOpen && setIsAnimating(false), children: _jsxs(PanelInner, { children: [_jsxs(Header, { "$panelTheme": panelTheme, children: [_jsx(Title, { "$panelTheme": panelTheme, children: mode === 'login' ? 'Welcome Back' : 'Join Us' }), _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 })) })] }) })] }));
|
|
}
|
|
//# sourceMappingURL=AuthSlidePanel.js.map
|