Skip to content

Commit

Permalink
Add keyboard navigation per issue getAlby#182
Browse files Browse the repository at this point in the history
  • Loading branch information
chebizarro committed Nov 1, 2024
1 parent 863b0b3 commit 2fe8058
Show file tree
Hide file tree
Showing 20 changed files with 304 additions and 55 deletions.
4 changes: 3 additions & 1 deletion src/components/BitcoinConnectElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,6 @@ export class BitcoinConnectElement extends InternalElement {
this._modalOpen = currentState.modalOpen;
});
}
}


}
5 changes: 5 additions & 0 deletions src/components/bc-balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export class Balance extends withTwind()(BitcoinConnectElement) {
class="font-medium font-sans mr-2 flex justify-center items-center gap-0.5 ${classes[
'text-brand-mixed'
]}"
role="status"
aria-live="${this._loading ? 'polite' : 'off'}"
aria-busy="${this._loading ? 'true' : 'false'}"
>
<span class="font-mono">${this._balance || 'Loading...'} </span></span
>`;
Expand Down Expand Up @@ -109,6 +112,8 @@ export class Balance extends withTwind()(BitcoinConnectElement) {
this._balance = '⚠️';
// FIXME: better error handling
console.error(error);
} finally {
this._loading = false;
}
})();
}
Expand Down
3 changes: 3 additions & 0 deletions src/components/bc-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export class Button extends withTwind()(BitcoinConnectElement) {
<div
class="relative inline-flex ${classes.interactive} cursor-pointer
rounded-lg gap-2 justify-center items-center"
aria-pressed="${this._connected ? 'true' : 'false'}"
aria-busy="${isLoading ? 'true' : 'false'}"
aria-label="${isLoading ? 'Connecting...' : this.title}"
@click=${this._onClick}
>
<div
Expand Down
39 changes: 38 additions & 1 deletion src/components/bc-currency-switcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,14 @@ export class CurrencySwitcher extends withTwind()(BitcoinConnectElement) {

override render() {
if (!this._isSwitchingCurrency) {
return html`<div class="flex justify-center items-center gap-2">
return html`<div class="flex justify-center items-center gap-2">
<div
class="${classes.interactive}"
role="button"
tabindex="0"
aria-label="Open currency selection"
@click=${this._showSelectVisibility}
@keydown=${this._handleKeydown}
>
<slot></slot>
</div>
Expand All @@ -81,7 +85,12 @@ export class CurrencySwitcher extends withTwind()(BitcoinConnectElement) {
class="${selectedCurrency === currency.value
? 'bg-blue-500 text-white'
: ''} flex items-center justify-center py-2 px-4 hover:text-white hover:bg-blue-500 rounded-lg hover:border-blue-500 cursor-pointer"
role="option"
aria-selected="${selectedCurrency === currency.value}"
tabindex="0"
@click=${() => this._selectCurrency(currency.value)}
@keydown=${(event: KeyboardEvent) =>
this._handleCurrencyKeydown(event, currency.value)}
>
<span class="text-orange-400 inline-block mr-2 text-xl"
>${currency.flag}</span
Expand All @@ -94,11 +103,39 @@ export class CurrencySwitcher extends withTwind()(BitcoinConnectElement) {

private _showSelectVisibility() {
this._isSwitchingCurrency = true;
// Focus the first currency option when switching is enabled
setTimeout(() => {
const firstCurrencyOption = this.shadowRoot?.querySelector(
'[role="option"]'
) as HTMLElement;
firstCurrencyOption?.focus();
}, 0);
}

private _selectCurrency(selectedCurrency: string) {
store.getState().setCurrency(selectedCurrency);
this._isSwitchingCurrency = false;
// Ensure focus returns to the triggering button
const triggerButton = this.shadowRoot?.querySelector(
'[role="button"]'
) as HTMLElement;
triggerButton?.focus();
}

// Handle keydown events for currency selection
private _handleCurrencyKeydown(event: KeyboardEvent, currency: string) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this._selectCurrency(currency);
}
}

// Handle keydown events for opening the currency list
public _handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this._showSelectVisibility();
}
}
}

Expand Down
26 changes: 26 additions & 0 deletions src/components/bc-modal-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,32 @@ export class ModalHeader extends withTwind()(BitcoinConnectElement) {
override render() {
return html`<div
class="flex justify-center items-center gap-2 w-full relative"
role="heading"
aria-level="1"
>
<div
class="absolute right-0 h-full flex items-center justify-center gap-2"
>
${this.showHelp
? html`<div
class="${classes.interactive} ${classes['text-neutral-tertiary']}"
role="button"
tabindex="0"
aria-label="Help"
@click=${() => store.getState().pushRoute('/help')}
@keydown=${this._handleKeydownHelp}
>
${helpIcon}
</div>`
: null}
${this.closable
? html`<div
class="${classes.interactive} ${classes['text-neutral-tertiary']}"
role="button"
tabindex="0"
aria-label="Close"
@click=${this._handleClose}
@keydown=${this._handleKeydownClose}
>
${crossIcon}
</div>`
Expand All @@ -54,6 +64,22 @@ export class ModalHeader extends withTwind()(BitcoinConnectElement) {
private _handleClose() {
this.dispatchEvent(new Event('onclose', {bubbles: true, composed: true}));
}

// Handle keyboard interactions for the close button
private _handleKeydownClose(event: KeyboardEvent) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this._handleClose();
}
}

