Files
swagger-ui/src/core/components/responses.jsx
Giles Wells 72811bd827 feat: Accessibility improvements (#7224)
* feat: adds a11y for ContentType & Responses region

* feat: adds a11y to expandable/collapsible elements

* fix: add aria label to select element for content types

* fix: add aria label prop to contentType component

* Change optag to h3 for better tag hierarchy


Co-authored-by: ediiotero <eddie.otero@oddball.io>
Co-authored-by: Mike Lumetta <mike.lumetta@adhocteam.us>
Co-authored-by: Alexander Valencia <alex.valencia@adhocteam.us>
2021-05-12 09:40:31 -07:00

170 lines
6.6 KiB
JavaScript

import React from "react"
import { fromJS, Iterable } from "immutable"
import PropTypes from "prop-types"
import ImPropTypes from "react-immutable-proptypes"
import { defaultStatusCode, getAcceptControllingResponse } from "core/utils"
import createHtmlReadyId from "../../helpers/create-html-ready-id"
export default class Responses extends React.Component {
static propTypes = {
tryItOutResponse: PropTypes.instanceOf(Iterable),
responses: PropTypes.instanceOf(Iterable).isRequired,
produces: PropTypes.instanceOf(Iterable),
producesValue: PropTypes.any,
displayRequestDuration: PropTypes.bool.isRequired,
path: PropTypes.string.isRequired,
method: PropTypes.string.isRequired,
getComponent: PropTypes.func.isRequired,
getConfigs: PropTypes.func.isRequired,
specSelectors: PropTypes.object.isRequired,
specActions: PropTypes.object.isRequired,
oas3Actions: PropTypes.object.isRequired,
oas3Selectors: PropTypes.object.isRequired,
specPath: ImPropTypes.list.isRequired,
fn: PropTypes.object.isRequired
}
static defaultProps = {
tryItOutResponse: null,
produces: fromJS(["application/json"]),
displayRequestDuration: false
}
// These performance-enhancing checks were disabled as part of Multiple Examples
// because they were causing data-consistency issues
//
// shouldComponentUpdate(nextProps) {
// // BUG: props.tryItOutResponse is always coming back as a new Immutable instance
// let render = this.props.tryItOutResponse !== nextProps.tryItOutResponse
// || this.props.responses !== nextProps.responses
// || this.props.produces !== nextProps.produces
// || this.props.producesValue !== nextProps.producesValue
// || this.props.displayRequestDuration !== nextProps.displayRequestDuration
// || this.props.path !== nextProps.path
// || this.props.method !== nextProps.method
// return render
// }
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val)
onResponseContentTypeChange = ({ controlsAcceptHeader, value }) => {
const { oas3Actions, path, method } = this.props
if(controlsAcceptHeader) {
oas3Actions.setResponseContentType({
value,
path,
method
})
}
}
render() {
let {
responses,
tryItOutResponse,
getComponent,
getConfigs,
specSelectors,
fn,
producesValue,
displayRequestDuration,
specPath,
path,
method,
oas3Selectors,
oas3Actions,
} = this.props
let defaultCode = defaultStatusCode( responses )
const ContentType = getComponent( "contentType" )
const LiveResponse = getComponent( "liveResponse" )
const Response = getComponent( "response" )
let produces = this.props.produces && this.props.produces.size ? this.props.produces : Responses.defaultProps.produces
const isSpecOAS3 = specSelectors.isOAS3()
const acceptControllingResponse = isSpecOAS3 ?
getAcceptControllingResponse(responses) : null
const regionId = createHtmlReadyId(`${method}${path}_responses`)
const controlId = `${regionId}_select`
return (
<div className="responses-wrapper">
<div className="opblock-section-header">
<h4>Responses</h4>
{ specSelectors.isOAS3() ? null : <label htmlFor={controlId}>
<span>Response content type</span>
<ContentType value={producesValue}
ariaControls={regionId}
ariaLabel="Response content type"
className="execute-content-type"
contentTypes={produces}
controlId={controlId}
onChange={this.onChangeProducesWrapper} />
</label> }
</div>
<div className="responses-inner">
{
!tryItOutResponse ? null
: <div>
<LiveResponse response={ tryItOutResponse }
getComponent={ getComponent }
getConfigs={ getConfigs }
specSelectors={ specSelectors }
path={ this.props.path }
method={ this.props.method }
displayRequestDuration={ displayRequestDuration } />
<h4>Responses</h4>
</div>
}
<table aria-live="polite" className="responses-table" id={regionId} role="region">
<thead>
<tr className="responses-header">
<td className="col_header response-col_status">Code</td>
<td className="col_header response-col_description">Description</td>
{ specSelectors.isOAS3() ? <td className="col col_header response-col_links">Links</td> : null }
</tr>
</thead>
<tbody>
{
responses.entrySeq().map( ([code, response]) => {
let className = tryItOutResponse && tryItOutResponse.get("status") == code ? "response_current" : ""
return (
<Response key={ code }
path={path}
method={method}
specPath={specPath.push(code)}
isDefault={defaultCode === code}
fn={fn}
className={ className }
code={ code }
response={ response }
specSelectors={ specSelectors }
controlsAcceptHeader={response === acceptControllingResponse}
onContentTypeChange={this.onResponseContentTypeChange}
contentType={ producesValue }
getConfigs={ getConfigs }
activeExamplesKey={oas3Selectors.activeExamplesMember(
path,
method,
"responses",
code
)}
oas3Actions={oas3Actions}
getComponent={ getComponent }/>
)
}).toArray()
}
</tbody>
</table>
</div>
</div>
)
}
}