Skip to content

Commit

Permalink
fix: account menu avatars
Browse files Browse the repository at this point in the history
fix menu patterns
  • Loading branch information
ingefossland committed Jan 28, 2025
1 parent 58d306f commit 7c286d6
Show file tree
Hide file tree
Showing 10 changed files with 394 additions and 382 deletions.
40 changes: 26 additions & 14 deletions lib/components/GlobalMenu/AccountMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
'use client';
import { useState } from 'react';
import type { BadgeProps } from '../Badge';
import { Menu, type MenuItemGroups, type MenuItemProps, type MenuSearchProps } from '../Menu';
"use client";
import { useState } from "react";
import type { BadgeProps } from "../Badge";
import {
Menu,
type MenuItemGroups,
type MenuItemProps,
type MenuSearchProps,
} from "../Menu";

export interface AccountSearchProps extends MenuSearchProps {
getResultsLabel?: (hits: number) => string;
hidden?: boolean;
}

export interface AccountMenuItem {
type: 'person' | 'company';
type: "person" | "company";
name: string;
id: string;
groupId?: string;
Expand Down Expand Up @@ -38,11 +43,12 @@ export const AccountMenu = ({
}: AccountMenuProps) => {
const accountMenu: MenuItemProps[] = accounts.map((account) => ({
id: account.id || account.name,
groupId: account.groupId || 'search',
groupId: account.groupId || "search",
selected: account.selected ?? currentAccount?.id === account.id,
title: account.name,
...(account?.accountNames && {
avatarGroup: {
size: "sm",
items: account.accountNames.map((name) => ({
name,
type: account.type,
Expand All @@ -51,6 +57,7 @@ export const AccountMenu = ({
}),
...(!account?.accountNames && {
avatar: {
size: "md",
type: account.type,
name: account.name,
},
Expand All @@ -60,15 +67,17 @@ export const AccountMenu = ({
onClick: () => onSelectAccount?.(account.id || account.name),
}));

const [filterString, setFilterString] = useState<string>('');
const [filterString, setFilterString] = useState<string>("");

const filteredAccountMenu = filterString
? accountMenu
.filter((item) => item?.title?.toLowerCase().includes(filterString.toLowerCase()))
.filter((item) =>
item?.title?.toLowerCase().includes(filterString.toLowerCase())
)
.map((item) => {
return {
...item,
groupId: 'search',
groupId: "search",
};
})
: accountMenu;
Expand All @@ -84,15 +93,18 @@ export const AccountMenu = ({
: accountGroups;

const accountSearchItem: MenuSearchProps = {
name: 'account-search',
name: "account-search",
value: filterString,
placeholder: accountSearch?.placeholder ?? 'Find account',
onChange: (event: React.ChangeEvent<HTMLInputElement>) => setFilterString(event.target.value),
onClear: () => setFilterString(''),
placeholder: accountSearch?.placeholder ?? "Find account",
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
setFilterString(event.target.value),
onClear: () => setFilterString(""),
};

const accountSwitcher: MenuItemProps[] = [
...(filteredAccountMenu.length > 0 ? filteredAccountMenu : [{ id: 'search', groupId: 'search', hidden: true }]),
...(filteredAccountMenu.length > 0
? filteredAccountMenu
: [{ id: "search", groupId: "search", hidden: true }]),
];

return (
Expand Down
36 changes: 28 additions & 8 deletions lib/components/Icon/IconOrAvatar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ReactNode, isValidElement } from 'react';
import { type ReactNode, isValidElement } from "react";
import {
Avatar,
AvatarGroup,
Expand All @@ -11,9 +11,9 @@ import {
type IconProps,
type IconSize,
type IconTheme,
} from '..';
} from "..";

export type IconOrAvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
export type IconOrAvatarSize = "xs" | "sm" | "md" | "lg" | "xl";

//import styles from "./menuItemIcon.module.css";

Expand All @@ -26,7 +26,13 @@ export interface IconOrAvatarProps {
badge?: BadgeProps | undefined;
}

export const IconOrAvatar = ({ size, icon, iconTheme, avatar, avatarGroup }: IconOrAvatarProps) => {
export const IconOrAvatar = ({
size,
icon,
iconTheme,
avatar,
avatarGroup,
}: IconOrAvatarProps) => {
if (!icon && !avatar && !avatarGroup) {
return null;
}
Expand All @@ -38,18 +44,32 @@ export const IconOrAvatar = ({ size, icon, iconTheme, avatar, avatarGroup }: Ico
return icon;
}

const applicableIcon = typeof icon === 'string' ? ({ name: icon } as IconProps) : (icon as IconProps);
const applicableIcon =
typeof icon === "string"
? ({ name: icon } as IconProps)
: (icon as IconProps);

return <Icon {...applicableIcon} theme={applicableIcon?.theme || iconTheme} size={size as IconSize} />;
return (
<Icon
{...applicableIcon}
theme={applicableIcon?.theme || iconTheme}
size={size as IconSize}
/>
);
}

/** Avatar or AvatarGroup */

if (avatar) {
return <Avatar {...avatar} size={size as AvatarSize} />;
return <Avatar {...avatar} size={avatar.size || size} />;
}

if (avatarGroup) {
return <AvatarGroup {...avatarGroup} size={size as AvatarSize} />;
return (
<AvatarGroup
{...avatarGroup}
size={avatarGroup.size || (size as AvatarSize)}
/>
);
}
};
37 changes: 3 additions & 34 deletions lib/components/List/ListItemIcon.module.css
Original file line number Diff line number Diff line change
@@ -1,38 +1,7 @@
/* media */

.media {
.icon {
flex-shrink: 0;
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-width: 1em;
height: 1em;
}

.media {
font-size: 1.5rem;
}

.media[data-size="sm"],
.media[data-size="md"] {
min-width: 2.25rem;
height: 2.25rem;
}

.media[data-size="md"],
.media[data-size="lg"] {
font-size: 1.875rem;
}

.media[data-size="md"] .icon,
.media[data-size="lg"] .icon {
font-size: 1.75rem;
}

.media[data-size="xl"] {
font-size: 2.25rem;
}

.media[data-size="xl"] .icon {
font-size: 2rem;
align-items: center;
}
58 changes: 27 additions & 31 deletions lib/components/List/ListItemIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { type ReactNode, isValidElement } from 'react';
import { Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, type AvatarSize } from '../Avatar';
import { Icon, type IconName, type IconProps, type IconSize } from '../Icon';
import type { ReactNode } from "react";
import { IconOrAvatar, type IconOrAvatarProps } from "../";
import styles from "./listItemIcon.module.css";

export type ListItemIconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
import { Icon, type IconSize } from "../Icon";

export interface ListItemIconProps {
export type ListItemIconSize = "xs" | "sm" | "md" | "lg" | "xl";

export interface ListItemIconProps extends IconOrAvatarProps {
loading?: boolean;
size?: ListItemIconSize;
icon?: IconProps | IconName | ReactNode;
avatar?: AvatarProps;
avatarGroup?: AvatarGroupProps;
children?: ReactNode;
}

export const ListItemIcon = ({ loading, size = 'sm', icon, avatar, avatarGroup, children }: ListItemIconProps) => {
export const ListItemIcon = ({
loading,
size = "sm",
icon,
avatar,
avatarGroup,
children,
}: ListItemIconProps) => {
if (!icon && !avatar && !avatarGroup && !children) {
return null;
}
Expand All @@ -22,25 +27,16 @@ export const ListItemIcon = ({ loading, size = 'sm', icon, avatar, avatarGroup,
return <Icon name="x-mark" size={size as IconSize} loading={true} />;
}

/** Icon can be custom, a string or an Icon object. */

if (icon) {
if (isValidElement(icon)) {
return icon;
}

const applicableIcon = typeof icon === 'string' ? ({ name: icon } as IconProps) : (icon as IconProps);

return <Icon {...applicableIcon} size={size as IconSize} />;
}

/** Avatar or AvatarGroup */

if (avatar) {
return <Avatar {...avatar} size={size as AvatarSize} />;
}

if (avatarGroup) {
return <AvatarGroup {...avatarGroup} size={size as AvatarSize} />;
}
return (
<span className={styles.icon}>
{children || (
<IconOrAvatar
size={size}
icon={icon}
avatar={avatar}
avatarGroup={avatarGroup}
/>
)}
</span>
);
};
Loading

0 comments on commit 7c286d6

Please sign in to comment.