// Handle keyboard interactions for the help button
private _handleKeydownHelp(event: KeyboardEvent) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
store.getState().pushRoute('/help');
}
}
}

declare global {
Expand Down
27 changes: 26 additions & 1 deletion src/components/bc-modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,38 @@ import {closeModal} from '../api';

@customElement('bc-modal')
export class Modal extends withTwind()(BitcoinConnectElement) {
private _focusTrapElement: HTMLSlotElement | null | undefined;

override firstUpdated() {
this._focusTrapElement = this.shadowRoot?.querySelector('slot');
window.addEventListener('keydown', this._handleKeydown);
}

override disconnectedCallback() {
window.removeEventListener('keydown', this._handleKeydown);
}

override render() {
return html` <div
return html`<div
class="fixed top-0 left-0 w-full h-full flex justify-center items-end sm:items-center z-[21000]"
role="dialog"
aria-modal="true"
aria-labelledby="modal-header"
aria-describedby="modal-content"
>
<div
class="absolute top-0 left-0 w-full h-full -z-10 ${classes[
'bg-foreground'
]} animate-darken"
@click=${this._handleClose}
role="button"
tabindex="0"
aria-label="Close modal"
></div>
<div
class="transition-all p-4 pt-6 pb-8 rounded-2xl shadow-2xl flex justify-center items-center w-full bg-white dark:bg-black max-w-md max-sm:rounded-b-none
animate-fade-in"
id="modal-content"
>
<slot @onclose=${this._handleClose}></slot>
</div>
Expand All @@ -32,6 +51,12 @@ export class Modal extends withTwind()(BitcoinConnectElement) {
private _handleClose = () => {
closeModal();
};

public _handleKeydown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
this._handleClose();
}
};
}

declare global {
Expand Down
24 changes: 21 additions & 3 deletions src/components/bc-navbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,45 @@ export class Navbar extends withTwind()(BitcoinConnectElement) {
heading?: string;

override render() {
return html`<div
return html`<nav
class="flex justify-center items-center gap-2 w-full relative pb-4"
role="navigation"
aria-label="${this.heading ? this.heading + ' navigation' : 'navigation'}"
>
<div class="absolute left-8 h-full flex items-center justify-center">
<div
class="${classes.interactive} ${classes['text-neutral-tertiary']}"
role="button"
tabindex="0"
aria-label="Go back"
@click=${this._goBack}
@keydown=${this._handleKeydown}
>
${backIcon}
</div>
</div>
<div class="font-sans font-medium ${classes['text-neutral-secondary']}">
<div
class="font-sans font-medium ${classes['text-neutral-secondary']}"
role="heading"
aria-level="1"
>
${this.heading}
</div>
</div>`;
</nav>`;
}

private _goBack = () => {
store.getState().popRoute();
store.getState().setError(undefined);
};

public _handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this._goBack();
}
}
}

