feat: Mejora del componente Dropdown con soporte para encabezados no interactivos y transiciones de fondo
This commit is contained in:
parent
01178e9532
commit
4575e3ce46
@ -43,7 +43,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropdownItem:hover {
|
.dropdownItem:hover {
|
||||||
background-color: rgba(255,255,255,0.03);
|
background-color: var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ensure header (non-interactive) doesn't get hover background */
|
||||||
|
.dropdownHeader:hover {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownItem {
|
||||||
|
transition: background-color 0.12s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdownItem .icon {
|
.dropdownItem .icon {
|
||||||
@ -53,6 +62,22 @@
|
|||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdownHeader {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0.90; /* user requested opacity */
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdownHeaderLabel {
|
||||||
|
opacity: 0.90;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background-color: rgba(255,255,255,0.03);
|
background-color: rgba(255,255,255,0.03);
|
||||||
|
|||||||
@ -4,8 +4,11 @@ import styles from './Dropdown.module.css';
|
|||||||
interface DropdownItem {
|
interface DropdownItem {
|
||||||
label: string;
|
label: string;
|
||||||
icon?: React.ReactNode;
|
icon?: React.ReactNode;
|
||||||
onClick: () => void;
|
onClick?: () => void;
|
||||||
divider?: boolean;
|
divider?: boolean;
|
||||||
|
disabled?: boolean; // non-interactive header-like item
|
||||||
|
containerProps?: React.HTMLAttributes<HTMLElement>;
|
||||||
|
labelProps?: React.HTMLAttributes<HTMLSpanElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DropdownProps {
|
interface DropdownProps {
|
||||||
@ -44,16 +47,39 @@ export const Dropdown: React.FC<DropdownProps> = ({ trigger, items }) => {
|
|||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<React.Fragment key={index}>
|
<React.Fragment key={index}>
|
||||||
{item.divider && <div className={styles.divider} />}
|
{item.divider && <div className={styles.divider} />}
|
||||||
<button
|
{item.disabled ? (
|
||||||
className={styles.dropdownItem}
|
<div className={styles.dropdownHeader} {...(item.containerProps || {})}>
|
||||||
onClick={() => {
|
{item.icon && <span className={styles.icon}>{item.icon}</span>}
|
||||||
item.onClick();
|
{
|
||||||
setIsOpen(false);
|
// merge className for the header label
|
||||||
}}
|
(() => {
|
||||||
>
|
const lp = item.labelProps || {};
|
||||||
{item.icon && <span className={styles.icon}>{item.icon}</span>}
|
const { className: lpClassName, ...lpOther } = lp as any;
|
||||||
<span>{item.label}</span>
|
const classes = [styles.dropdownHeaderLabel, lpClassName].filter(Boolean).join(' ');
|
||||||
</button>
|
return <span className={classes} {...lpOther}>{item.label}</span>;
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
className={styles.dropdownItem}
|
||||||
|
onClick={() => {
|
||||||
|
item.onClick && item.onClick();
|
||||||
|
setIsOpen(false);
|
||||||
|
}}
|
||||||
|
{...(item.containerProps || {})}
|
||||||
|
>
|
||||||
|
{item.icon && <span className={styles.icon}>{item.icon}</span>}
|
||||||
|
{
|
||||||
|
(() => {
|
||||||
|
const lp = item.labelProps || {};
|
||||||
|
const { className: lpClassName, ...lpOther } = lp as any;
|
||||||
|
const classes = [lpClassName].filter(Boolean).join(' ');
|
||||||
|
return <span className={classes} {...lpOther}>{item.label}</span>;
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -114,7 +114,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.userEmail {
|
.userEmail {
|
||||||
opacity: 0.95;
|
opacity: 0.90;
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.userTitleMenu {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,11 +13,55 @@ const Header: React.FC = () => {
|
|||||||
window.location.href = '/auth/login'
|
window.location.href = '/auth/login'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read mock user from localStorage (if set elsewhere in the app)
|
||||||
|
const storedUser = typeof window !== 'undefined' ? localStorage.getItem('mock_user') : null
|
||||||
|
let email = 'Usuario'
|
||||||
|
let avatarUrl: string | null = null
|
||||||
|
let displayName: string | null = null
|
||||||
|
try {
|
||||||
|
if (storedUser) {
|
||||||
|
const parsed = JSON.parse(storedUser)
|
||||||
|
email = parsed.email || storedUser || 'Usuario'
|
||||||
|
avatarUrl = parsed.avatar || parsed.photo || parsed.picture || null
|
||||||
|
displayName = parsed.name || parsed.fullname || null
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
email = storedUser || 'Usuario'
|
||||||
|
}
|
||||||
|
|
||||||
|
const computeInitials = (nameOrEmail: string) => {
|
||||||
|
if (!nameOrEmail) return 'U'
|
||||||
|
const name = nameOrEmail.split('@')[0]
|
||||||
|
const parts = name.split(/[^A-Za-z0-9]+/).filter(Boolean)
|
||||||
|
if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase()
|
||||||
|
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase()
|
||||||
|
return nameOrEmail.slice(0, 2).toUpperCase()
|
||||||
|
}
|
||||||
|
|
||||||
|
const initials = computeInitials(displayName || email)
|
||||||
|
|
||||||
|
const avatarElement = avatarUrl ? (
|
||||||
|
<img src={avatarUrl} alt="avatar" style={{ width: 28, height: 28, borderRadius: '50%', objectFit: 'cover' }} />
|
||||||
|
) : (
|
||||||
|
<div style={{ width: 28, height: 28, borderRadius: '50%', background: 'var(--primary-blue)', color: 'white', display: 'grid', placeItems: 'center', fontSize: 12, fontWeight: 700 }}>
|
||||||
|
{initials}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
const userMenuItems = [
|
const userMenuItems = [
|
||||||
|
// non-interactive header with email
|
||||||
|
{
|
||||||
|
label: email,
|
||||||
|
icon: avatarElement,
|
||||||
|
disabled: true,
|
||||||
|
containerProps: { 'data-test': 'user-email', id: 'user-email' },
|
||||||
|
labelProps: { style: { opacity: 0.7 } }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Mi perfil',
|
label: 'Mi perfil',
|
||||||
icon: <MdPerson size={18} />,
|
icon: <MdPerson size={18} />,
|
||||||
onClick: () => console.log('Ir a perfil')
|
onClick: () => console.log('Ir a perfil'),
|
||||||
|
divider: true // separator after the header
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Ayuda',
|
label: 'Ayuda',
|
||||||
@ -76,7 +120,7 @@ const Header: React.FC = () => {
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
trigger={
|
trigger={
|
||||||
<div className={styles.userMenu}>
|
<div className={styles.userMenu}>
|
||||||
<span className={styles.userEmail}>nextv.stream@gmail.com</span>
|
<span className={styles.userTitleMenu}>Mi cuenta</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
items={userMenuItems}
|
items={userMenuItems}
|
||||||
|
|||||||
@ -42,7 +42,8 @@ const Sidebar: React.FC<SidebarProps> = ({ activeLink = 'inicio' }) => {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{/* Secondary Navigation moved closer to storage */}
|
</nav>
|
||||||
|
{/* Secondary Navigation moved closer to storage */}
|
||||||
<div className={styles.secondaryNavGroup}>
|
<div className={styles.secondaryNavGroup}>
|
||||||
<ul className={styles.navList}>
|
<ul className={styles.navList}>
|
||||||
{secondaryNavItems.map(item => (
|
{secondaryNavItems.map(item => (
|
||||||
@ -55,8 +56,6 @@ const Sidebar: React.FC<SidebarProps> = ({ activeLink = 'inicio' }) => {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
|
||||||
|
|
||||||
{/* Storage Info */}
|
{/* Storage Info */}
|
||||||
<div className={styles.storageInfo}>
|
<div className={styles.storageInfo}>
|
||||||
<div className={styles.storageTitle}>
|
<div className={styles.storageTitle}>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user