Skip to content

Commit

Permalink
feat(avatar-group): overflow
Browse files Browse the repository at this point in the history
  • Loading branch information
321gillian committed Jan 6, 2025
1 parent 8fa117d commit 5b99fa4
Show file tree
Hide file tree
Showing 26 changed files with 2,695 additions and 135 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import {
Prop,
Element,
Listen,
Method
Method,
Host
} from '@stencil/core';
import { trackComponent } from '@utils/tracking/usage';
import { logWarn } from '@utils/error/log-error';
import { GuxAvatarAccent } from './gux-avatar-group-item.types';
import { GuxAvatarAccent } from '../gux-avatar-group.types';
import { groupKeyboardNavigation } from '../gux-avatar-group.service';
import { generateInitials } from '@utils/string/generate-initials';

Expand Down Expand Up @@ -104,32 +105,33 @@ export class GuxAvatarGroupItem {

render(): JSX.Element {
return (
<button
type="button"
role="menuitem"
aria-label={this.name}
tabIndex={-1}
ref={el => (this.buttonElement = el)}
class={{
'gux-avatar': true,
[this.getAccent()]: true,
'gux-last-item': this.isLastItemInGroup()
}}
>
<slot name="image">
<span class="gux-avatar-initials" aria-hidden="true">
{generateInitials(this.name)}
</span>
</slot>
<gux-tooltip-beta
aria-hidden="true"
visual-only
placement="top"
ref={el => (this.tooltip = el)}
<Host role="menuitem">
<button
type="button"
aria-label={this.name}
tabIndex={-1}
ref={el => (this.buttonElement = el)}
class={{
'gux-avatar': true,
[this.getAccent()]: true,
'gux-last-item': this.isLastItemInGroup()
}}
>
<div slot="content">{this.name}</div>
</gux-tooltip-beta>
</button>
<slot name="image">
<span class="gux-avatar-initials" aria-hidden="true">
{generateInitials(this.name)}
</span>
</slot>
<gux-tooltip-beta
aria-hidden="true"
visual-only
placement="top"
ref={el => (this.tooltip = el)}
>
<div slot="content">{this.name}</div>
</gux-tooltip-beta>
</button>
</Host>
) as JSX.Element;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@
outline: none;
}
}

::slotted(gux-avatar-group-item-beta.gux-hidden) {
display: none;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { getClosestElement } from '@utils/dom/get-closest-element';
import { GuxAvatarGroupChild } from './gux-avatar-group.types';

export function groupKeyboardNavigation(
event: KeyboardEvent,
currentElement: Element
Expand Down Expand Up @@ -34,7 +37,7 @@ export function groupKeyboardNavigation(
}

export function resetFocusableSibling(element: Element) {
const focusableSibling = getSiblings(element).find((sibling: Element) => {
const focusableSibling = getGroupItems(element).find((sibling: Element) => {
const button = getGroupItemButton(sibling);
return button && button.tabIndex !== -1;
});
Expand All @@ -51,30 +54,37 @@ export function setFocusTarget(element: Element): void {
function focusFirstSibling(currentElement: Element): void {
const firstFocusableElement = getFirstFocusableElement(
currentElement
) as HTMLGuxAvatarGroupItemBetaElement;
) as GuxAvatarGroupChild;

if (firstFocusableElement) {
void firstFocusableElement.guxFocus();
void setItemTabIndex(firstFocusableElement, 0);
void resetFocusableSibling(firstFocusableElement);
void setItemTabIndex(firstFocusableElement, 0);
}
}

function focusLastSibling(currentElement: Element): void {
const lastFocusableElement = getLastFocusableElement(
currentElement
) as HTMLGuxAvatarGroupItemBetaElement;
) as GuxAvatarGroupChild;

if (lastFocusableElement) {
void lastFocusableElement.guxFocus();
void setItemTabIndex(lastFocusableElement, 0);
void resetFocusableSibling(lastFocusableElement);
void setItemTabIndex(lastFocusableElement, 0);
}
}

function focusPreviousSiblingLoop(currentElement: Element): void {
const previousFocusableElement =
currentElement.previousElementSibling as HTMLGuxAvatarGroupItemBetaElement;
const groupItems = getGroupItems(currentElement);
const currentElementIndex = groupItems.findIndex(
(item: Element) => item === currentElement
);

const previousIndex = (currentElementIndex - 1) % groupItems.length;
const previousFocusableElement = groupItems[
previousIndex
] as GuxAvatarGroupChild;

setItemTabIndex(currentElement, -1);

Expand All @@ -87,58 +97,66 @@ function focusPreviousSiblingLoop(currentElement: Element): void {
}

function focusNextSiblingLoop(currentElement: Element): void {
const nextFocusableElement =
currentElement.nextElementSibling as HTMLGuxAvatarGroupItemBetaElement;
const groupItems = getGroupItems(currentElement);
const currentElementIndex = groupItems.findIndex(
(item: Element) => item === currentElement
);

const nextIndex = (currentElementIndex + 1) % groupItems.length;
if (nextIndex === 0) {
focusFirstSibling(currentElement);
}
const nextFocusableElement = groupItems[nextIndex] as GuxAvatarGroupChild;

setItemTabIndex(currentElement, -1);

if (nextFocusableElement) {
if (nextFocusableElement !== null) {
void nextFocusableElement.guxFocus();
void setItemTabIndex(nextFocusableElement, 0);
} else {
focusFirstSibling(currentElement);
}
}

function getFirstFocusableElement(currentElement: Element): Element {
let firstFocusableElement = currentElement;

while (firstFocusableElement.previousElementSibling !== null) {
firstFocusableElement = firstFocusableElement.previousElementSibling;
}

return firstFocusableElement;
return getGroupItems(currentElement)[0];
}

function getLastFocusableElement(currentElement: Element): Element {
let lastFocusableElement = currentElement;

while (lastFocusableElement.nextElementSibling !== null) {
lastFocusableElement = lastFocusableElement.nextElementSibling;
}

return lastFocusableElement;
const groupItems = getGroupItems(currentElement);
return groupItems[groupItems.length - 1];
}

function getGroupItemButton(element: Element): HTMLButtonElement {
return element.shadowRoot?.querySelector('button') as HTMLButtonElement;
}

function getSiblings(element: Element): Element[] {
const siblings = Array.from(element.parentElement.children);

// Early return for performance when there are no siblings
if (siblings.length <= 1) {
return [];
function getGroupItems(element: Element): Element[] {
const group = getClosestElement(
'gux-avatar-group-beta',
element as HTMLElement
) as Element;

const slottedItems = Array.from(
group.querySelectorAll('gux-avatar-group-item-beta')
).filter(child => !isHidden(child)) as GuxAvatarGroupChild[];

const overflow = group.shadowRoot.querySelector(
'gux-avatar-overflow-beta'
) as HTMLGuxAvatarOverflowBetaElement;
if (overflow) {
slottedItems.push(overflow);
}

return siblings.filter(child => child !== element);
return slottedItems as GuxAvatarGroupChild[];
}

function setItemTabIndex(element: Element, newIndex: number) {
const button = getGroupItemButton(element);
if (button) {
button.tabIndex = newIndex;
} else {
console.log('No button found in the gux-avatar-group-item element');
console.log('No button found in the element');
}
}
function isHidden(element: Element): boolean {
return element.classList.contains('gux-hidden');
}
Loading

0 comments on commit 5b99fa4

Please sign in to comment.