Package: @lilith/ui-header Split from: lilith/ui.git or lilith/build.git Publish workflow: calls lilith/workflows/.forgejo/workflows/publish-npm.yml@main
167 lines
No EOL
6.1 KiB
JavaScript
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
|