hawksdoves / react-aria-announcer

MIT License
5 stars 3 forks source link

react-aria-announcer

This lightweight package allows you to broadcast aria-live messages to screen-readers from anywhere within your React application.

This package was created to try and solve the problems that I faced when building an online accessible editor. Historically, online editors are known for being not very accessible. Using this package I was able to broadcast announcements such as "text block has been added" or an "image block has been deleted", which normally would not have been detected by a screenreader, but would have been visible on the screen.

Unlike other packages that also solve this issue, react-aria-announcer allows you to pre-define acceptable aria-live announcements. It also, takes advantage of HOCs, allowing for cleaner, more readable code.

Using react-aria-announcer you can broadcast these important announcements from any component in your application.

Installation


npm install react-aria-announcer

or

yarn add react-aria-announcer

Usage



The library exports 3 components and one react context.

HiddenMessages


This component contains your aria-live div.

import React, { Component } from 'react';
import { HiddenMessages } from 'react-aria-announcer';

class MyAccessibleApp extends Component {
  state = {
    message: '',
    timeStamp: '',
  }

  render() {
    return (
      <HiddenMessages 
        message={this.state.message} 
        timeStamp={this.state.timeStamp} 
      />
    )
  }
}

The props:

connectAriaAnnouncementProvider


This higher order component receives your app or a section of your app, where any number of it's ancestors might want to make an announcement.

import React, { Component } from 'react';
import { HiddenMessages, connectAriaAnnouncementProvider } from 'react-aria-announcer';
import ariaMessages from './ariaMessages';

function SearchableDashboard(props) {
  return (
    <div>
      ...lots of components within here!
    </div>
  )
}

const AccessibleSearchableDashboard = connectAriaAnnouncementProvider(SearchableDashboard)

class MyAccessibleApp extends Component {
  constructor(props) {
    super(props)
    this.state = {
      message: '',
      timeStamp: '',
    }
  }

  render() {
    return (
      <div>
      <HiddenMessages 
        message={this.state.message} 
        timeStamp={this.state.timeStamp} 
        manner='assertive'
      />
      <AccessibleSearchableDashboard
        announce={(message, timeStamp) => this.setState({ message, timeStamp })} 
        ariaMessages={ariaMessages}
        {...props}
      />
      </div>
    )
  }
}

The new component must receive 2 props: This new component must receive a function that calls the setState belonging to the component that holds the state; message and timestamp.

e.g.

const ariaMessages = {
  onToggleButton: () => 'Press shift enter to open toolbar, and navigate using tab ',
  addBlockGuide: type => `Press shift enter to add a new ${type} block`,
  blockAction: (type, action) => `${type} block ${action}`,
  moveBlock: (type, position) => `${type} block has moved to ${position}`,
  entered: (type) => `You are now in the ${type} block`,
  filter: (type, resultNumber) => `Now viewing ${resultNumber} of ${type} blocks`
};

Connecting to the Announcer


There are 2 ways to connect to the announcer:

connectAriaAnnouncementConsumer


This higher order component receives your component that might handle important visual changes and actions etc.

import React from 'react';
import { HiddenMessages, connectAriaAnnouncementConsumer } from 'react-aria-announcer';

function FilterByBlockButton({ announce, type }) {
  function handleClick() {
    fnFiltersDataBy(type)
      .then((res) => {
        announce('filter', type, res.length)
      })

  }

  return (
    <button onClick={() => handleClick()}>
      filter by {type}
    </button>
  )
}

const AccessibleFilterByBlock = connectAriaAnnouncementConsumer(FilterByBlockButton)

function MyAccessibleAppSectionChild() {
  return (
    <div>
      other components
      <AccessibleFilterByBlock type='text' />
      <AccessibleFilterByBlock type='headline' />
      <AccessibleFilterByBlock type='image' />
      even more components
    </div>
  )
}

Your component will receive a prop announce.

AnnounceMessageContext


This context object allows you to take advantage of react hooks. It's behaviour is then exactly the same as connectAriaAnnouncementConsumer

import React from 'react';
import { HiddenMessages, AnnounceMessageContext } from 'react-aria-announcer';

function FilterByBlockButton({ type }) {
  const announce = React.useContext(AnnounceMessageContext);
  function handleClick() {
    fnFiltersDataBy(type)
      .then((res) => {
        announce('filter', type, res.length)
      })

  }

  return (
    <button onClick={() => handleClick()}>
      filter by {type}
    </button>
  )
}

const AccessibleFilterByBlock = connectAriaAnnouncementConsumer(FilterByBlockButton)

function MyAccessibleAppSectionChild() {
  return (
    <div>
      other components
      <AccessibleFilterByBlock type='text' />
      <AccessibleFilterByBlock type='headline' />
      <AccessibleFilterByBlock type='image' />
      even more components
    </div>
  )
}

Your component will receive a prop announce.

Contributing to react-aria-announcer

Thank you for taking the time to contribute.

The community is a safe place for people to collaborate.

Please read the CODE of CONDUCT.


All contributions are welcome

...from typos in documentation to coding new features. If you require any help or have any questions please do not hesitate to ask, we are friendly and aim to reply within 48hours.