@atlassian/aui
Version:
Atlassian User Interface library
208 lines (173 loc) • 6.11 kB
JavaScript
import skate from './internal/skate';
const DEFAULT_SIZE = 'medium';
const VISIBLE_AVATAR_COUNT = 4;
const OVERLAP_FACTOR = 0.25;
const SIZES = {
xsmall: 16,
small: 24,
medium: 32,
large: 48,
xlarge: 64,
xxlarge: 96,
xxxlarge: 128,
};
const setAvatarGroupSize = (element, newValue, oldValue) => {
if (oldValue) {
element.classList.remove(`aui-avatar-group-${oldValue}`);
}
element.classList.add(`aui-avatar-group-${newValue}`);
};
const getAvatarGroupSize = (value) => {
return Object.keys(SIZES).includes(value) ? value : DEFAULT_SIZE;
};
const updateAvatarGroup = (element, size) => {
const avatars = element.querySelectorAll('aui-avatar');
const hiddenAvatars = [...avatars].slice(VISIBLE_AVATAR_COUNT);
updateAvatarGroupItems(avatars, size);
const oldDropdown = element.querySelector('.aui-avatar-group-dropdown');
if (oldDropdown) {
oldDropdown.remove();
}
if (hiddenAvatars.length === 0) {
return;
}
const dropdown = createDropdown(size, hiddenAvatars);
element.appendChild(dropdown);
};
const createDropdown = (size, hiddenAvatars) => {
const dropdown = document.createElement('div');
const button = createAvatarGroupDropdownButton(`+${hiddenAvatars.length}`);
const menu = createAvatarGroupDropdownMenu();
hiddenAvatars.forEach((hiddenAvatar) => {
menu.append(hiddenAvatar);
});
dropdown.classList.add('aui-avatar-group-dropdown');
dropdown.style.left = getLeftPosition(size, VISIBLE_AVATAR_COUNT + 0.2);
dropdown.appendChild(button);
dropdown.appendChild(menu);
return dropdown;
};
const createAvatarGroupDropdownButton = (content) => {
const button = document.createElement('button');
button.classList.add('aui-avatar-group-dropdown-button', 'aui-avatar-inner');
button.innerText = content;
button.setAttribute('aria-expanded', false);
button.setAttribute('aria-haspopup', 'dialog');
return button;
};
const createAvatarGroupDropdownMenu = () => {
const dropdown = document.createElement('div');
dropdown.classList.add('aui-avatar-group-dropdown-menu');
return dropdown;
};
const updateAvatarGroupItems = (avatars, size) => {
avatars.forEach((avatar, index) => {
const isHidden = index >= VISIBLE_AVATAR_COUNT;
avatar.classList.add(
!isHidden ? 'aui-avatar-group-item' : 'aui-avatar-group-dropdown-item'
);
if (!isHidden) {
avatar.setAttribute('size', size);
avatar.style.left = getLeftPosition(size, index);
avatar.style.zIndex = `${VISIBLE_AVATAR_COUNT + 1 - index}`;
} else {
avatar.setAttribute('size', 'medium');
}
});
};
const getLeftPosition = (size, index) => {
const avatarSize = SIZES[size];
const overlapFactor = avatarSize * OVERLAP_FACTOR;
const leftPosition = (avatarSize - overlapFactor) * index;
return `${leftPosition}px`;
};
document.addEventListener('click', (e) => {
const isDropdownButton = e.target.matches('.aui-avatar-group-dropdown-button');
const closestDropdown = e.target.closest('.aui-avatar-group-dropdown');
if (!isDropdownButton && closestDropdown !== null) {
return;
}
if (isDropdownButton) {
closestDropdown.classList.contains('aui-avatar-group-dropdown-show')
? closeDropdown(closestDropdown)
: openDropdown(closestDropdown);
}
document.querySelectorAll('.aui-avatar-group-dropdown-show').forEach((dropdown) => {
if (dropdown === closestDropdown) {
return;
}
closeDropdown(dropdown);
});
});
document.addEventListener('keydown', (e) => {
if (e.key !== 'Escape') {
return;
}
const dropdown = document.querySelector('.aui-avatar-group-dropdown-show');
if (!dropdown) {
return;
}
closeDropdown(dropdown);
});
const closeDropdown = (dropdown) => {
const button = dropdown.querySelector('.aui-avatar-group-dropdown-button');
dropdown.classList.remove('aui-avatar-group-dropdown-show');
button.setAttribute('aria-expanded', 'false');
};
const openDropdown = (dropdown) => {
const button = dropdown.querySelector('.aui-avatar-group-dropdown-button');
dropdown.classList.add('aui-avatar-group-dropdown-show');
button.setAttribute('aria-expanded', 'true');
};
const AvatarGroupEl = skate('aui-avatar-group', {
attributes: {
size: {
value: DEFAULT_SIZE,
fallback(element, { newValue, oldValue }) {
const size = getAvatarGroupSize(newValue);
setAvatarGroupSize(element, size, oldValue);
skate.init(element);
updateAvatarGroup(element, size);
},
},
},
created(element) {
element.classList.add('aui-avatar-group');
},
});
const wasNodeRemoved = (mutation, target) => {
return (
mutation.removedNodes.length > 0 &&
target.classList.contains('aui-avatar-group') &&
mutation.removedNodes[0].classList.contains('aui-avatar-group-item')
);
};
const wasNodeAdded = (mutation, target) => {
return (
mutation.addedNodes.length > 0 &&
target.classList.contains('aui-avatar-group') &&
mutation.addedNodes[0].classList.contains('aui-avatar')
);
};
const mutationObserver = new MutationObserver(function (mutation) {
mutation.forEach(function (mutation) {
const target = mutation.target;
if (wasNodeRemoved(mutation, target)) {
updateAvatarGroup(target, target.getAttribute('size'));
}
if (wasNodeAdded(mutation, target)) {
setTimeout(() => {
updateAvatarGroup(target, target.getAttribute('size'));
}, 0);
}
});
});
mutationObserver.observe(document.documentElement, {
attributes: false,
characterData: true,
childList: true,
subtree: true,
attributeOldValue: false,
characterDataOldValue: false,
});
export { AvatarGroupEl };