declare global {
Expand Down
11 changes: 10 additions & 1 deletion src/components/bc-pay-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,16 @@ export class PayButton extends withTwind()(BitcoinConnectElement) {
override render() {
const isLoading = this._waitingForInvoice || this._modalOpen;

return html` <div class="inline-flex" @click=${this._onClick}>
return html`<div
class="inline-flex"
aria-live="polite"
aria-label="${isLoading
? 'Loading payment'
: this._paid
? 'Payment complete'
: this.title}"
@click=${this._onClick}
>
<bci-button variant="primary">
${isLoading
? html`${waitingIcon(`w-11 h-11 -mr-2 -ml-2.5 `)}`
Expand Down
4 changes: 3 additions & 1 deletion src/components/bc-router-outlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import {routes} from './routes';
export class RouterOutlet extends withTwind()(BitcoinConnectElement) {
override render() {
//TODO: r = routes[this._route](this._routeParams);
return html`<div class="flex flex-col w-full">${routes[this._route]}</div>`;
return html`<div class="flex flex-col w-full" aria-live="polite">
${routes[this._route]}
</div>`;
}
}

Expand Down
23 changes: 18 additions & 5 deletions src/components/bc-start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export class Start extends withTwind()(BitcoinConnectElement) {
override render() {
return html`<div
class="flex flex-col justify-center items-center w-full font-sans"
aria-live="polite"
role="region"
aria-label="${this._connected
? 'Connected Wallet Section'
: 'Connect Wallet Section'}"
>
${this._connected
? html`
Expand All @@ -46,7 +51,7 @@ export class Start extends withTwind()(BitcoinConnectElement) {
<bc-currency-switcher>
<bc-balance class="text-2xl"></bc-balance>
</bc-currency-switcher>`
: html` <span
: html`<span
class="text-lg font-medium mt-4 -mb-4 ${classes[
'text-neutral-secondary'
]}"
Expand All @@ -59,21 +64,29 @@ export class Start extends withTwind()(BitcoinConnectElement) {
class="my-8 ${classes[
'text-neutral-primary'
]} w-64 max-w-full text-center"
role="heading"
aria-level="1"
>
How would you like to
connect${this._appName ? `\nto ${this._appName}` : ''}?
</h1>
<bc-connector-list></bc-connector-list>
<div class="flex flex-col items-center w-full font-sans text-sm">
<h1 class="mt-8 ${classes['text-neutral-primary']} text-center">
<h1
class="mt-8 ${classes['text-neutral-primary']} text-center"
role="heading"
aria-level="2"
>
Don't have a bitcoin lightning wallet?
<a
class="no-underline font-bold ${classes.interactive} ${classes[
'text-brand-mixed'
]} "
]}"
@click=${() => store.getState().pushRoute('/new-wallet')}
@keydown=${() => store.getState().pushRoute('/new-wallet')}
role="link"
tabindex="0"
aria-label="Get a bitcoin lightning wallet"
>Get one here</a
>
</h1>
Expand Down
17 changes: 15 additions & 2 deletions src/components/connectors/ConnectorElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,13 @@ export abstract class ConnectorElement extends withTwind()(
override render() {
return html`<div
class="flex flex-col justify-between items-center w-32 -mx-4 cursor-pointer ${classes.interactive}"
@click=${this._onClick}
>
role="button"
tabindex="0"
@click=${this._onClick} @keydown=${this._handleKeydown}
aria-label="Connect to ${this._title}"
aria-pressed="${this._connected ? 'true' : 'false'}"
aria-busy="${this._connecting ? 'true' : 'false'}"
>
<div
class="w-16 h-16 drop-shadow rounded-2xl flex justify-center items-center overflow-hidden"
style="background: ${this._background};"
Expand All @@ -48,6 +53,14 @@ export abstract class ConnectorElement extends withTwind()(
</div>`;
}

// Handle keyboard events for accessibility (Enter/Space key triggers click)
public _handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
this._onClick(); // Simulate click when Enter or Space is pressed
}
}

protected _connect(
config: Omit<ConnectorConfig, 'connectorName' | 'connectorType'>
) {
Expand Down
Loading

0 comments on commit 2fe8058

Please sign in to comment.