Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/eix-73-group-a11y.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@siemens/ix': minor
---

Make `ix-group` keyboard- and screen-reader-accessible with separate header select and expand controls, disclosure ARIA (`aria-expanded` / `aria-controls`), Space/Enter, Escape to collapse, accessible names from the header text (expanded state from `aria-expanded` only), and screen-reader-compatible context-menu keyboard navigation.
Comment thread
alexkaduk marked this conversation as resolved.
11 changes: 10 additions & 1 deletion packages/core/src/components/group/group-context-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export class GroupContextMenu {

@State() showContextMenu = false;

@State() contextMenuExpanded = false;

private getTrigger() {
return this.hostElement;
}
Expand Down Expand Up @@ -59,16 +61,23 @@ export class GroupContextMenu {
}

this.configureDropdown(dropdownElement, triggerElement);
dropdownElement.hostRole = 'menu';
}

render() {
return (
<Host>
<Host
onShowChange={(event: CustomEvent<boolean>) =>
(this.contextMenuExpanded = event.detail)
}
>
<ix-icon-button
class={{ hide: !this.showContextMenu }}
size="24"
variant="subtle-tertiary"
icon={iconContextMenu}
aria-haspopup="menu"
aria-expanded={this.contextMenuExpanded.toString()}
Comment thread
alexkaduk marked this conversation as resolved.
></ix-icon-button>
<slot onSlotchange={() => this.onSlotChange()}></slot>
</Host>
Expand Down
144 changes: 94 additions & 50 deletions packages/core/src/components/group/group.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,89 +11,59 @@
@use 'legacy/mixins/fonts';
@use 'mixins/hover';
@use 'mixins/text-truncation';
@use './group.vars' as *;

$header-height: 4rem;
$header-height-focus-visible: calc($header-height - 2px);

:host {
display: flex;
flex-direction: column;
position: relative;
width: 19.75rem;
min-width: 12rem;
border-color: var(--theme-group-item--border-color);
border-color: var(--ix-group-item-border-color);

@include text-truncation.ellipsis;

.group-header {
height: $header-height;
min-height: $header-height;
max-height: $header-height;
border-radius: var(--theme-group--border-radius)
var(--theme-group--border-radius) 0 0;
border-radius: var(--ix-group-border-radius) var(--ix-group-border-radius) 0
0;
display: flex;
background-color: var(--theme-group-item--background);
color: var(--theme-group-header--color);

cursor: pointer;
background-color: var(--ix-group-item-background);
color: var(--ix-group-header-color);

@include hover.hover {
background-color: var(--theme-group-item--background--hover);
background-color: var(--ix-group-item-background-hover);

&.selected {
background-color: var(--theme-group-item--background--selected);
background-color: var(--ix-group-item-background-selected);
}
}

@include hover.active {
background-color: var(--theme-group-item--background--active);
background-color: var(--ix-group-item-background-active);

&.selected {
background-color: var(--theme-group-item--background--selected);
}
}

@include hover.focus-visible {
height: $header-height-focus-visible;
min-height: $header-height-focus-visible;

border: 1px solid var(--theme-color-focus-bdr);
border-radius: var(--theme-default-border-radius)
var(--theme-default-border-radius) 0px 0px;
outline: none;

.group-header-selection-indicator {
width: calc(vars.$tiny-space - 1px);
}

.group-header-content {
padding: calc(0.5rem - 1px) calc(0.5rem - 1px) calc(0.5rem - 1px)
0;
}

.btn-expand-header {
margin-top: calc(vars.$small-space - 1px);
margin-bottom: calc(vars.$small-space - 1px);
background-color: var(--ix-group-item-background-selected);
}
}

&.selected {
background-color: var(--theme-group-item--background--selected);
background-color: var(--ix-group-item-background-selected);

.group-header-selection-indicator {
background-color: var(
--theme-group-item-indicator--background--selected
);
background-color: var(--ix-group-item-indicator-background-selected);
}
}

.group-header-selection-indicator {
background-color: var(--theme-color-input--focus);
background-color: var(--ix-group-item-indicator-background);

&.group-header-selection-indicator-item-selected {
background-color: var(
--theme-group-item-indicator--background--selected
);
background-color: var(--ix-group-item-indicator-background-selected);
}
}
}
Expand All @@ -102,19 +72,70 @@ $header-height-focus-visible: calc($header-height - 2px);
display: flex;
width: 100%;
min-width: 0;
height: 100%;
align-items: stretch;

// Select focus wraps the full header chrome (indicator + title + expand),
// excluding the context-menu slot
&:has(.group-header-select:focus-visible) {
outline: var(--ix-group-focus-border-thickness) solid
var(--ix-group-focus-border-color);
outline-offset: calc(-1 * var(--ix-group-focus-border-thickness));
}
}

@include text-truncation.ellipsis;
.group-header-actions {
display: flex;
flex: 1;
min-width: 0;
height: 100%;
align-items: stretch;
}

.group-header-selection-indicator {
flex: 0 0 auto;
width: vars.$tiny-space;
border-top-left-radius: var(--theme-group--border-radius);
align-self: stretch;
border-top-left-radius: var(--ix-group-border-radius);
}

.group-header-select-area {
position: relative;
display: flex;
flex: 1;
align-items: stretch;
align-self: stretch;
min-width: 0;
min-height: 100%;

&--static {
cursor: pointer;
}
}

.group-header-select {
appearance: none;
position: absolute;
inset: 0;
z-index: 1;
box-sizing: border-box;
margin: 0;
padding: 0;
border: none;
background: transparent;
cursor: pointer;
border-radius: 0 var(--ix-group-border-radius) 0 0;

@include hover.focus-visible {
outline: none;
}
}

.group-header-content {
display: flex;
flex-direction: row;
justify-content: space-between;
align-self: stretch;
min-width: 0;
flex-grow: 1;
flex-basis: 0;
Expand Down Expand Up @@ -145,28 +166,51 @@ $header-height-focus-visible: calc($header-height - 2px);
.group-subheader {
height: 1.25rem;
font-size: 0.875rem;
color: var(--theme-group-subheader--color);
color: var(--ix-group-subheader-color);

@include text-truncation.ellipsis();
}
}

.expand-icon {
padding: 0.125rem 0.437rem;
color: var(--theme-color-std-text);
color: var(--ix-group-expand-icon-color);
}

.btn-expand-header {
appearance: none;
box-sizing: border-box;
display: flex;
flex: 0 0 auto;
align-items: flex-start;
justify-content: center;

// Native <button> flex items do not stretch like the old <div>; pin to top.
align-self: flex-start;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
order: -1;
margin: vars.$small-space;
margin-inline-end: vars.$tiny-space;
min-width: 1.5rem;
padding: 0;
border: none;
background: transparent;
color: inherit;
line-height: 0;
cursor: pointer;
border-radius: var(--ix-group-focus-border-radius);

@include hover.focus-visible {
outline: var(--ix-group-focus-border-thickness) solid
var(--ix-group-focus-border-color);
outline-offset: calc(-1 * var(--ix-group-focus-border-thickness));
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

.group-content {
display: flex;
flex-direction: column;
gap: 1px;
margin-top: 1px;
gap: var(--ix-group-separator-width);
margin-top: var(--ix-group-separator-width);
}

.footer {
Expand Down
Loading
Loading