ui-header/dist/composites/HeaderUserMenu.js
autocommit 4215404598 chore: initial package split from monorepo
Package: @lilith/ui-header
Split from: lilith/ui.git or lilith/build.git
Publish workflow: calls lilith/workflows/.forgejo/workflows/publish-npm.yml@main
2026-04-20 01:11:51 -07:00

167 lines
No EOL
6.1 KiB
JavaScript

import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
* HeaderUserMenu - Avatar dropdown menu for authenticated users
*
* Shows user avatar with dropdown menu containing navigation and actions.
* Uses @lilith/ui-feedback Dropdown component.
*/
import { useState, useCallback } from 'react';
import { Dropdown } from '@lilith/ui-feedback';
import styled from '@lilith/ui-styled-components';
import { ChevronDown } from 'lucide-react';
const MenuTrigger = styled.button `
display: flex;
align-items: center;
gap: ${(props) => props.theme.spacing.sm};
padding: ${(props) => props.theme.spacing.xs} ${(props) => props.theme.spacing.sm};
background: ${(props) => props.theme.colors.surface};
border: 1px solid ${(props) => props.theme.colors.border.default};
border-radius: ${(props) => props.theme.borderRadius.full};
cursor: pointer;
transition: ${(props) => props.theme.transitions.fast};
font-family: ${(props) => props.theme.typography.fontFamily.body};
&:hover {
border-color: ${(props) => props.theme.colors.primary.main};
background: ${(props) => props.theme.colors.hover.surface};
}
${({ $isOpen, theme }) => $isOpen &&
`
border-color: ${theme.colors.primary.main};
background: ${theme.colors.hover.surface};
`}
`;
const Avatar = styled.div `
width: 32px;
height: 32px;
border-radius: ${(props) => props.theme.borderRadius.full};
background: linear-gradient(
135deg,
${(props) => props.theme.colors.primary.main} 0%,
${(props) => props.theme.colors.secondary.main} 100%
);
display: flex;
align-items: center;
justify-content: center;
color: ${(props) => props.theme.colors.background.primary};
font-weight: ${(props) => props.theme.typography.fontWeight.semibold};
font-size: 12px;
flex-shrink: 0;
overflow: hidden;
`;
const AvatarImage = styled.img `
width: 100%;
height: 100%;
object-fit: cover;
`;
const UserName = styled.span `
color: ${(props) => props.theme.colors.text.primary};
font-size: ${(props) => props.theme.typography.fontSize.sm};
font-weight: ${(props) => props.theme.typography.fontWeight.medium};
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@media (max-width: 640px) {
display: none;
}
`;
const ChevronIcon = styled(ChevronDown) `
width: 14px;
height: 14px;
color: ${(props) => props.theme.colors.text.secondary};
transition: transform ${(props) => props.theme.transitions.fast};
flex-shrink: 0;
${({ $isOpen }) => $isOpen &&
`
transform: rotate(180deg);
`}
@media (max-width: 640px) {
display: none;
}
`;
const MenuContent = styled.div `
display: flex;
flex-direction: column;
min-width: 220px;
`;
const MenuHeader = styled.div `
padding: ${(props) => props.theme.spacing.md};
border-bottom: 1px solid ${(props) => props.theme.colors.border.default};
`;
const MenuHeaderName = styled.div `
font-size: ${(props) => props.theme.typography.fontSize.sm};
font-weight: ${(props) => props.theme.typography.fontWeight.semibold};
color: ${(props) => props.theme.colors.text.primary};
`;
const MenuHeaderSecondary = styled.div `
font-size: ${(props) => props.theme.typography.fontSize.xs};
color: ${(props) => props.theme.colors.text.secondary};
margin-top: 2px;
`;
const MenuList = styled.div `
display: flex;
flex-direction: column;
padding: ${(props) => props.theme.spacing.xs} 0;
`;
const MenuItem = styled.button `
display: flex;
align-items: center;
gap: ${(props) => props.theme.spacing.sm};
width: 100%;
padding: ${(props) => props.theme.spacing.sm} ${(props) => props.theme.spacing.md};
background: transparent;
border: none;
cursor: pointer;
text-align: left;
font-family: ${(props) => props.theme.typography.fontFamily.body};
font-size: ${(props) => props.theme.typography.fontSize.sm};
color: ${({ $destructive, theme }) => $destructive ? theme.colors.error.main : theme.colors.text.primary};
transition: background ${(props) => props.theme.transitions.fast};
&:hover {
background: ${({ $destructive, theme }) => $destructive ? `${theme.colors.error.main}10` : theme.colors.hover.surface};
}
svg {
width: 16px;
height: 16px;
color: ${({ $destructive, theme }) => $destructive ? theme.colors.error.main : theme.colors.text.secondary};
}
`;
const MenuDivider = styled.div `
height: 1px;
background: ${(props) => props.theme.colors.border.default};
margin: ${(props) => props.theme.spacing.xs} 0;
`;
const getInitials = (name) => name
.split(' ')
.map((word) => word[0])
.join('')
.toUpperCase()
.slice(0, 2);
export const HeaderUserMenu = ({ avatarUrl, displayName, secondaryText, items, className, }) => {
const [isOpen, setIsOpen] = useState(false);
const handleToggle = useCallback(() => {
setIsOpen(!isOpen);
}, [isOpen]);
const handleItemClick = useCallback((item) => {
if (item.onClick) {
item.onClick();
}
setIsOpen(false);
}, []);
const trigger = (_jsxs(MenuTrigger, { "$isOpen": isOpen, "aria-expanded": isOpen, "aria-haspopup": "menu", children: [_jsx(Avatar, { children: avatarUrl ? (_jsx(AvatarImage, { src: avatarUrl, alt: displayName })) : (getInitials(displayName)) }), _jsx(UserName, { children: displayName }), _jsx(ChevronIcon, { "$isOpen": isOpen })] }));
return (_jsx(Dropdown, { trigger: trigger, isOpen: isOpen, onToggle: handleToggle, children: _jsxs(MenuContent, { className: className, children: [_jsxs(MenuHeader, { children: [_jsx(MenuHeaderName, { children: displayName }), secondaryText && _jsx(MenuHeaderSecondary, { children: secondaryText })] }), _jsx(MenuList, { children: items.map((item, index) => {
if (item.divider) {
return _jsx(MenuDivider, {}, `divider-${index}`);
}
const Icon = item.icon;
return (_jsxs(MenuItem, { "$destructive": item.destructive, onClick: () => handleItemClick(item), children: [Icon && _jsx(Icon, { size: 16 }), item.label] }, item.label));
}) })] }) }));
};
//# sourceMappingURL=HeaderUserMenu.js.map